Execution Context
Execution contexts provide access to data and metadata during subscriber hook execution. Different contexts are available for route and function subscribers, each with specific properties.
Helper Types (Recommended)
For simplified usage, helper types are provided that require only the Entity generic parameter:
import {
TApiSubscriberFunctionBeforeCreateContext,
TApiSubscriberFunctionAfterCreateContext,
TApiFunctionCreateProperties,
} from "@elsikora/nestjs-crud-automator";
// Simple - only one generic parameter needed
async onBeforeCreate(
context: TApiSubscriberFunctionBeforeCreateContext<PostEntity>
): Promise<TApiFunctionCreateProperties<PostEntity>> {
// Full type safety and autocomplete for DATA
const manager = context.DATA.eventManager;
const repository = context.DATA.repository;
return context.result;
}Available Helper Types
Function Subscribers:
TApiSubscriberFunctionBeforeCreateContext<E>TApiSubscriberFunctionAfterCreateContext<E>TApiSubscriberFunctionBeforeUpdateContext<E>TApiSubscriberFunctionAfterUpdateContext<E>TApiSubscriberFunctionBeforeGetContext<E>TApiSubscriberFunctionAfterGetContext<E>TApiSubscriberFunctionBeforeGetListContext<E>TApiSubscriberFunctionAfterGetListContext<E>TApiSubscriberFunctionBeforeGetManyContext<E>TApiSubscriberFunctionAfterGetManyContext<E>TApiSubscriberFunctionBeforeDeleteContext<E>TApiSubscriberFunctionAfterDeleteContext<E>
Route Subscribers:
TApiSubscriberRouteBeforeCreateContext<E>TApiSubscriberRouteAfterCreateContext<E>TApiSubscriberRouteBeforeUpdateContext<E>TApiSubscriberRouteAfterUpdateContext<E>TApiSubscriberRouteBeforePartialUpdateContext<E>TApiSubscriberRouteAfterPartialUpdateContext<E>TApiSubscriberRouteBeforeGetContext<E>TApiSubscriberRouteAfterGetContext<E>TApiSubscriberRouteBeforeGetListContext<E>TApiSubscriberRouteAfterGetListContext<E>TApiSubscriberRouteBeforeDeleteContext<E>TApiSubscriberRouteAfterDeleteContext<E>
Base Execution Context
All execution contexts extend the base interface:
interface IApiSubscriberExecutionContext<E, Result, Input> {
/**
* The entity instance the operation is being performed on
*/
readonly ENTITY: E;
/**
* Immutable container for metadata
* (e.g., transaction manager, HTTP headers, route info)
*/
readonly DATA: Input;
/**
* Mutable data payload that subscribers can modify
*/
result: Result;
}Route Execution Context
Used in route (controller-level) subscribers:
interface IApiSubscriberRouteExecutionContext<E, Result, Input> {
readonly ENTITY: E;
readonly DATA: Input; // Use IApiSubscriberRouteExecutionContextData<E, R> or IApiSubscriberRouteExecutionContextDataExtended<E, R>
readonly ROUTE_TYPE: EApiRouteType;
result: Result;
}Route Context DATA Interfaces
Base DATA (for before hooks):
interface IApiSubscriberRouteExecutionContextData<E extends IApiBaseEntity, R> {
readonly entityMetadata: IApiEntity<E>;
readonly method: EApiRouteType;
readonly methodName: string;
readonly properties: IApiControllerProperties<E>;
readonly authorizationDecision?: IApiAuthorizationDecision<E, R>;
}Extended DATA (for after/error hooks):
interface IApiSubscriberRouteExecutionContextDataExtended<E extends IApiBaseEntity, R> extends IApiSubscriberRouteExecutionContextData<E, R> {
readonly authenticationRequest?: IApiAuthenticationRequest;
readonly headers: Record<string, string>;
readonly ip: string;
readonly body?: DeepPartial<E>;
readonly parameters?: Partial<E>;
readonly query?: TApiControllerGetListQuery<E>;
}Accessing Route Context Data
Recommended approach with helper type:
import { TApiSubscriberRouteBeforeCreateContext } from "@elsikora/nestjs-crud-automator";
async onBeforeCreate(
context: TApiSubscriberRouteBeforeCreateContext<PostEntity>
): Promise<TApiSubscriberRouteBeforeCreateContext<PostEntity>["result"]> {
// Entity being operated on
const entity = context.ENTITY;
// Route information (fully typed)
const routeType = context.ROUTE_TYPE;
const method = context.DATA.method;
const methodName = context.DATA.methodName;
// Entity metadata
const entityMetadata = context.DATA.entityMetadata;
const primaryKey = entityMetadata.primaryKey;
// Mutable result
const { body } = context.result;
body.slug = this.generateSlug(body.title);
return context.result;
}For after hooks with extended DATA:
import { TApiSubscriberRouteAfterCreateContext } from "@elsikora/nestjs-crud-automator";
async onAfterCreate(
context: TApiSubscriberRouteAfterCreateContext<PostEntity>
): Promise<PostEntity> {
// Authenticated user
const user = context.DATA.authenticationRequest?.user;
const userId = user?.id;
// Request headers
const contentType = context.DATA.headers["content-type"];
const userAgent = context.DATA.headers["user-agent"];
const correlationId = context.DATA.headers["x-correlation-id"];
// Client IP
const clientIp = context.DATA.ip;
return context.result;
}Function Execution Context
Used in function (service-level) subscribers:
interface IApiSubscriberFunctionExecutionContext<E, Result, Input> {
readonly ENTITY: E;
readonly DATA: Input; // Use IApiSubscriberFunctionExecutionContextData<E> for typed access
readonly FUNCTION_TYPE: EApiFunctionType;
result: Result;
}Function Context DATA Interface
interface IApiSubscriberFunctionExecutionContextData<E extends IApiBaseEntity> {
readonly eventManager?: EntityManager;
readonly repository: Repository<E>;
readonly criteria?: TApiFunctionDeleteCriteria<E> | TApiFunctionUpdateCriteria<E>;
readonly properties?: TApiFunctionCreateProperties<E> | TApiFunctionGetProperties<E> | TApiFunctionGetListProperties<E> | TApiFunctionGetManyProperties<E>;
readonly getProperties?: TApiFunctionGetProperties<E>;
readonly getListProperties?: TApiFunctionGetListProperties<E>;
readonly getManyProperties?: TApiFunctionGetManyProperties<E>;
}Accessing Function Context Data
Recommended approach with helper type:
import { TApiSubscriberFunctionBeforeCreateContext } from "@elsikora/nestjs-crud-automator";
async onBeforeCreate(
context: TApiSubscriberFunctionBeforeCreateContext<PostEntity>
): Promise<TApiFunctionCreateProperties<PostEntity>> {
// Entity being created
const entity = context.ENTITY;
// Fully typed DATA access
const manager = context.DATA.eventManager;
const repository = context.DATA.repository;
if (manager) {
// Use transactional operations
await manager.save(RelatedEntity, { ... });
}
// Mutable result
context.result.slug = this.generateSlug(context.result.title);
return context.result;
}Alternative approach with explicit generic:
import { IApiSubscriberFunctionExecutionContextData } from "@elsikora/nestjs-crud-automator";
async onBeforeCreate(
context: IApiSubscriberFunctionExecutionContext<
PostEntity,
TApiFunctionCreateProperties<PostEntity>,
IApiSubscriberFunctionExecutionContextData<PostEntity>
>
): Promise<TApiFunctionCreateProperties<PostEntity>> {
const manager = context.DATA.eventManager;
// ...
}Error Execution Contexts
Route Error Context
interface IApiSubscriberRouteErrorExecutionContext<E extends IApiBaseEntity, Input = unknown> {
readonly DATA: Input;
readonly ENTITY: E;
readonly ROUTE_TYPE: EApiRouteType;
}Use IApiSubscriberRouteExecutionContextData<E, R> for before-error hooks (route metadata + optional authorizationDecision) and IApiSubscriberRouteExecutionContextDataExtended<E, R> for after-error hooks when you need authentication data or request context.
Function Error Context
interface IApiSubscriberFunctionErrorExecutionContext<E extends IApiBaseEntity, Input = unknown> {
readonly DATA: Input;
readonly ENTITY: E;
readonly FUNCTION_TYPE: EApiFunctionType;
}Use IApiSubscriberFunctionExecutionContextData<E> to access eventManager and repository.
Using Error Contexts
async onAfterErrorCreate(
context: IApiSubscriberRouteErrorExecutionContext<
PostEntity,
IApiSubscriberRouteExecutionContextDataExtended<PostEntity, unknown>
>,
error: Error
): Promise<void> {
// Log error with context
console.error("Post creation failed", {
error: error.message,
stack: error.stack,
entity: context.ENTITY,
user: context.DATA.authenticationRequest?.user,
ip: context.DATA.ip,
headers: context.DATA.headers,
});
// Send error notification
await this.notificationService.notifyAdmins({
type: "post_creation_error",
error: error.message,
userId: context.DATA.authenticationRequest?.user?.id,
});
}Modifying Results
Subscribers can modify the mutable result field:
Before Hooks
async onBeforeCreate(
context: TApiSubscriberRouteBeforeCreateContext<PostEntity>
): Promise<TApiSubscriberRouteBeforeCreateContext<PostEntity>["result"]> {
// Modify body before saving
context.result.body.slug = this.generateSlug(context.result.body.title);
context.result.body.status = "draft";
return context.result;
}After Hooks
async onAfterGet(
context: TApiSubscriberFunctionAfterGetContext<PostEntity>
): Promise<PostEntity> {
// Enrich result after fetching
context.result.readingTime = this.calculateReadingTime(context.result.content);
context.result.isBookmarked = await this.checkIfBookmarked(context.result.id);
return context.result;
}Type Parameters
Execution contexts are generic and accept type parameters:
// E: Entity type
// Result: Result type (varies by operation)
// Input: Input data type (metadata)
IApiSubscriberExecutionContext<E, Result, Input>;Common Result Types
// Create operation (function + route before)
TApiFunctionCreateProperties<E> = { body: DeepPartial<E> };
// Update operation (function + route before)
TApiFunctionUpdateProperties<E> = { body: DeepPartial<E> };
// Get operation (function + route after)
E; // The entity itself
// Get list operation (function + route after)
IApiGetListResponseResult<E>;
// Get many operation (function + route after)
Array<E>;
// Delete operation (function)
E; // The deleted entityRoute before hooks receive request payloads (body, parameters, query) rather than entities, so their result shape is based on the incoming DTO. After/error hooks also receive the last request payload on context.DATA (when available).
Practical Examples
Enriching Response from Context
async onAfterCreate(
context: TApiSubscriberRouteAfterCreateContext<PostEntity>
): Promise<PostEntity> {
const post = context.result;
const user = context.DATA.authenticationRequest?.user;
// Add request metadata to the response
post.authorId = user?.id;
post.authorIp = context.DATA.ip;
post.createdVia = context.DATA.headers["user-agent"];
return post;
}Conditional Logic Based on Context
async onAfterGet(
context: TApiSubscriberRouteAfterGetContext<PostEntity>
): Promise<PostEntity> {
const post = context.result;
const user = context.DATA.authenticationRequest?.user;
// Hide sensitive data based on user role
if (user?.role !== "admin") {
post.internalNotes = null;
post.authorEmail = null;
}
return post;
}Using Event Manager (Transactions)
async onBeforeCreate(
context: TApiSubscriberFunctionBeforeCreateContext<PostEntity>
): Promise<TApiFunctionCreateProperties<PostEntity>> {
const manager = context.DATA.eventManager;
if (manager) {
// Create related entities within same transaction
const tags = await manager.save(TagEntity, [
{ name: "tech" },
{ name: "programming" },
]);
context.result.body.tags = tags;
}
return context.result;
}Error Context with Cleanup
async onAfterErrorCreate(
context: IApiSubscriberFunctionErrorExecutionContext<
PostEntity,
IApiSubscriberFunctionExecutionContextData<PostEntity>
>,
error: Error
): Promise<void> {
// Clean up temporary resources
if (context.ENTITY.tempFileIds) {
await this.fileService.deleteTempFiles(context.ENTITY.tempFileIds);
}
// Log with full context
this.logger.error("Failed to create post", {
error: error.message,
entityId: context.ENTITY.id,
hasTransaction: Boolean(context.DATA.eventManager),
});
}Next Steps
- Route Subscribers - Using route contexts
- Function Subscribers - Using function contexts
- Lifecycle - Execution flow and order
- API Reference - Interfaces - Full context interfaces