3.0.0 • Published 11 months ago

@dxfrontier/cds-ts-dispatcher v3.0.0

Weekly downloads
-
License
MIT
Repository
github
Last release
11 months ago

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

NPM Downloads NPM Downloads NPM Version

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 install -g @sap/cds-dk typescript ts-node

Installation

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

Using: @sap/cds v8

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": {
    /* Base Options: */
    "esModuleInterop": true,
    "skipLibCheck": true,
    "allowJs": true,
    "strictPropertyInitialization": false,
    "forceConsistentCasingInFileNames": true,
    "allowSyntheticDefaultImports": true,
    "strictNullChecks": true,
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",

    /* Allow decorators */
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,

    /* Strictness */
    "strict": true,

    "lib": ["es2022"],

    "outDir": "./gen/srv"
  },
  "include": ["./srv"]
}
  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 8

Using: @sap/cds v7

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

  1. Create new folder :
mkdir new-sap-cap-project
cd new-sap-cap-project
  1. Initialize the CDS folder structure :
cds init
  1. Add CDS-Typer to your npm package.json:
cds add typer
npm install
  1. Add the the following NPM packages :
npm install @dxfrontier/cds-ts-dispatcher@2
npm install --save-dev @types/node
  1. Add a tsconfig.json :
tsc --init
  1. It is recommended to use the following tsconfig.json properties:
{
  "compilerOptions": {
    /* Base Options: */
    "esModuleInterop": true,
    "skipLibCheck": true,
    "allowJs": true,
    "strictPropertyInitialization": false,
    "forceConsistentCasingInFileNames": true,
    "allowSyntheticDefaultImports": true,
    "strictNullChecks": true,
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",

    /* Allow decorators */
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,

    /* Strictness */
    "strict": true,

    "lib": ["es2022"],

    "outDir": "./gen/srv"
  },
  "include": ["./srv"]
}
  1. Run the CDS-TS server
cds-ts watch

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@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": {
    "@dxfrontier/cds-ts-dispatcher": "^3.0.0",
    "@dxfrontier/cds-ts-repository": "^1.1.3",
    "@sap/cds": "^8.1.0",
    "express": "^4.19.2"
  },
  "devDependencies": {
    "@cap-js/sqlite": "^1.7.3",
    "@cap-js/cds-types": "^0.6.4",
    "typescript": "^5.5.4",
    "@types/node": "^22.1.0",
    "@cap-js/cds-typer": ">=0.24.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

Option 2 : Install CDS-TS-Dispatcher - Existing 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": {
    /* Base Options: */
    "esModuleInterop": true,
    "skipLibCheck": true,
    "allowJs": true,
    "strictPropertyInitialization": false,
    "forceConsistentCasingInFileNames": true,
    "allowSyntheticDefaultImports": true,
    "strictNullChecks": true,
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",

    /* Allow decorators */
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,

    /* Strictness */
    "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

Option 3 : Install CDS-TS-Dispatcher - .devcontainer on VSCode & Docker

The CDS-TS-Dispatcher dev container repository contains the CDS-TS-Dispatcher & CDS-TS-Repository and all dependencies needed to boot a new project :

Tools installed inside of the container :

  • Controller - Service - Repository project structure folders :
    • controller
    • service
    • repository
    • middleware
    • util
    • test
  • ESLint, Prettier
  • VSCode Extensions best extensions for SAP CAP TypeScript development
  • Cloud MTA Build tool for building MTA file
  • Cloud Foundry CLI (CF)
  • Git, Cds, Npm, Node
  • CDS-Typer for building typescript entities out of CDS files
  • tsconfig.json, .eslintrc, .prettierrc - predefined properties
  • package.json - predefined scripts

Steps

  1. Install Docker desktop
  2. Clone CDS-TS-Dispatcher devcontainer using below command :
git clone https://github.com/dxfrontier/cds-ts-dispatcher-dev-container
  1. Open project in VSCode using:
code cds-ts-dispatcher-dev-container
  1. Change GIT remote origin to your origin
git remote remove origin
git remote add origin https://github.com/user/YOUR_GIT_REPOSITORY.git
git branch -M main
git push -u origin main
  1. Install Remote development pack VScode extension

  2. COMMAND + SHIFT + P on MacOS or CTRL + SHIFT + P on Windows

    1. Type - Rebuild and Reopen in Container - This step will start creating the container project and start the Node server.
  3. Start development as usual.

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.

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.

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';

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

!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

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'
}

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

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

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

!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();

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

@ServiceLogic

@ServiceLogic()

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

Example

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

@ServiceLogic()
export class CustomerService {
  // ...
  constructor() {}
  // ...
}

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

@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.

@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 } from '@dxfrontier/cds-ts-dispatcher';
import { MyAction, MyFunction, MyEvent } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

