Interceptores
Interceptores
Sección titulada «Interceptores»Los interceptores envuelven los handlers de ruta en un pipeline estilo cebolla — antes y después del handler — sin tocar la lógica de negocio. Úsalos para logging, caché, transformación de respuestas, temporización y más.
La interfaz Interceptor
Sección titulada «La interfaz Interceptor»import type { Interceptor, ExecutionContext } from 'veloce-ts';
class LoggingInterceptor implements Interceptor { async intercept( ctx: ExecutionContext, next: () => Promise<Response> ): Promise<Response> { const start = Date.now(); const response = await next(); console.log(`${ctx.request.method} ${ctx.request.url} — ${Date.now() - start}ms`); return response; }}La llamada a next() ejecuta los interceptores restantes y después el handler de ruta. Todo antes de next() es lógica “pre”; todo después es lógica “post”.
ExecutionContext
Sección titulada «ExecutionContext»type ExecutionContext = { request: Request; // Request estándar Web controller: unknown; // instancia del controlador (puede ser undefined en rutas funcionales) handler: string; // nombre del método en el controlador};Registro global
Sección titulada «Registro global»const app = new Veloce();app.useInterceptor(new LoggingInterceptor());app.useInterceptor(new MetricsInterceptor());await app.compile();Los interceptores globales se ejecutan en todas las rutas, antes de los interceptores de clase/método.
@UseInterceptor — por método o clase
Sección titulada «@UseInterceptor — por método o clase»import { UseInterceptor } from 'veloce-ts';
@Controller('/users')@UseInterceptor(LoggingInterceptor) // se aplica a todos los métodos de la claseclass UserController { @Get('/') @UseInterceptor(CacheInterceptor) // se apila sobre el nivel de clase async getUsers() { return users; }}Orden de ejecución
Sección titulada «Orden de ejecución»Interceptores globales (orden de registro) → Interceptores de clase (orden del decorador) → Interceptores de método (orden del decorador) → Handler de ruta ← Interceptores de método (inverso) ← Interceptores de clase (inverso)← Interceptores globales (inverso)Ejemplos
Sección titulada «Ejemplos»Interceptor de temporización
Sección titulada «Interceptor de temporización»class TimingInterceptor implements Interceptor { async intercept(ctx: ExecutionContext, next: () => Promise<Response>) { const start = performance.now(); const response = await next(); const ms = (performance.now() - start).toFixed(2); const modified = new Response(response.body, response); modified.headers.set('X-Response-Time', `${ms}ms`); return modified; }}Interceptor de caché simple
Sección titulada «Interceptor de caché simple»const cache = new Map<string, Response>();
class CacheInterceptor implements Interceptor { async intercept(ctx: ExecutionContext, next: () => Promise<Response>) { const key = ctx.request.url; if (cache.has(key)) { return cache.get(key)!.clone(); } const response = await next(); if (response.ok) { cache.set(key, response.clone()); } return response; }}Interceptor de auditoría de autorización
Sección titulada «Interceptor de auditoría de autorización»class AuditInterceptor implements Interceptor { async intercept(ctx: ExecutionContext, next: () => Promise<Response>) { const response = await next(); if (response.status === 401 || response.status === 403) { console.warn(`Intento de acceso no autorizado: ${ctx.request.url}`); } return response; }}Transformar la forma de la respuesta
Sección titulada «Transformar la forma de la respuesta»class WrapResponseInterceptor implements Interceptor { async intercept(ctx: ExecutionContext, next: () => Promise<Response>) { const response = await next(); if (!response.headers.get('content-type')?.includes('json')) { return response; } const data = await response.json(); return Response.json({ data, timestamp: Date.now() }, response); }}Buenas prácticas
Sección titulada «Buenas prácticas»- Mantén los interceptores sin estado — inyecta el estado vía constructor o clausura.
- Siempre llama a
next()a menos que quieras cortocircuitar el pipeline. - Devuelve una Response clonada al añadir cabeceras —
Responsees inmutable. - Registra los interceptores de uso frecuente globalmente; los específicos de funcionalidad con
@UseInterceptor.