Creates an instance of ArgumentMethodProcessor
.
The target object.
+The name of the property.
+The arguments to be processed.
+Private
argsThe arguments to be processed.
+Private
Readonly
propertyThe name of the property.
+Private
Readonly
targetThe target object.
+Private
temporaryA temporary storage for arguments, organized by type.
+Private
applyPrivate
applyApplies a single decorator by key. This method is used only for single decorators, the ones which can appear only once, like Next, Req, Results, etc.
+The decorator metadata and data.
+Private
existsChecks if a decorator exists for the given metadata key.
+The metadata key.
+True if the decorator exists, otherwise undefined.
+Private
getRetrieves the assigned decorators, checking if for the current parameter if there's any decorator assigned.
+An array of decorator keys.
+Private
getRetrieves metadata for the given metadata key.
+The metadata key.
+The metadata fields or undefined.
+Private
hasPrivate
onStatic
createCreates metadata by given input.
+The metadata inputs.
+CDSDispatcher
is responsible for managing and registering event handlers for entities within the CDS framework.
It supports events such as Before
, After
, On
, and Prepend
.
Creates an instance of CDSDispatcher
.
An array of entity classes to manage event handlers for.
+Private
Readonly
containerThe dependency injection container for managing service instances and dependencies.
+This container is configured to:
+Private
Readonly
entitiesAn array of entity classes to manage event handlers for.
+Private
srvThe service instance used by the dispatcher.
+This is the service that the dispatcher will interact with to register handlers and perform operations.
+Private
buildBuilds the handler by type.
+A tuple containing the handler and entity.
+Private
buildPrivate
buildPrivate
executeExecutes an 'after' event handler.
+A tuple containing the handler and entity.
+The request object.
+The result of the request.
+The result of the handler's callback.
+Private
executeExecutes a 'before' event handler.
+A tuple containing the handler and entity.
+The request object.
+The result of the handler's callback.
+Private
executeExecutes an 'on' event handler.
+A tuple containing the handler and entity.
+The request object.
+The next middleware function.
+The result of the handler's callback.
+Private
executeExecutes an 'onError' event handler.
+A tuple containing the handler and entity.
+The error object.
+The request object.
+The result of the handler's callback.
+Private
getReturns the active entity or the draft entity of the current handler class.
+The handler instance.
+The entity instance.
+The active entity or draft entity, or undefined if not applicable.
+Private
getRetrieves the properties of the handler.
+The handler instance.
+The entity instance.
+The handler properties.
+Private
getGets the handlers for the entity instance.
+The entity instance.
+The handler registration functions if handlers are found.
+Initializes the entities within the CDSDispatcher
, registering their corresponding handlers.
An instance of ServiceImpl
representing the registered service implementation.
Private
registerRegisters all AFTER
event handlers.
A tuple containing the handler and entity.
+Private
registerRegisters AFTER - SingleInstance
event handlers.
A tuple containing the handler and entity.
+Private
registerRegisters all BEFORE
event handlers.
A tuple containing the handler and entity.
+Private
registerPrivate
registerRegisters all ON
event handlers.
A tuple containing the handler and entity.
+Private
registerRegisters all PREPEND
event handlers.
A tuple containing the handler and entity.
+Private
Readonly
registerPrivate
resolvePrivate
storeStores the service instance.
+The service instance.
+The MetadataDispatcher
class handles metadata operations for target objects,
+such as setting and retrieving metadata related to entity handlers, middlewares, and method handlers.
Creates an instance of MetadataDispatcher
.
The target object on which metadata operations will be performed.
+The key used to store metadata.
+Private
Readonly
metadataThe key used to store metadata.
+Private
Readonly
targetThe target object on which metadata operations will be performed.
+Adds method handler metadata to the target object.
+The BaseHandler
object containing handler metadata.
Private
getPrivate
hasSets the middlewares metadata for the target object.
+An array of middleware constructors.
+Static
getStatic
getRetrieves the method handlers metadata for the given entity.
+The entity constructor.
+An array of BaseHandler
objects.
Static
getRetrieves the middlewares metadata for the given entity.
+The entity constructor.
+An array of middleware constructors.
+Shortcut to target (entity) name
+Optional
featuresOptional
httpOptional
target: stringOptional
args: any[]Optional
args: any[]Optional
target: stringOptional
args: any[]Optional
args?: any[]Optional
code?: string | numberOptional
status?: numberOptional
target?: stringOptional
target: stringOptional
args: any[]Optional
args: any[]Optional
target: stringOptional
args: any[]Optional
args?: any[]Optional
code?: string | numberOptional
target?: stringOptional
target: stringOptional
args: any[]Optional
args: any[]Optional
target: stringOptional
args: any[]Optional
args?: any[]Optional
code?: string | numberOptional
target?: stringOptional
target: stringOptional
args: any[]Optional
args: any[]Optional
target: stringOptional
args: any[]Optional
args?: any[]Optional
code?: string | numberOptional
status?: numberOptional
target?: stringOptional
target: stringOptional
args: any[]Optional
args: any[]Optional
target: stringOptional
args: any[]Optional
args?: any[]Optional
code?: string | numberOptional
target?: stringClass cds.Service
+Optional
name: stringOptional
model: CSNOptional
options: { Constructs and sends a DELETE request.
+Constructs and emits an asynchronous event.
+Provides access to the entities exposed by a service
+Provides access to the events declared by a service
+The kind of the service
+The model from which the service's definition was loaded
+The name of the service
+Provides access to the operations, i.e. actions and functions, exposed by a service
+Constructs and sends a synchronous request.
+Provides access to the types exposed by a service
+This class registers the middleware classes for @Use
decorator.
Creates an instance of MiddlewareEntityRegistry.
+The entity instance to be used.
+The service instance to be used.
+Private
Readonly
entityThe entity instance to be used.
+Private
Readonly
srvThe service instance to be used.
+Private
Readonly
executeExecutes the middleware chain starting from the specified index.
+The request object.
+The index from which to start the middleware chain.
+Private
getPrivate
registerPrivate
registerPrivate
sortThis decorator is used to associate a handler class with a specific entity. +It ensures that all handlers within the class operate with the specified entity context.
+The entity to associate with the handler class. Must be a CDS-Typer
class.
Rest
...args: neverThis decorator is used to associate a handler class with all entities. +It ensures that all handlers within the class operate with a generic context applicable to all entities.
+A wildcard '*'
indicating all entities.
Rest
...args: neverThis decorator is used to mark a class as a repository, containing repository logic
.
It makes the class injectable and allows dependency injection to be used within the class.
+This decorator is used to mark a class as containing business logic
.
It makes the class injectable and allows dependency injection to be used within the class.
+This decorator is used to mark a class as containing unbound actions.
+Unbound actions are operations that are not tied to a specific entity. +It makes the class injectable and allows dependency injection to be used within the class.
+Use @AfterAll
decorator to execute custom logic after creating a new resource for all events ('CREATE', 'READ', 'UPDATE', 'DELETE', 'BOUND ACTION', 'BOUND FUNCTION')
Use @AfterCreateAll
decorator to execute custom logic after creating a new draft resource for all events ('CREATE', 'READ', 'UPDATE', 'DELETE', 'BOUND ACTION', 'BOUND FUNCTION')
Use @AfterCancelDraft
decorator to execute custom logic after a 'draft' is cancelled.
Use @AfterCreate
decorator to execute custom logic after creating a new resource.
Use @AfterCreateDraft
decorator to execute custom logic after creating a new DRAFT resource.
Use @AfterDelete
decorator to execute custom logic after performing a delete operation.
Use @AfterDeleteDraft
decorator to execute custom logic after performing a delete operation on a draft.
Use @AfterEditDraft
decorator to execute custom logic after a 'draft' is edited.
Use @AfterNewDraft
decorator to execute custom logic after a new 'draft' is created.
Use @AfterRead
decorator to execute custom logic after performing a read operation.
Use @AfterReadDraft
decorator to execute custom logic after performing a draft read operation.
The @AfterReadDraftEachInstance
decorator is used to execute custom logic after performing a read operation on each individual draft instance
. This behavior is analogous to the JavaScript Array.prototype.forEach
method.
Use @AfterReadDraftSingleInstance
decorator to execute custom logic after creating a new DRAFT single instance resource.
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.
Use @AfterReadSingleInstance
decorator to execute custom logic after creating a new single instance resource.
Use @AfterSaveDraft
decorator to execute custom logic after a 'draft' is saved.
Use @AfterUpdate
decorator to execute custom logic after performing an update operation.
Use @AfterUpdateDraft
decorator to execute custom logic after performing a DRAFT update operation.
Use @BeforeAll
decorator to execute custom logic before creating a new resource for all events ('CREATE', 'READ', 'UPDATE', 'DELETE', 'BOUND ACTION', 'BOUND FUNCTION')
Use @BeforeAllDraft
decorator to execute custom logic before creating a new draft
resource for all events ('CREATE', 'READ', 'UPDATE', 'DELETE', 'BOUND ACTION', 'BOUND FUNCTION')
Use @BeforeCancelDraft
decorator to execute custom logic before a 'draft' is cancelled.
Use @BeforeCreate
decorator to execute custom logic before creating a new resource.
Use @BeforeCreateDraft
decorator to execute custom logic before creating a new DRAFT resource.
Use @BeforeDelete
decorator to execute custom logic before performing a delete operation.
Use @BeforeDeleteDraft
decorator to execute custom logic before performing a delete operation on a draft.
Use @BeforeEditDraft
decorator to execute custom logic before a 'draft' is edited.
Use @BeforeNewDraft
decorator to execute custom logic before a 'draft' is created.
Use @BeforeRead
decorator to execute custom logic before performing a read operation.
Use @BeforeReadDraft
decorator to execute custom logic before performing a DRAFT read operation.
Use @BeforeSaveDraft
decorator to execute custom logic before a 'draft' is saved.
Use @BeforeUpdate
decorator to execute custom logic before performing an update operation.
Use @BeforeUpdateDraft
decorator to execute custom logic before performing a DRAFT update operation.
Use @ExecutionAllowedForRole
decorator to enforce role-based access control ensuring that only Users
with specific role are authorized to execute the event
(AfterRead
, AfterCreate
, ...) and the custom logic inside of the event.
Rest
...roles: string[]Use @FieldsFormatter
decorator to enhance / format
the fields.
The formatter method to apply.
+Rest
...fields: (keyof T)[]An array of fields to apply the formatter method on.
+Use @OnAction
decorator to execute custom logic when a custom action event is triggered.
Use @OnAll
decorator to execute custom logic when a new resource is (READ, CREATED, UPDATED, DELETED)
Use @OnAllDraft
decorator to execute custom logic.
Use @OnBoundAction
decorator to execute custom logic when a custom bound action event is triggered.
Use @OnBoundActionDraft
decorator to execute custom logic when a custom bound action event is triggered on a DRAFT resource.
Use @OnBoundFunction
decorator to execute custom logic when a custom bound function event is triggered.
Use @OnBoundFunctionDraft
decorator to execute custom logic when a custom bound function event is triggered on a DRAFT resource.
Use @OnCancelDraft
decorator to execute custom logic when a 'draft' is cancelled.
Use @OnCreate
decorator to execute custom logic when a new resource is created.
Use @OnCreateDraft
decorator to execute custom logic when a new DRAFT resource is created.
Use @OnDelete
decorator to execute custom logic when a delete operation is performed.
Use @OnDeleteDraft
decorator to execute custom logic when a delete operation is performed on a DRAFT resource.
Use @OnEditDraft
decorator to execute custom logic when a new draft is created from an active instance.
Use @OnError
decorator to execute custom logic when an error occurs.
Use @OnEvent
decorator to execute custom logic when a custom event is triggered.
Use @OnFunction
decorator to execute custom logic when a custom function event is triggered.
Use @OnNewDraft
decorator to execute custom logic when a 'draft' is created.
Use @OnRead
decorator to execute custom logic when a read operation is performed.
Use @OnReadDraft
decorator to execute custom logic when a read operation is performed on a DRAFT resource.
Use @OnSaveDraft
decorator to execute custom logic when the 'active entity' is changed.
Use @OnUpdate
decorator to execute custom logic when an update operation is performed.
Use @OnUpdateDraft
decorator to execute custom logic when an update operation is performed on a DRAFT resource.
Use @Prepend
decorator to register an event handler to run before existing ones.
The options object.
+Use @PrependDraft
decorator to register an event handler to run before existing ones.
The options object.
+Use the @Use
decorator to associate a method or a class with a specified middleware classes, mainly used to verify
, enhance
, validate
various request related-information.
Rest
...MiddlewareClasses: Middleware[]The middleware classes to be applied.
+Optional
propertyKey: stringOptional
descriptor: TypedPropertyDescriptor<RequestType>Use @Validate
decorator to validate fields.
The validation method to apply.
+Rest
...fields: (keyof T)[]An array of fields to validate.
+Annotates a parameter of a method to get
the request.query[INSERT, SELECT, UPDATE, UPSERT, DELETE][property]
properties.
The key indicating the type of query operation (INSERT
, SELECT
, UPDATE
, UPSERT
, DELETE
).
The specific property to get within the request.query[key][property]
.
Annotates a parameter of a method to get
the Request
properties.
NOTE:
This is a convenient decorator to get only some properties of the Request
object, to get all properties use @Req()
decorator.
The Request
property to get.
Annotates a parameter of a method to check
existence of request.query[INSERT, SELECT, UPSERT].columns - item
with the value from field
parameter.
The name of the column
to verify in the request.query[INSERT, SELECT, UPSERT].columns
.
boolean
+Annotates a parameter of a method to check
existence of request.query[INSERT, SELECT, UPDATE, UPSERT, DELETE][property]
various properties.
The key indicating the type of query operation (INSERT
, SELECT
, UPDATE
, UPSERT
, DELETE
).
The specific property to check within the request.query[key][property]
.
boolean
+Annotates a parameter of a method to check
the existence
of specific role values in the request.
NOTE
: IsRole
applies a logical OR
between the roles, meaning it checks if at least one of the specified roles exists.
Rest
...roles: string[]An array of role names to verify in req.user.is(role)
.
boolean
+Optional
targetKey: string | symbolOptional
indexOrPropertyDescriptor: number | TypedPropertyDescriptor<T>
+
+
+
+
+
+
+
+
+
+
+
+
The goal of CDS-TS-Dispatcher is to significantly reduce the boilerplate code required to implement Typescript handlers provided by the SAP CAP framework.
+Architecture
CDSDispatcher
Decorators
+Class
+
+Field
+
+Parameter
+
+Method
-active entity
+
+Method
-draft entity
+
+Method
-helpers
+
+Deployment
to BTP using MTABest practices
& tips
Examples
Install @sap/cds-dk, typescript
, ts-node
globally:
npm install -g @sap/cds-dk typescript ts-node
+
+
+Option 1 :
Install CDS-TS-Dispatcher - New project
Use the following steps if you want to create a new SAP CAP project.
+mkdir new-sap-cap-project
cd new-sap-cap-project
+
+
+cds init
+
+
+cds add typer
npm install
+
+
+npm install @dxfrontier/cds-ts-dispatcher
npm install --save-dev @types/node
+
+
+tsc --init
+
+
+{
"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"]
}
+
+
+CDS-TS
servercds-ts watch
+
+
+
+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 @sap/cds@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 developmentCloud 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 propertiespackage.json
- predefined scripts
Steps
git clone https://github.com/dxfrontier/cds-ts-dispatcher-dev-container
+
+
+VSCode
using:code cds-ts-dispatcher-dev-container
+
+
+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
+
+
+Install Remote development pack VScode extension
+COMMAND + SHIFT + P on MacOS
or CTRL + SHIFT + P on Windows
Rebuild and Reopen in Container
- This step will start creating the container project and start the Node server.Start development as usual.
+Generate CDS Typed entities
Recommended
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
+
+
+./srv/util/types/entities
- Change to your desired destination folder.Important
++[!CAUTION] +Import always the
+generated entities
from theservice
folders and not from theindex.ts
++ +[!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';
Architecture
We recommend adhering to the Controller-Service-Repository design pattern using the following folder structure:
+(Controller)
- Responsible for managing the REST interface to the business logic implemented in ServiceLogic(Service)
- Contains business logic implementations(Repository)
- This component is dedicated to handling entity manipulation operations by leveraging the power of CDS-QL.Controller-Service-Repository
suggested folder structure
<= expanded folders =>
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
initialize
: The initialize
method of the CDSDispatcher
class is used to initialize Entity handler(s) and all of their dependencies : Services, Repositories, UnboundActionsExample
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
The @EntityHandler
decorator is utilized at the class-level
to annotate a class with:
entity
that will serve as the base entity for all handler decorators within the class.'*'
as all entities
that will serve as the base entity for all handler decorators within the class.Overloads
Method | +Parameters | +Description | +
---|---|---|
1. EntityHandler(entity : CDSTyper) |
+Must be a CDS-Typer generated class |
+It ensures that all handlers within the class operate with the specified entity context . |
+
2. EntityHandler(entity : '*' ) |
+A wildcard '*' indicating all entities |
+It 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 canimport 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()
+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 forDependency injection
.
@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 forDependency injection
.
[Optional]
- CDS-TS-Repository - BaseRepositoryThe 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.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()
+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 specificEntity
but instead these actions belongs to the Service itself.
@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:
+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]
++
+- Think of it (middleware) like as a reusable class, enhancing the functionality of all events within the class.
+- Middlewares when applied with
+@Use
are executed before the normal events.- 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(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
) private srv: Service
This specialized @Inject
can be used as a constant
in and contains the CDS.ApplicationService
for further enhancements :
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 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.
+
Parameter
@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()
+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
+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()
+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()
+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()
+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<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
otherwiseExample
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(...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 yourCDS
models.
@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
otherwiseExample
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<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'
, 'columns'
) columns: GetQueryType['columns']['forSelect']
'SELECT'
, 'distinct'
) distinct: GetQueryType['distinct']
'SELECT'
, 'excluding'
) excluding: GetQueryType['excluding']
'SELECT'
, 'from'
) from: GetQueryType['from']['forSelect']
'SELECT'
, 'groupBy'
) groupBy: GetQueryType['groupBy']
'SELECT'
, 'having'
) having: GetQueryType['having']
'SELECT'
, 'limit'
) limit: GetQueryType['limit']
'SELECT'
, 'limit.rows'
) limitRows: GetQueryType['limit']['rows']
'SELECT'
, 'limit.offset'
) limitOffset: GetQueryType['limit']['offset']
'SELECT'
, 'mixin'
) mixin: GetQueryType['mixin']
'SELECT'
, 'one'
) one: GetQueryType['one']
'SELECT'
, 'orderBy'
) orderBy: GetQueryType['orderBy']
'SELECT'
, 'where'
) where: GetQueryType['where']
'INSERT'
, 'as'
) as: GetQueryType['as']
'INSERT'
, 'columns'
) columns: GetQueryType['columns']['forInsert']
'INSERT'
, 'entries'
) entries: GetQueryType['entries']
'INSERT'
, 'into'
) into: GetQueryType['into']
'INSERT'
, 'rows'
) rows: GetQueryType['rows']
'INSERT'
, 'values'
) values: GetQueryType['values']
'UPDATE'
, 'data'
) data: GetQueryType['data']
'UPDATE'
, 'entity'
) entity: GetQueryType['entity']
'UPDATE'
, 'where'
) where: GetQueryType['where']
'UPSERT'
, 'columns'
) columns: GetQueryType['columns'][forUpsert]
'UPSERT'
, 'entries'
) entries: GetQueryType['entries']
'UPSERT'
, 'into'
) into: GetQueryType['into']
'UPSERT'
, 'rows'
) rows: GetQueryType['rows']
'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(property : keyof Request)
+The @GetRequest
decorator is utilized at the parameter level
. It allows you tu 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 :
'entity'
) entity: Request['entity']
,'event'
) event: Request['event']
,'features'
) features: Request['features']
,'headers'
) headers: Request['headers']
,'http'
) http: Request['http']
,'id'
) id: Request['id']
,'locale'
) locale: Request['locale']
,'method'
) method: Request['method']
,'params'
) params: Request['params']
,'query'
) query: Request['query']
,'subject'
) subject: Request['subject']
,'target'
) target: Request['target']
,'tenant'
) tenant: Request['tenant']
,'timestamp'
) timestamp: Request['timestamp']
,'user'
) user: Request['user']
,Example
import { EntityHandler, Results, GetRequest } from '@dxfrontier/cds-ts-dispatcher';
import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';
import type { GetTypeLocale, GetTypeMethod, 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
+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()
+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 EntityHandlerargument
=>MyEntity
.
++ +[!NOTE] +MyEntity was generated using CDS-Typer and imported in the the class.
+
@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 EntityHandlerargument
=>MyEntity
.
++ +[!NOTE] +MyEntity was generated using CDS-Typer and imported in the the class.
+
@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 EntityHandlerargument
=>MyEntity
++ +[!NOTE] +MyEntity was generated using CDS-Typer and imported in the the class.
+
@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 EntityHandlerargument
=>MyEntity
.
++ +[!NOTE] +MyEntity was generated using CDS-Typer and imported in the the class.
+
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:
+CREATE
@BeforeCreate(), @AfterCreate(), @OnCreate()READ
@BeforeRead(), @AfterRead(), @OnRead()UPDATE
@BeforeUpdate(), @AfterUpdate(), @OnUpdate()DELETE
@BeforeDelete(), @AfterDelete(), @OnDelete()BOUND ACTIONS
@OnBoundAction()BOUND FUNCTIONS
@OnBoundFunction()DRAFT
For draft entities, the @BeforeAll decorator will be triggered when at least one of the following events occurs:
+CREATE
@BeforeNewDraft(), @AfterNewDraft(), @OnNewDraft()CANCEL
@BeforeCancelDraft(), @AfterCancelDraft(), @OnCancelDraft()EDIT
@BeforeEditDraft(), @AfterEditDraft(), @OnEditDraft()SAVE
@BeforeSaveDraft(), @AfterSaveDraft(), @OnSaveDraft()Draft
variant.@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 EntityHandlerargument
=>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:
+Parameters | +Decorator | +Description | +
---|---|---|
results, req |
+@AfterRead |
+An array of type MyEntity[] and the Request . |
+
result, req |
+@AfterUpdate @AfterCreate |
+An object of type MyEntity and the Request . |
+
deleted, req |
+@AfterDelete |
+A 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()
+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 EntityHandlerargument
=>MyEntity
.
++ +[!NOTE] +MyEntity was generated using CDS-Typer and imported in the the class.
+
@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 EntityHandlerargument
MyEntity
.
++ +[!NOTE] +MyEntity was generated using CDS-Typer and imported in the the class.
+
@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 EntityHandlerargument
MyEntity
.
++ +[!NOTE] +MyEntity was generated using CDS-Typer and imported in the the class.
+
@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 EntityHandlerargument
=>MyEntity
.
++ +[!NOTE] +MyEntity was generated using CDS-Typer and imported in the the class.
+
@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 EntityHandlerargument
=>MyEntity
.
++ +[!NOTE] +MyEntity was generated using CDS-Typer and imported in the the class.
+
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:
+CREATE
@BeforeCreate(), @AfterCreate(), @OnCreate()READ
@BeforeRead(), @AfterRead(), @OnRead()UPDATE
@BeforeUpdate(), @AfterUpdate(), @OnUpdate()DELETE
@BeforeDelete(), @AfterDelete(), @OnDelete()BOUND ACTIONS
@OnBoundAction()BOUND FUNCTIONS
@OnBoundFunction()DRAFT
For draft entities, the @BeforeAll decorator will be triggered when at least one of the following events occurs:
+CREATE
@BeforeNewDraft(), @AfterNewDraft(), @OnNewDraft()CANCEL
@BeforeCancelDraft(), @AfterCancelDraft(), @OnCancelDraft()EDIT
@BeforeEditDraft(), @AfterEditDraft(), @OnEditDraft()SAVE
@BeforeSaveDraft(), @AfterSaveDraft(), @OnSaveDraft()Draft
variant.@AfterAll()
+Example
import { AfterAll} from "@dxfrontier/cds-ts-dispatcher";
import type { Request } from '@dxfrontier/cds-ts-dispatcher';
import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';
@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) => {
// ...
});
+
+
+++[!IMPORTANT] +Decorator
+@AfterAll()
will be triggered based on the EntityHandlerargument
=>MyEntity
.
++[!TIP] +If the entity has drafts enabled
+@odata.draft.enabled: true
, the@AfterAll
decorator will still be triggered for draft events.
++ +[!NOTE] +MyEntity was generated using CDS-Typer and imported in the the class.
+
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 TypedRequest
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()
@OnCreate()
+Example
import { OnCreate, Next } from "@dxfrontier/cds-ts-dispatcher";
import type { TypedRequest, NextEvent } from '@dxfrontier/cds-ts-dispatcher';
import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';
@OnCreate()
private async onCreateMethod(@Req() req: TypedRequest<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 EntityHandlerargument
=>MyEntity
.
++ +[!NOTE] +MyEntity was generated using CDS-Typer and imported in the the class.
+
@OnRead()
+Example
import { OnRead, Next } from "@dxfrontier/cds-ts-dispatcher";
import type { TypedRequest, NextEvent } from '@dxfrontier/cds-ts-dispatcher';
import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';
@OnRead()
private async onReadMethod(@Req() req: TypedRequest<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 EntityHandlerargument
=>MyEntity
.
++ +[!NOTE] +MyEntity was generated using CDS-Typer and imported in the the class.
+
@OnUpdate()
+Example
import { OnUpdate, Next } from "@dxfrontier/cds-ts-dispatcher";
import type { TypedRequest, NextEvent } from '@dxfrontier/cds-ts-dispatcher';
import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';
@OnUpdate()
private async onUpdateMethod(@Req() req: TypedRequest<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 EntityHandlerargument
=>MyEntity
.
++ +[!NOTE] +MyEntity was generated using CDS-Typer and imported in the the class.
+
@OnDelete()
+Example
import { OnDelete, Next } from "@dxfrontier/cds-ts-dispatcher";
import type { TypedRequest, NextEvent } from '@dxfrontier/cds-ts-dispatcher';
import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';
@OnDelete()
private async onDeleteMethod(@Req() req: TypedRequest<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 EntityHandlerargument
=>MyEntity
.
++ +[!NOTE] +MyEntity was generated using CDS-Typer and imported in the the class.
+
@OnAction(name
: CdsAction)
Parameters
name (CdsAction)
: Representing the CDS action
defined in the CDS file
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.
@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.
@OnEvent(name
: CdsEvent)
The @OnEvent
decorator facilitates the listening of messages from a message broker.
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 } from "@dxfrontier/cds-ts-dispatcher";
import type { TypedRequest } from '@dxfrontier/cds-ts-dispatcher';
import { AEvent } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';
@OnEvent(AEvent)
private async onEventMethod(@Req() req: TypedRequest<AEvent>) {
// ...
}
+
+
+Equivalent to 'JS'
this.on('AEvent', async (req) => {
// ...
});
+
+
+++[!NOTE] > AEvent was generated using CDS-Typer and imported in the the class.
+
++[!IMPORTANT]
+
+Decorator@OnEvent
should be used inside @UnboundActions class.
++ +[!TIP] +More info can be found at https://cap.cloud.sap/docs/guides/messaging/
+
@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 returningPromises
.
++ +[!TIP] +More info can be found at SAP CAP Error
+
@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 EntityHandlerargument
=>MyEntity
.
++ +[!NOTE] +MyEntity was generated using CDS-Typer and imported in the the class.
+
@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 EntityHandlerargument
=>MyEntity
.
++ +[!NOTE] +MyEntity was generated using CDS-Typer and imported in the the class.
+
@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 @BeforeAll decorator will be triggered when at least one of the following events occurs:
+CREATE
@BeforeCreate(), @AfterCreate(), @OnCreate()READ
@BeforeRead(), @AfterRead(), @OnRead()UPDATE
@BeforeUpdate(), @AfterUpdate(), @OnUpdate()DELETE
@BeforeDelete(), @AfterDelete(), @OnDelete()BOUND ACTIONS
@OnBoundAction()BOUND FUNCTIONS
@OnBoundFunction()DRAFT
For draft entities, the @BeforeAll decorator will be triggered when at least one of the following events occurs:
+CREATE
@BeforeNewDraft(), @AfterNewDraft(), @OnNewDraft()CANCEL
@BeforeCancelDraft(), @AfterCancelDraft(), @OnCancelDraft()EDIT
@BeforeEditDraft(), @AfterEditDraft(), @OnEditDraft()SAVE
@BeforeSaveDraft(), @AfterSaveDraft(), @OnSaveDraft()Draft
variant.++[!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
import { OnAll, Next } from "@dxfrontier/cds-ts-dispatcher";
import type { Request, NextEvent } from '@dxfrontier/cds-ts-dispatcher';
import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';
@OnAll()
private async onAll(@Req() req: Request, @Next() next: NextEvent) {
// ...
return next();
}
+
+
+Equivalent to 'JS'
this.on('*', MyEntity, async (req, next) => {
// ...
});
+
+
+++[!IMPORTANT] +Decorator
+@OnAll()
will be triggered based on the EntityHandlerargument
=>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.
+
Method
-draft entity
Before
Use @BeforeNewDraft(), @BeforeCancelDraft(), @BeforeEditDraft(), @BeforeSaveDraft(), @BeforeCreateDraft(), @BeforeReadDraft(), @BeforeUpdateDraft(), @BeforeDeleteDraft()
to register handlers to run before.on
handlers, frequently used for validating user input.
The handlers receive one argument:
+req
of type TypedRequest
@BeforeNewDraft()
+Use this decorator when you want to validate inputs before a new draft is created.
+Example
import { BeforeNewDraft, TypedRequest } from "@dxfrontier/cds-ts-dispatcher";
import type { TypedRequest } from '@dxfrontier/cds-ts-dispatcher';
import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';
@BeforeNewDraft()
private async beforeCreateDraftMethod(@Req() req: TypedRequest<MyEntity>) {
// ...
}
+
+
+Equivalent to 'JS'
this.before('NEW', MyEntity.drafts, async (req) => {
// ...
});
+
+
+++[!IMPORTANT] +Decorator
+@BeforeNewDraft()
will be triggered based on the EntityHandlerargument
=>MyEntity
.
++ +[!NOTE] +MyEntity was generated using CDS-Typer and imported in the the class.
+
@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 { TypedRequest } from '@dxfrontier/cds-ts-dispatcher';
import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';
@BeforeCancelDraft()
private async beforeCancelDraftMethod(@Req() req: TypedRequest<MyEntity>) {
// ...
}
+
+
+Equivalent to 'JS'
this.before('CANCEL', MyEntity.drafts, async (req) => {
// ...
});
+
+
+++[!IMPORTANT] +Decorator
+@BeforeCancelDraft()
will be triggered based on the EntityHandlerargument
=>MyEntity
.
++ +[!NOTE] +MyEntity was generated using CDS-Typer and imported in the the class.
+
@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 { TypedRequest } from '@dxfrontier/cds-ts-dispatcher';
import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';
@BeforeEditDraft()
private async beforeEditDraftMethod(@Req() req: TypedRequest<MyEntity>) {
// ...
}
+
+
+Equivalent to 'JS'
this.before('EDIT', MyEntity, async (req) => {
// ...
});
+
+
+++[!IMPORTANT] +Decorator
+@BeforeEditDraft()
will be triggered based on the EntityHandlerargument
=>MyEntity
.
++ +[!NOTE] +MyEntity was generated using CDS-Typer and imported in the the class.
+
@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 { TypedRequest } from '@dxfrontier/cds-ts-dispatcher';
import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';
@BeforeSaveDraft()
private async beforeSaveDraftMethod(@Req() req: TypedRequest<MyEntity>) {
// ...
}
+
+
+Equivalent to 'JS'
this.before('SAVE', MyEntity, async (req) => {
// ...
});
+
+
+++[!IMPORTANT] +Decorator
+@BeforeSaveDraft()
will be triggered based on the EntityHandlerargument
=>MyEntity
.
++ +[!NOTE] +MyEntity was generated using CDS-Typer and imported in the the class.
+
After
Use @AfterNewDraft(), @AfterCancelDraft(), @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:
Parameters | +Decorator | +Description | +
---|---|---|
results, req |
+@AfterRead |
+An array of type MyEntity[] and the Request . |
+
+ | + | + |
result, req |
+@AfterUpdate @AfterCreate |
+An object of type MyEntity and the Request . |
+
+ | + | + |
deleted, req |
+@AfterDelete |
+A boolean indicating whether the instance was deleted and the Request . |
+
@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 { TypedRequest } from '@dxfrontier/cds-ts-dispatcher';
import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';
@AfterNewDraft()
private async afterNewDraftMethod(@Result() result: MyEntity, @Req() req: TypedRequest<MyEntity>) {
// ...
}
+
+
+Equivalent to 'JS'
this.after('NEW', MyEntity.drafts, async (results, req) => {
// ...
});
+
+
+++[!IMPORTANT] +Decorator
+@AfterNewDraft()
will be triggered based on the EntityHandlerargument
=>MyEntity
.
++ +[!NOTE] +MyEntity was generated using CDS-Typer and imported in the the class.
+
@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 { TypedRequest } from '@dxfrontier/cds-ts-dispatcher';
import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';
@AfterCancelDraft()
private async afterCancelDraftMethod(@Result() result: MyEntity, @Req() req: TypedRequest<MyEntity>) {
// ...
}
+
+
+Equivalent to 'JS'
this.after('CANCEL', MyEntity.drafts, async (results, req) => {
// ...
});
+
+
+++[!IMPORTANT] +Decorator
+@AfterCancelDraft()
will be triggered based on the EntityHandlerargument
=>MyEntity
.
++ +[!NOTE] +MyEntity was generated using CDS-Typer and imported in the the class.
+
@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 { TypedRequest } from '@dxfrontier/cds-ts-dispatcher';
import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';
@AfterEditDraft()
private async afterEditDraftMethod(@Result() result: MyEntity, @Req() req: TypedRequest<MyEntity>) {
// ...
}
+
+
+Equivalent to 'JS'
this.after('EDIT', MyEntity, async (results, req) => {
// ...
});
+
+
+++[!IMPORTANT] +Decorator
+@AfterEditDraft()
will be triggered based on the EntityHandlerargument
=>MyEntity
.
++ +[!NOTE] +MyEntity was generated using CDS-Typer and imported in the the class.
+
@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 { TypedRequest } from '@dxfrontier/cds-ts-dispatcher';
import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';
@AfterSaveDraft()
private async afterSaveDraftMethod(@Result() result: MyEntity, @Req() req: TypedRequest<MyEntity>) {
// ...
}
+
+
+Equivalent to 'JS'
this.after('SAVE', MyEntity, async (results, req) => {
// ...
});
+
+
+++[!IMPORTANT] +Decorator
+@AfterSaveDraft()
will be triggered based on the EntityHandlerargument
=>MyEntity
.
++ +[!NOTE] +MyEntity was generated using CDS-Typer and imported in the the class.
+
On
Use @OnNewDraft(), @OnCancelDraft(), @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 TypedRequest
next
of type NextEvent
See Official SAP Fiori-draft
+@OnNewDraft()
+This decorator will be triggered when a new draft is created
.
Example
import { OnNewDraft, Req, Next } from "@dxfrontier/cds-ts-dispatcher";
import type { TypedRequest, NextEvent } from '@dxfrontier/cds-ts-dispatcher';
import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';
@OnNewDraft()
private async onNewDraft(@Req() req: TypedRequest<MyEntity>, @Next() next: NextEvent) {
// ...
}
+
+
+Equivalent to 'JS'
this.on('NEW', MyEntity.drafts, async (req, next) => {
// ...
});
+
+
+++[!IMPORTANT] +Decorator
+@OnNewDraft()
will be triggered based on the EntityHandlerargument
=>MyEntity
.
++ +[!NOTE] +MyEntity was generated using CDS-Typer and imported in the the class.
+
@OnCancelDraft()
+This decorator will be triggered when a draft is cancelled
.
Example
import { OnCancelDraft, Req, Next } from "@dxfrontier/cds-ts-dispatcher";
import type { TypedRequest, NextEvent } from '@dxfrontier/cds-ts-dispatcher';
import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';
@OnCancelDraft()
private async onCancelDraft(@Req() req: TypedRequest<MyEntity>, @Next() next: NextEvent) {
// ...
}
+
+
+Equivalent to 'JS'
this.on('CANCEL', MyEntity.drafts, async (req, next) => {
// ...
});
+
+
+++[!IMPORTANT] +Decorator
+@OnCancelDraft()
will be triggered based on the EntityHandlerargument
=>MyEntity
.
++ +[!NOTE] +MyEntity was generated using CDS-Typer and imported in the the class.
+
@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 { TypedRequest, NextEvent } from '@dxfrontier/cds-ts-dispatcher';
import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';
@OnEditDraft()
private async onEditDraft(@Req() req: TypedRequest<MyEntity>, @Next() next: NextEvent) {
// ...
}
+
+
+Equivalent to 'JS'
this.on('EDIT', MyEntity, async (req, next) => {
// ...
});
+
+
+++[!IMPORTANT] +Decorator
+@OnEditDraft()
will be triggered based on the EntityHandlerargument
=>MyEntity
.
++ +[!NOTE] +MyEntity was generated using CDS-Typer and imported in the the class.
+
@OnSaveDraft()
+This decorator will be triggered when the active entity is changed
Example
import { OnSaveDraft, Req, Next } from "@dxfrontier/cds-ts-dispatcher";
import type { TypedRequest, NextEvent } from '@dxfrontier/cds-ts-dispatcher';
import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';
@OnSaveDraft()
private async onSaveDraft(@Req() req: TypedRequest<MyEntity>, @Next() next: NextEvent) {
// ...
}
+
+
+Equivalent to 'JS'
this.on('SAVE', MyEntity, async (req, next) => {
// ...
});
+
+
+++[!IMPORTANT] +Decorator
+@OnSaveDraft()
will be triggered based on the EntityHandlerargument
=>MyEntity
.
++ +[!NOTE] +MyEntity was generated using CDS-Typer and imported in the the class.
+
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.
+
Method
-helpers
@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 you can apply the parameter @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 { TypedRequest } from '@dxfrontier/cds-ts-dispatcher';
import { MyEntity } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';
@AfterReadSingleInstance()
private async afterReadSingleInstance(@Result() result: MyEntity, @Req() req: TypedRequest<MyEntity>) {
// This will be executed only when single instance is called : http://localhost:4004/odata/v4/main/MyEntity(ID=2f12d711-b09e-4b57-b035-2cbd0a023a09)
// ...
}
+
+
+++[!IMPORTANT] +Decorator @AfterReadSingleInstance() will be triggered based on the EntityHandler
+argument
MyEntity
.
++[!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
@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.
} +
++[!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.
+
@Prepend({ eventDecorator : string })
+The @Prepend
decorator is utilized as a method-level
decorator to register an event handler to run before existing ones like @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
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
import { Prepend, OnEvent, Req } from '@dxfrontier/cds-ts-dispatcher';
import type { TypedRequest } from '@dxfrontier/cds-ts-dispatcher';
import { MyEvent } from 'YOUR_CDS_TYPER_ENTITIES_LOCATION';
@Prepend({ eventDecorator: 'OnEvent', eventName: MyEvent })
private async prepend(@Req() req: TypedRequest<MyEvent>) {
req.locale = 'DE_de';
}
@OnEvent(MyEvent)
public async handleMyEvent(@Req() req: TypedRequest<MyEvent>) {
// req.locale will have the value 'DE_de' ...
// ...
}
+
+
+++[!TIP] +The
+@Prepend
decorator can be used for example :+
+- When you want to prepare various things
+before
reaching the actual event.- Making transformation on
+Request
,Result
, ...before
reaching the actual event.- ...
+
++ +[!TIP] +If
+@odata.draft.enabled: true
and you need to read the draft then you should use@PrependDraft()
decorator.
@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 can be used when you want to validate
the Request
.data
(Request Body) of the @sap/cds - Request
object on the following decorators :
ON
+
+BEFORE
+
+Parameters
action
: Choose from a list of predefined Validators
.options?
: [Optional] Additional options for customizing the validation process....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:
+Action | +Description | +Options | +
---|---|---|
contains | +Check if the string contains the seed. | +ignoreCase: boolean | undefined; default : false minOccurrences: number | undefined; default 1 |
+
equals | +Check if the string matches the comparison. | ++ |
matches | +Check if the string matches the pattern. | ++ |
startsWith | +Checks if the string starts with the given target string. |
++ |
endsWith | +Checks if the string ends with the given target string. |
++ |
isMailtoURI | +Check 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. |
+
isNumeric | +Check if the string contains only numbers. | + no_symbols: If set to locale: An object specifying the locale information for alpha validation. |
+
isTime | +Check if the string is a valid time e.g. 23:01:59 . |
+ hourFormat: Specify the hour format to validate. Use mode: Specify the time format to validate. Use |
+
isLatLong | +check if the string is a valid latitude-longitude coordinate in the format lat,long or lat, long . |
++ |
isMD5 | +check if the string is a MD5 hash. | ++ |
isMimeType | +check if the string matches to a valid [MIME type] format. | ++ |
isPort | +check if the string is a valid port number. | ++ |
isSlug | +check if the string is of type slug. | ++ |
isISBN | +check if the string is an [ISBN] | +version: "10", "13", 10, 13; | +
isEmail | +check if the string is an email. | +Same as isMailtoURI |
+
isAlpha | +Check if the string contains only letters (a-zA-Z). | ++ |
isAlphanumeric | +Check if the string contains only letters and numbers. | ++ |
isCreditCard | +Check if the string is a credit card. | + provider: Specify the credit card provider to validate. Use one of the following: |
+
isCurrency | +Check 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 |
+
isDataURI | +Check if the string is a data URI format. | ++ |
isDate | +Check 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: ['/', '-'] |
+
isEmpty | +Check if the string has a length of zero. | +ignore_whitespace: If set to true , whitespace characters will be ignored. Default: false |
+
isIBAN | +Check 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 |
+
isIMEI | +Check if the string is a valid IMEI. | +allow_hyphens: If set to true , allows IMEI numbers with hyphens. Default: false |
+
isIP | +Check if the string is an IP (version 4 or 6). | + "4", "6", 4, 6; |
+
isIdentityCard | +Check 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' | +
isIn | +Check if the string is in an array of allowed values. | ++ |
isJSON | +Check if the string is valid JSON (note: uses JSON.parse ). |
++ |
isJWT | +Check if the string is a valid JWT token. | ++ |
isLength | +Check 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 |
+
isLowercase | +Check if the string is lowercase. | ++ |
isMobilePhone | +Check 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 |
+
isPassportNumber | +Check if the string is a valid passport number relative to a specific country code. | ++ |
isPostalCode | +Check 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" | +
isURL | +Check 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 |
+
isUUID | +Check if the string is a UUID (version 1, 2, 3, 4, or 5). | ++ |
isUppercase | +Check if the string is uppercase. | ++ |
isVAT | +Checks that the string is a valid VAT number. | ++ |
isWhitelisted | +Checks if characters appear in the whitelist. | ++ |
isInt | +Check 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 |
+
isHexadecimal | +Check if the string is a hexadecimal number. | ++ |
isFloat | +Check if the string is a float. | +min: less or equal max: greater or equal gt: greater than lt: less than locale: FloatLocale |
+
isHash | +Check if the string is a hash of export type algorithm. | +"md4", "md5", "sha1", "sha256", "sha384", "sha512", "ripemd128", "ripemd160", "tiger128", "tiger160", "tiger192", "crc32", "crc32b" | +
isEAN | +Check if the string is an EAN (European Article Number). | ++ |
isDecimal | +Check 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' |
+
isBoolean | +Check 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 |
+
isBIC | +Check if a string is a BIC (Bank Identification Code) or SWIFT code. | ++ |
isBefore | +Check if the string is a date that's before the specified date. | ++ |
isAfter | +Check if the string is a date that's after the specified date. | ++ |
Example 1
import {
EntityHandler,
Inject,
CDS_DISPATCHER,
Validate,
BeforeCreate,
BeforeUpdate,
OnCreate,
OnUpdate,
Req,
Next,
} from '@dxfrontier/cds-ts-dispatcher';
import type { TypedRequest, 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: TypedRequest<MyEntity>) {
// ...
}
@BeforeUpdate()
@Validate<MyEntity>({ action: 'startsWith', target: 'COMMENT:' }, 'comment')
@Validate<MyEntity>({ action: 'isAlphanumeric' }, 'description')
private async beforeUpdate(@Req() req: TypedRequest<MyEntity>) {
// ...
}
@OnCreate()
@Validate<MyEntity>({ action: 'isAlphanumeric' }, 'book_ID')
private async onCreate(@Req() req: TypedRequest<MyEntity>, @Next() next: NextEvent) {
// ...
return next();
}
@OnUpdate()
@Validate<MyEntity>({ action: 'isLength', options: { min: 5 } }, 'comment')
private async onUpdate(@Req() req: TypedRequest<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, TypedRequest, 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: TypedRequest<OrderedBook>) {
// ...
}
}
+
+
+++ +[!IMPORTANT] +To get the fields for
+ +you must use the
+ExposeFields
type inside of the@Validate
decorator.
@FieldsFormatter<T>({ action, options? }, ...fields: Array<keyof T>)
+The @FieldsFormatter
is used as a method level
decorator to modify
/enhance
fields.
The @FieldsFormatter
decorator can be used on the following decorators :
When you want to modify
/enhance
the results
of your callback.
AFTER
+When you want to modify
/enhance
the Request
.data
(Request Body) of the @sap/cds - Request
object.
ON
+
+BEFORE
+
+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:
+Action | +Description | +
---|---|
blacklist | +Remove characters that appear in the blacklist. | +
ltrim | +Trim characters from the left-side of the input. | +
rtrim | +Trim characters from the right-side of the input. | +
trim | +Trim characters from both sides of the input. | +
escape | +Replace < , > , & , ' , " and / with HTML entities. |
+
unescape | +Replaces HTML encoded entities with < , > , & , ' , " and / . |
+
toLower | +Converts string, as a whole, to lower case. | +
toUpper | +Converts string, as a whole, to upper case. | +
upperFirst | +Converts the first character of the string to upper case. | +
lowerFirst | +Converts the first character of the string to lower case. | +
replace | +Replaces matches for pattern in string with replacement. Note: This method is based on String#replace. |
+
truncate | +Truncates 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 "…". |
+
snakeCase | +Snake 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". |
+
kebabCase | +Kebab 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. |
+
camelCase | +The format indicates the first word starting with either case, then the following words having an initial uppercase letter. CustomerName, LastName ... |
+
customFormatter | +Apply a custom formatter when standard ones do not satisfy your needs. | +
Example 1
import {
EntityHandler,
Inject,
CDS_DISPATCHER,
BeforeCreate,
BeforeUpdate,
AfterRead,
OnCreate,
OnUpdate,
FieldsFormatter,
Req,
Next,
} from '@dxfrontier/cds-ts-dispatcher';
import type { Service, TypedRequest, 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' }, 'format')
private async beforeCreate(@Req() req: TypedRequest<MyEntity>) {
// ...
}
@BeforeUpdate()
@FieldsFormatter<MyEntity>({ action: 'truncate', options: { length: 7 } }, 'format')
private async beforeUpdate(@Req() req: TypedRequest<MyEntity>) {
// ...
}
@AfterRead()
@FieldsFormatter<MyEntity>({ action: 'toUpper' }, 'format')
@FieldsFormatter<MyEntity>(
{
action: 'customFormatter',
callback(req, results) {
if (results) {
// make first item 'toLowerCase' and leave the rest 'toUpper'
results[0].format = results[0].format?.toLowerCase();
}
},
},
'format',
)
private async afterRead(@Results() results: MyEntity[], @Req() req: TypedRequest<MyEntity>) {
// ...
}
@OnCreate()
@FieldsFormatter<MyEntity>({ action: 'ltrim' }, 'language')
private async onCreate(@Req() req: TypedRequest<MyEntity>, @Next() next: NextEvent) {
// ...
return next();
}
@OnUpdate()
@FieldsFormatter<MyEntity>({ action: 'trim' }, 'format')
private async onUpdate(@Req() req: TypedRequest<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: TypedRequest<AnEvent>) {
// ...
}
}
+
+
+++ +[!IMPORTANT] +To get the fields for
+ +you must use the
+ExposeFields type
inside of the@FieldsFormatter
decorator.
@ExecutionAllowedForRole(...roles: string[])
+The @ExecutionAllowedForRole
is used as a method level
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 )
}
+
+
+
+@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:
+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();
}
}
+
+
+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]
++
+- Middlewares when applied with
+@Use
are executed before the normal events.- If you need to apply middleware to
+class
you can have a look over class specific @Use decorator .
++[!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.
+
Deployment
to BTP using MTAmta.yaml
to your project using the following command :cds add mta
+
+
+npm-run-all
package:npm install --save-dev npm-run-all
+
+
+package.json
by adding the following scripts
:"build:cds": "echo 'STEP 1 : Build CDS' && cds build --production",
"build:ts": "echo 'STEP 2 : Transpile TS => JS' && tsc",
"build:srv:clean:ts": "echo 'Step 3: Clean TS files from srv folder' && find gen/srv/srv -type f -name '*.ts' -delete",
"build:production": "run-s build:cds build:ts build:srv:clean:ts"
+
+
+mta.yaml
as follows :- builder: custom
+ commands:
+ - npm ci
+ - npm run build:production
+ - npx @cap-js/cds-typer "*" --outputDirectory gen/srv/@cds-models
+
+
+Steps
explained :
npm ci
- Will do a clean installnpm run build:production
- 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.npm i -g mbt
+
+
+.mtar file
mbt build
+
+
+mtar
to BTPBest practices
& tips
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: TypedRequest<MyEntity>) {
// ..
}
+
+
+Yes, it is important as typescript executes the decorators :
+Class
- from bottom
to top
method
- from top
to bottom
@SecondClassDecorator() // second executed
@FirstClassDecorator() // first executed
class MyClass {
@FirstDecorator() // first executed
@SecondDecorator() // second executed
myMethod() {
console.log('Method called');
}
}
+
+
+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: TypedRequest<MyEntity>) {
// ...
}
+
+
+++[!TIP] +To get all the Formatters typing you can import the
+Formatters\<T>
type whereT
is the entity of yourCDS
provided by CDS-Typer
Examples
Find here a collection of samples for the CDS-TS-Dispatcher-Samples
+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.
+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.
+Remove characters that appear in the blacklist.
+The characters are used in a RegExp
and so you will need to escape some chars, e.g. blacklist(input, '\\[\\]')
.
Converts string to camel case.
+Replace <
, >
, &
, '
, "
and /
with HTML entities.
Converts string to kebab case.
+Trim characters from the left-side of the input.
+Optional
chars?: stringcharacters (defaults to whitespace)
+Converts the first character of string
to lower case.
Trim characters from the right-side of the input.
+Optional
chars?: stringcharacters (defaults to whitespace)
+Converts string to kebab case.
+Converts string
, as a whole, to lower case.
Converts string
, as a whole, to upper case.
Trim characters from both sides of the input.
+Optional
chars?: stringcharacters (defaults to whitespace)
+Truncates 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 "…".
+Optional
options?: TruncateOptionsReplaces HTML encoded entities with <
, >
, &
, '
, "
and /
.
Converts the first character of string
to upper case.
Use ActionRequest
type to have the Request
of @OnAction
, @OnBoundAction
, @OnFunction
, @OnBoundFunction
typed.
Use ActionReturn
type to have the return
of the @OnAction
, @OnBoundAction
, @OnFunction
, @OnBoundFunction
typed.
Use NextEvent
type to annotate the next
parameter of the implementation of the ON
events.
Use NextMiddleware
type to annotate the next
parameter of the implementation of the middleware.
This object is created internally by an HTTP server, not by the user. Contains various methods and properties related to the response
object.
Check if a string is a BIC (Bank Identification Code) or SWIFT code.
+check if a string is a boolean.
+Optional
options?: OptionsCheck if the string is a data uri format.
+Check if the string is an EAN (European Article Number).
+Check if the string is a hexadecimal number.
+Check if the string is valid JSON (note: uses JSON.parse
).
Check if the string is valid JWT token.
+Check if the string is a valid latitude-longitude coordinate in the format:
+lat,long
or lat, long
.
Check if the string is lowercase.
+Check if the string is a MD5 hash.
+Check if the string matches to a valid [MIME export type](https://en.wikipedia.org/wiki/Media_export type) format.
+check if the string is a valid port number.
+Check if the string is of export type slug.
+Check if the string is uppercase.
+Checks that the string is a [valid VAT number
+Optional
custom'customMessage' will replace the standard message of the validator.
+Optional
mandatoryIf mandatoryFieldValidation : true
this will mandatory check the Request.data
object for the field which needs to be validated.
+if mandatoryFieldValidation : false
or omitted, this will let the Request.data
not to have the field in the body
and the validation will just be omitted.
+Default
is set to false
Const
CDS-TS-Dispatcher app helper constants.
+Readonly
ALL_Represents all entities. Use CDS_DISPATCHER.ALL_ENTITIES
with the EntityHandler
decorator to target all entities in your application.
This is a constant, but you can also use the asterisk (*
) instead of CDS_DISPATCHER.ALL_ENTITIES
.
Readonly
SRV: "srv"Represents the CDS Service. Use CDS_DISPATCHER.SRV
to inject the CDS Service into your class as a dependency.
This is a constant, but you can also use the string 'srv'
instead of CDS_DISPATCHER.SRV
.
Const
Const
Const
Utility functions for applying formatters to various data structures.
+Applies the specified formatter to the given value.
+The formatter to apply.
+The value to format.
+The formatted value.
+Applies the specified formatter to a single field of multiple items.
+The formatter to apply.
+The items to format.
+The field of the items to format.
+Applies the specified formatter to a single field of a single item.
+The formatter to apply.
+The item to format.
+The field of the item to format.
+Applies the specified formatter to a single field of the request data.
+The request containing the data to format.
+The formatter to apply.
+The field of the request data to format.
+Const
Utility object for handling middleware operations.
+Executes a chain of middleware functions.
+The request object.
+The current index in the middleware chain. Defaults to 0.
+An array of middleware classes to execute.
+Optional
entityInstance: Constructable<any>An optional entity instance.
+A promise that resolves when the middleware chain is complete.
+Checks if the request object has been rejected.
+The request object.
+True if the request has been rejected, otherwise false.
+Registers middleware functions to a class.
+The target class.
+An array of middleware classes to register.
+Registers middleware functions to a method.
+An array of middleware classes to register.
+The property descriptor of the method.
+Const
Utility object for handling parameter operations.
+Applies the 'is column supplied' parameter to the request arguments.
+The request object.
+The arguments array.
+The metadata fields array.
+Applies the 'is present' or 'get' decorator to the request arguments.
+The type of decorator, either 'Get' or 'IsPresent'.
+The metadata parameters array.
+The request object.
+The arguments array.
+Applies the 'is role' parameter to the request arguments.
+The request object.
+The arguments array.
+The metadata fields array.
+Handles request options and applies them to the arguments.
+The request object.
+The arguments array.
+The metadata fields array.
+Checks if the request is for a single instance.
+The request object.
+True if the request is for a single instance, otherwise false.
+Retrieves the JWT from the request.
+The request object.
+The JWT string if present, otherwise undefined.
+Retrieves the response object from the request.
+The request object.
+The response object.
+Const
This util contains common methods for reusable purposes. +Methods don't contain business logic.
+Type guard to check if an argument is a Request.
+Single point of lodash.
+Checks if a field is empty.
+The field value to check.
+True if the field is empty, otherwise false.
+Raises a bad request error indicating that a required field is empty.
+The SAP CAP Request object.
+The validator or formatter that identified the empty field.
+The field that is empty.
+Raises a bad request error from the SAP CAP Request with '400' error code.
+The SAP CAP Request object.
+The message to be raised.
+Const
Utility object for handling validation operations.
+Applies the specified validator to the given field in the request.
+The request object.
+The validator object.
+The field to validate.
+Determines if validation can be applied to the given field in the request.
+The request object.
+The validator object.
+The field to validate.
+True if validation can be applied, otherwise false.
+Displays a message indicating the validation error.
+An object containing the validation error details.
+The field that failed validation.
+The input value that failed validation.
+Optional
message?: null | stringAn optional custom message for the validation error.
+The request object.
+The validator that failed.
+
The
+ArgumentMethodProcessor
class is responsible for processing method arguments, including reordering arguments by type and applying decorators.