PackagePackagetypeA library you install and call yourself.See every one →What this type is →independently maintainedindependently maintainedoriginPublished by one person or a small company outside SAP. Says nothing about quality; the readings say that.See every one →

@dxfrontier/cds-ts-dispatcher

Decorator-driven handler registration for CAP in TypeScript, where handlers are declared on a class rather than wired up one by one in an init function.

Facts

Latestv6.1.0ReleasedLast pushLicenceMIT

Where it sits

Lives in
Provides
TypeScript patternsCAPTypeScript patternscapabilityHow an application is arranged once it is written in TypeScript — where handlers live, what registers them, and how business logic is kept apart from persistence. Distinct from typing, which is about the model being visible to the compiler; this is about the shape of the code around it.
Flavors
Requires
@sap/cds^10@sap/cdsartifactThe peer range, as published.

Who is behind it

dx frontier GmbHdx frontier GmbHOrganisationMaintains this
In the atlas: maintains 2
@dxfrontier
maintainerDaniel DragoleaPersonContributes to this
In the atlas: contributes to 2, wrote 4
@dragolea
Tomas RimkusPersonContributes to this
In the atlas: contributes to 4
@hakimio
Sebastian BlessingPersonContributes to this
In the atlas: contributes to 2
@sblessing
3 contributors

What it declares

Every artifact

Type
Package
Origin
Independently maintained

As a package

Declared for
^10
Where it runs
does not say
Distributed through
npm

For typeScript patterns

Supports
Dependency injectionDraft events
How handlers bind
decorators
Layers it names
handlersservice logicpersistence
property statedstated nonot said

Tasks this contributes to3Beta

Handle a draft's own events apart from the active entity'sA draft-enabled entity's own events — NEW, PATCH, EDIT, SAVE and DISCARD — are handled in places of their own in a CAP service written in TypeScript, apart from the CREATE, UPDATE and DELETE handlers of the active entity.Node.jsCovers, under conditionsdraft decorators
What it does

Carries a second set of decorators for the draft version of an entity's events beside the active-entity ones, so a method is marked as a draft handler where it is written rather than registered against a drafts entity by hand.

Conditions

The entity is annotated @odata.draft.enabled: true

Remaining work

The method bodies are the application's. The draft decorators are offered for an entity whose model says drafts are enabled; the README frames them that way rather than as a general set.

All options on the TypeScript patterns page →
Keep data access out of a CAP service's handlersThe data access of a CAP service written in TypeScript is called through classes with methods rather than written as queries inside the event handlers, so a handler holds the business logic and the queries sit behind one object.Node.jsCoverslayering and injection
What it does

Names the layer and puts the object in the handler's hands: its documentation recommends Controller-Service-Repository, gives the persistence layer the @Repository class decorator, and @Inject places a repository or a service class into a field of another class.

Remaining work

The repository class itself is the application's, or the matching package. The decorators place the object; what it does for a given entity is written either way.

All options on the TypeScript patterns page →
Split one CAP service implementation into a class per entityThe custom logic of a CAP service written in TypeScript sits in one class per entity rather than in a single service implementation file, and each class's methods still reach the framework as the before, on and after handlers for that entity.Node.jsCovershandler registration from decorators
What it does

A class decorated with @EntityHandler is the place one entity's handlers go, and the method decorators beside it, @BeforeCreate, @BeforeRead and the rest, register what srv.before and srv.on would have. A CDSDispatcher instance in the service implementation is given the handler classes.

Remaining work

The service implementation keeps one place that lists the handler classes, the CDSDispatcher constructor, so a new class is added there as well as written. The decorator compiler options and the generated entity types the decorators take are the project's own setup.

All options on the TypeScript patterns page →

Connections1

Install

npm add @dxfrontier/cds-ts-dispatcher

Derived from the package name. No recipe recorded; the README has the configuration.

Learn more2

Readings
  1. SAP CAP: Controller - Service - Repository architectureDaniel Dragolea, SAP Community
  2. CDS-TS-Dispatcher: Simplifying SAP CAP TypeScript DevelopmentDaniel Dragolea, SAP Community

README

quoted from dxfrontier/cds-ts-dispatcher under MIT, read
dxfrontier/cds-ts-dispatcher/README.mdOn GitHub

CDS-TS-Dispatcher

SAP ts-node Node.js Express.js json npm

NPM Downloads NPM Downloads NPM Version

Tests GitHub Actions Workflow Status GitHub last commit (branch) GitHub issues GitHub contributors GitHub Repo stars

The goal of CDS-TS-Dispatcher is to significantly reduce the boilerplate code required to implement Typescript handlers provided by the SAP CAP framework.

Table of Contents

Prerequisites

Install @sap/cds-dk, typescript, ts-node globally:

npm i -g @sap/cds-dk typescript ts-node tsx

Installation

Option 1 : Install CDS-TS-Dispatcher - New project

Use the following steps if you want to create a new SAP CAP project.

  1. Create new folder :
mkdir project
cd project
  1. Initialize the CDS folder structure :
cds init
  1. Add TypeScript and CDS-Typer to your npm package.json:
cds add typescript
  1. Add CDS-TS-Dispatcher to your npm package.json :
npm install @dxfrontier/cds-ts-dispatcher
  1. It is recommended to use the following tsconfig.json properties:
{
  "compilerOptions": {
    "esModuleInterop": true,
    "skipLibCheck": true,
    "allowJs": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "strictNullChecks": true,
    "strictPropertyInitialization": false,
    "forceConsistentCasingInFileNames": true,
    "allowSyntheticDefaultImports": true,

    "strict": true,

    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,

    "target": "esnext",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",

    "outDir": "./gen/srv",
    "rootDir": ".",

    "paths": {
      "#cds-models/*": ["./@cds-models/*/index.ts"]
    }
  },
  "include": ["./srv", "./@dispatcher"]
}
  1. Install packages
npm install
  1. Run the CDS-TS server
cds-ts w

Important

CDS-TS-Dispatcher uses @sap/cds, @sap/cds-dk version 9.

If you're using @sap/cds, @sap/cds-dk version 8 then install npm install @dxfrontier/cds-ts-dispatcher@4.

(back to top)

Option 2 : Install CDS-TS-Dispatcher - Existing TypeScript project

Use the following steps if you want to add only the @dxfrontier/cds-ts-dispatcher to an existing project :

npm install @dxfrontier/cds-ts-dispatcher

It is recommended to use the following tsconfig.json properties:

{
  "compilerOptions": {
    "esModuleInterop": true,
    "skipLibCheck": true,
    "allowJs": true,
    "strictPropertyInitialization": false,
    "forceConsistentCasingInFileNames": true,
    "allowSyntheticDefaultImports": true,
    "strictNullChecks": true,
    "target": "esnext",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",

    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,

    "strict": true,

    "lib": ["es2022"],

    "outDir": "./gen/srv"
  },
  "include": ["./srv"]
}

Warning

If below message appears

-----------------------------------------------------------------------
WARNING: Package '@sap/cds' was loaded from different installations: [
 '***/node_modules/@sap/cds/lib/index.js',
 '***/node_modules/@dxfrontier/cds-ts-dispatcher/node_modules/@sap/cds/lib/index.js'
] Rather ensure a single install only to avoid hard-to-resolve errors.
-----------------------------------------------------------------------

Run the following command :

npm install -g @sap/cds-dk@latest

(back to top)

Generate CDS Typed entities

Execute the following commands :

cds add typer
npm install

Tip

If above option is being used, this means whenever we change a .CDS file the changes will reflect in the generated @cds-models folder.

(back to top)

Execute the command :

npx @cap-js/cds-typer "*" --outputDirectory ./srv/util/types/entities
  • Target folder :./srv/util/types/entities - Change to your desired destination folder.

(back to top)

-->
Important

Important

Import always the generated entities from the service folders and not from the index.ts

alt text

Tip

By default cds-typer will create in your package.json a quick path alias like :

"imports": {
  "#cds-models/*": "./@cds-models/*/index.js"
}

Use import helper to import entities from #cds-models like example :

  • import { Book } from '#cds-models/CatalogService';

(back to top)

Migration guides

Migration: from @sap/cds v7 to v8

Use the following steps if you want to migrate from @sap/cds@7 to @sap/cds@8:

  1. Verify you've installed the cds@v8 globally by running the following command:
cds -v -i
packagesversion
@cap-js/asyncapi1.0.1
@cap-js/cds-typer0.24.0
@cap-js/cds-types0.6.4
@cap-js/openapi1.0.4
@cap-js/sqlite1.7.3
@sap/cds8.1.0
@sap/cds-compiler5.1.2
@sap/cds-dk (global)8.0.2
@sap/cds-fiori1.2.7
@sap/cds-foss5.0.1
@sap/cds-lsp8.0.0
@sap/cds-mtxs1.18.2
@sap/eslint-plugin-cds3.0.4
Node.jsv22.4.1

Tip

If you see a smaller version than @sap/cds-dk (global) 8.0.2 run the following command :

npm install -g @sap/cds-dk@8
  1. Run the following command inside of your project:
cds add typescript

Tip

Command above will add the following packages:

  • @types/node
  • @cap-js/cds-types
  • @cap-js/cds-typer
  • typescript
  1. After running command above the package.json will look similar to :
{
  "dependencies": {
    "@dxfrontier/cds-ts-dispatcher": "^4.0.0",
    "@dxfrontier/cds-ts-repository": "^5.1.6",
    "@sap/cds": "^8.7.2"
  },
  "devDependencies": {
    "@cap-js/sqlite": "^1.8.0",
    "@cap-js/cds-types": "^0.9.0",
    "typescript": "^5.5.4",
    "@types/node": "^22.1.0",
    "@cap-js/cds-typer": "0.33.0"
  },
  "scripts": {
    "start": "cds-serve",
    "watch": "cds-ts w",
  },
}

Important

You might delete the node_modules folder and package-lock.json in case npm run watch fails working.

Re-run the following command :

npm install
Migration: from @sap/cds v8 to v9

Use the following steps if you want to migrate from @sap/cds@8 to @sap/cds@9:

  1. Verify you've installed the cds@v9 globally by running the following command:
cds -v -i
packagesversion
@cap-js/asyncapi1.0.3
@cap-js/cds-typer0.35.0
@cap-js/cds-types0.11.0
@cap-js/db-service2.1.1
@cap-js/openapi1.2.3
@cap-js/sqlite2.0.1
@sap/cds9.0.3
@sap/cds-compiler6.0.12
@sap/cds-dk (local)9.0.4
@sap/cds-dk (global)9.0.4
@sap/cds-fiori2.0.1
@sap/cds-mtxs3.0.1
Node.jsv22.14.0
home/Users/dragolea/Developer/abs-projects/cds-ts-dispatcher/node_modules/@sap/cds

Tip

If you see a smaller version than @sap/cds-dk (global) 9.0.0 run the following command :

npm install -g @sap/cds-dk@latest
  1. Run the following command inside of your project:
cds add typescript

Tip

Command above will add the following packages:

  • @types/node
  • @cap-js/cds-types
  • @cap-js/cds-typer
  • typescript
  1. After running command above the package.json will look similar to :

{
  "dependencies": {
    "@sap/cds": "^9",
    "express": "^4"
  },
  "engines": {
    "node": "^22"
  },
  "dependencies": {
    "@dxfrontier/cds-ts-dispatcher": "^5.0.0",
    "@dxfrontier/cds-ts-repository": "^6.0.0",
    "@sap/cds": "^8.7.2",
    "express": "^4"
  },
  "devDependencies": {
    "@cap-js/cds-test": "^0.4.0",
    "@cap-js/cds-typer": ">=0.1",
    "@cap-js/cds-types": "^0.10.0",
    "@cap-js/sqlite": "^2",
    "@types/jest": "^30.0.0",
    "@types/node": "^22.0.0",
    "jest": "^30.0.3",
    "ts-jest": "^29.4.0",
    "tsx": "^4",
    "typescript": "^5"
  },
  "scripts": {
    "start": "cds-serve"
  },
  "imports": {
    "#cds-models/*": "./@cds-models/*/index.js"
  }
}

Important

You might delete the node_modules folder and package-lock.json in case npm run watch fails working.

Re-run the following command :

npm install
Migration: from @sap/cds v9 to v10

Use the following steps if you want to migrate from @sap/cds@9 to @sap/cds@10:

  1. Verify you've installed the cds@v10 globally by running the following command:
cds -v -i
packagesversion
@cap-js/cds-types0.18.0
@cap-js/db-service3.0.1
@cap-js/sqlite3.0.2
@sap/cds10.0.3
@sap/cds-compiler7.0.2
@sap/cds-dk10.0.5
Node.jsv22.14.0
home/Users/dragolea/Developer/abs-projects/cds-ts-dispatcher/node_modules/@sap/cds

Tip

If you see a smaller version than @sap/cds-dk 10.0.5 run the following command :

npm install -g @sap/cds-dk@latest
  1. Update the @sap/cds-related dependencies in your package.json:
{
  "dependencies": {
    "@dxfrontier/cds-ts-dispatcher": "^6.0.0",
    "@sap/cds": "^10",
    "express": "^4"
  },
  "devDependencies": {
    "@cap-js/cds-types": "^0.18",
    "@cap-js/sqlite": "^3"
  },
  "engines": {
    "node": ">=22"
  }
}
  1. Review the following @sap/cds@10 specifics relevant to this dispatcher:

Important

  • Node.js >= 22 is required (Node.js 20 support was dropped).
  • @cap-js/cds-types >= 0.18 no longer symlinks into node_modules/@types — run cds add typescript, or add to tsconfig.json: "paths": { "@sap/cds": ["./node_modules/@cap-js/cds-types"] }.
  • Write operations (srv.create / srv.update / srv.delete) now resolve to an array with an additional .affected property. The dispatcher's @AfterCreate, @AfterUpdate and @AfterDelete decorators keep their previous contract (entity data / deleted: boolean) — no handler changes are required.
  • The validation error code ASSERT_NOT_NULL was replaced by ASSERT_MANDATORY.
  • Draft-enabled entities now accept direct CRUD operations on their active data by default (cds.fiori.bypass_draft: true).

See the official migration guide for the full list of breaking changes. Running cds upgrade (available with @sap/cds-dk@10) analyzes your project and reports what needs to change.

Usage

Architecture

We recommend adhering to the Controller-Service-Repository design pattern using the following folder structure:

  1. EntityHandler (Controller) - Responsible for managing the REST interface to the business logic implemented in ServiceLogic
  2. ServiceLogic (Service) - Contains business logic implementations
  3. Repository (Repository) - This component is dedicated to handling entity manipulation operations by leveraging the power of CDS-QL.

Controller-Service-Repository suggested folder structure

alt text <= expanded folders => alt text

(back to top)

Tip

You can have a look over the CDS-TS-Dispatcher-Samples where we use the Controller-Service-Repository pattern and Dispatcher.

CDSDispatcher

CDSDispatcher(entities : Constructable[])

The CDSDispatcher constructor allows you to create an instance for dispatching and managing entities.

Parameters

  • entities (Array): An array of Entity handler(s) (Constructable) that represent the entities in the CDS.

Method

Example

import { CDSDispatcher } from '@dxfrontier/cds-ts-dispatcher';

export = new CDSDispatcher([
  // Entities
  BookHandler,
  ReviewHandler,
  BookStatsHandler,
  // Draft
  BookEventsHandler,
  // Unbound actions
  UnboundActionsHandler,
]).initialize();

// or use
// module.exports = new CDSDispatcher([ ...

Visual image

(back to top)

Decorators
Class
@EntityHandler

The @EntityHandler decorator is utilized at the class-level to annotate a class with:

  1. A specific entity that will serve as the base entity for all handler decorators within the class.
  2. '*' as all entities that will serve as the base entity for all handler decorators within the class.

Overloads

MethodParametersDescription
1. EntityHandler(entity: CDSTyper)Must be a CDS-Typer generated classIt ensures that all handlers within the class operate with the specified entity context.
2. EntityHandler(entity: '*')A wildcard '*' indicating all entitiesIt ensures that all handlers within the class operate with a generic context indicating that registered events will be triggered for all all entities (active entities and draft entities)

Excluded will be @OnAction(), @OnFunction(), @OnEvent(), @OnError() as these actions belongs to the Service itself.

Parameters

  • entity (CDSTyperEntity | '*'): A specialized class generated using the CDS-Typer or generic wild card '*' applicable to all entities.

Example 1 using CDS-Typer

import { EntityHandler } from '@dxfrontier/cds-ts-dispatcher';
import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@EntityHandler(MyEntity)
export class BookHandler {
  // ...
  constructor() {}
  // All events like @AfterRead, @BeforeRead, ... will be triggered based on 'MyEntity'
}

Tip

After creation of BookHandler class, you can import it into the CDSDispatcher.

import { CDSDispatcher } from '@dxfrontier/cds-ts-dispatcher';

export = new CDSDispatcher([
  // Entities
  BookHandler,
  // Unbound actions
  // ...
]).initialize();

Example 2 using * wildcard indicating that events will be triggered for all entities

import { EntityHandler, CDS_DISPATCHER } from '@dxfrontier/cds-ts-dispatcher';

@EntityHandler(CDS_DISPATCHER.ALL_ENTITIES) // or use the '*'
export class AllEntities {
  // ...
  constructor() {}
  // All events like @AfterRead, @BeforeRead, ... will be triggered on all entities using wildcard '*'
}

Tip

After creation of AllEntities class, you can import it into the CDSDispatcher.

import { CDSDispatcher } from '@dxfrontier/cds-ts-dispatcher';

export = new CDSDispatcher([
  // Entities
  AllEntities,
  // Unbound actions
  // ...
]).initialize();

Note

MyEntity was generated using CDS-Typer and imported in the class.

(back to top)

@ServiceLogic

@ServiceLogic()

The @ServiceLogic decorator is utilized at the class-level to annotate a class as a specialized class containing only business logic.

This decorator configures the dependency injection scope for the service class, allowing control over instance lifecycle and sharing across the application.

Parameters

  • scope [Optional] - The dependency injection scope for the service class:

    • 'Singleton' - Single instance shared per CDSDispatcher container (one per CAP service implementation)
    • 'Transient' - New instance created for each injection (default)

    If not provided, defaults to 'Transient' scope behavior.

Example 1 - Default Transient scope

import { ServiceLogic } from '@dxfrontier/cds-ts-dispatcher';

@ServiceLogic()
export class CustomerService {
  private customerCount = 0;

  constructor() {}

  public processCustomer(): void {
    this.customerCount++;
    console.log(`Processing customer #${this.customerCount}`);
  }

  public getCustomerCount(): number {
    return this.customerCount;
  }
}

Example 2 - Singleton scope

import { ServiceLogic } from '@dxfrontier/cds-ts-dispatcher';

@ServiceLogic('Singleton')
export class AnyService {
  private message: string;

  constructor() {}

  public setMessage(message: string): void {
    this.message = message;
  }

  public getMessage(): any {
    return this.message;
  }
}

Important

When using @ServiceLogic('Singleton'), the same instance is shared across the owning CDSDispatcher container — i.e. one CAP service implementation. Any values set in the class properties (like message in the example above) persist across requests within that dispatcher; a class injected by SEPARATE dispatchers gets a separate instance per dispatcher.

This means:

  • State modifications made by one handler class are visible in all other classes of the same dispatcher
  • Data stored in singleton services persists across multiple requests
  • All classes that @Inject this singleton service will receive the exact same instance with the same state

Use 'Singleton' scope carefully and only when you need shared state across a service implementation.

Tip

When applying @ServiceLogic() decorator, the class becomes eligible to be used with Inject decorator for Dependency injection.

(back to top)

@Repository

@Repository()

The @Repository decorator is utilized as a class-level annotation that designates a particular class as a specialized Repository, this class should contain only CDS-QL code.

import { Repository } from '@dxfrontier/cds-ts-dispatcher';

@Repository()
export class CustomerRepository {
  // ...
  constructor() {}
  // ...
}

Tip

When applying @Repository() decorator, the class becomes eligible to be used with Inject decorator for Dependency injection.

[Optional] - CDS-TS-Repository - BaseRepository

The CDS-TS-Repository - BaseRepository was designed to reduce the boilerplate code required to implement data access layer for persistance entities.

It simplifies the implementation by offering a set of ready-to-use actions for interacting with the database. These actions include:

  • .create(): Create new records in the database.
  • .getAll(): Retrieve all records from the database.
  • .find(): Query the database to find specific data.
  • .delete(): Remove records from the database.
  • .exists(): Check the existence of data in the database.
  • and many more ...

Example

import { Repository } from '@dxfrontier/cds-ts-dispatcher';
import { BaseRepository } from '@dxfrontier/cds-ts-repository';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@Repository()
export class CustomerRepository extends BaseRepository<MyEntity> {
  constructor() {
    super(MyEntity);
  }

  public async aMethod() {
    const created = await this.create(...)
    const createdMany = await this.createMany(...)
    const updated = await this.update(...)
    // ...
  }
}

To get started, refer to the official documentation CDS-TS-Repository - BaseRepository. Explore the capabilities it offers and enhance your data access layer with ease.

Note

MyEntity was generated using CDS-Typer and imported in the class.

(back to top)

@UnboundActions

@UnboundActions()

The @UnboundActions decorator is utilized at the class-level to annotate a class as a specialized class which will be used only for Unbound actions.

The following decorators can be used inside of @UnboundActions() :

Example

import { UnboundActions, OnAction, OnFunction, OnEvent, Req, Next, Error, OnSubscribe } from '@dxfrontier/cds-ts-dispatcher';
import { MyAction, MyFunction, MyEvent } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

import type { ActionRequest, ActionReturn, Request, NextEvent } from '@dxfrontier/cds-ts-dispatcher';

@UnboundActions()
export class UnboundActionsHandler {
  // ... @Inject dependencies, if needed.

  constructor() {}

  // Unbound action
  @OnAction(MyAction)
  private async onActionMethod(
    @Req() req: ActionRequest<typeof MyAction>,
    @Next() next: NextEvent,
  ): ActionReturn<typeof MyAction> {
    // ...
  }

  // Unbound Function
  @OnFunction(MyFunction)
  private async onFunctionMethod(
    @Req() req: ActionRequest<typeof MyFunction>,
    @Next() next: NextEvent,
  ): ActionReturn<typeof MyFunction> {
    // ...
  }

  // Unbound event
  @OnEvent(MyEvent)
  private async onEventMethod(@Req() req: Request<MyEvent>) {
    // ...
  }

  // Unbound error
  @OnError()
  private onErrorMethod(@Error() err: Error, @Req() req: Request) {
    // ...
  }

  // Unbound event
  @OnSubscribe({ 
    eventName: 'event_name'
    type: 'SAME_NODE_PROCESS' 
  })
  private async onSubscribe(@Req() req: Request<{foo: string, bar: number}>): Promise<void> {
    // 
  }
}

Imported it in the CDSDispatcher

import { CDSDispatcher } from '@dxfrontier/cds-ts-dispatcher';

export = new CDSDispatcher([ UnboundActionsHandler, ...])
// or
// use module.exports = new CDSDispatcher( ... )

Note

The reason behind introducing a distinct decorator for Unbound actions stems from the fact that these actions are not associated with any specific Entity but instead these actions belongs to the Service itself.

(back to top)

@ServerLifecycle

@ServerLifecycle()

The @ServerLifecycle decorator is utilized at the class-level to annotate a class as the host for the server lifecycle method decorators. Unlike every other host class, it does not register against srv - the three decorators it hosts register against CAP's process-global cds.on('served' | 'listening' | 'shutdown', ...) events instead, because server lifecycle has nothing to do with "the unbound actions of this service".

The following decorators can be used inside of @ServerLifecycle():

Example

import { ServerLifecycle, OnServed, OnListening, OnShutdown } from '@dxfrontier/cds-ts-dispatcher';

@ServerLifecycle()
export class Bootstrap {
  @OnServed()
  public async seed(services: object) {
    // ... one-time startup work, may throw to abort the boot
  }

  @OnListening()
  public logUrl(payload: { server: unknown; url: string }) {
    console.log(`Listening on ${payload.url}`);
  }

  @OnShutdown()
  public async cleanup(error: Error | null) {
    // ... release resources, may run more than once
  }
}

Imported it in the CDSDispatcher

import { CDSDispatcher } from '@dxfrontier/cds-ts-dispatcher';

export = new CDSDispatcher([Bootstrap, ...]).initialize();

Important

A @ServerLifecycle class registers once per process, no matter how many CDSDispatcher instances (or bootstraps, e.g. in tests) list it - the first dispatcher to initialize() resolves the instance the callbacks stay bound to; later dispatchers do not re-register or re-bind.

Note

That once-per-process guard lives in this package's own module scope, so it is per loaded bundle - a consumer that ends up loading both the CJS and ESM build of @dxfrontier/cds-ts-dispatcher in the same process (e.g. through a bundler that duplicates the dependency) would register a @ServerLifecycle class twice. Stick to a single resolved build of the package.

Important

@Use middleware does not apply to @ServerLifecycle classes (lifecycle hooks are not request handlers) - stacking @Use on one fails fast at bootstrap.

Note

A @ServerLifecycle class may only host @OnServed(), @OnListening(), @OnShutdown() - any other handler decorator on the class throws at bootstrap. Conversely, those three decorators throw at bootstrap if hosted outside a @ServerLifecycle class.

(back to top)

@Use

@Use(...Middleware[])

The @Use decorator simplifies the integration of middlewares into your classes.

When @Use decorator applied at the class-level this decorator inject middlewares into the class and gain access to the req: Request and next: NextMiddleware middleware across all events (@AfterRead, @OnRead ...) within that class.

Middleware decorators can perform the following tasks:

  • Execute any code.
  • Make changes to the request object.
  • End the request-response cycle.
  • Call the next middleware function in the stack.
  • If the current middleware function does not end the request-response cycle, it must call next() to pass control to the next middleware function. Otherwise, the request will be left hanging.

Parameters

  • ...Middleware[]): Middleware classes to be injected.

Example: middleware implementation:

import type { MiddlewareImpl, NextMiddleware, Request } from '@dxfrontier/cds-ts-dispatcher';

