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.
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> {
readonly ENTITY: E;
readonly DATA: {
readonly method: string;
readonly methodName: string;
readonly properties: IApiControllerProperties<E>;
readonly entityMetadata: IApiEntity<E>;
readonly authenticationRequest?: IApiAuthenticationRequest;
readonly headers: Record<string, string>;
readonly ip: string;
};
readonly ROUTE_TYPE: EApiRouteType;
result: Result;
}
Accessing Route Context Data
async onBeforeCreate(
context: IApiSubscriberRouteExecutionContext<PostEntity, any>
): Promise<any> {
// Entity being operated on
const entity = context.ENTITY;
// Authenticated user
const user = context.DATA.authenticationRequest?.user;
const userId = user?.id;
const userRole = user?.role;
// 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;
// Route information
const routeType = context.ROUTE_TYPE; // "create", "update", etc.
const method = context.DATA.method;
// Entity metadata
const entityName = context.DATA.entityMetadata.name;
const primaryKey = context.DATA.entityMetadata.primaryKey;
// Mutable result
const { body } = context.result;
body.createdById = userId;
return context.result;
}
Function Execution Context
Used in function (service-level) subscribers:
interface IApiSubscriberFunctionExecutionContext<E, Result> {
readonly ENTITY: E;
readonly DATA: {
readonly method: string;
readonly entityMetadata: IApiEntity<E>;
readonly transactionManager?: EntityManager;
};
result: Result;
}
Accessing Function Context Data
async onBeforeCreate(
context: IApiSubscriberFunctionExecutionContext<
PostEntity,
TApiFunctionCreateProperties<PostEntity>
>
): Promise<TApiFunctionCreateProperties<PostEntity>> {
// Entity being created
const entity = context.ENTITY;
// Entity metadata
const entityName = context.DATA.entityMetadata.name;
const primaryKey = context.DATA.entityMetadata.primaryKey;
// Transaction manager (if within transaction)
const manager = context.DATA.transactionManager;
if (manager) {
// Use transactional operations
await manager.save(RelatedEntity, { ... });
}
// Mutable result
const { body } = context.result;
body.slug = this.generateSlug(body.title);
return context.result;
}
Error Execution Contexts
Route Error Context
interface IApiSubscriberRouteErrorExecutionContext<E> {
readonly ENTITY: E;
readonly DATA: {
readonly method: string;
readonly methodName: string;
readonly properties: IApiControllerProperties<E>;
readonly entityMetadata: IApiEntity<E>;
readonly authenticationRequest?: IApiAuthenticationRequest;
readonly headers: Record<string, string>;
readonly ip: string;
};
readonly ROUTE_TYPE: EApiRouteType;
}
Function Error Context
interface IApiSubscriberFunctionErrorExecutionContext<E> {
readonly ENTITY: E;
readonly DATA: {
readonly method: string;
readonly entityMetadata: IApiEntity<E>;
readonly transactionManager?: EntityManager;
};
}
Using Error Contexts
async onAfterErrorCreate(
context: IApiSubscriberRouteErrorExecutionContext<PostEntity>,
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: IApiSubscriberRouteExecutionContext<PostEntity, { body: DeepPartial<PostEntity> }>
): Promise<{ body: DeepPartial<PostEntity> }> {
// 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: IApiSubscriberFunctionExecutionContext<PostEntity, 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
TApiFunctionCreateProperties<E> = { body: DeepPartial<E> }
// Update operation
TApiFunctionUpdateProperties<E> = { body: DeepPartial<E> }
// Get operation
E // The entity itself
// Get list operation
E[] // Array of entities
// Delete operation
E // The entity being deleted
Practical Examples
Enriching Data from Context
async onBeforeCreate(
context: IApiSubscriberRouteExecutionContext<PostEntity, any>
): Promise<any> {
const { body } = context.result;
const user = context.DATA.authenticationRequest?.user;
// Add user and request data to entity
body.authorId = user?.id;
body.authorIp = context.DATA.ip;
body.createdVia = context.DATA.headers["user-agent"];
return context.result;
}
Conditional Logic Based on Context
async onAfterGet(
context: IApiSubscriberRouteExecutionContext<PostEntity, 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 Transaction Manager
async onBeforeCreate(
context: IApiSubscriberFunctionExecutionContext<
PostEntity,
TApiFunctionCreateProperties<PostEntity>
>
): Promise<TApiFunctionCreateProperties<PostEntity>> {
const manager = context.DATA.transactionManager;
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>,
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,
method: context.DATA.method,
});
}
Next Steps
- Route Subscribers - Using route contexts
- Function Subscribers - Using function contexts
- Lifecycle - Execution flow and order
- API Reference - Interfaces - Full context interfaces
Last updated on