Skip to Content

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.

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 entity

Route 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

Last updated on