export class MiddlewareClass implements MiddlewareImpl {
  public async use(req: Request, next: NextMiddleware) {
    console.log('Middleware use method called.');

    await next(); // call next middleware
  }
}

Tip

Inside of the decorator use method you can use @CatchAndSetErrorCode and @CatchAndSetErrorMessage decorators.

Example usage:

import { EntityHandler, Use, Inject, CDS_DISPATCHER } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';
import { Middleware1, Middleware2, MiddlewareN } from 'YOUR_MIDDLEWARE_LOCATION';

import type { Service } from '@dxfrontier/cds-ts-dispatcher';

@EntityHandler(MyEntity)
@Use(Middleware1, Middleware2, MiddlewareN)
export class CustomerHandler {
  // ...
  @Inject(CDS_DISPATCHER.SRV) private srv: Service;
  // ...
  constructor() {}
  // ...
}

Tip

  1. Think of it (middleware) like as a reusable class, enhancing the functionality of all events within the class.
  2. Middlewares when applied with @Use are executed before the normal events.
  3. If you need to apply middleware to a method you should use the method specific @Use decorator .

Warning

If req.reject() is used inside of middleware this will stop the stack of middlewares, this means that next middleware will not be executed.

Note

MyEntity was generated using CDS-Typer and imported in the class.

(back to top)

Field
@Inject

@Inject(serviceIdentifier: ServiceIdentifierOrFunc<unknown>)

The @Inject decorator is utilized as a field-level decorator and allows you to inject dependencies into your classes.

Parameters

  • serviceIdentifier(ServiceIdentifierOrFunc<unknown>): A Class representing the service to inject.

Example

import { EntityHandler, Inject, CDS_DISPATCHER } from "@dxfrontier/cds-ts-dispatcher";
import type { Service } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@EntityHandler(MyEntity)
export class CustomerHandler {
  ...
  @Inject(CustomerService) private customerService: CustomerService
  @Inject(CustomerRepository) private customerService: CustomerRepository
  @Inject(AnyOtherInjectableClass) private repository: AnyOtherInjectableClass

  @Inject(CDS_DISPATCHER.SRV) private srv: Service
  // ...
  constructor() {}
  // ...
}

Note

MyEntity was generated using CDS-Typer and imported in the class.

(back to top)

@Inject(CDS_DISPATCHER.SRV)

@Inject(CDS_DISPATCHER.SRV) private srv: Service

This specialized @Inject can be used as a constant in and contains the CDS.ApplicationService for further enhancements.

It can be injected in the following :

Example

import { EntityHandler, Inject, CDS_DISPATCHER } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

import type { Service } from '@dxfrontier/cds-ts-dispatcher';

@EntityHandler(MyEntity)
// OR @ServiceLogic()
// OR @Repository()
// OR @UnboundActions()
export class CustomerHandler {
  // @Inject dependencies
  @Inject(CDS_DISPATCHER.SRV) private readonly srv: Service;

  constructor() {}
  // ...
}

Tip

The CDS.ApplicationService can be accessed trough this.srv.

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

@Inject(CDS_DISPATCHER.OUTBOXED_SRV)

@Inject(CDS_DISPATCHER.OUTBOXED_SRV) private srv: Service

This specialized @Inject can be used as a constant and contains the CDS.outboxed service.

It can be injected in the following :

Example

import { EntityHandler, Inject, CDS_DISPATCHER } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

import type { Service } from '@dxfrontier/cds-ts-dispatcher';

@EntityHandler(MyEntity)
// OR @ServiceLogic()
// OR @Repository()
// OR @UnboundActions()
export class CustomerHandler {
  // @Inject dependencies
  @Inject(CDS_DISPATCHER.OUTBOXED_SRV) private readonly outboxedSrv: Service;

  constructor() {}
  // ...
}

Tip

More info about outboxed ca be found at SAP CAP Node.js Outboxed

Tip

The CDS.ApplicationService can be accessed trough this.outboxedSrv

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

Parameter
@Req

@Req()

The @Req decorator is utilized at the parameter level to annotate a parameter with the Request object, providing access to request-related information of the current event.

Return

  • Request: An instance of @sap/cds - Request

Example

import { EntityHandler, Req, Results } from '@dxfrontier/cds-ts-dispatcher';
import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

import type { Request } from '@dxfrontier/cds-ts-dispatcher';

@EntityHandler(MyEntity)
export class BookHandler {
  // ...
  constructor() {}
  // ... all events like @AfterRead, @BeforeRead ...

  @AfterRead()
  private async aMethod(@Req() req: Request, @Results() results: MyEntity[]) {
    // ... req...
  }
}

(back to top)

@Res

@Res()

The @Res decorator is utilized at the parameter level to annotate a parameter with the Request.http.res - (Response) object, providing access to response-related information of the current event and it can be used to enhance the Response.

Return

  • RequestResponse: An instance of RequestResponse providing you response-related information.

Example

import { EntityHandler, Req, Results } from '@dxfrontier/cds-ts-dispatcher';
import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

import type { Request, RequestResponse } from '@dxfrontier/cds-ts-dispatcher';

@EntityHandler(MyEntity)
export class BookHandler {
  // ...
  constructor() {}
  // ... all events like @AfterRead, @BeforeRead ...

  @AfterRead()
  private async aMethod(@Req() req: Request, @Res() response: RequestResponse, @Results() results: MyEntity[]) {
    // Example: we assume we want to add a new header language on the response
    // We use => res.setHeader('Accept-Language', 'DE_de');
  }
}

Tip

Decorator @Res can be used in all After, Before and On events.

(back to top)

@Results / @Result

@Results() / @Result

The @Results decorator is utilized at the parameter level to annotate a parameter with the request Results.

Return

  • Array / object: Contains the OData Request Body.

Example

import { EntityHandler, Req, Results } from '@dxfrontier/cds-ts-dispatcher';
import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

import type { Request } from '@dxfrontier/cds-ts-dispatcher';

@EntityHandler(MyEntity)
export class BookHandler {
  // ...
  constructor() {}
  // ... all events like @AfterRead, @BeforeRead ...

  @AfterRead()
  private async aMethod(@Req() req: Request, @Results() results: MyEntity[]) {
    // ...
  }
}

Tip

When using @AfterCreate(), @AfterUpdate() and @AfterDelete() it's recommended to use the @Result decorator for single object result and @Results for arrays of objects.

@AfterCreate()
@AfterUpdate()
private async aMethod(
   @Result() result: Book, // <== @Result() decorator used to annotate it's a an object and not an array
   @Req() req: Request,
 ) {
   // ...
 }

@AfterRead()
private async aMethod(
  @Results() result: Book[], // <== @Results() decorator used to annotate as array of objects
  @Req() req: Request,
) {
  // ...
}

@AfterDelete()
private async aMethod(
@Result() deleted: boolean, // <== @Result() decorator used to annotate as a boolean
@Req() req: Request,
) {
  // ...
}

Tip

Decorators @Results() and @Result() can be applied to all After events.

(back to top)

@Next

@Next()

The @Next decorator is utilized at the parameter level to annotate a parameter with the Next function, which is used to proceed to the next event in the chain of execution.

Return

  • NextEvent: The next event in chain to be called.

Example

import { EntityHandler, Req, Results } from '@dxfrontier/cds-ts-dispatcher';
import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

import type { Request, NextEvent } from '@dxfrontier/cds-ts-dispatcher';

@EntityHandler(MyEntity)
export class BookHandler {
  // ...
  constructor() {}
  // ... all events like @AfterRead, @BeforeRead, @OnCreate ...

  @OnCreate()
  public async onCreate(@Req() req: Request<MyEntity>, @Next() next: NextEvent) {
    return next();
  }
}

Tip

Decorator @Next can be applied to all On, On - draft event decorators.

(back to top)

@Error

@Error()

The @Error decorator is utilized at the parameter level to annotate a parameter with the Error and contains information regarding the failed Request.

Return

  • Error: An instance of type Error.

Example

import { UnboundActions, Req, Error } from '@dxfrontier/cds-ts-dispatcher';
import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

import type { Request } from '@dxfrontier/cds-ts-dispatcher';

@UnboundActions()
export class UnboundActionsHandler {
  // ...
  constructor() {}

  @OnError()
  public onError(@Error() err: Error, @Req() req: Request): void {
    // ...
  }
}

Tip

Decorator @Error can be applied to @OnError() decorator which resides inside of the @UnboundActions().

@Jwt

@Jwt()

The @Jwt decorator is utilized at the parameter level. It will retrieve the to retrieve JWT from the Request that is based on the node req.http.req - IncomingMessage.

Fails if no authorization header is given or has the wrong format.

Return

  • string | undefined : The retrieved JWT token or undefined if no token was found.

Example

import { EntityHandler, Req, Results, Jwt } from '@dxfrontier/cds-ts-dispatcher';
import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

import type { Request } from '@dxfrontier/cds-ts-dispatcher';

@EntityHandler(MyEntity)
export class BookHandler {
  // ...
  constructor() {}
  // ... all events like @AfterRead, @BeforeRead ...

  @AfterRead()
  private async aMethod(@Req() req: Request, @Results() results: MyEntity[], @Jwt() jwt: string | undefined) {
    // ... req...
  }
}

Important

Expected format is Bearer <TOKEN>.

(back to top)

@IsPresent

@IsPresent<Key extends CRUDQueryKeys>(key: Key, property: PickQueryPropsByKey<Key>)

The @IsPresent decorator is utilized at the parameter level. It allows you to verify the existence of a specified Query property values.

Parameters

  • key (string): Specifies the type of query operation. Accepted values are INSERT, SELECT, UPDATE, UPSERT, DELETE.
  • property (string): Specifies the property based on the key.

Return

  • boolean: This decorator returns true if property value is filled, false otherwise

Example

import { EntityHandler, Req, Results, IsPresent } from '@dxfrontier/cds-ts-dispatcher';
import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

import type { Request } from '@dxfrontier/cds-ts-dispatcher';

@EntityHandler(MyEntity)
class BookHandler {
  // ...
  constructor() {}

  @AfterRead()
  private async aMethod(
    @Req() req: Request,
    @Results() results: MyEntity[],

    @IsPresent('SELECT', 'columns') columnsPresent: boolean,
  ) {
    if (columnsPresent) {
      // ...
    }

    // ...
  }
}

Tip

Decorator @IsPresent() works well with @GetQuery().

Note

Protocol creates build INSERT...entries(data) without a .columns list — @IsPresent('INSERT', 'columns') is true only for queries that set columns explicitly (programmatic CQN).

(back to top)

@IsRole

@IsRole(...roles: string[])

The @IsRole decorator is utilized at the parameter level. It allows you to verify if the User has assigned a given role.

It applies an logical OR on the specified roles, meaning it checks if at least one of the specified roles is assigned

Parameters

  • role (...string[]): An array of role names to check if are assigned.

Return

  • boolean: This decorator returns true if at least one of the specified roles is assigned to the current request user, otherwise false.

Example

import { EntityHandler, Req, Results, IsPresent, IsRole } from '@dxfrontier/cds-ts-dispatcher';
import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

import type { Request } from '@dxfrontier/cds-ts-dispatcher';

@EntityHandler(MyEntity)
class BookHandler {
  // ...
  constructor() {}

  @AfterRead()
  private async aMethod(
    @Req() req: Request,
    @Results() results: MyEntity[],

    @IsRole('role', 'anotherRole') roleAssigned: boolean,
  ) {
    if (roleAssigned) {
      // ...
    }

    // ...
  }
}

Tip

The role names correspond to the values of @requires and the @restrict.grants.to annotations in your CDS models.

(back to top)

@IsColumnSupplied

@IsColumnSupplied<T>(field : keyof T)

The @IsColumnSupplied<T>(field : keyof T) decorator is utilized at the parameter level. It allows your to verify the existence of a column in the SELECT, INSERT or UPSERT Query.

Parameters

  • field (string): A string representing the name of the column to be verified.

Type Parameters

  • T: The entity type (e.g., MyEntity) representing the table or collection on which the decorator operates. This allows TypeScript to enforce type safety for the field parameter.

Return :

  • boolean: This decorator returns true if field / column was found, false otherwise

Note

For INSERT / UPSERT queries an explicit .columns list wins when present; otherwise the field is looked up in the entries keys (the shape protocol POST requests produce); false when neither is present.

Example

import { EntityHandler, Req, Results, IsPresent } from '@dxfrontier/cds-ts-dispatcher';
import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

import type { Request } from '@dxfrontier/cds-ts-dispatcher';

@EntityHandler(MyEntity)
class BookHandler {
  // ...
  constructor() {}

  @AfterRead()
  private async aMethod(
    @Req() req: Request,
    @Results() results: MyEntity[],

    @IsColumnSupplied<MyEntity>('price') priceSupplied: boolean,
  ) {
    if (priceSupplied) {
      // ...
    }

    // ...
  }
}

(back to top)

@GetQuery

@GetQuery<Key extends CRUDQueryKeys>(key: Key, property: PickQueryPropsByKey<Key>)

The @GetQuery decorator is utilized at the parameter level. It allows you to retrieve Query property values.

Parameters

  • key (string): Specifies the type of query operation. Accepted values are INSERT, SELECT, UPDATE, UPSERT, DELETE.
  • property (string): Specifies the property based on the key.

Return: Varies based on the specified property :

  • SELECT
    • @GetQuery('SELECT', 'columns') columns: GetQueryType['columns']['forSelect']
    • @GetQuery('SELECT', 'distinct') distinct: GetQueryType['distinct']
    • @GetQuery('SELECT', 'excluding') excluding: GetQueryType['excluding']
    • @GetQuery('SELECT', 'from') from: GetQueryType['from']['forSelect']
    • @GetQuery('SELECT', 'groupBy') groupBy: GetQueryType['groupBy']
    • @GetQuery('SELECT', 'having') having: GetQueryType['having']
    • @GetQuery('SELECT', 'limit') limit: GetQueryType['limit']
    • @GetQuery('SELECT', 'limit.rows') limitRows: GetQueryType['limit']['rows']
    • @GetQuery('SELECT', 'limit.offset') limitOffset: GetQueryType['limit']['offset']
    • @GetQuery('SELECT', 'mixin') mixin: GetQueryType['mixin']
    • @GetQuery('SELECT', 'one') one: GetQueryType['one']
    • @GetQuery('SELECT', 'orderBy') orderBy: GetQueryType['orderBy']
    • @GetQuery('SELECT', 'where') where: GetQueryType['where']
  • INSERT
    • @GetQuery('INSERT', 'columns') columns: GetQueryType['columns']['forInsert']
    • @GetQuery('INSERT', 'entries') entries: GetQueryType['entries']
    • @GetQuery('INSERT', 'into') into: GetQueryType['into']
    • @GetQuery('INSERT', 'rows') rows: GetQueryType['rows']
    • @GetQuery('INSERT', 'values') values: GetQueryType['values']
  • UPDATE
    • @GetQuery('UPDATE', 'data') data: GetQueryType['data']
    • @GetQuery('UPDATE', 'entity') entity: GetQueryType['entity']
    • @GetQuery('UPDATE', 'where') where: GetQueryType['where']
  • UPSERT
    • @GetQuery('UPSERT', 'columns') columns: GetQueryType['columns'][forUpsert]
    • @GetQuery('UPSERT', 'entries') entries: GetQueryType['entries']
    • @GetQuery('UPSERT', 'into') into: GetQueryType['into']
    • @GetQuery('UPSERT', 'rows') rows: GetQueryType['rows']
    • @GetQuery('UPSERT', 'values') values: GetQueryType['values']
  • DELETE
    • @GetQuery('DELETE', 'from') from: GetQueryType['from'][forDelete]

    • @GetQuery('DELETE', 'where') columns: GetQueryType['where']

Example

import { EntityHandler, Req, Results, IsPresent, GetQuery } from '@dxfrontier/cds-ts-dispatcher';
import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

import type { Request, GetQueryType } from '@dxfrontier/cds-ts-dispatcher';

@EntityHandler(MyEntity)
class BookHandler {
  // ...
  constructor() {}

  @AfterRead()
  private async aMethod(
    @Req() req: Request,
    @Results() results: MyEntity[],

    // Check existence of columns
    @IsPresent('SELECT', 'columns') columnsPresent: boolean,

    // Get columns
    @GetQuery('SELECT', 'columns') columns: GetQueryType['columns']['forSelect'],

    @GetQuery('SELECT', 'orderBy') orderBy: GetQueryType['orderBy'],
    @GetQuery('SELECT', 'groupBy') groupBy: GetQueryType['groupBy'],
  ) {
    if (columnsPresent) {
      // do something with columns values
      // columns.forEach(...)
    }

    // ...
  }
}

Tip

Decorator @GetQuery() can be used to get the Query property and @IsPresent() can check if the Query property is empty or not.

(back to top)

@GetRequest

@GetRequest(property : keyof Request)

The @GetRequest decorator is utilized at the parameter level. It allows you to retrieve the specified property value from the Request object.

Parameters

  • property (string): Specifies the property to retrieve from the Request object.

Return: Varies based on the specified property :

  • @GetRequest('entity') entity: Request['entity'],
  • @GetRequest('event') event: Request['event'],
  • @GetRequest('features') features: Request['features'],
  • @GetRequest('headers') headers: Request['headers'],
  • @GetRequest('http') http: Request['http'],
  • @GetRequest('id') id: Request['id'],
  • @GetRequest('locale') locale: Request['locale'],
  • @GetRequest('method') method: Request['method'],
  • @GetRequest('params') params: Request['params'],
  • @GetRequest('query') query: Request['query'],
  • @GetRequest('subject') subject: Request['subject'],
  • @GetRequest('target') target: Request['target'],
  • @GetRequest('tenant') tenant: Request['tenant'],
  • @GetRequest('timestamp') timestamp: Request['timestamp'],
  • @GetRequest('user') user: Request['user'],

Example

import { EntityHandler, Results, GetRequest } from '@dxfrontier/cds-ts-dispatcher';
import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

import type { Request } from '@dxfrontier/cds-ts-dispatcher';

@EntityHandler(MyEntity)
class BookHandler {
  // ...
  constructor() {}

  @AfterRead()
  private async aMethod(
    // @Req() req: Request, we assume we don't need the hole Request object and we need only 'locale' and 'method'
    @Results() results: MyEntity[],

    @GetRequest('locale') locale: Request['locale'],
    @GetRequest('method') method: Request['method'],
  ) {
    // do something with 'locale' and 'method' ...
  }
}

Tip

Type Request can be import from :

import type { Request } from '@dxfrontier/cds-ts-dispatcher';

(back to top)

@SingleInstanceSwitch

@SingleInstanceSwitch

The @SingleInstanceSwitch() decorator is applied at the parameter level.

It allows you to manage different behaviors based on whether the request is for a single entity instance or an entity set, the parameter assigned to the decorator will behave like a switch.

Return

  • true when the Request is single instance
  • false when the Request is entity set

Example 1

Single request : http://localhost:4004/odata/v4/main/`MyEntity(ID=2f12d711-b09e-4b57-b035-2cbd0a023a09)`

import { AfterRead, SingleInstanceCapable } from "@dxfrontier/cds-ts-dispatcher";
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@AfterRead()
private async singeInstanceMethodAndEntitySet(@Results() results : MyEntity[], @Req() req: Request<MyEntity>, @SingleInstanceSwitch() isSingleInstance: boolean) {
  if(isSingleInstance) {
    // This will be executed only when single instance is called : http://localhost:4004/odata/v4/main/MyEntity(ID=2f12d711-b09e-4b57-b035-2cbd0a023a09)
    return this.customerService.handleSingleInstance(req)
  }

  // nothing to entity set
}

Example 2

Entity request : http://localhost:4004/odata/v4/main/`MyEntity`

import { AfterRead, SingleInstanceCapable } from "@dxfrontier/cds-ts-dispatcher";
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@AfterRead()
private async singeInstanceMethodAndEntitySet(@Results() results : MyEntity[], @Req() req: Request<MyEntity>, @SingleInstanceSwitch() isSingleInstance: boolean) {
  if(isSingleInstance) {
    // This will be executed only when single instance is called : http://localhost:4004/odata/v4/main/MyEntity(ID=2f12d711-b09e-4b57-b035-2cbd0a023a09)
    // ...
  }

  // ... this will be executed when entity set is called : http://localhost:4004/odata/v4/main/MyEntity
  results[0] = {
    name : 'new value'
  }
}

Tip

Decorator @SingleInstanceSwitch can be used together with the following decorator events:

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

@ValidationResults

@ValidationResults

The @ValidationResults decorator allows to capture and inject validation results directly into a method parameter, allowing access to individual validation flags within the decorated method.

When used alongside the @Validate decorator, it enables you to perform conditional logic based on specific validation outcomes.

Example

@BeforeCreate()
@Validate<MyEntity>({ action: 'isLowercase', exposeValidatorResult: true }, 'comment')
@Validate<MyEntity>({ action: 'endsWith', target: 'N', exposeValidatorResult: true }, 'description')
public async beforeCreate(
  @Req() req: Request<MyEntity>,
  @ValidationResults() validator: ValidatorFlags<'isLowercase' | 'endsWith'>
) {
    // Conditional handling based on validation flags
    if (validator.isLowercase) {
      // Execute logic when field `comment` is lowercase 
    }
    else {
      // Execute logic when field `comment` is not lowercase
    }

    if (validator.endsWith) {
      // Execute logic when field `description` is endsWith with letter 'N'
    }
    else {
      // Execute logic when field `description` doesn't endsWith with letter 'N'
    }
}

Important

For @ValidationResults to work, each @Validate decorator must set the exposeValidatorResult option to true. This ensures that the validation results are available as flags in the method.

(back to top)

@Locale

@Locale

Parameter decorator used to inject locale information into a method parameter.

Example

@BeforeCreate()
public async beforeCreate(
  @Req() req: Request<MyEntity>,
  @Locale() locale: string
) {
  if (locale === 'en-US') {
    // handle logic specific to the 'en-US' locale
  }
}

(back to top)

@Env

@Env<T>(env: PropertyStringPath<T>)

The @Env decorator is a parameter decorator used to inject values from the cds.env configuration object directly into a method parameter.

Parameters

  • env (string): A string path representing a property from cds.env. This path follows the format property string path, which allows access to deeply nested configuration properties.
    • E.g. : 'requires.db.credentials.url' corresponds to cds.env.requires.db.credentials.url object.

Type Parameters

  • T: The CDS environmental variables type (e.g., cds env get) representing the collection on which the decorator operates. This allows TypeScript to enforce type safety.

Return :

  • The decorator returns the value of the specified cds.env property value.

Example

import { CDS_ENV } from '#dispatcher';  

@BeforeCreate()
public async beforeCreate(
  @Req() req: Request<MyEntity>,
  @Env<CDS_ENV>('requires.db.credentials.url') dbUrl: CDS_ENV['requires']['db']['credentials']['url'],
  // or @Env<CDS_ENV>('requires.db.credentials.url') dbUrl: string
  // or @Env<CDS_ENV>('requires.db.credentials.url') dbUrl: any
  // or any other type if you do not want to use the CDS_ENV generated types
) {
  if (dbUrl) {
    // handle custom logic ...
  }
}

Note