import type { ActionRequest, ActionReturn, TypedRequest, 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: TypedRequest<MyEvent>) {
    // ...
  }

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

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.

@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
  }
}

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.

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.

@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.

@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.

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...
  }
}
@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.

@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.

@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: TypedRequest<MyEntity>, @Next() next: NextEvent) {
    return next();
  }
}

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

@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(): string | undefined) {
    // ... req...
  }
}

!IMPORTANT Expected format is Bearer <TOKEN>.

@IsPresent

@IsPresent\(key: Key, property: PickQueryPropsByKey\)

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().

@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.

@IsColumnSupplied

@IsColumnSupplied\<T>(field : keyof T)

The @IsColumnSupplied<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

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

Return :

  • boolean: This decorator returns true if column was found, 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[],

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

    // ...
  }
}
@GetQuery

@GetQuery\(key: Key, property: PickQueryPropsByKey\)

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 :

    • @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']
    • @GetQuery('INSERT', 'as') as: GetQueryType['as']
    • @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']
    • @GetQuery('UPDATE', 'data') data: GetQueryType['data']
    • @GetQuery('UPDATE', 'entity') entity: GetQueryType['entity']
    • @GetQuery('UPDATE', 'where') where: GetQueryType['where']
    • @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']
    • @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.

@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';
@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 { TypedRequest } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@AfterRead()
private async singeInstanceMethodAndEntitySet(@Results() results : MyEntity[], @Req() req: TypedRequest<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 { TypedRequest } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@AfterRead()
private async singeInstanceMethodAndEntitySet(@Results() results : MyEntity[], @Req() req: TypedRequest<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.

Method-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 TypedRequest

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()
@BeforeCreate

@BeforeCreate()

Example

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

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@BeforeCreate()
private async beforeCreateMethod(@Req() req: TypedRequest<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.

@BeforeRead

@BeforeRead()

Example

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

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@BeforeRead()
private async beforeReadMethod(@Req() req: TypedRequest<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.

@BeforeUpdate

@BeforeUpdate()

Example

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

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@BeforeUpdate()
private async beforeUpdateMethod(@Req() req: TypedRequest<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.

@BeforeDelete

@BeforeDelete()

Example

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

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@BeforeDelete()
private async beforeDeleteMethod(@Req() req: TypedRequest<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.

@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

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

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

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

Equivalent to 'JS'

this.before('*', MyEntity, 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.

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 @AfterCreateAn 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()
@AfterCreate

@AfterCreate()

Example

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

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@AfterCreate()
private async afterCreateMethod(@Result() result: MyEntity, @Req() req: TypedRequest<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.

@AfterRead

@AfterRead()

Example

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

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@AfterRead()
private async afterReadMethod(@Results() results: MyEntity[], @Req() req: TypedRequest<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.

@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 { TypedRequest } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@AfterReadEachInstance()
private async afterEach(@Result() result: MyEntity, @Req() req: TypedRequest<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.

@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 { TypedRequest } from '@dxfrontier/cds-ts-dispatcher';

import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';

@AfterUpdate()
private async afterUpdateMethod(@Result() result: MyEntity, @Req() req: TypedRequest<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.

@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.

@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 @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:

3.0.0

11 months ago

2.1.2

1 year ago

2.1.1

1 year ago

2.1.4

12 months ago

2.1.3

12 months ago

2.1.0

1 year ago

2.0.19

1 year ago

2.0.26

1 year ago

2.0.24

1 year ago

2.0.22

1 year ago

2.0.20

1 year ago

2.0.21

1 year ago

2.0.15

1 year ago

2.0.16

1 year ago

2.0.14

1 year ago

2.0.17

1 year ago

2.0.18

1 year ago

2.0.13

1 year ago

2.0.12

1 year ago

2.0.11

1 year ago

2.0.10

1 year ago

2.0.5

1 year ago

2.0.6

1 year ago

2.0.9

1 year ago

2.0.8

1 year ago

2.0.3

1 year ago

2.0.2

1 year ago

2.0.4

1 year ago

2.0.1

1 year ago

2.0.0

1 year ago

1.1.2

1 year ago

1.1.1

1 year ago

1.1.0

1 year ago

1.0.6

1 year ago

1.0.5

1 year ago

1.0.4

1 year ago

1.0.3

1 year ago

1.0.2

1 year ago

1.0.1

1 year ago

1.0.0

1 year ago

0.1.25

1 year ago

0.1.23

1 year ago

0.1.24

1 year ago

0.1.22

1 year ago

0.1.21

1 year ago

0.1.20

1 year ago

0.1.19

2 years ago

0.1.18

2 years ago

0.1.17

2 years ago

0.1.16

2 years ago

0.1.15

2 years ago

0.1.14

2 years ago

0.1.13

2 years ago

0.1.12

2 years ago

0.1.11

2 years ago