import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
Scope,
} from "@nestjs/common";
import { DecafHandlerExecutor, DecafRequestContext } from "../request";
import { Adapter, Context, DefaultAdapterFlags } from "@decaf-ts/core";
import { DecafServerCtx, DecafServerFlags } from "../constants";
import "../overrides";
import { Logging } from "@decaf-ts/logging";
import { InternalError } from "@decaf-ts/db-decorators";
import { RequestToContextTransformer } from "./context";
/**
* @description
* Interceptor responsible for executing all registered Decaf request handlers
* before the controller method is invoked.
*
* @summary
* The {@link DecafRequestHandlerInterceptor} integrates the Decaf request-handling pipeline
* into NestJS' interceptor mechanism. Before passing execution to the next handler in the
* NestJS chain, it delegates request processing to the {@link DecafHandlerExecutor}, which
* sequentially runs all registered {@link DecafRequestHandler} instances. This allows
* behaviors such as authentication, logging, tenant resolution, or metadata enrichment
* to occur prior to controller execution.
*
* @class DecafRequestHandlerInterceptor
*
* @example
* ```ts
* // Apply globally:
* app.useGlobalInterceptors(new DecafRequestHandlerInterceptor(executor));
*
* // Or in a module:
* @Module({
* providers: [
* DecafHandlerExecutor,
* {
* provide: APP_INTERCEPTOR,
* useClass: DecafRequestHandlerInterceptor,
* },
* ],
* })
* export class AppModule {}
* ```
*
* @mermaid
* sequenceDiagram
* participant Client
* participant Interceptor
* participant Executor
* participant Controller
*
* Client->>Interceptor: HTTP Request
* Interceptor->>Executor: exec(request)
* Executor-->>Interceptor: handlers completed
* Interceptor->>Controller: next.handle()
* Controller-->>Client: Response
*/
@Injectable({ scope: Scope.REQUEST })
export class DecafRequestHandlerInterceptor implements NestInterceptor {
constructor(
protected readonly requestContext: DecafRequestContext,
protected readonly executor: DecafHandlerExecutor
) {}
protected async contextualize(req: any): Promise<DecafServerCtx> {
const headers = req.headers;
const flags: DecafServerFlags = {
headers: headers,
overrides: {},
} as any;
const flavours = Adapter.flavoursToTransform();
if (flavours)
for (const flavour of flavours) {
try {
const transformer = Adapter.transformerFor(
flavour
) as RequestToContextTransformer<any>;
const from = await transformer.from(req);
Object.assign(flags.overrides, from);
} catch (e: unknown) {
throw new InternalError(`Failed to contextualize request: ${e}`);
}
}
const ctx = new Context().accumulate(
Object.assign(
{},
DefaultAdapterFlags,
{
logger: Logging.get(),
timestamp: new Date(),
},
flags
)
);
return ctx as any;
}
async intercept(context: ExecutionContext, next: CallHandler) {
const req = context.switchToHttp().getRequest();
const res = context.switchToHttp().getResponse();
const log = Logging.for(DecafRequestHandlerInterceptor).for(this.intercept);
log.debug(
`CONTEXT ${this.requestContext.uuid} - request: ${req.method} ${req.url}`
);
const ctx = await this.contextualize(req);
log.debug(
`CONTEXT ${this.requestContext.uuid} contextualized - request: ${req.method} ${req.url}`
);
this.requestContext.applyCtx(ctx);
log.debug(
`CONTEXT ${this.requestContext.uuid} applied - request: ${req.method} ${req.url}`
);
await this.executor.exec(req, res);
log.debug(
`CONTEXT ${this.requestContext.uuid} executors finished - request: ${req.method} ${req.url}`
);
return next.handle();
}
}
Source