When you install cds-ts-dispatcher (e.g. npm install @dxfrontier/cds-ts-dispatcher) or run a general npm install, the following will be generated or updated :

  • New @dispatcher folder is generated at the project root. This folder contains the CDS ENV TS interfaces, generated based on the structure of your current cds.env project specific configuration (read programmatically from your project's @sap/cds, equivalent to the cds env get cli command output).
...
@dispatcher
...
  • package.json will be updated with a new import:
 "imports": {
   "#dispatcher": "./@dispatcher/index.js"
 }
  • tsconfig.json will be updated:
 "include": [
   "...",
   "./@dispatcher"
 ]
  • .gitignore will be updated::
...
@dispatcher

Note

The @dispatcher folder is regenerated each time you run npm install.

Tip

You can import the generated CDS env from the generated @dispatcher folder by using :

import { CDS_ENV } from '#dispatcher'; 

(back to top)

@Subject

@Subject()

The @Subject decorator is utilized at the parameter level. It injects req.subject — the CQN ref identifying the request's target instance — directly into a method parameter.

It is the sanctioned replacement for req.query on bound actions / functions in @sap/cds 10 (req.query on bound operations is planned for removal in @sap/cds 11).

Return

  • ref : The CQN ref pointing to the request's target instance.

Example

import { OnBoundFunction, Req, Subject } from '@dxfrontier/cds-ts-dispatcher';
import type { Request } from '@dxfrontier/cds-ts-dispatcher';
import type { ref } from '@sap/cds';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@OnBoundFunction(MyEntity.actions.SomeFunction)
public async someBoundFunction(@Req() req: Request, @Subject() subject: ref) {
  const instance = await SELECT.one.from(subject);
}

Tip

Import the ref type from @sap/cds to type the injected subject.

(back to top)

@Affected

@Affected()

The @Affected decorator is utilized at the parameter level. It injects the database affected row count of the current request into a method parameter.

It is defined for CREATE / UPDATE / DELETE @After* handlers under @sap/cds >= 10 (the row count reported by the database) and is undefined for READ.

Return

  • number | undefined : The number of rows affected by the current request, or undefined for READ.

Example

import { AfterDelete, Affected, Req } from '@dxfrontier/cds-ts-dispatcher';
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@AfterDelete()
public async afterDelete(@Req() req: Request<MyEntity>, @Affected() affected: number | undefined) {
  req.notify(`Deleted ${affected} row(s)`);
}

Note

@Affected does not change what @Results / @Result receive, it only exposes the database row count.

(back to top)

@Data

@Data()

The @Data decorator is utilized at the parameter level. It injects req.data — the payload of the current request — directly into a method parameter, typed.

Return

  • T : The payload of the current request (req.data).

Example

import { OnCreate, Data } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@OnCreate()
public async onCreate(@Data() data: MyEntity) {
  // ... custom logic based on data
}

Tip

@Data keeps handler signatures and unit tests simple — pass a plain object instead of mocking the whole Request.

(back to top)

@Param

@Param<T>(field: keyof T)

The @Param decorator is utilized at the parameter level. It injects req.data[field] — a single field of the current request's payload — directly into a method parameter.

Parameters

  • field (keyof T) : The name of the field to get within req.data.

Type Parameters

  • T : The entity type the field is checked against, allowing TypeScript to enforce type safety.

Return

  • The value of req.data[field].

Example

import { OnCreate, Param } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@OnCreate()
public async onCreate(@Param<MyEntity>('title') title: string, @Param<MyEntity>('stock') stock: number) {
  // ... custom logic based on title and stock
}

Tip

@Param is repeatable — decorate as many parameters as the fields you need from req.data.

(back to top)

@UserInfo

@UserInfo()

The @UserInfo decorator is utilized at the parameter level. It injects req.user — the authenticated user of the current request — directly into a method parameter.

Return

  • User : The authenticated user of the current request.

Example

import { OnUpdate, UserInfo } from '@dxfrontier/cds-ts-dispatcher';
import type { User } from '@sap/cds';

@OnUpdate()
public async onUpdate(@UserInfo() user: User) {
  if (user.is('Manager')) {
    // ... custom ownership / authorization logic
  }
}

Tip

Import the User type from @sap/cds to type the injected user.

(back to top)

@Tenant

@Tenant()

The @Tenant decorator is utilized at the parameter level. It injects req.tenant — the tenant of the current request — directly into a method parameter.

Return

  • string | undefined : The tenant of the current request.

Example

import { OnRead, Tenant } from '@dxfrontier/cds-ts-dispatcher';

@OnRead()
public async onRead(@Tenant() tenant: string | undefined) {
  // ... custom logic based on tenant
}

(back to top)

@Diff

@Diff()

The @Diff decorator is utilized at the parameter level. It injects await req.diff() — CAP's deep old-vs-new change-set of the current request against the current database state (compositions expanded, draft-aware) — directly into a method parameter.

Return

  • T : The before/after delta of the request's target entity.

Example

import { BeforeUpdate, Diff } from '@dxfrontier/cds-ts-dispatcher';
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@BeforeUpdate()
public async beforeUpdate(@Req() req: Request<MyEntity>, @Diff() diff: MyEntity) {
  // ... custom logic based on diff
}

Important

@Diff costs one extra database read (it re-reads the current state to compute the delta) and resolves asynchronously - the handler body starts a microtask later than sibling handlers on the same event. Do not rely on synchronous side-effect ordering against sibling handlers. @Diff is only supported on @EntityHandler write events and is not supported on @OnError (a decoration-time error).

Note

req.diff() is a semi-stable @sap/cds API not documented in the current capire docs. Observed payload shape on @sap/cds 10 (pinned by the dispatcher's integration tests): for an UPDATE the top-level fields hold the NEW (incoming) values, the OLD (pre-update) values sit nested under _old, plus an _op: 'update' marker — unchanged fields are omitted entirely, key fields are always present. Do not assume diff.<field> is the old value.

(back to top)

@Msg

@Msg()

Warning

DEPRECATED: The @Msg decorator is deprecated and will be removed in a future version. Please use @Req decorator instead for handling message data in event subscribers.

The @Msg decorator is a parameter decorator used to inject response Emitter directly into a method parameter.

Parameters

Return :

  • The decorator returns an object of type :
{
  event: string,
  data: T,
  headers: any,
  inbound: boolean
}

Example

import { OnSubscribe, Req } from '@dxfrontier/cds-ts-dispatcher';
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

@OnSubscribe({
  eventName: 'AnEventName',
  type: 'MESSAGE_BROKER',
})
public async onSubscribe(@Req() req: Request<{ foo: number; bar: string }>): Promise<void> {
  // ...
}

Important

To have the req typed you can use type Request<T> where T can be a CDS event or a plain object.

Tip

Decorator @Req should be used on decorator @OnSubscribe.

(back to top)

Method — CRUD, active entity
Before

Use @BeforeCreate(), @BeforeRead(), @BeforeUpdate(), @BeforeDelete() to register handlers to run before .on handlers, frequently used for validating user input.

The handlers receive one argument:

  • req of type Request

See also the official SAP JS CDS-Before event

Tip

If @odata.draft.enabled: true to manage event handlers for draft version you can use

  • @BeforeCreateDraft()
  • @BeforeReadDraft()
  • @BeforeUpdateDraft()
  • @BeforeDeleteDraft()

(back to top)

@BeforeCreate

@BeforeCreate()

Example

import { BeforeCreate } from "@dxfrontier/cds-ts-dispatcher";
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@BeforeCreate()
private async beforeCreateMethod(@Req() req: Request<MyEntity>) {
  // ...
}

Equivalent to 'JS'

this.before('CREATE', MyEntity, async (req) => {
  // ...
});

Important

It is important to note that the decorator @BeforeCreate() will be triggered based on the EntityHandler argument => MyEntity.

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

@BeforeRead

@BeforeRead()

Example

import { BeforeRead } from "@dxfrontier/cds-ts-dispatcher";
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@BeforeRead()
private async beforeReadMethod(@Req() req: Request<MyEntity>) {
  // ...
}

Equivalent to 'JS'

this.before('READ', MyEntity, async (req) => {
  // ...
});

Important

Decorator @BeforeRead() will be triggered based on the EntityHandler argument => MyEntity.

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

@BeforeUpdate

@BeforeUpdate()

Example

import { BeforeUpdate } from "@dxfrontier/cds-ts-dispatcher";
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@BeforeUpdate()
private async beforeUpdateMethod(@Req() req: Request<MyEntity>) {
  // ...
}

Equivalent to 'JS'

this.before('UPDATE', MyEntity, async (req) => {
  // ...
});

Important

Decorator @BeforeUpdate() will be triggered based on the EntityHandler argument => MyEntity

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

@BeforeDelete

@BeforeDelete()

Example

import { BeforeDelete } from "@dxfrontier/cds-ts-dispatcher";
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@BeforeDelete()
private async beforeDeleteMethod(@Req() req: Request<MyEntity>) {
  // ...
}

Equivalent to 'JS'

this.before('DELETE', MyEntity, async (req) => {
  // ...
});

Important

Decorator @BeforeDelete() will be triggered based on the EntityHandler argument => MyEntity.

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

@BeforeAction

@BeforeAction(name : CdsAction)

The @BeforeAction decorator is utilized as a method-level decorator to execute custom logic before an unbound action is triggered.

This decorator allows you to run validation, authorization, or preparation logic before the actual action implementation is executed.

Parameters

  • name (CdsAction) : Representing the CDS action defined in the CDS file.

Example

import { BeforeAction, Req } from '@dxfrontier/cds-ts-dispatcher';
import type { ActionRequest } from '@dxfrontier/cds-ts-dispatcher';

import { MyAction } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@BeforeAction(MyAction)
private async beforeMyAction(@Req() req: ActionRequest<typeof MyAction>): Promise<void> {
  // Validate input parameters before action execution
  if (!req.data.requiredParam) {
    req.reject(400, 'Required parameter is missing');
  }
  
  // Additional validation or preparation logic
  console.log('Action validation completed');
}

Equivalent to 'JS'

this.before(MyAction, async (req) => {
  // ...
});

Note

MyAction was generated using CDS-Typer and imported in the class.

Important


Decorator @BeforeAction should be used inside @UnboundActions() class.

Tip

Use @BeforeAction to:

  • Validate action parameters before execution
  • Perform authorization checks
  • Log action invocations
  • Prepare or transform input data

(back to top)

@BeforeBoundAction

@BeforeBoundAction(name : CdsFunction | string)

The @BeforeBoundAction decorator is utilized as a method-level decorator to execute custom logic before a bound action is triggered on an entity.

This decorator allows you to run validation, authorization, or preparation logic before the actual bound action implementation is executed on a specific entity instance.

Parameters

  • name (CdsFunction | string) : Representing the CDS bound action defined in the CDS file or the name of the action as string.

Example

import { BeforeBoundAction, Req } from '@dxfrontier/cds-ts-dispatcher';
import type { ActionRequest } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@BeforeBoundAction(MyEntity.actions.approve)
private async beforeApprove(@Req() req: ActionRequest<typeof MyEntity.actions.approve>) {
  console.log('Bound action validation completed');
}

Equivalent to 'JS'

this.before(MyEntity.actions.approve, MyEntity, async (req) => {
  // ...
});

Note

MyEntity was generated using CDS-Typer and imported in the class.

Important


Decorator @BeforeBoundAction should be used inside @EntityHandler() class as it operates on a specific entity.

Tip

Use @BeforeBoundAction to:

  • Validate entity state before bound action execution
  • Perform authorization checks on specific entity instances
  • Log bound action invocations
  • Prepare or transform entity data before processing
  • Check business rules specific to the entity instance

(back to top)

@BeforeFunction

@BeforeFunction(name : CdsFunction | string)

The @BeforeFunction decorator is utilized as a method-level decorator to execute custom logic before an unbound function is triggered.

This decorator allows you to run validation, authorization, or preparation logic before the actual function implementation is executed.

Parameters

  • name (CdsFunction | string) : Representing the CDS function defined in the CDS file.

Example

import { BeforeFunction, Req } from '@dxfrontier/cds-ts-dispatcher';
import type { Request, ActionRequest } from '@dxfrontier/cds-ts-dispatcher';

import { MyFunction } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@BeforeFunction(MyFunction)
private async beforeMyFunction(@Req() req: ActionRequest<typeof MyFunction>): Promise<void> {
  // Additional validation or preparation logic
  console.log('Function validation completed');
}

Equivalent to 'JS'

this.before(MyFunction, async (req) => {
  // ...
});

Note

MyFunction was generated using CDS-Typer and imported in the class.

Important


Decorator @BeforeFunction should be used inside @UnboundActions() class.

Tip

Use @BeforeFunction to:

  • Validate function parameters before execution
  • Perform authorization checks
  • Log function invocations
  • Prepare or transform input data
  • Check system prerequisites before function execution

(back to top)

@BeforeBoundFunction

@BeforeBoundFunction(name : CdsFunction | string)

The @BeforeBoundFunction decorator is utilized as a method-level decorator to execute custom logic before a bound function is triggered on an entity.

This decorator allows you to run validation, authorization, or preparation logic before the actual bound function implementation is executed on a specific entity instance.

Parameters

  • name (CdsFunction | string) : Representing the CDS bound function defined in the CDS file or the name of the function as a string.

Example

import { BeforeBoundFunction, Req } from '@dxfrontier/cds-ts-dispatcher';
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@BeforeBoundFunction(MyEntity.actions.myFunction)
private async someAction(@Req() req: ActionRequest<typeof MyEntity.actions.myFunction>) {
  // Additional validation or preparation logic
  console.log('Bound function validation completed');
}

Equivalent to 'JS'

this.before(MyEntity.actions.myFunction, MyEntity, async (req) => {
  // ...
});

Note

MyEntity was generated using CDS-Typer and imported in the class.

Important


Decorator @BeforeBoundFunction should be used inside @EntityHandler() class as it operates on a specific entity.

Tip

Use @BeforeBoundFunction to:

  • Validate entity state before bound function execution
  • Perform authorization checks on specific entity instances
  • Log bound function invocations
  • Prepare or transform entity data before processing
  • Check business rules specific to the entity instance

(back to top)

@BeforeAll

The @BeforeAll decorator is triggered whenever any CRUD (Create, Read, Update, Delete) event occurs, whether the entity is active or in draft mode.

ACTIVE ENTITY

For active entities, the @BeforeAll decorator will be triggered when at least one of the following events occurs:

DRAFT

For draft entities, the @BeforeAll decorator will be triggered when at least one of the following events occurs:

@BeforeAll()

Example 1

In this example, the @BeforeAll() decorator of the beforeAllEvents method is executed before any CRUD operation (CREATE, READ, UPDATE, DELETE, BOUND ACTIONS, BOUND FUNCTIONS)) on the MyEntity entity.

Since the class is annotated with @EntityHandler(MyEntity), the decorator is scoped to this specific entity, meaning it will only be triggered for operations related to MyEntity.

import { BeforeAll, EntityHandler } from "@dxfrontier/cds-ts-dispatcher";
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@EntityHandler(MyEntity)
export class BookHandler {
  // ...
  constructor() {}
  // All events like @AfterRead, @BeforeRead, ... will be triggered based on 'MyEntity'

  @BeforeAll()
  private async beforeAllEvents(@Req() req: Request<MyEntity>) {
    // ...
  }
}

Equivalent to 'JS'

this.before('*', MyEntity, async (req) => {
  // ...
});

Example 2

In this example, the @BeforeAll() decorator is used in a more generic way. Unlike Example 1, where it applies only to MyEntity, this setup ensures that beforeAllEvents is triggered for all entities.

This means the method will execute before any CRUD operation on any entity handled within the class.

import { BeforeAll, EntityHandler, CDS_DISPATCHER } from "@dxfrontier/cds-ts-dispatcher";
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@EntityHandler(CDS_DISPATCHER.ALL_ENTITIES)
export class BookHandler {
  // ...
  constructor() {}
  // All events like @AfterRead, @BeforeRead, ... will be triggered based on 'MyEntity'

  @BeforeAll()
  private async beforeAllEvents(@Req() req: Request<MyEntity>) {
    // ...
  }
}

Equivalent to 'JS'

this.before('*', '*', async (req) => {
  // ...
});

Important

Decorator @BeforeAll() will be triggered based on the EntityHandler argument => MyEntity.

Tip

If the entity has drafts enabled @odata.draft.enabled: true, the @BeforeAll decorator will still be triggered for draft events.

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

After

Use @AfterCreate(), @AfterRead(), @AfterUpdate(), @AfterDelete() register handlers to run after the .on handlers, frequently used to enrich outbound data.

The handlers receive two arguments:

ParametersDecoratorDescription
results, req@AfterReadAn array of type MyEntity[] and the Request.
result, req@AfterUpdate
@AfterCreate
An object of type MyEntity and the Request.
deleted, req@AfterDeleteA boolean indicating whether the instance was deleted and the Request.

Tip

If @odata.draft.enabled: true to manage event handlers for draft version you can use :

  • @AfterCreateDraft()
  • @AfterReadDraft()
  • @AfterReadDraftSingleInstance()
  • @AfterUpdateDraft()
  • @AfterDeleteDraft()

(back to top)

@AfterCreate

@AfterCreate()

Example

import { AfterCreate } from "@dxfrontier/cds-ts-dispatcher";
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@AfterCreate()
private async afterCreateMethod(@Result() result: MyEntity, @Req() req: Request<MyEntity>) {
  // ...
}

Equivalent to 'JS'

this.after('CREATE', MyEntity, async (result, req) => {
  // ...
});

Important

Decorator @AfterCreate() will be triggered based on the EntityHandler argument => MyEntity.

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

@AfterRead

@AfterRead()

Example

import { AfterRead, Results, Req } from "@dxfrontier/cds-ts-dispatcher";
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@AfterRead()
private async afterReadMethod(@Results() results: MyEntity[], @Req() req: Request<MyEntity>) {
  // ...
}

Equivalent to 'JS'

this.after('READ', MyEntity, async (results, req) => {
  // ...
});

Important

Decorator @AfterRead() will be triggered based on the EntityHandler argument MyEntity.

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

@AfterReadEachInstance

@AfterReadEachInstance()

The @AfterReadEachInstance decorator is used to execute custom logic after performing a read operation on each individual instance. This behavior is analogous to the JavaScript Array.prototype.forEach method.

Example

import { AfterReadEachInstance, Result, Req } from "@dxfrontier/cds-ts-dispatcher";
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@AfterReadEachInstance()
private async afterEach(@Result() result: MyEntity, @Req() req: Request<MyEntity>) {
  // ...
}

Equivalent to 'JS'

this.after('each', MyEntity, async (result, req) => {
  // ...
});

Important

Decorator @AfterReadEachInstance() will be triggered based on the EntityHandler argument MyEntity.

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

@AfterUpdate

@AfterUpdate()

Example

Single request : http://localhost:4004/odata/v4/main/`MyEntity(ID=2f12d711-b09e-4b57-b035-2cbd0a023a09)`

import { AfterUpdate } from "@dxfrontier/cds-ts-dispatcher";
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@AfterUpdate()
private async afterUpdateMethod(@Result() result: MyEntity, @Req() req: Request<MyEntity>) {
  // ...
}

Equivalent to 'JS'

this.after('UPDATE', MyEntity, async (result, req) => {
  // ...
});

Important

Decorator @AfterUpdate() will be triggered based on the EntityHandler argument => MyEntity.

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

@AfterDelete

@AfterDelete()

Example

import { AfterDelete} from "@dxfrontier/cds-ts-dispatcher";
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@AfterDelete()
private async afterDeleteMethod(@Result() deleted: boolean, @Req() req: Request) {
  // ...
}

Equivalent to 'JS'

this.after('DELETE', MyEntity, async (deleted, req) => {
  // ...
});

Important

Decorator @AfterDelete() will be triggered based on the EntityHandler argument => MyEntity.

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

@AfterAction

@AfterAction(name : CdsAction)

The @AfterAction decorator is utilized as a method-level decorator to execute custom logic after an unbound action has been executed.

This decorator allows you to run post-processing logic, such as logging, data cleanup, or triggering subsequent operations after the actual action implementation has completed.

Parameters

  • name (CdsAction) : Representing the CDS action defined in the CDS file.

Example

import { AfterAction, Req, Result } from '@dxfrontier/cds-ts-dispatcher';
import type { ActionRequest, ActionReturn } from '@dxfrontier/cds-ts-dispatcher';

import { MyAction } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@AfterAction(MyAction)
private async afterMyAction(
  @Result() result: ActionReturn<typeof MyAction>, 
  @Req() req: ActionRequest<typeof MyAction>
): Promise<void> {
  // Post-processing logic after action execution
  console.log('Action completed successfully');
  
  // Log the result or perform cleanup operations
  if (result) {
    console.log('Action result:', result);
  }
  
  // Additional post-processing logic
  await this.auditService.logActionExecution(req.user, 'MyAction', result);
}

Equivalent to 'JS'

this.after(MyAction, async (result, req) => {
  // ...
});

Note

MyAction was generated using CDS-Typer and imported in the class.

Important


Decorator @AfterAction should be used inside @UnboundActions() class.

Tip

Use @AfterAction to:

  • Log action execution results
  • Perform cleanup operations
  • Trigger subsequent business processes
  • Send notifications based on action outcomes
  • Update audit trails or analytics

(back to top)

@AfterAll

The @AfterAll decorator is triggered whenever any CRUD (Create, Read, Update, Delete) event occurs, whether the entity is active or in draft mode.

ACTIVE ENTITY

For active entities, the @AfterAll decorator will be triggered when at least one of the following events occurs:

DRAFT

For draft entities, the @AfterAll decorator will be triggered when at least one of the following events occurs:

@AfterAll()

Example 1

In this example, the @AfterAll() decorator of the afterAll method is executed after any CRUD operation (CREATE, READ, UPDATE, DELETE, BOUND ACTIONS, BOUND FUNCTIONS)) on the MyEntity entity.

Since the class is annotated with @EntityHandler(MyEntity), the decorator is scoped to this specific entity, meaning it will only be triggered for operations related to MyEntity.

import { AfterAll, EntityHandler } from "@dxfrontier/cds-ts-dispatcher";
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@EntityHandler(MyEntity)
export class BookHandler {
  // ...
  constructor() {}
  // All events like @AfterRead, @BeforeRead, ... will be triggered based on 'MyEntity'

  @AfterAll()
  private async afterAll(@Result() result: MyEntity | MyEntity[] | boolean, @Req() req: Request) {
    if(Array.isArray(result)) {
      // when after `READ` event was triggered
    }
    else if(typeof result === 'boolean' ) {
      // when after `DELETE` event was triggered
    }
    else {
      // when after `CREATE`, `UPDATE` was triggered
    }

    // ...
  }
}

Equivalent to 'JS'

this.after('*', MyEntity, async (result, req) => {
  // ...
});

Example 2

In this example, the @AfterAll() decorator is used in a more generic way. Unlike Example 1, where it applies only to MyEntity, this setup ensures that afterAll is triggered for all entities.

This means the method will execute before any CRUD operation on any entity handled within the class.

import { AfterAll, EntityHandler} from "@dxfrontier/cds-ts-dispatcher";
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@EntityHandler(CDS_DISPATCHER.ALL_ENTITIES)
export class BookHandler {
  // ...
  constructor() {}
  // All events like @AfterRead, @BeforeRead, ... will be triggered based on 'MyEntity'

  @AfterAll()
  private async afterAll(@Result() result: MyEntity | MyEntity[] | boolean, @Req() req: Request) {
    if(Array.isArray(result)) {
      // when after `READ` event was triggered
    }
    else if(typeof result === 'boolean' ) {
      // when after `DELETE` event was triggered
    }
    else {
      // when after `CREATE`, `UPDATE` was triggered
    }

    // ...
  }
}

Equivalent to 'JS'

this.after('*', '*', async (result, req) => {
  // ...
});

Important

Decorator @AfterAll() will be triggered based on the EntityHandler argument => MyEntity.

Tip

If the entity has drafts enabled @odata.draft.enabled: true, the @AfterAll decorator will also be triggered for draft events.

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

On

Use @OnCreate(), @OnRead(), @OnUpdate(), @OnDelete(), OnAction(), @OnFunction(), @OnBoundAction(), @OnBoundFunction() handlers to fulfill requests, e.g. by reading/writing data from/to databases handlers.

The handlers receive two arguments:

  • req of type Request
  • next of type NextEvent

Tip

If @odata.draft.enabled: true to manage event handlers for draft version you can use :

  • @OnCreateDraft()
  • @OnReadDraft()
  • @OnUpdateDraft()
  • @OnDeleteDraft()
  • @OnBoundActionDraft()
  • @OnBoundFunctionDraft()

(back to top)

@OnCreate

@OnCreate()

Example

import { OnCreate, Next } from "@dxfrontier/cds-ts-dispatcher";
import type { Request, NextEvent } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@OnCreate()
private async onCreateMethod(@Req() req: Request<MyEntity>, @Next() next: NextEvent) {
  // ...

  return next();
}

Equivalent to 'JS'

this.on('CREATE', MyEntity, async (req, next) => {
  // ...
});

Important

Decorator @OnCreate() will be triggered based on the EntityHandler argument => MyEntity.

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

@OnRead

@OnRead()

Example

import { OnRead, Next } from "@dxfrontier/cds-ts-dispatcher";
import type { Request, NextEvent } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@OnRead()
private async onReadMethod(@Req() req: Request<MyEntity>, @Next() next: NextEvent) {
  // ...

  return next();
}

Equivalent to 'JS'

this.on('READ', MyEntity, async (req, next) => {
  // ...
});

Important

Decorator @OnRead() will be triggered based on the EntityHandler argument => MyEntity.

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

@OnUpdate

@OnUpdate()

Example


import { OnUpdate, Next } from "@dxfrontier/cds-ts-dispatcher";
import type { Request, NextEvent } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@OnUpdate()
private async onUpdateMethod(@Req() req: Request<MyEntity>, @Next() next: NextEvent) {
  // ...

  return next();
}

Equivalent to 'JS'

this.on('UPDATE', MyEntity, async (req, next) => {
  // ...
});

Important

Decorator @OnUpdate() will be triggered based on the EntityHandler argument => MyEntity.

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

@OnDelete

@OnDelete()

Example

import { OnDelete, Next } from "@dxfrontier/cds-ts-dispatcher";
import type { Request, NextEvent } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@OnDelete()
private async onDeleteMethod(@Req() req: Request<MyEntity>, @Next() next: NextEvent) {
  // ...

  return next();
}

Equivalent to 'JS'

this.on('DELETE', MyEntity, async (req, next) => {
  // ...
});

Important

Decorator @OnDelete() will be triggered based on the EntityHandler argument => MyEntity.

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

@OnAction

@OnAction(name : CdsFunction | string)

Parameters

  • name (CdsFunction | string) : Representing the CDS action defined in the CDS file or the action name as a string.

Example


import { OnAction, Req, Next } from "@dxfrontier/cds-ts-dispatcher";
import type { ActionRequest, ActionReturn, NextEvent } from '@dxfrontier/cds-ts-dispatcher';

import { AnAction } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@OnAction(AnAction)
private async onActionMethod(@Req() req: ActionRequest<typeof AnAction>, @Next() next: NextEvent): ActionReturn<typeof AnAction> {
  // ...
}

Equivalent to 'JS'

this.on(AnAction, async (req, next) => {
  // ...
});

Note

AnAction was generated using CDS-Typer and imported in the the class.

Important


Decorator @OnAction should be used inside @UnboundActions() class.

(back to top)

@OnFunction

@OnFunction(name : CdsFunction)

Parameters

  • name (CdsFunction) : Representing the CDS action defined in the CDS file.

Example

import { OnFunction, Req, Next } from "@dxfrontier/cds-ts-dispatcher";
import type { ActionRequest, ActionReturn, NextEvent } from '@dxfrontier/cds-ts-dispatcher';

import { AFunction } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@OnFunction(AFunction)
private async onFunctionMethod(@Req() req: ActionRequest<typeof AFunction>, @Next() next: NextEvent): ActionReturn<typeof AFunction> {
  // ...
}

Equivalent to 'JS'

this.on(AFunction, async (req) => {
  // ...
});

Note

AFunction was generated using CDS-Typer and imported in the the class.

Important


Decorator @OnFunction should be used inside @UnboundAction() class.

(back to top)

@OnBoundAction

@OnBoundAction(name : CdsAction)

Parameters

  • name (CdsAction) : Representing the CDS action defined in the CDS file.

Example

import { OnBoundAction, Req, Next } from "@dxfrontier/cds-ts-dispatcher";
import type { ActionRequest, ActionReturn, NextEvent } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@OnBoundAction(MyEntity.actions.AnAction)
private async onActionMethod(@Req() req: ActionRequest<typeof MyEntity.actions.AnAction>, @Next() next: NextEvent): ActionReturn<typeof MyEntity.actions.AnAction> {
  // ...
}

Equivalent to 'JS'

this.on(MyEntity.actions.AnAction, MyEntity, async (req) => {
  // ...
});

Important

Decorator @OnBoundAction() will be triggered based on the EntityHandler argument => MyEntity.

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

@OnBoundFunction

@OnBoundFunction(name : CdsFunction)

Parameters

  • name (CdsFunction) : Representing the CDS action defined in the CDS file.

Example

import { OnBoundFunction, Req, Next } from "@dxfrontier/cds-ts-dispatcher";
import type { ActionRequest, ActionReturn, NextEvent } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@OnBoundFunction(MyEntity.actions.AFunction)
private async onFunctionMethod(@Req() req: ActionRequest<typeof MyEntity.actions.AFunction>, @Next() next: NextEvent): ActionReturn<typeof MyEntity.actions.AFunction> {
  // ...
}

Equivalent to 'JS'

this.on(MyEntity.actions.AFunction, MyEntity, async (req) => {
  // ...
});

Important

Decorator @OnBoundFunction() will be triggered based on the EntityHandler argument => MyEntity.

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

@OnAll

@OnAll()

The @OnAll decorator is triggered whenever any CRUD (Create, Read, Update, Delete) event occurs, whether the entity is active or in draft mode.

ACTIVE ENTITY

For active entities, the @OnAll decorator will be triggered when at least one of the following events occurs:

DRAFT

For draft entities, the @OnAll decorator will be triggered when at least one of the following events occurs:

Note

Exception will be the following decorators @OnEvent(), @OnError() and UNBOUND ACTIONS @OnAction(), UNBOUND FUNCTIONS @OnFunction() as these are bound to the service itself and not to an entity.

Example 1

In this example, the @OnAll() decorator of the onAll method is executed after any CRUD operation (CREATE, READ, UPDATE, DELETE, BOUND ACTIONS, BOUND FUNCTIONS) on the MyEntity entity.

Since the class is annotated with @EntityHandler(MyEntity), the decorator is scoped to this specific entity, meaning it will only be triggered for operations related to MyEntity.

import { OnAll, Next, EntityHandler } from "@dxfrontier/cds-ts-dispatcher";
import type { Request, NextEvent } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@EntityHandler(MyEntity)
export class BookHandler {
  // ...
  constructor() {}
  // All events like @AfterRead, @BeforeRead, ... will be triggered based on 'MyEntity'

  @OnAll()
  private async onAll(@Req() req: Request, @Next() next: NextEvent) {
    // ...
    return next();
  }
}

Equivalent to 'JS'

this.on('*', MyEntity, async (req, next) => {
  // ...
});

Example 2

In this example, the @OnAll() decorator is used in a more generic way. Unlike Example 1, where it applies only to MyEntity, this setup ensures that afterAll is triggered for all entities.

This means the method will execute before any CRUD operation on any entity handled within the class.

import { OnAll, Next, EntityHandler, CDS_DISPATCHER } from "@dxfrontier/cds-ts-dispatcher";
import type { Request, NextEvent } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@EntityHandler(CDS_DISPATCHER.ALL_ENTITIES)
export class BookHandler {
  // ...
  constructor() {}
  // All events like @AfterRead, @BeforeRead, ... will be triggered based on 'MyEntity'

  @OnAll()
  private async onAll(@Req() req: Request, @Next() next: NextEvent) {
    // ...
    return next();
  }
}

Equivalent to 'JS'

this.on('*', '*', async (req, next) => {
  // ...
});

Important

Decorator @OnAll() will be triggered based on the EntityHandler argument => MyEntity.

Tip

If the entity has drafts enabled @odata.draft.enabled: true, the @OnAll decorator will still be triggered for draft events.

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

Method — CRUD, draft entity
Before

Use @BeforeNewDraft(), @BeforeCancelDraft(), @BeforePatchDraft(), @BeforeDiscardDraft(), @BeforeEditDraft(), @BeforeSaveDraft(), @BeforeCreateDraft(), @BeforeReadDraft(), @BeforeUpdateDraft(), @BeforeDeleteDraft() to register handlers to run before.onhandlers, frequently used for validating user input.

The handlers receive one argument:

  • req of type Request
@BeforeNewDraft

@BeforeNewDraft()

Use this decorator when you want to validate inputs before a new draft is created.

Example

import { BeforeNewDraft, Request } from "@dxfrontier/cds-ts-dispatcher";
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@BeforeNewDraft()
private async beforeCreateDraftMethod(@Req() req: Request<MyEntity>) {
  // ...
}

Equivalent to 'JS'

this.before('NEW', MyEntity.drafts, async (req) => {
  // ...
});

Important

Decorator @BeforeNewDraft() will be triggered based on the EntityHandler argument => MyEntity.

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

@BeforeCancelDraft

@BeforeCancelDraft()

Use this decorator when you want to validate inputs before a draft is discarded.

Example

import { BeforeCancelDraft } from "@dxfrontier/cds-ts-dispatcher";
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@BeforeCancelDraft()
private async beforeCancelDraftMethod(@Req() req: Request<MyEntity>) {
  // ...
}

Equivalent to 'JS'

this.before('CANCEL', MyEntity.drafts, async (req) => {
  // ...
});

Important

Decorator @BeforeCancelDraft() will be triggered based on the EntityHandler argument => MyEntity.

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

@BeforePatchDraft

@BeforePatchDraft()

Use this decorator when you want to validate inputs before a field of an in-progress draft is changed.

PATCH is an HTTP-method alias CAP normalizes to UPDATE at registration time (req.event is 'UPDATE' at runtime, never 'PATCH') and fires every time a field of an in-progress draft changes.

Example

import { BeforePatchDraft } from "@dxfrontier/cds-ts-dispatcher";
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@BeforePatchDraft()
private async beforePatchDraftMethod(@Req() req: Request<MyEntity>) {
  // ...
}

Equivalent to 'JS'

this.before('PATCH', MyEntity.drafts, async (req) => {
  // ...
});

Important

Decorator @BeforePatchDraft() will be triggered based on the EntityHandler argument => MyEntity.

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

@BeforeDiscardDraft

@BeforeDiscardDraft()

Use this decorator when you want to validate inputs before an in-progress draft is discarded.

DISCARD is an alias CAP normalizes to CANCEL at registration time (req.event is 'CANCEL' at runtime) and fires when an in-progress draft is discarded.

Example

import { BeforeDiscardDraft } from "@dxfrontier/cds-ts-dispatcher";
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@BeforeDiscardDraft()
private async beforeDiscardDraftMethod(@Req() req: Request<MyEntity>) {
  // ...
}

Equivalent to 'JS'

this.before('DISCARD', MyEntity.drafts, async (req) => {
  // ...
});

Important

Decorator @BeforeDiscardDraft() will be triggered based on the EntityHandler argument => MyEntity.

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

@BeforeEditDraft

@BeforeEditDraft()

Use this decorator when you want to validate inputs when a new draft is created from an active instance.

Example

import { BeforeEditDraft } from "@dxfrontier/cds-ts-dispatcher";
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@BeforeEditDraft()
private async beforeEditDraftMethod(@Req() req: Request<MyEntity>) {
  // ...
}

Equivalent to 'JS'

this.before('EDIT', MyEntity, async (req) => {
  // ...
});

Important

Decorator @BeforeEditDraft() will be triggered based on the EntityHandler argument => MyEntity.

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

@BeforeSaveDraft

@BeforeSaveDraft()

Use this decorator when you want to validate inputs when active entity is changed.

Example

import { BeforeSaveDraft } from "@dxfrontier/cds-ts-dispatcher";
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@BeforeSaveDraft()
private async beforeSaveDraftMethod(@Req() req: Request<MyEntity>) {
  // ...
}

Equivalent to 'JS'

this.before('SAVE', MyEntity, async (req) => {
  // ...
});

Important

Decorator @BeforeSaveDraft() will be triggered based on the EntityHandler argument => MyEntity.

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

After

Use @AfterNewDraft(), @AfterCancelDraft(), @AfterPatchDraft(), @AfterDiscardDraft(), @AfterEditDraft(), @AfterSaveDraft(), @AfterCreateDraft(), @AfterReadDraft(), @AfterUpdateDraft(), @AfterDeleteDraft() register handlers to run after the .on handlers, frequently used to enrich outbound data. The handlers receive two arguments:

The results from the preceding .on handler, with the following types:

ParametersDecoratorDescription
results, req@AfterReadAn array of type MyEntity[] and the Request.
result, req@AfterUpdate
@AfterCreate
An object of type MyEntity and the Request.
deleted, req@AfterDeleteA boolean indicating whether the instance was deleted and the Request.
@AfterNewDraft

@AfterNewDraft()

Use this decorator when you want to enhance outbound data when a new draft is created.

Example

import { AfterNewDraft } from "@dxfrontier/cds-ts-dispatcher";
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@AfterNewDraft()
private async afterNewDraftMethod(@Result() result: MyEntity, @Req() req: Request<MyEntity>) {
  // ...
}

Equivalent to 'JS'

this.after('NEW', MyEntity.drafts, async (results, req) => {
  // ...
});

Important

Decorator @AfterNewDraft() will be triggered based on the EntityHandler argument => MyEntity.

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

@AfterCancelDraft

@AfterCancelDraft()

Use this decorator when you want to enhance outbound data when a draft is discarded.

Example

import { AfterCancelDraft } from "@dxfrontier/cds-ts-dispatcher";
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@AfterCancelDraft()
private async afterCancelDraftMethod(@Result() result: MyEntity, @Req() req: Request<MyEntity>) {
  // ...
}

Equivalent to 'JS'

this.after('CANCEL', MyEntity.drafts, async (results, req) => {
  // ...
});

Important

Decorator @AfterCancelDraft() will be triggered based on the EntityHandler argument => MyEntity.

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

@AfterPatchDraft

@AfterPatchDraft()

Use this decorator when you want to enhance outbound data when a field of an in-progress draft is changed.

PATCH is an HTTP-method alias CAP normalizes to UPDATE at registration time (req.event is 'UPDATE' at runtime, never 'PATCH') and fires every time a field of an in-progress draft changes.

Example

import { AfterPatchDraft } from "@dxfrontier/cds-ts-dispatcher";
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@AfterPatchDraft()
private async afterPatchDraftMethod(@Result() result: MyEntity, @Req() req: Request<MyEntity>) {
  // ...
}

Equivalent to 'JS'

this.after('PATCH', MyEntity.drafts, async (results, req) => {
  // ...
});

Important

Decorator @AfterPatchDraft() will be triggered based on the EntityHandler argument => MyEntity.

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

@AfterDiscardDraft

@AfterDiscardDraft()

Use this decorator when you want to enhance outbound data when an in-progress draft is discarded.

DISCARD is an alias CAP normalizes to CANCEL at registration time (req.event is 'CANCEL' at runtime) and fires when an in-progress draft is discarded.

Example

import { AfterDiscardDraft } from "@dxfrontier/cds-ts-dispatcher";
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@AfterDiscardDraft()
private async afterDiscardDraftMethod(@Result() result: MyEntity, @Req() req: Request<MyEntity>) {
  // ...
}

Equivalent to 'JS'

this.after('DISCARD', MyEntity.drafts, async (results, req) => {
  // ...
});

Important

Decorator @AfterDiscardDraft() will be triggered based on the EntityHandler argument => MyEntity.

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

@AfterEditDraft

@AfterEditDraft()

Use this decorator when you want to enhance outbound data when a new draft is created from an active instance.

Example

import { AfterEditDraft } from "@dxfrontier/cds-ts-dispatcher";
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@AfterEditDraft()
private async afterEditDraftMethod(@Result() result: MyEntity, @Req() req: Request<MyEntity>) {
  // ...
}

Equivalent to 'JS'

this.after('EDIT', MyEntity, async (results, req) => {
  // ...
});

Important

Decorator @AfterEditDraft() will be triggered based on the EntityHandler argument => MyEntity.

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

@AfterSaveDraft

@AfterSaveDraft()

Use this decorator when you want to enhance outbound data when the active entity is changed.

Example

import { AfterSaveDraft } from "@dxfrontier/cds-ts-dispatcher";
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@AfterSaveDraft()
private async afterSaveDraftMethod(@Result() result: MyEntity, @Req() req: Request<MyEntity>) {
  // ...
}

Equivalent to 'JS'

this.after('SAVE', MyEntity, async (results, req) => {
  // ...
});

Important

Decorator @AfterSaveDraft() will be triggered based on the EntityHandler argument => MyEntity.

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

On

Use @OnNewDraft(), @OnCancelDraft(), @OnPatchDraft(), @OnDiscardDraft(), @OnSaveDraft(), @OnEditDraft(), @OnReadDraft(), @OnUpdateDraft(), @OnCreateDraft(), @OnDeleteDraft(), @OnBoundActionDraft(), @OnBoundFunctionDraft() handlers to support for both, active and draft entities.

The handlers receive two arguments:

  • req of type Request
  • next of type NextEvent

See Official SAP Fiori-draft

@OnNewDraft

@OnNewDraft()

This decorator will be triggered when a new draft is created.

Example

import { OnNewDraft, Req, Next } from "@dxfrontier/cds-ts-dispatcher";
import type { Request, NextEvent } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@OnNewDraft()
private async onNewDraft(@Req() req: Request<MyEntity>, @Next() next: NextEvent) {
  // ...
}

Equivalent to 'JS'

this.on('NEW', MyEntity.drafts, async (req, next) => {
  // ...
});

Important

Decorator @OnNewDraft() will be triggered based on the EntityHandler argument => MyEntity.

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

@OnCancelDraft

@OnCancelDraft()

This decorator will be triggered when a draft is cancelled.

Example

import { OnCancelDraft, Req, Next } from "@dxfrontier/cds-ts-dispatcher";
import type { Request, NextEvent } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@OnCancelDraft()
private async onCancelDraft(@Req() req: Request<MyEntity>, @Next() next: NextEvent) {
  // ...
}

Equivalent to 'JS'

this.on('CANCEL', MyEntity.drafts, async (req, next) => {
  // ...
});

Important

Decorator @OnCancelDraft() will be triggered based on the EntityHandler argument => MyEntity.

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

@OnPatchDraft

@OnPatchDraft()

This decorator will be triggered when a field of an in-progress draft is changed.

PATCH is an HTTP-method alias CAP normalizes to UPDATE at registration time (req.event is 'UPDATE' at runtime, never 'PATCH') and fires every time a field of an in-progress draft changes.

Example

import { OnPatchDraft, Req, Next } from "@dxfrontier/cds-ts-dispatcher";
import type { Request, NextEvent } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@OnPatchDraft()
private async onPatchDraft(@Req() req: Request<MyEntity>, @Next() next: NextEvent) {
  return next(); // preserve the default draft PATCH behavior
}

Equivalent to 'JS'

this.on('PATCH', MyEntity.drafts, async (req, next) => {
  // ...
});

Important

Decorator @OnPatchDraft() will be triggered based on the EntityHandler argument => MyEntity.

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

@OnDiscardDraft

@OnDiscardDraft()

This decorator will be triggered when an in-progress draft is discarded.

DISCARD is an alias CAP normalizes to CANCEL at registration time (req.event is 'CANCEL' at runtime) and fires when an in-progress draft is discarded.

Example

import { OnDiscardDraft, Req, Next } from "@dxfrontier/cds-ts-dispatcher";
import type { Request, NextEvent } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@OnDiscardDraft()
private async onDiscardDraft(@Req() req: Request<MyEntity>, @Next() next: NextEvent) {
  return next(); // preserve the default draft DISCARD behavior
}

Equivalent to 'JS'

this.on('DISCARD', MyEntity.drafts, async (req, next) => {
  // ...
});

Important

Decorator @OnDiscardDraft() will be triggered based on the EntityHandler argument => MyEntity.

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

@OnEditDraft

@OnEditDraft()

This decorator will be triggered when a new draft is created from an active instance

Example

import { OnEditDraft, Req, Next } from "@dxfrontier/cds-ts-dispatcher";
import type { Request, NextEvent } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@OnEditDraft()
private async onEditDraft(@Req() req: Request<MyEntity>, @Next() next: NextEvent) {
  // ...
}

Equivalent to 'JS'

this.on('EDIT', MyEntity, async (req, next) => {
  // ...
});

Important

Decorator @OnEditDraft() will be triggered based on the EntityHandler argument => MyEntity.

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

@OnSaveDraft

@OnSaveDraft()

This decorator will be triggered when the active entity is changed

Example


import { OnSaveDraft, Req, Next  } from "@dxfrontier/cds-ts-dispatcher";
import type { Request, NextEvent } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@OnSaveDraft()
private async onSaveDraft(@Req() req: Request<MyEntity>, @Next() next: NextEvent) {
  // ...
}

Equivalent to 'JS'

this.on('SAVE', MyEntity, async (req, next) => {
  // ...
});

Important

Decorator @OnSaveDraft() will be triggered based on the EntityHandler argument => MyEntity.

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

Other draft decorators

All active entity On, Before, After events have also a Draft variant.

Note

Except the @OnAction(), @OnFunction(), @OnEvent(), @OnError() as these actions are bound to the service and not to an entity.

(back to top)

Method — Events & messaging
@OnEvent

@OnEvent(name : CdsEvent)

The @OnEvent decorator facilitates the listening of events when the Emit and receiver are in the same NODE JS PROCESS and SAME SERVICE.

This decorator is particularly useful in conjunction with the Emit method to handle triggered events.

Parameters

  • name (CdsEvent) : Representing the CDS event defined in the CDS file.

Example

import { OnEvent, Req, OnSubscribe } from "@dxfrontier/cds-ts-dispatcher";
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { AEvent } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@OnEvent(AEvent)
private async onEventMethod(@Req() req: Request<AEvent>) {
  // ...
}

// same as

@OnSubscribe({
  eventName: AEvent,
  type: 'SAME_NODE_PROCESS',
})
private async onEventMethod(@Req() req: Request<AEvent>) {
  // ...
}

Equivalent to 'JS'

this.on('AEvent', async (req) => {
  // ...
});

Note

The AEvent was generated using CDS-Typer and imported in the the class.

Important


Decorator @OnEvent should be used inside @UnboundActions class.

Tip

An exended version of this event can be found @OnSubscribe.

(back to top)

@OnSubscribe
@OnSubscribe(options : {
  eventName: string | object,
  type : 'SAME_NODE_PROCESS' | 'MESSAGE_BROKER' | 'SAME_NODE_PROCESS_DIFFERENT_SERVICE'
  externalServiceName: string // applicable only for 'SAME_NODE_PROCESS_DIFFERENT_SERVICE'
  showReceiverMessage?: boolean
  consoleStyle?: 'table | 'debug'
})

Use @OnSubscribe decorator enables execution of custom logic when messaging events (event bus / publish subscribe) are triggered in your SAP CAP application.

This decorator is particularly useful in conjunction with the Emit method to handle triggered events.

Parameters

  • options:
    • name: CdsEvent - The name of the event to subscribe to. This can be a string or a CDS event type imported from your @cds-models folder.
    • type: Defines the messaging transport mechanism for SAP CAP applications.
      • 'SAME_NODE_PROCESS' - Use when both emitter and receiver run in the same CAP server instance & same service.
      • 'SAME_NODE_PROCESS_DIFFERENT_SERVICE' - Use when emitter can be found in E.g. Service A and receiver can reside in E.g. Service B, having same CAP server instance but different services.
      • 'MESSAGE_BROKER' - Recommended for production with external message brokers, different CAP Server instances, different services.
    • showReceiverMessage? [optional]: boolean - When enabled, logs inbound message payloads in the specified consoleStyle format.
    • externalServiceName: - The name of the external service to attach the subscribe decorator to. [Applicable only when type is 'SAME_NODE_PROCESS_DIFFERENT_SERVICE' ]
    • consoleStyle? [optional] - Specifies the log output format for received messages (when showReceiverMessage is true).
      • 'table': Displays data using console.table(), ideal for structured messages.
      • 'debug': Displays data using console.debug(), ideal for nested or dynamic messages.

Example 1

In this example we emit the event in same node process and same service and we subscribe to it.

Emitting and subscribing:

import { OnSubscribe, Req, OnEvent } from '@dxfrontier/cds-ts-dispatcher';
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

// Emitting the event
@AfterRead()
private async afterRead(
  @Req() req: Request,
  @Results() results: MyEntity[],
): Promise<void> {
  this.srv.emit('anEventName', { foo: 11, bar: '22' });
}

// Subscribing to the event
@OnSubscribe({
  eventName: 'anEventName'
  type: 'SAME_NODE_PROCESS',
})
private async onSubscribe(@Req() req: Request<{ foo: number, bar: string }>): Promise<void> {
  //
}

// same as

@OnEvent('anEventName')
private async onEventMethod(@Req() req: Request<{ foo: number, bar: string }>) {
  // ...
}

Equivalent to 'JS'

this.after('READ', MyEntity, async (req, res) => {
  this.emit('anEventName', { foo: 11, bar: '22' });
});

this.on('anEventName', async (msg) => {
  // ...
});

Example 2

In this example we emit the event in same node process and same service and we subscribe to it, the only difference, is in this case we get the Event from the @cds-models folder

Service Definition (CDS):

// We declare an event into our Service
service CatalogService {
  event SendData : {
    foo : Integer;
    bar : String;
  }
}

Emitting and subscribing:

import { OnSubscribe, Req, AfterRead, Results, OnEvent } from '@dxfrontier/cds-ts-dispatcher';
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { SendData } from '#cds-models/CatalogService'; // <== location of @cds-models can differ.

// Emitting the event
@AfterRead()
private async afterRead(
  @Req() req: Request,
  @Results() results: MyEntity[],
): Promise<void> {
  this.srv.emit('SendData', { foo: 11, bar: '22' });
}

// Receiving the event + data
@OnSubscribe({
  eventName: SendData
  type: 'SAME_NODE_PROCESS'
})
private async onSubscribe(@Req() req: Request<SendData>): Promise<void> {
  //
  // req.data.foo ...
  // req.data.bar ...
  // req.headers ...
  // ...
}

// same as

@OnEvent(SendData)
private async onEventMethod(@Req() req: Request<SendData>) {
  // ...
}

Equivalent to 'JS'

this.after('READ', MyEntity, async (req, res) => {
  this.emit('SendData', { foo: 11, bar: '22' });
});

this.on('SendData', async (msg) => {
  // ...
});

Note

The SendData was generated using CDS-Typer and imported in the the class.

Example 3

In this example we assume we have 2 node instances and 2 services and we use external messaging Cross-Instance Messaging (Message Broker)

Emitter (Service 1 & node instance 1):

import { Req, AfterRead, Results } from '@dxfrontier/cds-ts-dispatcher';
import cds from '@sap/cds';

@AfterRead()
private async afterRead(
  @Req() req: Request,
  @Results() results: MyEntity[],
): Promise<void> {

  const messaging = await cds.connect.to('messaging');
        messaging.emit('SendData', { foo: 11, bar: '22' });

}

Equivalent to 'JS'

this.after('READ', MyEntity, async (req, res) => {
  const messaging = await cds.connect.to('messaging');
        messaging.emit('SendData', { foo: 11, bar: '22' });
});

Subscriber (Service 2 & Node instance 2):


import { SendData } from '#cds-models/CatalogService'; // <== location of @cds-models can differ.
import { Messaging, OnSubscribe, Req } from '@dxfrontier/cds-ts-dispatcher';
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

// Receiving the event + data 
@OnSubscribe({
  eventName: SendData,
  type: 'MESSAGE_BROKER' 
})
private async onSubscribe(@Req() req: Request): Promise<void> {
  // 
  // req.data.foo ...
  // req.data.bar ...
  // req.headers ...
  // ...
}

Equivalent to 'JS'

const messaging = await cds.connect.to('messaging');
      messaging.on(SendData, async (msg) => {
        // ...
      });

Example 4

In this example we emit an event in Service 1 & node process 1, and we attach from Service 2 a subscribe to Service 1 & node process 1, meaning that, when emit SendData the service Service 2 will be notified and the onSubscribe method will be triggered which resides attached to Service 1.

Emitter (Service 1 & node process 1):

import { Req, AfterRead, Results } from '@dxfrontier/cds-ts-dispatcher';
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { SendData } from '#cds-models/CatalogService'; // <== location of @cds-models can differ.

// Emitting the event
// This event resides in Service_1
@AfterRead()
private async afterRead(
  @Req() req: Request,
  @Results() results: MyEntity[],
): Promise<void> {
  this.srv.emit('SendData', { foo: 11, bar: '22' });
}

Equivalent to 'JS'

// This event resides in Service_1
this.after('READ', MyEntity, async (req, res) => {
  this.emit('SendData', { foo: 11, bar: '22' });
});

Subscriber (Service 2 & node process 1):


import { OnSubscribe, Req } from '@dxfrontier/cds-ts-dispatcher';

// Receiving the event + data
// This event resides in Service_2 but the subscribe will be attached to Service_1
 @OnSubscribe({
  eventName: 'SendData',
  type: 'SAME_NODE_PROCESS_DIFFERENT_SERVICE',
  externalService: 'Service_1'
})
private async onSubscribe(@Req() req: Request<SendData>): Promise<void> {
  // req.data.foo ...
  // req.data.bar ...
  // req.headers ...
  // ...
}

Equivalent to 'JS'

// This code resides in Service_2 but it is subscribed to Service_1
const Service_1 = cds.connect.to('Service_1');
      Service_1.on('SendData', (msg) => {
        //
      });

Tip

You can find more info about SAP CAP messaging in the following url SAP CAP Events and Messaging.

Important


Decorator @OnSubscribe should be used inside @UnboundActions class, but not mandatory it can reside also in @EntityHandler class.

(back to top)

@OnError

@OnError()

Use @OnError decorator to register custom error handler.

Error handlers are invoked whenever an error occurs during event processing of all potential events and requests, and are used to augment or modify error messages, before they go out to clients.

Example

import { OnError, Error, Req } from "@dxfrontier/cds-ts-dispatcher";
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

@OnError()
private onError(@Error() err: Error, @Req() req: Request) { // sync func
  err.message = 'New message'
  // ...
}

Equivalent to 'JS'

this.on('error', (err, req) => {
  err.message = 'New message';
  // ...
});

Important


Decorator @OnError should be used inside @UnboundActions class.

Caution

OnError callback are expected to be a sync function, i.e., not async, not returning Promises.

Tip

More info can be found at SAP CAP Error

(back to top)

Method — Request lifecycle

Use @BeforeCommit(), @AfterCommit(), @AfterRollback(), @OnRequestDone() to hook into the commit / succeeded / failed / done phases of the current ROOT request's transaction.

The handlers receive one argument:

  • req of type Request

Note

Ordering across multiple handler classes: @BeforeCommit callbacks run in CDSDispatcher([...]) array order, while @AfterCommit / @AfterRollback / @OnRequestDone callbacks run in reverse array order (within one class, declaration order always holds). With more than ~10 handler classes hooking the same lifecycle event, Node's default MaxListenersExceededWarning may appear for the shared root-context emitter — harmless log noise, not a leak.

(back to top)

@BeforeCommit

@BeforeCommit()

The @BeforeCommit decorator runs custom logic inside the transaction, immediately before commit, after all other handlers of the request (including handlers of other services touched by the same request) have run. Throwing an error here vetoes the commit — the transaction is rolled back and the error is returned to the client.

Hosted in an @EntityHandler class it is scoped to requests targeting that entity, hosted in an @UnboundActions class it applies to every request of the service.

Example

import { BeforeCommit } from "@dxfrontier/cds-ts-dispatcher";
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@BeforeCommit()
private async beforeCommit(@Req() req: Request<MyEntity>) {
  if (/* cross-entity invariant violated */ false) {
    throw new Error('Total stock must stay non-negative'); // vetoes the commit
  }
}

Equivalent to 'JS'

this.before('*', MyEntity, (req) => {
  req.before('commit', async () => {
    // ...
  });
});

Important

The injected req is the first sub-request of the root request that reached the hook. Use @BeforeCommit for cross-request / final-state invariants that need to read the current database state, keep per-operation validation in your Before / On handlers.

Note

It runs once per ROOT request (once per OData $batch changeset). For draft-enabled entities the hook fires on draft ACTIVATION, not during the draft-editing roundtrip.

(back to top)

@AfterCommit

@AfterCommit()

The @AfterCommit decorator runs custom logic only after the transaction of the current request was durably committed, outside any transaction. Errors cannot veto anything anymore — they are caught and logged by the dispatcher.

Hosted in an @EntityHandler class it is scoped to requests targeting that entity, hosted in an @UnboundActions class it applies to every request of the service.

Example

import { AfterCommit } from "@dxfrontier/cds-ts-dispatcher";
import type { Request } from '@dxfrontier/cds-ts-dispatcher';
import cds from '@sap/cds';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@AfterCommit()
private async afterCommit(@Req() req: Request<MyEntity>) {
  await cds.tx(async () => {
    // ... e.g. send a confirmation e-mail, invalidate a cache
  });
}

Equivalent to 'JS'

this.before('*', MyEntity, (req) => {
  req.on('succeeded', async () => {
    // ...
  });
});

Important

The request's transaction is already closed by the time this handler runs - a plain query would fail. Always open a new transaction with await cds.tx(async () => { ... }) for any database work.

Note

It runs once per ROOT request (once per OData $batch changeset). For draft-enabled entities the hook fires on draft ACTIVATION, not during the draft-editing roundtrip.

(back to top)

@AfterRollback

@AfterRollback()

The @AfterRollback decorator runs custom logic after the transaction of the current request was rolled back, outside any transaction. Errors cannot veto anything anymore — they are caught and logged by the dispatcher.

Hosted in an @EntityHandler class it is scoped to requests targeting that entity, hosted in an @UnboundActions class it applies to every request of the service.

Example

import { AfterRollback } from "@dxfrontier/cds-ts-dispatcher";
import type { Request } from '@dxfrontier/cds-ts-dispatcher';
import cds from '@sap/cds';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@AfterRollback()
private async afterRollback(@Req() req: Request<MyEntity>) {
  await cds.tx(async () => {
    // ... e.g. release a reservation in a remote system, alert on the failure
  });
}

Equivalent to 'JS'

this.before('*', MyEntity, (req) => {
  req.on('failed', async () => {
    // ...
  });
});

Important

The request's transaction is already closed (rolled back) by the time this handler runs - a plain query would fail. Always open a new transaction with await cds.tx(async () => { ... }) for any database work.

Note

It runs once per ROOT request (once per OData $batch changeset). For draft-enabled entities the hook fires on draft ACTIVATION, not during the draft-editing roundtrip.

(back to top)

@OnRequestDone

@OnRequestDone()

The @OnRequestDone decorator runs custom logic when the current request is done, no matter if it succeeded or failed — finally semantics. It runs outside any transaction and errors cannot veto anything anymore — they are caught and logged by the dispatcher.

Hosted in an @EntityHandler class it is scoped to requests targeting that entity, hosted in an @UnboundActions class it applies to every request of the service.

Example

import { OnRequestDone } from "@dxfrontier/cds-ts-dispatcher";
import type { Request } from '@dxfrontier/cds-ts-dispatcher';
import cds from '@sap/cds';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@OnRequestDone()
private async requestDone(@Req() req: Request<MyEntity>) {
  await cds.tx(async () => {
    // ... e.g. release a lock, stop a timer, emit duration metrics
  });
}

Equivalent to 'JS'

this.before('*', MyEntity, (req) => {
  req.on('done', async () => {
    // ...
  });
});

Important

The request's transaction is already closed by the time this handler runs - a plain query would fail. Always open a new transaction with await cds.tx(async () => { ... }) for any database work.

Note

It runs once per ROOT request (once per OData $batch changeset). For draft-enabled entities the hook fires on draft ACTIVATION, not during the draft-editing roundtrip.

(back to top)

Method — Server lifecycle

Use @OnServed(), @OnListening(), @OnShutdown() - hosted in a @ServerLifecycle class - to hook into the CAP server's served / listening / shutdown process lifecycle.

Note

Unlike every other method decorator in this library, these three receive CAP's arguments verbatim - no @Req()-style parameter decorator applies.

(back to top)

@OnServed

@OnServed()

The @OnServed decorator executes custom logic once, right before the CAP server starts listening for requests. The handler receives cds.services (the bootstrapped services) as its only argument. CAP awaits every @OnServed handler sequentially - in declaration order inside a class, and in CDSDispatcher array order across classes - before app.listen runs. A thrown error fails server startup.

Example

import { ServerLifecycle, OnServed } from '@dxfrontier/cds-ts-dispatcher';

@ServerLifecycle()
export class Bootstrap {
  @OnServed()
  public async seed(services: object) {
    // ... one-time startup work, may throw to abort the boot
  }
}

Equivalent to 'JS'

cds.on('served', async (services) => {
  // ...
});

Important

Must be hosted in a @ServerLifecycle class.

(back to top)

@OnListening

@OnListening()

The @OnListening decorator executes custom logic once the CAP server is listening for requests. The handler receives { server, url }. CAP dispatches @OnListening synchronously - any return value (including a Promise) is discarded, so treat the handler as fire-and-forget.

Example

import { ServerLifecycle, OnListening } from '@dxfrontier/cds-ts-dispatcher';

@ServerLifecycle()
export class Bootstrap {
  @OnListening()
  public logUrl(payload: { server: unknown; url: string }) {
    console.log(`Listening on ${payload.url}`);
  }
}

Equivalent to 'JS'

cds.on('listening', ({ server, url }) => {
  // ...
});

Important

Must be hosted in a @ServerLifecycle class.

(back to top)

@OnShutdown

@OnShutdown()

The @OnShutdown decorator executes custom logic while the CAP server is shutting down. The handler receives err: Error | null. CAP runs all @OnShutdown handlers (of every class) in parallel and awaits them before the server closes.

Example

import { ServerLifecycle, OnShutdown } from '@dxfrontier/cds-ts-dispatcher';

@ServerLifecycle()
export class Bootstrap {
  @OnShutdown()
  public async cleanup(error: Error | null) {
    // ... release resources, may run more than once
  }
}

Equivalent to 'JS'

cds.on('shutdown', async (error) => {
  // ...
});

Important

Must be hosted in a @ServerLifecycle class.

Warning

CAP has NO once-guard on shutdown - unlike served / listening, this callback may fire more than once per process. Make the handler idempotent.

Warning

Catch your own errors: a rejected @OnShutdown handler propagates out of CAP's shutdown dispatch, so server.close (and the force-exit fallback timer) never run - the process stays alive with an unhandled rejection instead of shutting down.

(back to top)

Method — Scheduling

Use @OnScheduled() and @Schedule() to handle and register recurring background tasks on top of the @sap/cds 10 event-queue.

@OnScheduled

@OnScheduled(name: string)

The @OnScheduled decorator is a method-level decorator that handles a scheduled task by name. It registers srv.on(name, cb), so handling a scheduled task is a normal ON handler — use @Req() to get the req and read the payload from req.data.

Parameters

  • name (string) : The scheduled task name to handle. The name is registered verbatim (dots are not stripped), so fully-qualified names like 'my.namespace.Task' match exactly.

Example

import { OnScheduled, Req, UnboundActions } from '@dxfrontier/cds-ts-dispatcher';
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

@UnboundActions()
class ScheduledTasksHandler {
  // handle a fully-qualified (dotted) task name, registered verbatim
  @OnScheduled('my.namespace.reindexCatalog')
  public async reindex(@Req() req: Request) {
    // req.data holds the task payload
    // ...
  }
}

Equivalent to 'JS'

this.on('my.namespace.reindexCatalog', async (req) => {
  // ...
});

Note

Handling a scheduled task is a normal ON handler, the payload is available on req.data.

(back to top)

@Schedule

@Schedule(options: { name: string; every: string; data?: Record<string, unknown> })

The @Schedule decorator is a method-level decorator that handles the task and also schedules it as a recurring task at bootstrap via srv.schedule(name, data).every(every).as(name).

Because .every().as(name) makes it a named singleton, re-scheduling on every boot upserts rather than duplicates.

Parameters

  • options.name (string) : The task name. It is handled and scheduled under this exact name (registered verbatim).
  • options.every (string) : The recurrence. Accepts an interval string (e.g. '10m') or a cron expression — passed through verbatim (CAP's ms4 / cron parse it).
  • options.data (Record<string, unknown>) optional : The payload passed to each run, available on req.data.

Example

import { Schedule, Req, UnboundActions } from '@dxfrontier/cds-ts-dispatcher';
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

@UnboundActions()
class ScheduledTasksHandler {
  // handle AND schedule a recurring singleton (every 2 minutes)
  @Schedule({ name: 'cleanupExpiredCarts', every: '2m' })
  public async cleanup(@Req() req: Request) {
    // ...
  }
}

Equivalent to 'JS'

// scheduled once at bootstrap ...
srv.schedule('cleanupExpiredCarts').every('2m').as('cleanupExpiredCarts');

// ... and handled on every run
this.on('cleanupExpiredCarts', async (req) => {
  // ...
});

Note

If scheduling infrastructure (db + queue) is missing or misconfigured, scheduling fails loudly in the logs but never crashes boot — the task handler stays registered.

Important

One-shot scheduling (.after) is intentionally not a decorator. Inject the service and call it programmatically:

import { Inject, CDS_DISPATCHER } from '@dxfrontier/cds-ts-dispatcher';
import type { Service } from '@dxfrontier/cds-ts-dispatcher';

@UnboundActions()
class ScheduledTasksHandler {
  @Inject(CDS_DISPATCHER.SRV) private readonly srv: Service;

  public async scheduleOnce() {
    // run once, 10 minutes from now
    this.srv.schedule('cleanupExpiredCarts', { cartId: '42' }).after('10m').as('cleanupExpiredCarts');
  }
}

(back to top)

@OnScheduledSuccess

@OnScheduledSuccess(name: string)

The @OnScheduledSuccess decorator is a method-level decorator that handles the successful outcome of a scheduled task. It registers srv.after('<name>/#succeeded', cb) — the handler receives the result returned by the task handler (@OnScheduled / @Schedule) via @Result() and the req.

Parameters

  • name (string) : The scheduled task name whose success to handle. The name is registered verbatim (dots are not stripped), so fully-qualified names like 'my.namespace.Task' match exactly.

Example

import { OnScheduledSuccess, Result, Req, UnboundActions } from '@dxfrontier/cds-ts-dispatcher';
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

@UnboundActions()
class ScheduledTasksHandler {
  @OnScheduledSuccess('cleanupExpiredCarts')
  public async succeeded(@Result() result: unknown, @Req() req: Request) {
    // ... e.g. kick off follow-up work now that the task ran through
  }
}

Equivalent to 'JS'

this.after('cleanupExpiredCarts/#succeeded', (result, req) => {
  // ...
});

(back to top)

@OnScheduledFailure

@OnScheduledFailure(name: string)

The @OnScheduledFailure decorator is a method-level decorator that handles the failed outcome of a scheduled task, once its retries are exhausted. It registers srv.after('<name>/#failed', cb) — the handler receives the failure payload via @Result() and the req.

Parameters

  • name (string) : The scheduled task name whose failure to handle. The name is registered verbatim (dots are not stripped), so fully-qualified names like 'my.namespace.Task' match exactly.

Example

import { OnScheduledFailure, Result, Req, UnboundActions } from '@dxfrontier/cds-ts-dispatcher';
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

@UnboundActions()
class ScheduledTasksHandler {
  @OnScheduledFailure('cleanupExpiredCarts')
  public async failed(@Result() error: { name: string; message: string; stack: string; code: string }, @Req() req: Request) {
    // ... e.g. alert ops
  }
}

Equivalent to 'JS'

this.after('cleanupExpiredCarts/#failed', (error, req) => {
  // ...
});

Important

@OnScheduledFailure only fires once the task's retries are exhausted (event-queue maxAttempts, 10 by default) — not on every failed attempt.

Important

The failure payload is a serialized plain object ({ name, message, stack, code }), NOT an Error instance. Inject it with @Result() — @Error() will not populate here, as it only matches arguments that are instanceof Error.

(back to top)

Method — Streaming

Use @Stream() to pipe a handler's Readable return value straight to the HTTP response.

@Stream

@Stream(contentType?: StreamContentType)

The @Stream decorator is a method-level decorator for ON read handlers (@OnRead(), @OnFunction(), @OnBoundFunction()). Place it directly on the method, below the ON decorator, so it wraps the returned value.

If the decorated method returns a Readable (an object exposing a .pipe function), @Stream sets the response Content-Type and pipes the stream straight to the HTTP response, destroying the response on stream error. Any non-stream return value passes through unchanged.

Parameters

  • contentType (StreamContentType) optional : The response Content-Type. Defaults to 'application/octet-stream'. StreamContentType is a union of common MIME types ('application/json', 'application/x-ndjson', 'text/csv', 'application/pdf', 'image/png', ...) so the editor suggests them - any other valid MIME type string is accepted as well.

Example

import { OnFunction, Req, Stream } from '@dxfrontier/cds-ts-dispatcher';
import type { Request } from '@dxfrontier/cds-ts-dispatcher';
import { Readable } from 'node:stream';

@OnFunction('streamBooks')
@Stream('application/json')
public async streamBooks(@Req() req: Request): Promise<Readable> {
  const books = await SELECT.from('CatalogService.Books').columns('ID', 'title');
  return Readable.from([books.map((b) => JSON.stringify(b)).join('\n')]); // NDJSON
}

Important

@Stream works end-to-end over the OData adapter for @OnFunction(), @OnBoundFunction() and @OnRead() reads — it flushes headers synchronously so the adapter does not double-write.

Tip

Real streaming reads via SELECT.pipeline(), SELECT.foreach(), or for-await iteration are the intended producers.

(back to top)

Method — WebSocket

Use @OnWebSocketConnect(), @OnWebSocketDisconnect(), @OnWebSocketMessage() to handle realtime operations of a CDS service exposed over the @cap-js-community/websocket plugin - an optional peer the consumer app installs separately. They are pure sugar for @OnEvent() - @OnWebSocketConnect() ≈ @OnEvent('wsConnect'), @OnWebSocketDisconnect() ≈ @OnEvent('wsDisconnect') - and register through the exact same @UnboundActions host; there is no dedicated websocket host class, since a websocket service impl is just a regular CAP service impl (module.exports = (srv) => ...).

The service is annotated @protocol: 'websocket' (or @ws):

@protocol: 'websocket'
@path    : 'chat'
service ChatService {
  action wsConnect();
  action wsDisconnect(reason : String);
  action sendMessage(text : String) returns String;
}

Example

import { OnWebSocketConnect, OnWebSocketDisconnect, OnWebSocketMessage, Req, UnboundActions } from '@dxfrontier/cds-ts-dispatcher';
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

@UnboundActions() // bound as ChatService's impl via CDSDispatcher
export class ChatHandler {
  @OnWebSocketConnect()
  public async onConnect(@Req() req: Request) {
    console.log('[Chat] connect');
  }

  @OnWebSocketMessage('sendMessage')
  public async onMessage(@Req() req: Request<{ text: string }>) {
    console.log(`[Chat] message ${req.data.text}`);
    return req.data.text;
  }

  @OnWebSocketDisconnect()
  public async onDisconnect(@Req() req: Request<{ reason?: string }>) {
    // Under kind 'ws' the plugin delivers the socket close CODE as a string (e.g. '1000'), not a phrase.
    console.log(`[Chat] disconnect ${req.data?.reason ?? ''}`);
  }
}

Wire format - clients send plain JSON frames to ws://host:port/ws/<path> (/ws/chat for the example above):

{ "event": "sendMessage", "data": { "text": "hello" } }

event is matched against the plain string passed to @OnWebSocketMessage() (or the fixed 'wsConnect' / 'wsDisconnect' names); data becomes req.data.

Important

@OnWebSocketConnect() / @OnWebSocketDisconnect() only fire when wsConnect() / wsDisconnect(reason) are modeled as actions on the service, exactly as above - the plugin's adapter only calls modeled operations, it does not synthesize connect/disconnect events for services that omit them.

Warning

Observed on kind 'ws' (plugin 1.11.x): action return values are not echoed back as a reply frame - raw ws has no ack-callback channel (unlike socket.io), so whatever @OnWebSocketMessage() returns is discarded on the wire. Use srv.emit(...) / srv.broadcast(...) from inside the handler if the client needs a message back.

(back to top)

@OnWebSocketConnect

@OnWebSocketConnect()

Fires when a websocket client connects. Sugar for @OnEvent('wsConnect').

Example

@OnWebSocketConnect()
public async onConnect(@Req() req: Request) {
  // ...
}

Equivalent to 'JS'

this.on('wsConnect', async (req) => {
  // ...
});

Important

The service must model action wsConnect(); - see the WebSocket prerequisites above.

(back to top)

@OnWebSocketDisconnect

@OnWebSocketDisconnect()

Fires when a websocket client disconnects. Sugar for @OnEvent('wsDisconnect').

Example

@OnWebSocketDisconnect()
public async onDisconnect(@Req() req: Request<{ reason?: string }>) {
  // Under kind 'ws' the plugin delivers the socket close CODE as a string (e.g. '1000'), not a phrase.
  console.log(`[Chat] disconnect ${req.data?.reason ?? ''}`);
}

Equivalent to 'JS'

this.on('wsDisconnect', async (req) => {
  // ...
});

Important

The service must model action wsDisconnect(reason: String); to receive a disconnect detail in req.data.reason - see the WebSocket prerequisites above.

Warning

Under the plugin's default kind: 'ws', the delivered value is the socket close code as a string (e.g. '1000', '1005'), not a reason phrase - raw ws passes (code, reason) and the plugin forwards only the first argument.

(back to top)

@OnWebSocketMessage

@OnWebSocketMessage(name: string)

Handles an incoming websocket operation/event by name. Sugar for @OnEvent(name).

Parameters

  • name (string) : the websocket operation name, matched against the event field of the wire payload ({"event":"<name>","data":{...}}) - a plain string, not a @cds-model action reference.

Note

Like @OnEvent(), everything before the last dot is stripped at registration time - 'ChatService.sendMessage' registers as 'sendMessage'. Pass the plain operation name.

Example

@OnWebSocketMessage('sendMessage')
public async onMessage(@Req() req: Request<{ text: string }>) {
  // req.data.text
}

Equivalent to 'JS'

this.on('sendMessage', async (req) => {
  // ...
});

(back to top)

Method — Middleware & guards
@Use

@Use(...Middleware[])

The @Use decorator is utilized as a method-level decorator and allows you to inject middlewares into your method.

Middleware decorators can perform the following tasks:

  • Execute any code.
  • Make changes to the request object.
  • End the request-response cycle.
  • Call the next middleware function in the stack.
  • If the current middleware function does not end the request-response cycle, it must call next() to pass control to the next middleware function. Otherwise, the request will be left hanging.

Parameters

  • ...Middleware[]): Middleware classes to be injected.

Example: middleware implementation:

import type { MiddlewareImpl, NextMiddleware, Request } from '@dxfrontier/cds-ts-dispatcher';

export class MiddlewareClass implements MiddlewareImpl {
  public async use(req: Request, next: NextMiddleware) {
    console.log('Middleware use method called.');

    await next();
  }
}

Tip

Inside of the use method you can use @CatchAndSetErrorCode and @CatchAndSetErrorMessage decorators if you want to catch errors.

Example usage:

import { EntityHandler, Use, Inject, CDS_DISPATCHER } from '@dxfrontier/cds-ts-dispatcher';
import type { Service, Request } from '@dxfrontier/cds-ts-dispatcher';

import { MiddlewareClass } from 'YOUR_MIDDLEWARE_LOCATION';
import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@EntityHandler(MyEntity)
export class CustomerHandler {
  // ...
  @Inject(CDS_DISPATCHER.SRV) private srv: Service;
  // ...
  constructor() {}

  @AfterRead()
  @Use(MiddlewareClass)
  private async aMethod(@Results() results: MyEntity[], @Req() req: Request) {
    // ...
  }

  // ...
}

Tip

  1. Middlewares when applied with @Use are executed before the normal events.
  2. If you need to apply middleware to class you can have a look over class specific @Use decorator .
  3. The wrapped handler's return value passes through the middleware wrapper — an @On* handler with method-level @Use still replies its payload.

Warning

If req.reject() is being used inside of middleware this will stop the stack of middlewares, this means that next middleware will not be executed.

Note

MyEntity was generated using CDS-Typer and imported in the class.

(back to top)

@ExecutionAllowedForRole

@ExecutionAllowedForRole(...roles: string[])

The @ExecutionAllowedForRole is used as a method decorator and was designed to enforce role-based access control, ensuring that only users with specific roles are authorized to execute the event.

It applies an logical OR on the specified roles, meaning it checks if at least one of the specified roles is assigned to the current request, then the execution will be allowed.

Parameters

  • ...roles: string[]: Specifies the roles that are permitted to execute the event logic.

Example

@AfterRead()
@ExecutionAllowedForRole('Manager', 'User', 'CEO')
private async afterRead(
  @Req() req: Request,
  @Results() results: BookSale[],
) {
  // Method implementation
  // Code will be executed only in case of User ( Manager, User and CEO )
}

(back to top)

@Throttle

@Throttle(options: { limit: number; window: number; by?: 'user' | 'tenant' })

The @Throttle decorator is a method decorator that rate-limits a handler with a fixed window, counted per user (default) or per tenant. Once the limit is exhausted inside the current window, the request is rejected with HTTP 429 through req.reject(...) - the decorated method body never runs.

It works on any handler decorator that receives a real cds.Request - CRUD (@OnCreate(), @BeforeUpdate(), ...), actions and functions (@OnAction(), @OnBoundFunction(), ...) - hosted in either @EntityHandler or @UnboundActions.

Parameters

OptionTypeDefaultDescription
limitnumber(required)Maximum number of invocations allowed per window. Must be >= 1.
windownumber (ms)(required)Fixed window length in milliseconds. Must be >= 1.
by'user' | 'tenant''user'Counter key source: 'user' keys by req.user.id (fallback 'anonymous'), 'tenant' keys by req.tenant (fallback 'no-tenant').

Example

import { OnAction, Req, Throttle, UnboundActions } from '@dxfrontier/cds-ts-dispatcher';
import type { ActionRequest, ActionReturn } from '@dxfrontier/cds-ts-dispatcher';

import { GenerateReport } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@UnboundActions()
export class ReportHandler {
  @OnAction(GenerateReport)
  @Throttle({ limit: 10, window: 60_000 }) // 10 calls per minute, per user
  public async generate(@Req() req: ActionRequest<typeof GenerateReport>): ActionReturn<typeof GenerateReport> {
    // ...
  }
}

Important

Stack @Throttle directly below the handler decorator (@OnAction, @OnCreate, ...), closer to the method - the same rule as @ExecutionAllowedForRole. Decorators wrap descriptor.value bottom-up, so a wrapper placed above the handler decorator never becomes part of the registered callback - it silently does nothing. Keep a @Req() parameter: the wrapper reads the current cds.Request off the handler arguments to resolve the counter key and to call req.reject(...).

Important

@Throttle targets request handlers only. On messaging-event handlers (@OnEvent() / @OnSubscribe()) there is no cds.Request among the arguments - every delivery fails loudly with a descriptive error instead of silently letting messages through unthrottled.

Note

Counters are fixed-window, kept in-memory, per app instance and per decorated method - in a multi-instance deployment the limit is effectively per pod, not global. Each OData $batch sub-request invokes the handler (and therefore the counter) individually - there is no batch-level dedup like @BeforeCommit has.

Note

Over the limit, the rejection message has the shape: Rate limit exceeded: max ${limit} requests per ${window} ms for this ${by}. Retry in ${retryAfter} ms.

Warning

@Throttle cannot be combined with @OnError() - stacking it under @OnError throws at decoration time (error handlers run synchronously while the transaction unwinds, which a throttling wrapper cannot honor).

Tip

For a service-global (not per-handler) limit, use CAP's own extension point instead: cds.middlewares.add(rateLimit(), { after: 'auth' }) in a custom server.js. @Throttle stays the per-handler tool.

(back to top)

Method — Utilities
@AfterReadSingleInstance

@AfterReadSingleInstance()

The @AfterReadSingleInstance decorator is utilized as a method-level and it can be used when you want to execute custom logic for single instance request.

If you want to execute logic for both cases (single instance and entity set) then you should use the @AfterRead() and applying the @SingleInstanceSwitch() decorator to switch between entity set and single instance.

Example

Single request : http://localhost:4004/odata/v4/main/MyEntity(ID=2f12d711-b09e-4b57-b035-2cbd0a023a09)

import { AfterReadSingleInstance, Result, Req } from "@dxfrontier/cds-ts-dispatcher";
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@AfterReadSingleInstance()
private async afterReadSingleInstance(@Result() result: MyEntity, @Req() req: Request<MyEntity>) {
  // This will be executed only when single instance is called : http://localhost:4004/odata/v4/main/MyEntity(ID=2f12d711-b09e-4b57-b035-2cbd0a023a09)
  // ...
}

Caution

If @AfterReadSingleInstance() is used all together with @AfterRead(), the event GET (Read), PATCH (Update) will trigger both decorators, this applies only when both decorators are used in the same @EntityHandler().

The vice-versa doesn't apply, this means that if you trigger the entity set request the @AfterReadSingleInstance() will not be triggered.

Example GET: http://localhost:4004/odata/v4/main/MyEntity(ID=2f12d711-b09e-4b57-b035-2cbd0a023a09)

// use this
@AfterReadSingleInstance()
private async afterReadSingleInstance(
  @Req() req: Request,
  @Result() result: MyEntity
): Promise<void> {
  // The `GET` will trigger the single instance request
}

// or this as alternative to above `@AfterReadSingleInstance`
@AfterRead()
private async afterRead(
  @Req() req: Request,
  @Results() results: MyEntity[],
  @SingleInstanceSwitch() singleInstance: boolean
): Promise<void> {
  // The `GET` will trigger for both cases (single instance & entity instance), but you can use the `singleInstance` flag to verify if it's single or entity set.
  if(singleInstance) {
 
  }
}

Important

Decorator @AfterReadSingleInstance() will be triggered based on the EntityHandler argument MyEntity.

Tip

If @odata.draft.enabled: true and you need to read the draft then you should use @AfterReadDraftSingleInstance() decorator.

Note

MyEntity was generated using CDS-Typer and imported in the the class.

(back to top)

@Prepend

@Prepend({ eventDecorator : string })

The @Prepend decorator is a method-level decorator used to register an event handler that executes before event handlers such as @BeforeCreate(), @AfterCreate(), @OnAction(), etc.

Parameters

  • eventDecorator (string) : The eventDecorator can be one of the following :
    • BEFORE: 'BeforeCreate', 'BeforeRead', 'BeforeUpdate', 'BeforeDelete', 'BeforeAll'.
    • AFTER: 'AfterCreate', 'AfterRead', 'AfterReadEachInstance', 'AfterReadSingleInstance', 'AfterUpdate', 'AfterDelete', 'AfterAll'.
    • ON: 'OnCreate', 'OnRead', 'OnUpdate', 'OnDelete', 'OnAll', 'OnAction', 'OnFunction', 'OnBoundAction', 'OnBoundFunction', 'OnEvent', 'OnError'.
  • actionName: (CDSFunction) : Action name, applicable only for OnAction, OnBoundAction, OnFunction, OnBoundFunction.
  • eventName: (CDSEvent) : Event name, applicable only for OnEvent.

Example 1: Prepending to AfterRead

import { Prepend, AfterRead, Req, Results } from '@dxfrontier/cds-ts-dispatcher';
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@Prepend({ eventDecorator: 'AfterRead' })
private async prepend(@Req() req: Request): Promise<void> {
  req.locale = 'DE_de';
}

@AfterRead()
private async afterRead(MyEntity
  @Req() req: Request,
  @Results() results: MyEntity[],
) {

  // req.locale will have the value 'DE_de' ...
  // ...
}

Example 2: Prepending to OnBoundAction

import { Prepend, OnBoundAction, Req, Next } from '@dxfrontier/cds-ts-dispatcher';
import type { ActionRequest, NextEvent } from '@dxfrontier/cds-ts-dispatcher';  

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@Prepend({ eventDecorator: 'OnBoundAction', actionName: MyEntity.actions.AnAction })
private async prepend(@Req() req: ActionRequest<typeof MyEntity.actions.AnAction>, @Next() next: NextEvent) {
  req.locale = 'DE_de';
  return next();
}

@OnBoundAction(MyEntity.actions.AnAction)
private async onBoundAction(@Req() req: ActionRequest<typeof MyEntity.actions.AnAction>) {
  // req.locale will have the value 'DE_de' ...
  // ...
}

Example 3: Prepending to OnEvent

import { Prepend, OnEvent, Req } from '@dxfrontier/cds-ts-dispatcher';
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEvent } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@Prepend({ eventDecorator: 'OnEvent', eventName: MyEvent })
private async prepend(@Req() req: Request<MyEvent>) {
  req.locale = 'DE_de';
}

@OnEvent(MyEvent)
public async handleMyEvent(@Req() req: Request<MyEvent>) {
  // req.locale will have the value 'DE_de' ...
  // ...
}

Important

When using @Prepend on decorators like @OnCreate, @OnRead, @OnUpdate, @OnDelete, @OnAction, @OnFunction, @OnEvent, @OnSubscribe, @OnError, @OnBoundAction, @OnBoundFunction, @OnAll calling return next() is mandatory to ensure the actual action is executed.

Tip

Use @Prepend decorator to :

  • Prepare data or perform transformations before reaching the actual event.
  • Modify Request or Result objects before they are processed by the main handler.
  • ...

Tip

If @odata.draft.enabled: true and you need to read the draft then you should use @PrependDraft() decorator.

(back to top)

@Validate

@Validate<T>({ action, options? }, ...fields: Array<keyof T>)

The @Validate decorator is utilized as a method-level decorator, used to validate fields of your entity before reaching your event callback.

Tip

Think of it as a pre-validation helper.

The @Validate decorator is useful when you need to validate the Request.data (Request Body) of the @sap/cds - Request object in the following contexts:

Parameters

  • action: Choose from a list of predefined Validators .

  • options?: [Optional] Additional options for customizing the validation process.

    The options parameter accepts the following settings to customize validation behavior:

    • exposeValidatorResult (boolean):
      Determines if the validator results are captured and passed to the method as parameters.

      • If true, the validator results (e.g., ValidatorFlags<'endsWith' | 'isEmail'>) will be available as a parameter in the method.
      • To use this, apply the @ValidationResults decorator to the method parameter where you want the validator flags injected.

      Default: false

      Example:

      @Validate<MyEntity>({ action: 'endsWith', target: 'N', exposeValidatorResult: true }, 'description')
      @Validate<MyEntity>({ action: 'isEmail', exposeValidatorResult: true }, 'description')
      public async beforeCreate(
        @Req() req: Request<MyEntity>,
        @ValidationResults() validator: ValidatorFlags<'endsWith' | 'isEmail'>,
      ) {
        if (validator.endsWith) {
          // logic based on validation result endsWith
        }
      
        if (validator.isEmail) {
          // logic based on validation result isEmail
        }
      }
    • customMessage (string):
      Custom message to replace the default validation error message.

      • If provided, this message will be shown instead of the default error.
    • mandatoryFieldValidation (boolean):
      Controls whether the validator requires the presence of the fields in Request.data.

      • If true, the specified fields must be present in Request.data, or validation will fail.
      • If false (or omitted), validation will pass even if the field is missing.

      Default: false

  • ...fields: Specify the fields of your entity that require validation.

Returns

The decorator will raise a Request.reject message if the validation requirements are not met.

Validators

Below is a list of available validators:

ActionDescriptionOptions
containsCheck if the string contains the seed.ignoreCase: boolean | undefined; default : false
minOccurrences: number | undefined; default 1
equalsCheck if the string matches the comparison.
matchesCheck if the string matches the pattern.
startsWithChecks if the string starts with the given target string.
endsWithChecks if the string ends with the given target string.
isMailtoURICheck if the string is a Mailto URI format.allow_display_name: If set to true, the validator will also match Display Name <email-address>. Default: false
require_display_name: If set to true, the validator will reject strings without the format Display Name <email-address>. Default: false
allow_utf8_local_part: If set to false, the validator will not allow any non-English UTF8 character in the email address' local part. Default: true
require_tld: If set to false, email addresses without a TLD in their domain will also be matched. Default: true
ignore_max_length: If set to true, the validator will not check for the standard max length of an email. Default: false
allow_ip_domain: If set to true, the validator will allow IP addresses in the host part. Default: false
domain_specific_validation: If set to true, some additional validation will be enabled, e.g., disallowing certain syntactically valid email addresses that are rejected by GMail. Default: false
host_blacklist: An array of strings. If the part of the email after the @ symbol matches any string in the array, validation fails.
host_whitelist: An array of strings. If the part of the email after the @ symbol does not match any string in the array, validation fails.
blacklisted_chars: A string. If any character in the string appears in the name part of the email, validation fails.
isNumericCheck if the string contains only numbers.

no_symbols: If set to true, the validator will reject numeric strings that feature a symbol (e.g., +, -, or .). _Default: false*

locale: An object specifying the locale information for alpha validation.

isTimeCheck if the string is a valid time e.g. 23:01:59.

hourFormat: Specify the hour format to validate. Use 'hour12' for 12-hour format and 'hour24' for 24-hour format. Default: 'hour24'

mode: Specify the time format to validate. Use 'default' for HH:MM format and 'withSeconds' for HH:MM:SS format. Default: 'default'

isLatLongcheck if the string is a valid latitude-longitude coordinate in the format lat,long or lat, long.
isMD5check if the string is a MD5 hash.
isMimeTypecheck if the string matches to a valid [MIME type] format.
isPortcheck if the string is a valid port number.
isSlugcheck if the string is of type slug.
isISBNcheck if the string is an [ISBN] version: "10", "13", 10, 13;
isEmailcheck if the string is an email.Same as isMailtoURI
isAlphaCheck if the string contains only letters (a-zA-Z).
isAlphanumericCheck if the string contains only letters and numbers.
isCreditCardCheck if the string is a credit card.

provider: Specify the credit card provider to validate. Use one of the following: 'amex', 'dinersclub', 'discover', 'jcb', 'mastercard', 'unionpay', 'visa', or '' for any provider. Default: undefined

isCurrencyCheck if the string is a valid currency amount.symbol: The currency symbol to be expected. Default: '$'
require_symbol: If set to true, the validator will expect the currency symbol to be present. Default: false
allow_space_after_symbol: If set to true, the validator will allow a space after the currency symbol. Default: false
symbol_after_digits: If set to true, the currency symbol will be expected after the digits. Default: false
allow_negatives: If set to true, negative currency values will be allowed. Default: true
parens_for_negatives: If set to true, negative currency values will be enclosed in parentheses. Default: false
negative_sign_before_digits: If set to true, the negative sign will be placed before the digits. Default: false
negative_sign_after_digits: If set to true, the negative sign will be placed after the digits. Default: false
allow_negative_sign_placeholder: If set to true, the validator will allow a placeholder - for negative values. Default: false
thousands_separator: The thousands separator to be expected. Default: ','
decimal_separator: The decimal separator to be expected. Default: '.'
allow_decimal: If set to true, decimal values will be allowed. Default: true
require_decimal: If set to true, a decimal value will be required. Default: false
digits_after_decimal: An array of numbers representing the exact number of digits allowed after the decimal point. Default: [2]
allow_space_after_digits: If set to true, the validator will allow a space after the digits. Default: false
isDataURICheck if the string is a data URI format.
isDateCheck if the string is a valid date.format: A string representing the expected date format. Default: undefined
strictMode: If set to true, the validator will reject inputs different from the specified format. Default: false
delimiters: An array of allowed date delimiters. Default: ['/', '-']
isEmptyCheck if the string has a length of zero.ignore_whitespace: If set to true, whitespace characters will be ignored. Default: false
isIBANCheck if a string is an IBAN.whitelist: An array of IBAN countries to whitelist. Default: undefined
blacklist: An array of IBAN countries to blacklist. Default: undefined
isIMEICheck if the string is a valid IMEI.allow_hyphens: If set to true, allows IMEI numbers with hyphens. Default: false
isIPCheck if the string is an IP (version 4 or 6)."4", "6", 4, 6;
isIdentityCardCheck if the string is a valid identity card code.locale: An array of supported locales for identity cards. Acceptable values: "ar-LY", "ar-TN", "ES", "FI", "he-IL", "IN", "IR", "IT", "LK", "NO", "PL", "TH", "zh-CN", "zh-HK", "zh-TW" or 'any'
isInCheck if the string is in an array of allowed values.
isJSONCheck if the string is valid JSON (note: uses JSON.parse).
isJWTCheck if the string is a valid JWT token.
isLengthCheck if the string's length falls in a range.min: The minimum length allowed for the string. Default: 0
max: The maximum length allowed for the string. Default: undefined
isLowercaseCheck if the string is lowercase.
isMobilePhoneCheck if the string is a mobile phone number.strictMode: If set to true, the mobile phone number must be supplied with the country code and must start with +. Default: false
isPassportNumberCheck if the string is a valid passport number relative to a specific country code.
isPostalCodeCheck if the string is a postal code."AD", "AT", "AU", "BE", "BG", "BR", "CA", "CH", "CN", "CZ", "DE", "DK", "DZ", "EE", "ES", "FI", "FR", "GB", "GR", "HR", "HU", "ID", "IE", "IL", "IN", "IR", "IS", "IT", "JP", "KE", "KR", "LI", "LT", "LU", "LV", "MX", "MT", "NL", "NO", "NZ", "PL", "PR", "PT", "RO", "RU", "SA", "SE", "SI", "SK", "TN", "TW", "UA", "US", "ZA", "ZM"
isURLCheck if the string is a URL.protocols: An array of allowed protocols. Default: ['http', 'https', 'ftp']
require_tld: If set to true, URLs must have a top-level domain. Default: true
require_protocol: If set to true, URLs must have a protocol. Default: false
require_host: If set to true, URLs must have a host. Default: true
require_port: If set to true, isURL will check if a port is present in the URL. Default: false
require_valid_protocol: If set to true, URLs must have a valid protocol. Default: true
allow_underscores: If set to true, underscores are allowed in URLs. Default: false
host_whitelist: An array of allowed hosts.
host_blacklist: An array of disallowed hosts.
allow_trailing_dot: If set to true, trailing dots are allowed in URLs. Default: false
allow_protocol_relative_urls: If set to true, protocol-relative URLs are allowed. Default: false
disallow_auth: If set to true, authentication credentials in URLs are disallowed. Default: false
allow_fragments: If set to true, URL fragments are allowed. Default: true
allow_query_components: If set to true, URL query components are allowed. Default: true
validate_length: If set to true, URLs will be validated for length. Default: true
isUUIDCheck if the string is a UUID (version 1, 2, 3, 4, or 5).
isUppercaseCheck if the string is uppercase.
isVATChecks that the string is a valid VAT number.
isWhitelistedChecks if characters appear in the whitelist.
isIntCheck if the string is an integer.min: to check the integer min boundary
max: to check the integer max boundary
allow_leading_zeroes: if false, will disallow integer values with leading zeroes. Default: true
lt: enforce integers being greater than the value provided
gt: enforce integers being less than the value provided
isHexadecimalCheck if the string is a hexadecimal number.
isFloatCheck if the string is a float.min: less or equal
max: greater or equal
gt: greater than
lt: less than
locale: FloatLocale
isHashCheck if the string is a hash of export type algorithm."md4", "md5", "sha1", "sha256", "sha384", "sha512", "ripemd128", "ripemd160", "tiger128", "tiger160", "tiger192", "crc32", "crc32b"
isEANCheck if the string is an EAN (European Article Number).
isDecimalCheck if the string represents a decimal number, such as 0.1, .3, 1.1, 1.00003, 4.0 etc.force_decimal: If set to true, the validator will only return true if the string contains a decimal number. Default: false
decimal_digits: Specifies the number of decimal digits allowed. It can be given as a range like '1,3', a specific value like '3', or min like '1,'. Default: '1,'
locale: The locale to use for number formatting. Default: 'en-US'
isBooleanCheck if a string is a boolean.loose: If set to true, the validator will match a valid boolean string of any case, including ['true', 'True', 'TRUE'], and also 'yes' and 'no'. If set to false, the validator will strictly match ['true', 'false', '0', '1']. Default: false
isBICCheck if a string is a BIC (Bank Identification Code) or SWIFT code.
isBeforeCheck if the string is a date that's before the specified date.
isAfterCheck if the string is a date that's after the specified date.

(back to top)

Example 1

import {
  EntityHandler,
  Inject,
  CDS_DISPATCHER,
  Validate,
  BeforeCreate,
  BeforeUpdate,
  OnCreate,
  OnUpdate,
  Req,
  Next,
} from '@dxfrontier/cds-ts-dispatcher';
import type { Request, Service, NextEvent } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@EntityHandler(MyEntity)
export class CustomerHandler {
  // ...
  @Inject(CDS_DISPATCHER.SRV) private srv: Service;
  // ...
  constructor() {}

  @BeforeCreate()
  @Validate<MyEntity>({ action: 'isLowercase' }, 'comment')
  @Validate<MyEntity>({ action: 'endsWith', target: 'N' }, 'description')
  private async beforeCreate(@Req() req: Request<MyEntity>) {
    // ...
  }

  @BeforeUpdate()
  @Validate<MyEntity>({ action: 'startsWith', target: 'COMMENT:' }, 'comment')
  @Validate<MyEntity>({ action: 'isAlphanumeric' }, 'description')
  private async beforeUpdate(@Req() req: Request<MyEntity>) {
    // ...
  }

  @OnCreate()
  @Validate<MyEntity>({ action: 'isAlphanumeric' }, 'book_ID')
  private async onCreate(@Req() req: Request<MyEntity>, @Next() next: NextEvent) {
    // ...
    return next();
  }

  @OnUpdate()
  @Validate<MyEntity>({ action: 'isLength', options: { min: 5 } }, 'comment')
  private async onUpdate(@Req() req: Request<MyEntity>, @Next() next: NextEvent) {
    // ...
    return next();
  }

  // ...
}

Example 2: @Validate is used inside of @UnboundActions

import { UnboundActions, OnAction, OnFunction, OnEvent, Validate, Next, Req } from '@dxfrontier/cds-ts-dispatcher';
import type { ExposeFields, Request, ActionRequest, ActionReturn, NextEvent } from '@dxfrontier/cds-ts-dispatcher';

import { SomeAction, SomeFunction, OrderedBook } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@UnboundActions()
class UnboundActionsHandler {
  @OnAction(SomeAction)
  @Validate<ExposeFields<typeof SomeAction>>({ action: 'isIn', values: [1, 2] }, 'book', 'quantity')
  private async onActionMethod(
    @Req() req: ActionRequest<typeof SomeAction>,
    @Next() _: NextEvent,
  ): ActionReturn<typeof SomeAction> {
    // ...
  }

  @OnFunction(SomeFunction)
  @Validate<ExposeFields<typeof SomeFunction>>({ action: 'isIn', values: [1, 2] }, 'book', 'quantity')
  private async onFunctionMethod(
    @Req() req: ActionRequest<typeof SomeFunction>,
    @Next() next: NextEvent,
  ): ActionReturn<typeof SomeFunction> {
    // ...
  }

  @OnEvent(OrderedBook)
  @Validate<OrderedBook>({ action: 'isIn', values: [1, 2] }, 'book', 'quantity')
  private async onEvent(@Req() req: Request<OrderedBook>) {
    // ...
  }
}

Tip

If you want to catch the validators use the following decorator @ValidationResults

This approach is useful if you need to conditionally process data based on validation outcomes within the same function.

Example Use Case: Suppose you want to validate multiple fields but only perform certain actions when specific validations pass. By using @ValidationResults, you gain access to a detailed validator object containing the results for each validation, allowing for flexible and targeted logic.

 @Validate<MyEntity>({ action: 'isLowercase', exposeValidatorResult: true }, 'comment')
 @Validate<MyEntity>({ action: 'endsWith', target: 'N', exposeValidatorResult: true }, 'description')
public async beforeCreate(
 @Req() req: Request<MyEntity>,
 @ValidationResults() validator: ValidatorFlags<'isLowercase' | 'endsWith'>) {
 // Conditional handling based on validation flags
 if (validator.isLowercase) {
   // Execute logic when the "isLowercase" validation succeeds
 }

 if (validator.endsWith) {
   // Separate handling for "endsWith" validation result
 }
}

Important

To get the fields for

you must use the ExposeFields type inside of the @Validate decorator.

(back to top)

@FieldsFormatter

@FieldsFormatter<T>({ action, options? }, ...fields: Array<keyof T>)

The @FieldsFormatter is used as a method decorator to modify/enhance fields.

The @FieldsFormatter decorator can be used on the following decorators :

  1. When you want to modify/enhance the results of your callback.

  2. When you want to modify/enhance the Request.data (Request Body) of the @sap/cds - Request object.

Parameters

  • action: Choose from a list of predefined Formatters.
  • options?: [Optional] Additional options for customizing the formatter process.
  • ...fields: Specify the fields of your entity that require formatting.

Formatters

Here are the available formatter methods:

ActionDescription
blacklistRemove characters that appear in the blacklist.
ltrimTrim characters from the left-side of the input.
rtrimTrim characters from the right-side of the input.
trimTrim characters from both sides of the input.
escapeReplace <, >, &, ', " and / with HTML entities.
unescapeReplaces HTML encoded entities with <, >, &, ', " and /.
toLowerConverts string, as a whole, to lower case.
toUpperConverts string, as a whole, to upper case.
upperFirstConverts the first character of the string to upper case.
lowerFirstConverts the first character of the string to lower case.
replaceReplaces matches for pattern in string with replacement.
Note: This method is based on String#replace.
truncateTruncates string if it’s longer than the given maximum string length.
The last characters of the truncated, string are replaced with the omission string which defaults to "…".
snakeCaseSnake case (or snakecase) is the process of writing compound words so that the words are separated with an underscore symbol () instead of a space. The first letter is usually changed to lowercase. Some examples of Snake case would be "foo_bar" or "hello_world".
kebabCaseKebab case, also known as "spinal case" or "hyphen case," involves writing compound words in lowercase letters and separating them with hyphens ("-"). For example, the phrase "user settings panel" would be represented as "user-settings-panel" in the kebab case.
camelCaseThe format indicates the first word starting with either case, then the following words having an initial uppercase letter. CustomerName, LastName ...
customFormatterApply a custom formatter when standard ones do not satisfy your needs.

Note

Every listed field is formatted. customFormatter invokes your callback(req, results) exactly once — with or without field arguments; on the BEFORE/ON path results is undefined (work on req.data via req).

(back to top)

Example 1

import {
  EntityHandler,
  Inject,
  CDS_DISPATCHER,
  BeforeCreate,
  BeforeUpdate,
  AfterRead,
  OnCreate,
  OnUpdate,
  FieldsFormatter,
  Req,
  Next,
} from '@dxfrontier/cds-ts-dispatcher';
import type { Service, Request, NextEvent } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@EntityHandler(MyEntity)
export class CustomerHandler {
  // ...
  @Inject(CDS_DISPATCHER.SRV) private srv: Service;
  // ...
  constructor() {}

  @BeforeCreate()
  @FieldsFormatter<MyEntity>({ action: 'blacklist', charsToRemove: 'le' }, 'name')
  private async beforeCreate(@Req() req: Request<MyEntity>) {
    // ...
  }

  @BeforeUpdate()
  @FieldsFormatter<MyEntity>({ action: 'truncate', options: { length: 7 } }, 'comment')
  private async beforeUpdate(@Req() req: Request<MyEntity>) {
    // ...
  }

  @AfterRead()
  @FieldsFormatter<MyEntity>({ action: 'toUpper' }, 'lastName')
  @FieldsFormatter<MyEntity>(
    {
      action: 'customFormatter',
      callback(req, results) {
        if (results) {
          // make first item 'toLowerCase' and leave the rest 'toUpper'
          results[0].lastName = results[0].lastName?.toLowerCase();
        }
      },
    },
    'lastName',
  )
  private async afterRead(@Results() results: MyEntity[], @Req() req: Request<MyEntity>) {
    // ...
  }

  @OnCreate()
  @FieldsFormatter<MyEntity>({ action: 'ltrim' }, 'language')
  private async onCreate(@Req() req: Request<MyEntity>, @Next() next: NextEvent) {
    // ...
    return next();
  }

  @OnUpdate()
  @FieldsFormatter<MyEntity>({ action: 'trim' }, 'format')
  private async onUpdate(@Req() req: Request<MyEntity>, @Next() next: NextEvent) {
    // ...
    return next();
  }

  // ...
}

Tip

See best practice for customFormatter

Example 2 : using @FieldsFormatter decorator inside the @UnboundActions

import {
  UnboundActions,
  OnAction,
  OnFunction,
  OnEvent,
  FieldsFormatter,
  Req,
  Next,
} from '@dxfrontier/cds-ts-dispatcher';
import type { ActionRequest, ActionReturn, ExposeFields, NextEvent } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@UnboundActions()
class UnboundActionsHandler {
  @OnAction(AnAction)
  @FieldsFormatter<ExposeFields<typeof AnAction>>({ action: 'toLower' }, 'descr', 'bookName')
  private async onActionMethod(
    @Req() req: ActionRequest<typeof AnAction>,
    @Next() _: NextEvent,
  ): ActionReturn<typeof AnAction> {
    // ...
    return next();
  }

  @OnFunction(AFunction)
  @FieldsFormatter<ExposeFields<typeof AFunction>>({ action: 'toUpper' }, 'lastName')
  private async onFunctionMethod(
    @Req() req: ActionRequest<typeof AFunction>,
    @Next() next: NextEvent,
  ): ActionReturn<typeof AFunction> {
    // ...
    return next();
  }

  @OnEvent(AnEvent)
  @FieldsFormatter<AnEvent>({ action: 'upperFirst' }, 'name')
  private async onEvent(@Req() req: Request<AnEvent>) {
    // ...
  }
}

Important

To get the fields for

you must use the ExposeFields type inside of the @FieldsFormatter decorator.

(back to top)

@CatchAndSetErrorCode

@CatchAndSetErrorCode(newStatusCode: string)

The @CatchAndSetErrorCode decorator is a method decorator that automatically catches errors and assigns a new status code to the response. It eliminates the need for explicit try-catch blocks within the method, ensuring consistent error handling across your service.

Parameters

  • newStatusCode - The status code to return when an error occurs, replacing the original error code.

Example 1

In the example below, we call a web service (await axios.get('https://jsonplaceholder.typicode')) which always throws a 500 Internal Server Error, by using @CatchAndSetErrorCode('BAD_REQUEST-400'), we overwrite the default 500 Internal Server Error with 400 Bad Request.

@AfterRead()
@CatchAndSetErrorCode('BAD_REQUEST-400') // <== Catch all errors and overwrite with `BAD REQUEST 400`
private async afterRead(
  @Req() req: Request,
  @Results() results: Book[],
): Promise<void> {

  // No need to wrap the service with a try-catch
  await axios.get('https://jsonplaceholder.typicode'); // <== Throw 500 Internal Server Error
}

Example 2

In the example below, we stack 2 decorators

  • @CatchAndSetErrorCode
  • @CatchAndSetErrorMessage

as a result the second @CatchAndSetErrorMessage decorator will not be executed due to the stop of execution of the first @CatchAndSetErrorCode decorator

@AfterRead()
@CatchAndSetErrorCode('BAD_REQUEST-400') // <== Catch all errors and overwrite with `BAD REQUEST 400`
@CatchAndSetErrorMessage('The new message of the error') // <== This will not be executed !!
private async afterRead(
  @Req() req: Request,
  @Results() results: Book[],
): Promise<void> {

  // No need to wrap the service with a try-catch.
  await axios.get('https://jsonplaceholder.typicode'); // <== This service will throw a 500 Internal Server Error
}

Example 3

In this example we added @CatchAndSetErrorCode decorator to throw a new custom message to the use method of @Use middleware.

import { CatchAndSetErrorCode } from '@dxfrontier/cds-ts-dispatcher';
import type { MiddlewareImpl, NextMiddleware, Request } from '@dxfrontier/cds-ts-dispatcher'

import type { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

export class Middleware1 implements MiddlewareImpl {
  @CatchAndSetErrorCode('New error message')
  public async use(req: Request<MyEntity>, next: NextMiddleware): Promise<void> {
    // ...
    await next();
  }
}

then use it inside of MyHandler class

import { EntityHandler, Use, AfterRead, Results, Req, Request} from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';
import { Middleware1 } from 'YOUR_MIDDLEWARE_LOCATION';

@EntityHandler(MyEntity)
@Use(Middleware1) // <== Add Middleware1 
export class MyHandler {
  // ...
  constructor() {}

  @AfterRead()
  @Use(Middleware1) // <== Or use it as method middleware
  private async aMethod(@Results() results: MyEntity[], @Req() req: Request) {
    // ...
  }
}

Tip

Decorators @CatchAndSetErrorCode and @CatchAndSetErrorMessage can be used in use method of @Use middleware and class use method of @Use middleware.

Tip

Always stack @CatchAndSetErrorCode and @CatchAndSetErrorMessage decorators last (at the bottom of the of the method) so that other decorators are executed.

(back to top)

@CatchAndSetErrorMessage

CatchAndSetErrorMessage(newMessage: string, newStatusCode?: keyof StatusCodeMapping)

The @CatchAndSetErrorMessage decorator is a method decorator that automatically catches errors and assigns a new message and a new error code to the response. It eliminates the need for explicit try-catch blocks within the method, ensuring consistent error handling across your service.

Parameters

  • newMessage - The message to return when an error occurs, placing the original error message.
  • newStatusCode? [optional] - The status code to return when an error occurs, replacing the original error code.

Example 1

In this example, we fetch user data from an external API. If the API call fails, we replace the error message and return a 404 Not Found response.

import { Req, CatchAndSetErrorMessage, OnUpdate } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@OnUpdate()
@CatchAndSetErrorMessage('User data could not be retrieved', 'NOT_FOUND-404') 
private async onUpdate(@Req() req: Request<MyEntity>): Promise<MyEntity> {
  const userId = req.data.ID;
  
  const response = await axios.get(`https://jsonplaceholder.typicode.com/users/${userId}`); // <=== No need to explicitly catch in a try-catch statement, @CatchAndSetErrorMessage will take care of that.
}

Example 2

In this example we added @CatchAndSetErrorMessage decorator to throw a new custom message to the @Use - method of a middleware.

import { CatchAndSetErrorMessage } from '@dxfrontier/cds-ts-dispatcher';
import type { MiddlewareImpl, NextMiddleware, Request } from '@dxfrontier/cds-ts-dispatcher'

import type { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

export class Middleware1 implements MiddlewareImpl {
  @CatchAndSetErrorMessage('New error message')
  public async use(req: Request<MyEntity>, next: NextMiddleware): Promise<void> {
    // ...
    await next();
  }
}

then use it inside of MyHandler class

import { EntityHandler, Use, AfterRead, Results, Req, Request} from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';
import { Middleware1 } from 'YOUR_MIDDLEWARE_LOCATION';

@EntityHandler(MyEntity)
@Use(Middleware1) // <== Add Middleware1 
export class MyHandler {
  // ...
  constructor() {}

  @AfterRead()
  @Use(Middleware1) // <== Or use it as method middleware
  private async aMethod(@Results() results: MyEntity[], @Req() req: Request) {
    // ...
  }
}

Tip

Always stack @CatchAndSetErrorCode and @CatchAndSetErrorMessage decorators last (at the bottom of the of the method) so that other decorators are executed.

Tip

Decorators @CatchAndSetErrorCode and @CatchAndSetErrorMessage can be used in use method of @Use middleware and class use method of @Use middleware.

(back to top)

@Exclude

@Exclude<T>(...fields: Array<keyof T>)

The @Exclude decorator is used as a method decorator to automatically remove specified fields from the response data.

The @Exclude decorator can be used on the following decorators:

Note

On write events (@AfterCreate / @AfterUpdate) with @sap/cds 10+ the generic handlers expose only key columns (CREATE) or an empty affected-count array (UPDATE) in req.results, and generic OData/REST write response bodies are rebuilt after this phase via read-after-write — attach the transformer to @AfterRead() to shape write responses (a custom @On* handler returning full rows is the exception).

Parameters

  • ...fields: Specify the fields of your entity that should be excluded from the response.

Example

import { EntityHandler, AfterRead, Exclude, Results, Req } from '@dxfrontier/cds-ts-dispatcher';
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@EntityHandler(MyEntity)
export class MyHandler {
  constructor() {}

  @AfterRead()
  @Exclude<MyEntity>('createdAt', 'modifiedAt', 'internalField')
  private async afterRead(@Results() results: MyEntity[], @Req() req: Request) {
    // The response will NOT contain 'createdAt', 'modifiedAt', 'internalField' fields
  }
}

Tip

Use @Exclude when you want to hide sensitive or internal fields from the API response, such as audit fields, internal IDs, or computed fields.

(back to top)

@Include

@Include<T>(...fields: Array<keyof T>)

The @Include decorator is used as a method decorator to automatically keep only the specified fields in the response data, removing all others.

The @Include decorator can be used on the following decorators:

Note

On write events (@AfterCreate / @AfterUpdate) with @sap/cds 10+ the generic handlers expose only key columns (CREATE) or an empty affected-count array (UPDATE) in req.results, and generic OData/REST write response bodies are rebuilt after this phase via read-after-write — attach the transformer to @AfterRead() to shape write responses (a custom @On* handler returning full rows is the exception).

Parameters

  • ...fields: Specify the fields of your entity that should be included in the response (all other fields will be removed).

Example

import { EntityHandler, AfterRead, Include, Results, Req } from '@dxfrontier/cds-ts-dispatcher';
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@EntityHandler(MyEntity)
export class MyHandler {
  constructor() {}

  @AfterRead()
  @Include<MyEntity>('ID', 'name', 'description')
  private async afterRead(@Results() results: MyEntity[], @Req() req: Request) {
    // The response will ONLY contain 'ID', 'name', 'description' fields
  }
}

Tip

Use @Include when you want to create a minimal response with only specific fields, which is useful for list views or summary endpoints.

(back to top)

@Mask

@Mask<T>(fields: Array<keyof T>, options?: MaskOptions)

The @Mask decorator is used as a method decorator to automatically mask specified fields in the response data, useful for hiding sensitive information while still showing part of the value.

The @Mask decorator can be used on the following decorators:

Note

On write events (@AfterCreate / @AfterUpdate) with @sap/cds 10+ the generic handlers expose only key columns (CREATE) or an empty affected-count array (UPDATE) in req.results, and generic OData/REST write response bodies are rebuilt after this phase via read-after-write — attach the transformer to @AfterRead() to shape write responses (a custom @On* handler returning full rows is the exception).

Parameters

  • fields: An array of fields of your entity that should be masked.
  • options?: [Optional] Configuration options for the masking:
    • char?: string - The character to use for masking (default: '*')
    • visibleStart?: number - Number of characters to leave visible at the start (default: 0)
    • visibleEnd?: number - Number of characters to leave visible at the end (default: 4)

Example 1 - Default masking

import { EntityHandler, AfterRead, Mask, Results, Req } from '@dxfrontier/cds-ts-dispatcher';
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@EntityHandler(MyEntity)
export class MyHandler {
  constructor() {}

  @AfterRead()
  @Mask<MyEntity>(['creditCardNumber', 'socialSecurityNumber'])
  private async afterRead(@Results() results: MyEntity[], @Req() req: Request) {
    // 'creditCardNumber': '1234567890123456' => '************3456'
    // 'socialSecurityNumber': '123-45-6789' => '*******6789'
  }
}

Example 2 - Custom masking options

@AfterRead()
@Mask<MyEntity>(['phoneNumber'], { char: '#', visibleEnd: 2 })
private async afterRead(@Results() results: MyEntity[], @Req() req: Request) {
  // 'phoneNumber': '+1234567890' => '#########90'
}

Tip

Use @Mask for sensitive data like credit card numbers, phone numbers, social security numbers, or any field where you want to show a partial value.

(back to top)

@LogExecution

@LogExecution(options?: LogExecutionOptions)

The @LogExecution decorator is used as a method decorator to automatically log the execution of a handler method, including input arguments, return value, and execution duration.

The @LogExecution decorator can be used on all method decorators.

Parameters

  • options?: [Optional] Configuration options for logging:
    • logArgs?: boolean - Whether to log the input arguments (default: false)
    • logResult?: boolean - Whether to log the return value (default: false)
    • logDuration?: boolean - Whether to log the execution duration (default: true)

Example 1 - Log duration (default)

import { EntityHandler, AfterRead, LogExecution, Results, Req } from '@dxfrontier/cds-ts-dispatcher';
import type { Request } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@EntityHandler(MyEntity)
export class MyHandler {
  constructor() {}

  @AfterRead()
  @LogExecution()
  private async afterRead(@Results() results: MyEntity[], @Req() req: Request) {
    // Simulate some async work
    await new Promise(resolve => setTimeout(resolve, 100));

    // Logs: [LOG] MyHandler.afterRead - Duration: 100ms
  }
}

Example 2 - Log everything

@AfterRead()
@LogExecution({ logArgs: true, logResult: true, logDuration: true })
private async afterRead(@Results() results: MyEntity[], @Req() req: Request) {
  // Logs: [LOG] MyHandler.afterRead - Args: [...]
  // Logs: [LOG] MyHandler.afterRead - Result: ...
  // Logs: [LOG] MyHandler.afterRead - Duration: 5ms
}

Important

The @LogExecution decorator measures only the execution time of the method body itself, not the entire request lifecycle. This means:

  • Middleware execution time (from @Use) is not included
  • Other decorator transformations (like @Exclude, @Mask) are not included
  • Database queries or network calls are only included if they are awaited inside the method body

Tip

Use @LogExecution during development or debugging to trace method calls. Consider disabling logArgs and logResult in production if they contain sensitive data.

(back to top)

Deployment to BTP using MTA

  1. Add mta.yaml to your project using the following command :
cds add mta
  1. Install npm-run-all and rimraf packages:
npm install --save-dev npm-run-all rimraf
  1. Modify your package.json by adding the following scripts:
"build:cds": "cds build --production",
"build:ts": "tsc",
"build:srv:clean:ts": "rimraf --glob 'gen/srv/srv/**/*.ts'",

"build": "run-s build:cds build:ts build:srv:clean:ts"
  1. Modify mta.yaml as follows :
- builder: custom
  commands:
    - npm ci
    - npm run build
    - npx @cap-js/cds-typer "*" --outputDirectory gen/srv/@cds-models

Steps explained :

  • npm ci - Will do a clean install
  • npm run build - will run the package.json script command for CDS build and transpilation of TS to JS and clean the TS files.
  • npx @cap-js/cds-typer "*" --outputDirectory gen/srv/@cds-models - will make sure the @cds-models are generated.
  1. Install MTA Build tool globally:
npm i -g mbt
  1. Run command to produce the .mtar file
mbt build
  1. Deploy your mtar to BTP

Best practices & tips

Can I stack multiple decorators on the same callback ?

Yes, you can stack multiple decorators.

Example 1

@AfterRead()
@Use(MiddlewareMethodAfterRead1, MiddlewareMethodAfterRead2)
@FieldsFormatter<MyEntity>({ action: 'blacklist', charsToRemove: 'Mysterious' }, 'title')
private async aMethod(@Results() results: MyEntity[], @Req() req: Request) {
  // ...
}

Example 2

@BeforeRead()
@BeforeCreate()
@BeforeUpdate()
@BeforeDelete()
private async aMethod(@Req() req: Request<MyEntity>) {
  // ..
}
Is the sequence of decorators important ?

Yes, it is important as typescript executes the decorators :

  • for Class - from bottom to top
  • for method - from top to bottom
@SecondClassDecorator() // second executed
@FirstClassDecorator() // first executed
class MyClass {
  @FirstDecorator() // first executed
  @SecondDecorator() // second executed
  myMethod() {
    console.log('Method called');
  }
}
Best practices for @FieldsFormatter - customFormatter

To have all the custom formatters in one place you can create a new formatters.ts where you will place all custom formatters and use export to make them visible.

// formatter.ts
export const customFormatter: Formatters<BookFormat> = {
  action: 'customFormatter',
  callback(req, results) {
    if (results && results.length > 0) {
      // make first item 'toLowerCase' and leave the rest 'toUpperCase'
      results[0].format = results[0].format?.toLowerCase();
    }
  },
};

Import the customFormatter in your handler

@AfterRead()
@FieldsFormatter<MyEntity>(customFormatter, 'format') // import it here
private async afterRead(@Results() results: MyEntity[], @Req() req: Request<MyEntity>) {
  // ...
}

Tip

To get all the Formatters typing you can import the Formatters\<T> type where T is the entity of your CDS provided by CDS-Typer

(back to top)

Samples

Find here a collection of samples for the CDS-TS-Dispatcher-Samples

Contributing

Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.

Please make sure to update tests as appropriate.

License

Licence

Copyright (c) 2024 DXFrontier

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Authors

(back to top)

MIT, read

History

Latest

v6.1.0

Features 8, Fixes 7, Docs 29, Other 19

Notes
🚀 Features
  • (params) Add @Data, @Param, @UserInfo, @Tenant and @Diff parameter decorators
  • (lifecycle) Add request-lifecycle and scheduled-outcome decorators
  • (throttle) Add @Throttle fixed-window rate limiting decorator
  • (server-lifecycle) Add @ServerLifecycle host with @OnServed/@OnListening/@OnShutdown metadata
  • (server-lifecycle) Register cds.on hooks with per-process class dedup and host guards
  • (server-lifecycle) Fail fast on @Use middleware and pin ordering semantics after review
  • (websocket) Add OnWebSocketConnect/Disconnect/Message sugar decorators
  • (stream) Suggest common content types via StreamContentType union
🐛 Bug Fixes
  • (lifecycle) Guard request-lifecycle attach against event-queue internal dispatches
  • (sample) Guard fixture setHeader calls; pin two-group $batch lifecycle semantics
  • (server-lifecycle) Keep literal event names for cds.on overloads in the dts build
  • (parameter) Guard @IsColumnSupplied INSERT/UPSERT, key @IsPresent columns (C3, C4)
  • (transformers) FindResults falls back to req.results on write AFTER events (C5)
  • (decorators) Format every FieldsFormatter field, mirror PrependDraft isDraft (C6, M6)
  • (middleware) Return the wrapped handler result, run class chain tail once (M8, M9)
📚 Documentation
  • (backlog) Add decorator research backlog (Tier-1 batch source of truth)
  • (readme) Document Tier-1 request-lifecycle, scheduled-outcome and parameter decorators
  • (readme) Pin observed req.diff() payload shape in the @Diff section
  • (backlog) Record as-built notes for the implemented Tier-1 batch
  • (specs) Add decorator batch 2 design (throttle, server lifecycle, websocket)
  • (specs) Drop the WebSocketHandler alias; host ws handlers in UnboundActions
  • (plans) Add decorator batch 2 implementation plan
  • (readme) Document @Throttle, @ServerLifecycle family and websocket decorators
  • (readme) Restructure table of contents and decorator taxonomy
  • (spec) Add agentic-dx design spec for AI-assistant guidance
  • (spec) Relocate shipped agent guide to docs/ per ecosystem research
  • (plan) Add agentic-dx implementation plan (three-PR rollout)
  • (agentic-dx) Rev 2 - reduce scope to JSDoc-only, rewrite spec and plan
  • (plan) Use --no-verify for red-phase commits (pre-commit runs unit suite)
  • (plan) Fix drift-gate regex to forbid crossing comment boundaries
  • (jsdoc) Add @packageDocumentation header and CDSDispatcher bootstrap contract
  • (jsdoc) Canonical agent-oriented JSDoc for class and parameter decorators
  • (plan) Fence @example bodies and escape line-start decorators; fix @Env reference
  • (jsdoc) Fix EachInstance payload docs, fence examples, per-overload EntityHandler docs
  • (jsdoc) Canonical agent-oriented JSDoc for BEFORE/AFTER method decorators
  • (jsdoc) Correct wildcard-host ALL registration and draft CREATE illustration
  • (jsdoc) Complete decorator JSDoc pass; drift gate fully green
  • (jsdoc) Fix exposeValidatorResult nesting, Next return types, on-phase dispatch claims
  • (jsdoc) Fix contradictory OnAll first-match clause
  • (jsdoc) Carry module docs on CDSDispatcher per dts contingency; ci grep for d.ts survival
  • (jsdoc) Final-review fixes - subject semantics, escape-convention note, gate sentinels
  • (jsdoc) Fix verified review findings, docs-only tranche (C1-C2, M1-M5, M7, M10-M17, N1-N9)
  • (jsdoc) Switch @ guard to @, guard mid-line decorators, split @see trailers
  • (backlog) Flip tier-1 and batch-2 statuses to shipped (6.1.0)
🧪 Testing
  • (integration) Pin request-lifecycle, scheduled-outcome and req.diff runtime behavior
  • (e2e) Cover the @BeforeCommit veto in the postman collection
  • (throttle) Fixture throttledPing action and integration coverage
  • (e2e) Cover the @Throttle 429 flow in the postman collection
  • (throttle) Harden by-tenant keying and 429 message assertions after review
  • (server-lifecycle) Fixture handler, integration markers and e2e seed visibility
  • (websocket) Add ChatService fixture over @cap-js-community/websocket with integration coverage
  • (e2e) Websocket smoke script chained after the newman collection
  • (websocket) Pin close-code delivery and harden socket hygiene after review
  • (agent-docs) Add JSDoc drift gate (@example per export, package docs)
  • (agent-docs) Enforce comment-boundary adjacency in drift gate regex
  • (agent-docs) Enforce @example on every documented declaration (M18)
⚙️ Miscellaneous Tasks
  • Bump version, CHANGELOG.md generated
  • (workflows) Publish with org-level NPM_ORG_TOKEN secret
  • (mcp) Register the official CAP MCP server and document usage rules
  • (models) Commit regenerated bookshop @cds-models for throttledPing
  • (typedoc) Remove generated docs and typedoc tooling
  • (bookshop) Refresh generated @dispatcher env types
  • (tests) Align JSDoc drift-gate sentinel with backslash escape wording
  1. v6.0.0

    Features 2, Fixes 6, Docs 1, Other 6

    Notes
    🚀 Features
    • (cds) [breaking] Upgraded cds core to v10
    • (decorators) Added scheduling, streaming, subject, affected and draft patch/discard decorators
    🐛 Bug Fixes
    • (decorators) Fixed prepend OnFunction mapping, error wrapper return and single-result lookup
    • (deps) Synced package-lock for npm 11 peer resolution
    • (postinstall) Force cds env get --json and harden env generation
    • (ci) Set NO_COLOR for cds env json output during npm ci
    • (postinstall) Programmatic cds.env acquisition, never-break-install hardening
    • (deps) Restore cds-dk nested eslint lock entries pruned by npm 11
    📚 Documentation
    • (readme) Documented new decorators and the v9 to v10 migration guide
    🎨 Styling
    • (ci) Prettier quote style for workflow env values
    🧪 Testing
    • (tests) Added in-process integration layer, utility unit tests and hardened e2e
    ⚙️ Miscellaneous Tasks
    • Bump version, CHANGELOG.md generated
    • (workflows) Fixed node matrix, added lint/unit/integration jobs and merged-gated releases
    • (claude) Added CLAUDE.md and shared claude project config
    • (entities) Regenerate monorepo admin @cds-models against cds 10
  2. v5.2.0

    Features 2, Docs 1, Other 3

    Notes
    🚀 Features
    • (actions) Added and for and and removed SubscriberType
    • (decorators) New decorators added like , and many others
    📚 Documentation
    • (readme) Updated readme with new decorators
    🧪 Testing
    • (tests) Updated e2e tests and unit tests
    ⚙️ Miscellaneous Tasks
    • Bump version, CHANGELOG.md generated
    • (readme) Updated with new actions
  3. v5.1.5

    Fixes 1, Other 3

    Notes
    🐛 Bug Fixes
    • (validate) Decorator customMessage property is taken in consideration if it's used
    🧪 Testing
    • (e2e) Updated
    ⚙️ Miscellaneous Tasks
    • Bump version, CHANGELOG.md generated
    • (prettier) Prettied files
  4. v5.1.4

    Other 3

    Notes
    ⚙️ Miscellaneous Tasks
    • Bump version, CHANGELOG.md generated
    • (prettier) Prettied files
    • (test) Added cds as dependency
  5. v5.1.3

    Fixes 1, Other 3

    Notes
    🐛 Bug Fixes
    • (validate) Now @Validate decorator is throwing correct message
    🧪 Testing
    • (action) Added a new action to test the Validate decorator better
    ⚙️ Miscellaneous Tasks
    • Bump version, CHANGELOG.md generated
    • (e2e) Updated
  6. v5.1.2

    Changed 1, Fixes 1, Other 1

    Notes
    🐛 Bug Fixes
    • (table) Now outputs correctly the data
    🚜 Refactor
    • (receiverMessage) Removed testing object from the CDSDispatcher
    ⚙️ Miscellaneous Tasks
    • Bump version, CHANGELOG.md generated
  7. v5.1.1

    Fixes 1, Docs 1, Other 2

    Notes
    🐛 Bug Fixes
    • (msg) Deprecated in favor for as currently SAP doesn't have a clear path for this
    📚 Documentation
    • (readme) Updated by making decorator deprecated until sap defines a new type
    🧪 Testing
    • (msg) Removed msg and added req
    ⚙️ Miscellaneous Tasks
    • Bump version, CHANGELOG.md generated
  8. v5.1.0

    Features 1, Docs 1, Other 4

    Notes
    🚀 Features
    • (decorators) New @ServiceLogic('Singleton'), container creates 1 instance for all lifecycle
    📚 Documentation
    • (readme) Updated with newly feature for @ServiceLogic('Singleton')
    🧪 Testing
    • (e2e) Updated for @ServiceLogic('Singleton')
    • (sharedService) Corrected to Singleton instead of Transient
    ⚙️ Miscellaneous Tasks
    • Bump version, CHANGELOG.md generated
    • (build) Small refactoring on the enforce labeling
  9. v5.0.2

    Docs 1, Other 7

    Notes
    📚 Documentation
    • (readme) Updated
    🧪 Testing
    • (e2e) Improved coverage for e2e tests
    ⚙️ Miscellaneous Tasks
    • (release) Updated CHANGELOG.md and bump version increased
    • (build) Prettied the files
    • (pipeline) Fixed pipeline by reusing dx-frontier release
    • (readme) Added more detail documentation for @Prepend
    • (prepend) Added better TSDoc for @Prepend decorator
    • (build) Small refactoring on the enforce labeling
  10. v5.0.1

    Other 4

    Notes
    🧪 Testing
    • (pipeline) Merged back the test env decorator test to tests pipeline
    ⚙️ Miscellaneous Tasks
    • (release) Updated CHANGELOG.md and bump version increased
    • (readme) Updated
    • (tests) Commented env tests for the moment
  11. v5.0.0

    Features 1, Fixes 1, Other 16

    Notes
    🚀 Features
    • (cds) Upgraded cds core component to v9
    🐛 Bug Fixes
    • (as) Insert as was removed from cds@9
    🧪 Testing
    • (tests) Updated tests after increase of cds to v9
    • (tests) Refactored the matrix of the pipeline
    ⚙️ Miscellaneous Tasks
    • (release) Updated CHANGELOG.md and bump version increased
    • (readme) Add migration guide for @sap/cds v8 to v9
    • (build) Project rebuilded
    • (build) Postinstall rebuilded
    • (test) Fixed pipeline to use node v22
    • (test) Fix by removing node 18 from testing
    • (test) Fix by changing the matrix run of the pipeline
    • (test) Removed node v 20 as it is not supported anymore by sap cap
    • (build) Rebuild the project
    • (build) Small refactoring of the private fields of class FileManager
    • (test) Verify the platform only for ubuntu 24 and 22
    • (test) Split the Env decorator into a new workflow
    • (readme) Made clear that dispatcher uses v9
    • (readme) Updated readme
  12. v4.2.2

    Other 6

    Notes
    🧪 Testing
    • (sample-project) Regenerated package.json
    • (monorepo) Regenerated cds-typer entities
    • (sample-project) Deps updated
    ⚙️ Miscellaneous Tasks
    • (release) Updated CHANGELOG.md and bump version increased
    • (postinstall) Prettier
    • (readme) Small corrections
  13. v4.2.1

    Fixes 3, Other 2

    Notes
    🐛 Bug Fixes
    • (postinstall) Fixed postinstall for wildcard * was used inside of workspaces from package.json
    • (postinstall) Added mjs.map for PostInstall for better debug
    • (subscriberType) Exported publicly the type to be used in projects
    🧪 Testing
    • (e2e) Fixed by adding SubscriberType to the UnboundActions
    ⚙️ Miscellaneous Tasks
    • (release) Updated CHANGELOG.md and bump version increased
  14. v4.2.0

    Features 1, Other 9

    Notes
    🚀 Features
    • (decorators) New decorator @Msg and @OnSubscribe to catch event messaging
    🧪 Testing
    • (e2e) Updated tests for @Msg and @OnSubscribe decorators
    ⚙️ Miscellaneous Tasks
    • (release) Updated CHANGELOG.md and bump version increased
    • (files) Deleted @dispatcher folder as it was pushed by mistake
    • (gitignore) Added @dispatcher folder to gitignore
    • (readme) Updated readme for new decorators @Msg and @OnSubscribe
    • (postinstall) Removed some unused comments
    • (prettier) Pretty test folder
    • (readme) Small update to readme
    • (readme) Small updates to @OnSubscribe and @UnboundActions description
  15. v4.1.3

    Fixes 1, Other 13

    Notes
    🐛 Bug Fixes
    • (workspaces) Fixed the postinstall when project had workspaces
    🧪 Testing
    • (test) Updated tests
    ⚙️ Miscellaneous Tasks
    • (release) Updated CHANGELOG.md and bump version increased
    • (tsup) Removed 'splitting', and 'dts' from tsup of postinstall
    • (package) Small changes to scripts
    • (readme) Small fix to documentation of BeforeAll decorator
    • (gitattributes) Small change to the 'lib/docs' so it can be visible to github
    • (postinstall) Deleted PostInstall.mts and d.ts as it was not needed for postinstall
    • (prettier) Files formatted
    • (test) Updated tests for monorepo dispatcher
    • (build) Fix for test verifier scripts
    • (test) Small changes to the gitignore and dispatcher folder
    • (gitignore) Added dispatcher folder as ignore
    • (gitignore) Suppressing the workspace specific gitignore to be added to git
  16. v4.1.2

    Other 2

    Notes
    ⚙️ Miscellaneous Tasks
    • (release) Updated CHANGELOG.md and bump version increased
    • (readme) Updated readme for @OnAll, @AfterAll, @BeforeAll decorators
  17. v4.1.1

    Changed 1, Docs 1, Other 2

    Notes
    🚜 Refactor
    • (cdsdispatcher) Upgraded inversify to latest version
    📚 Documentation
    • (decorators) Small fixes for links of @CatchAndSetErrorCode and @CatchAndSetErrorMessage
    🧪 Testing
    • (cds-models) Updated tests
    ⚙️ Miscellaneous Tasks
    • (release) Updated CHANGELOG.md and bump version increased
  18. v4.1.0

    Features 1, Docs 1, Other 3

    Notes
    🚀 Features
    • (decorators) New @CatchAndSetErrorMessage,@CatchAndSetErrorCode decorators to catch errors
    📚 Documentation
    • (readme) Created docu for @CatchAndSetErrorMessage, @CatchAndSetErrorCode decorators
    🧪 Testing
    • (e2e) Updated tests for @CatchAndSetErrorCode and @CatchAndSetErrorMessage
    ⚙️ Miscellaneous Tasks
    • (release) Updated CHANGELOG.md and bump version increased
    • (vscode) Added npm debug for e2e
  19. v4.0.1

    Other 2

    Notes
    ⚙️ Miscellaneous Tasks
    • (release) Updated CHANGELOG.md and bump version increased
    • (readme) Updated
  20. v4.0.0

    Fixes 1, Other 5

    Notes
    🐛 Bug Fixes
    • (request) According to cds-types v9 TypedRequest was renamed to Request
    ⚙️ Miscellaneous Tasks
    • (release) Updated CHANGELOG.md and bump version increased
    • (workflows) Small modifications to release and tests
    • (test) Updates tests after renaming TypedRequest to Request
    • (readme) Changed TypedRequest to only Request
    • (workflow) Commented unit testing
  21. v3.2.7

    Fixes 1, Other 2

    Notes
    🐛 Bug Fixes
    • (dispatcher) Fixed CDS_DISPATCHER.ALLENTITIES due to cds 8.6.0
    🧪 Testing
    • (cds-typer) Updated generated cds typed entities
    ⚙️ Miscellaneous Tasks
    • (release) Updated CHANGELOG.md and bump version increased
  22. v3.2.6

    Other 3

    Notes
    ⚙️ Miscellaneous Tasks
    • (release) Updated CHANGELOG.md and bump version increased
    • (readme) Fixed deployment badge
    • (actions) Renamed enforce-label and tests github actions for better visibility
  23. v3.2.5

    Other 3

    Notes
    ⚙️ Miscellaneous Tasks
    • (release) Updated CHANGELOG.md and bump version increased
    • (readme) Small changes to README.md
    • (readme) Changed build shield to deployment.yml
  24. v3.2.4

    Features 1, Fixes 3, Other 5

    Notes
    🚀 Features
    • (postinstall) Regenerated postinstall scripts
    🐛 Bug Fixes
    • Updated "ActionReturn" type to be compatible with the latest version of "cds-typer"
    • (postinstall) Fixed postinstall on windows 10 by using the cross-spawn package
    • (github-actions) Fixed actions after were refactored
    ⚙️ Miscellaneous Tasks
    • (deployment,release) Refactored github actions for better visibility by moving some steps
    • (readme) Small update to last commit shield
    • (test) Extending tests for windows
    • (github-action) Fixed the release github action after refactoring
    • (release) Fixed create pull request of the release workflow
  25. v3.2.3

    Other 3

    Notes
    ⚙️ Miscellaneous Tasks
    • (tsdoc) Removed unnecesary tsdoc from some properties and rearanged a bit some tsdocs
    • (readme) Fixed the shield of the last commit by changing to dev branch
    • Version bump to 3.2.3
  26. v3.2.2

    Changed 2, Other 4

    Notes
    🚜 Refactor
    • (env) Moved Verifier outside of postinstall folder, added esm and cjs to compilation of env
    • (tsup) Added cjs and esm to compilation of postinstall
    ⚙️ Miscellaneous Tasks
    • (readme) Readme updates
    • (eslint) Added eslint to ignore
    • (gitignore) Removed dispatcher form gitignore
    • Version bump to 3.2.2
  27. v3.2.1

    Fixes 1, Other 1

    Notes
    🐛 Bug Fixes
    • (env) Fixed @Env when project was mbt build for production ready
    ⚙️ Miscellaneous Tasks
    • Version bump to 3.2.1
  28. v3.2.0

    Features 2, Fixes 3, Other 11

    Notes
    🚀 Features
    • (env) New @Env parameter decorator to fetch cds env directly into method parameter
    • (postinstall) npm install will generate @dispatcher folder which contains env interfaces
    🐛 Bug Fixes
    • (tests) Fixed tests by adding a npm run build before testing the new @Env decorator
    • (tests) Added npm run build before e2e tests
    • (test) Excluded from ignore the @dispatcher folder for test
    ⚙️ Miscellaneous Tasks
    • (commitlint) Removed mandatory scopes
    • (lint) Added dist and @dispatcher folder to lint ignore
    • (test) Test workflow now tests the newly @Env decorator on all platforms (windows,mac,linux)
    • (test) Updated tests for the newly @Env decorator
    • (readme) Updated readme with the usage of the new @Env decorator
    • (gitignore) Updated .gitignore
    • Version bump to 3.2.0
    • (tsup) Split compilation into 2 parts, one for postinstall one for the library
    • (dist) PostInstall will always be pushed to dist as is needed for npm install command
    • (gitignore) Excluded from gitingore the ./dist/postinstall as is needed when npm install
    • (tsconfig) Excluded form compilation the ./dist/postinstall
  29. v3.1.2

    Other 4

    Notes
    ⚙️ Miscellaneous Tasks
    • (tests) Updated tests
    • (github) Added linguist-vendored to be excluded from github stats the html files
    • (readme) Updated readme
    • Version bump to 3.1.2
  30. v3.1.1

    Features 2, Fixes 3, Other 6

    Notes
    🚀 Features
    • (locale) New @Locale decorator to get the locale in the parameter method
    • (validator) @Validate now can behave like an if-else by catching the flags in the method
    🐛 Bug Fixes
    • (gitattributes) Fix gittattributes
    • (package) Reverted back the deleted lint-staged
    • (readme) Fixed readme anchors
    ⚙️ Miscellaneous Tasks
    • (gitattributes) Added gitattribute to remove the lib/docs from the stats of githubc
    • (commitlint) Removed mandatory scopes
    • (gitattributes) Refactored to remove from that github stats html files
    • Version bump to 3.1.0
    • (actions) Refactored github actions to split the bump and release and deployment
    • Version bump to 3.1.1
  31. v3.0.0

    Features 1, Changed 3, Other 16

    Notes
    🚀 Features
    • (dispatcher) Added outboxed srv, changes to CDSDipatcher affected by migration of cds to v8
    🚜 Refactor
    • (util) Deprecated SRV in favor of CDS_DISPATCHER.SRV
    • (types) Eslint v9 changes
    • (types) Created custom Constructable type as sap deleted it in v8
    ⚙️ Miscellaneous Tasks
    • (config) Migrated to ESLint v9
    • (config) Commitlintrc, new type enums
    • (readme) Readme updated
    • (config) Removed deprecated husky sh
    • (build) Dropped node 21 and changed cds-dk to latest
    • (e2e) Generated cds-typer new entities
    • (e2e) Cleanup testing files
    • (unit) Updated unit tests due to migration of v8
    • (unit) Cds-format has been used on this file
    • (e2e) Fixed a date in a CSV which failed when deployed
    • (e2e) Added 2 instead of cents for the minorUnit as deployed it failed
    • (e2e) Updated newman tests
    • (build) Dropped ubuntu-latest on the matrix of the tests
    • Version bump to 3.0.0
    • (build) Dropped unit tests as fails on github actions
    • (build) Commented the needs of test-e2e
  32. v2.1.4

    Docs 1, Other 1

    Notes
    📚 Documentation
    • (doc) Readme updated
    ⚙️ Miscellaneous Tasks
    • Version bump to 2.1.4
  33. v2.1.3

    Docs 2, Other 3

    Notes
    📚 Documentation
    • (doc) Readme updated
    • (doc) Removed from README.md unused types
    🧪 Testing
    • (tests) Updated tests due to cds-typer version increase
    ⚙️ Miscellaneous Tasks
    • (build) Added node version 21 and restricted cds-dk to 7.9.5
    • Version bump to 2.1.3
  34. v2.1.2

    Fixes 1, Docs 1, Other 1

    Notes
    🐛 Bug Fixes
    • (config) Fixed package.json script
    📚 Documentation
    • (doc) Created technical documentation
    ⚙️ Miscellaneous Tasks
    • Version bump to 2.1.2
  35. v2.1.1

    Other 9

    Notes
    ⚙️ Miscellaneous Tasks
    • (config) Commit lint type enum now follows cliff categories
    • (build) Package will be deployed for testing internally on github npm
    • (config) Fixing release workflow
    • (config) Fixing release workflow
    • (config) Prettier, eslint will not touch docs folder
    • (doc) Added typedoc configs
    • (tests) Tests updated to to increase of cds-typer version
    • (doc) Added new typedoc/jsdoc description and changed some descriptions
    • Version bump to 2.1.1
  36. v2.1.0

    Features 1, Other 5

    Notes
    🚀 Features
    • (decorators) Added new events to the prepend decorator
    🧪 Testing
    • (tests) Updated tests after adding new prepend events
    ⚙️ Miscellaneous Tasks
    • (build) Added create PR step in release workflow
    • (build) Updated pull-requests to write
    • (doc) Refactored AfterAll, BeforeAll and OnAll documentation
    • Version bump to 2.1.0
  37. v2.0.26

    Other 14

    Notes
    🧪 Testing
    • (tests) Added allhandler.ts file to show example of usage of the overload for entityhandler
    ⚙️ Miscellaneous Tasks
    • (config) Commitlint added to husky and lint-staged
    • (config) Added .md files to prettier and release now uses conventional commits
    • (doc) Small changes to documentation of the project
    • Version bump to 2.0.23
    • (build) Added create pull request to release.yml
    • Version bump to 2.0.24
    • (build) Changes to release.yml workflow
    • Version bump to 2.0.25
    • (build) Added initial version of to 2.0.24
    • (build) Changes to name of the release
    • Version bump to 2.0.25
    • (build) Changes to release.yml
    • Version bump to 2.0.26
  38. v2.0.22

    Other 2

    Notes
    ⚙️ Miscellaneous Tasks
    • Readme updates.
    • Version bump to 2.0.22
  39. v2.0.21

    Other 2

    Notes
    ⚙️ Miscellaneous Tasks
    • Readme updates
    • Version bump to 2.0.21
  40. v2.0.20

    Features 1, Other 1

    Notes
    🚀 Features
    • Added new decorator AfterReadEachInstance
    ⚙️ Miscellaneous Tasks
    • Version bump to 2.0.20
  41. v2.0.19

    Features 2, Other 3

    Notes
    🚀 Features
    • Updated tests
    • Added @AfterAll, @BeforeAll, @OnAll decorators to combine multiple decorators in only one go, new @EntityHandler overload decorator to support all entities
    ⚙️ Miscellaneous Tasks
    • Bump versions
    • Update readme
    • Version bump to 2.0.19
  42. v2.0.18

    Other 1

    Notes
    ⚙️ Miscellaneous Tasks
    • Version bump to 2.0.18
  43. v2.0.17

    Other 1

    Notes
    ⚙️ Miscellaneous Tasks
    • Version bump to 2.0.17
  44. v2.0.16

    Changed 1, Other 1

    Notes
    ⚙️ Miscellaneous Tasks
    • Version bump to 2.0.16
    Update
    • Release.yml
  45. v2.0.15

    Changed 1, Other 1

    Notes
    ⚙️ Miscellaneous Tasks
    • Version bump to 2.0.15
    Update
    • Release workflow by adding to publish github npm the organizaiton
  46. v2.0.14

    Changed 2, Other 1

    Notes
    ⚙️ Miscellaneous Tasks
    • Version bump to 2.0.14
    Update
    • Added to release workflow the publish to github npm
    • Readme added tip for cds typer
  47. v2.0.13

    Features 1, Changed 3, Other 1

    Notes
    🚀 Features
    • New @PrependDraft decorator which can be used to catch the event before reaching the original event.
    ⚙️ Miscellaneous Tasks
    • Version bump to 2.0.13
    Update
    • Unit test & e2e tests for @PrependDraft
    • Package versions
    • Readme.md with @PrependDraft
  48. v2.0.12

    Features 1, Changed 3, Other 2

    Notes
    🚀 Features
    • New @Prepend, @Res and @AfterReadSingleInstance decorators.
    ⚙️ Miscellaneous Tasks
    • Changes to versions
    • Version bump to 2.0.12
    Update
    • Unit test & e2e tests
    • Readme.md for @Prepend, @Res and @AfterReadSingleInstance
    Updated
    • Tests
  49. v2.0.11

    Changed 1, Other 1

    Notes
    ⚙️ Miscellaneous Tasks
    • Version bump to 2.0.11
    Update
    • Readme
  50. v2.0.10

    Changed 1, Other 1

    Notes
    ⚙️ Miscellaneous Tasks
    • Version bump to 2.0.10
    Update
    • Readme.md
  51. v2.0.9

    Changed 1, Fixes 1, Other 2

    Notes
    🐛 Bug Fixes
    • NextEvent now returns Function instead of void
    ⚙️ Miscellaneous Tasks
    • Updated eslintignore
    • Version bump to 2.0.9
    Update
    • Tests
  52. v2.0.8

    Fixes 1, Other 1

    Notes
    🐛 Bug Fixes
    • Package.version increased manually due to inconsistency between NPM version and github version.
    ⚙️ Miscellaneous Tasks
    • Version bump to 2.0.8
  53. v2.0.7

    Changed 1, Fixes 1, Other 1

    Notes
    🐛 Bug Fixes
    • `GetQueryType['columns']['forDelete'] was replaced by ['forSelect'] as delete does not has type columns
    ⚙️ Miscellaneous Tasks
    • Version bump to 2.0.6
    Update
    • E2e tests & unit tests
  54. v2.0.6

    Changed 1, Other 1

    Notes
    ⚙️ Miscellaneous Tasks
    • Version bump to 2.0.5
    Update
    • Readme
  55. v2.0.5

    Other 2

    Notes
    ⚙️ Miscellaneous Tasks
    • Cleanup project
    • Version bump to 2.0.4
  56. v2.0.4

    Changed 2, Other 1

    Notes
    ⚙️ Miscellaneous Tasks
    • Version bump to 2.0.3
    Update
    • Tests
    • Clean up project and refactored findRequest method
  57. v2.0.3

    Changed 1

    Notes
    Update
    • README
  58. v2.0.2

    Fixes 1, Other 1

    Notes
    🐛 Bug Fixes
    • ExecutionAllowedForRoles renamed to ExecutionAllowedForRole
    ⚙️ Miscellaneous Tasks
    • Version bump to 2.0.2
  59. v2.0.1

    Changed 2, Fixes 1, Other 4

    Notes
    🐛 Bug Fixes
    • FieldsFormatter now works as expected when parameter decorators are being used
    ⚙️ Miscellaneous Tasks
    • Settings.json updates
    • Update package-lock.json
    • Version bump to 2.0.1
    Feautre
    • New decorator ExecutionAllowedForRoles to allow execution of methods when user roles are being used
    Update
    • Tests
    • README
  60. v2.0.0

    Features 2, Changed 11, Other 7

    Notes
    🚀 Features
    • New decorators added : @Req @Results / @Result @Next @Error @IsPresent @IsRole @IsColumnSupplied @GetQueryProperty @GetRequestProperty @SingleInstanceSwitch
    • Final refactoring for new parameter decorators.
    ⚙️ Miscellaneous Tasks
    • Deleted files
    • Package.json changes to scrips.
    • Moved files
    • Updated devcontainer
    • Jsdoc updated
    • Version bump to 2.0.0
    Update
    • Readme.md
    Upadte
    • E2e tests
    Update
    • Tests.yaml
    • Unit tests
    • Tests
    • Readme.md
    • Added connectivity sdk
    • Prettierignore
    • Types for GetQuery decorator
    • Tests
    Updated
    • .eslintrc
    • Tests
  61. v1.1.2

    Features 1, Other 1

    Notes
    🚀 Features
    • Added to formatter the following actions: 'camelCase', 'kebabCase' and 'snakeCasse'
    ⚙️ Miscellaneous Tasks
    • Version bump to 1.1.2
  62. v1.1.1

    Features 1, Changed 3, Other 3

    Notes
    🚀 Features
    • Added more actions to @Validate & @FieldsFormatter
    ⚙️ Miscellaneous Tasks
    • Added launch.json and settings.json for .vscode folder.
    • Added .devcontainer for easy development around team.
    • Version bump to 1.1.1
    Update
    • README
    • Package.json versions
    Updated
    • Tests
  63. v1.1.0

    Features 1, Changed 5, Other 2

    Notes
    🚀 Features
    • New @FieldsFormatter decorator which can be used to format the fields based on predefined actions before reaching the callback. @Validate decorator can be used to validate fields based on on predefined actions.
    ⚙️ Miscellaneous Tasks
    • Added .vscode predefined settings for inlay TS
    • Version bump to 1.1.0
    Update
    • Unit tests
    • E2e tests for@Validator and @FieldsFormatter
    • Added documentation for 2 new decorators @FieldsFormatter and @Validate
    • Package.json versions increased.
    • Files moved
  64. v1.0.6

    Changed 2, Other 1

    Notes
    ⚙️ Miscellaneous Tasks
    • Version bump to 1.0.6
    Update
    • Readme and workflow actions versions
    • Readme.md
  65. v1.0.5

    Changed 1, Fixes 1, Other 1

    Notes
    ⚙️ Miscellaneous Tasks
    • Version bump to 1.0.5
    FIX
    • Middleware next now is a Promise, it must be called with await next(), initially this was not a promise and caused issue when req.reject was used.
    Update
    • Readme
  66. v1.0.4

    Other 2

    Notes
    ⚙️ Miscellaneous Tasks
    • Version bump to 1.0.4
    Chore
    • Updated versions
  67. v1.0.3

    Changed 1, Other 1

    Notes
    ⚙️ Miscellaneous Tasks
    • Version bump to 1.0.3
    Update
    • Readme
  68. v1.0.2

    Changed 1, Other 1

    Notes
    ⚙️ Miscellaneous Tasks
    • Version bump to 1.0.2
    Update
    • Next middleware now returns unknown.
  69. v1.0.1

    Other 1

    Notes
    ⚙️ Miscellaneous Tasks
    • Version bump to 1.0.1
  70. v1.0.0

    Changed 1, Other 1

    Notes
    ⚙️ Miscellaneous Tasks
    • Version bump to 1.0.0
    UPDATE
    • README.md
  71. v0.1.25

    Other 1

    Notes
    ⚙️ Miscellaneous Tasks
    • Version bump to 0.1.25
  72. v0.1.24

    Other 1

    Notes
    ⚙️ Miscellaneous Tasks
    • Version bump to 0.1.24
  73. v0.1.23

    Changed 1, Other 2

    Notes
    ⚙️ Miscellaneous Tasks
    • Version bump to 0.1.23
    Deleted
    • Tests file
    Updated
    • Tests due to fail on github of the unit tests
  74. v0.1.22

    Other 1

    Notes
    ⚙️ Miscellaneous Tasks
    • Version bump to 0.1.22
  75. v0.1.21

    Other 1

    Notes
    ⚙️ Miscellaneous Tasks
    • Version bump to 0.1.21
  76. v0.1.20

    Other 1

    Notes
    ⚙️ Miscellaneous Tasks
    • Version bump to 0.1.20
  77. v0.1.19

    Changed 1, Other 1

    Notes
    ⚙️ Miscellaneous Tasks
    • Version bump to 0.1.19
    Updated
    • Tests, readme and development process updates
  78. v0.1.18

    Changed 1, Other 1

    Notes
    ⚙️ Miscellaneous Tasks
    • Version bump to 0.1.18
    Updated
    • Package.json versions
  79. v0.1.17

    Other 1

    Notes
    ⚙️ Miscellaneous Tasks
    • Version bump to 0.1.17
  80. v0.1.16

    Other 1

    Notes
    ⚙️ Miscellaneous Tasks
    • Version bump to 0.1.16
  81. v0.1.15

    Other 1

    Notes
    ⚙️ Miscellaneous Tasks
    • Version bump to 0.1.15
  82. v0.1.14

    Other 1

    Notes
    ⚙️ Miscellaneous Tasks
    • Version bump to 0.1.14
  83. v0.1.13

    Other 1

    Notes
    ⚙️ Miscellaneous Tasks
    • Version bump to 0.1.13
  84. v0.1.0

10downloads per week, npm
Weekly downloads for @dxfrontier/cds-ts-dispatcher26 complete calendar weeks from 2026-04-27 to 2026-10-25; the latest total is 10.27 Apr – 3 May: 1324 May – 10 May: 20011 May – 17 May: 27018 May – 24 May: 17325 May – 31 May: 361 Jun – 7 Jun: 1158 Jun – 14 Jun: 11215 Jun – 21 Jun: 14822 Jun – 28 Jun: 6729 Jun – 5 Jul: 856 Jul – 12 Jul: 13713 Jul – 19 Jul: 5320 Jul – 26 Jul: 41627 Jul – 2 Aug: 5393 Aug – 9 Aug: 34810 Aug – 16 Aug: 25617 Aug – 23 Aug: 7624 Aug – 30 Aug: 4131 Aug – 6 Sept: 327 Sept – 13 Sept: 14714 Sept – 20 Sept: 6121 Sept – 27 Sept: 9728 Sept – 4 Oct: 705 Oct – 11 Oct: 12612 Oct – 18 Oct: 1319 Oct – 25 Oct: 10
TypeScript patterns
  1. @dxfrontier/cds-ts-repositoryTyped BaseRepository over CAP entities
  2. cds-plugin-handlersOne handler file per entity in CAP services
  3. cds-routing-handlersDecorator-based routing of CAP service handlers in TypeScript
  4. cds2typesCLI generating TypeScript interfaces from a CDS model
  5. cap-handler-frameworkConvention-based handler layer for CAP with draft lifecycle hooks
  6. cds-plugin-typescriptCDS build task compiling TypeScript sources during cds build
  7. cds-hyper-app-serviceTyped class-based service layer over CAP ApplicationService

Details

Sources5
npm registrydiscovered herechecked
The discovery passdiscovered herechecked
npm registry, @dxfrontier/cds-ts-dispatcherversion, releases, installschecked
github.com/git+https://github.com/dxfrontier/cds-ts-dispatcher.gittree, pushes, contributors, CIchecked
README.mdquoted, and what the entry declareschecked

Sources are graph entities: the same page may back several entries, and an unreachable source flags every row that rests on it.

Compatibility and provenance
declared for^10
latestv6.1.0
first release
releases and cadence84, ~4d median
discovered vianpm registry, The discovery pass
first seen in the atlas
ecosystem and domaincap, developer-experience
Flavors1
Node.js
“> CDS-TS-Dispatcher uses `@sap/cds`, `@sap/cds-dk` [version 9](https://cap.cloud.sap/docs/releases/may25).” — dxfrontier/cds-ts-dispatcher — READMEchecked
Contributors3

From the repository’s contributor list, .

History in the atlas4
  1. what_it_does and four facet values
  2. Typed cds-plugin by placement, which defaults every candidate to that. It is not one.
  3. Decorator-driven handler registration for CAP in TypeScript: handlers declared on a class rather than wired up in an init function.
  4. First seen.
For your agent
Ref
artifact:dxfrontier-cds-ts-dispatcher
JSON
/artifacts/dxfrontier-cds-ts-dispatcher.json
MCP
Connecting an agent