From 82cc416b6f699be3f6bb604eeffd4e4b6a03c97d Mon Sep 17 00:00:00 2001 From: D050513 Date: Mon, 22 Jun 2026 13:25:40 +0200 Subject: [PATCH 1/2] Node.js: Rework Generic Handlers --- node.js/app-services.md | 121 +++++++++++++++++++++++++++------------- 1 file changed, 81 insertions(+), 40 deletions(-) diff --git a/node.js/app-services.md b/node.js/app-services.md index 9a4394c17c..f38ebc5d51 100644 --- a/node.js/app-services.md +++ b/node.js/app-services.md @@ -45,113 +45,154 @@ module.exports = class AdminService extends cds.ApplicationService { -### Generic Handlers in `srv.init()` +### Generic Handlers -Generic handlers are registered by via respective class methods documented below in `cds.ApplicationService.prototype.init()` like so: +`cds.ApplicationService` overrides `srv.handle(req)` to call several per-request handler functions directly — inline, before delegating to `super.handle(req)`. These are assigned as instance properties during `init()`: ```tsx class cds.ApplicationService extends cds.Service { init() { - const generics = //... all static method with prefix 'handle_' - for (let each of generics) this[each].call(this) + const generics = //... all static methods with prefix 'handle_' + for (let each of generics) this.constructor[each].call(this) + // per-request handlers — assigned unless a subclass already defined a static handle_* for it + this.handle_authorization ??= get_handle_authorization.call(this) + this.handle_etags ??= get_handle_etags.call(this) + this.handle_validations ??= get_handle_validations.call(this) + this.handle_media_type ??= get_handle_media_type.call(this) + this.handle_temporal_data ??= get_handle_temporal_data.call(this) + this.handle_paging ??= get_handle_paging.call(this) + this.handle_sorting ??= get_handle_sorting.call(this) return super.init() } - static handle_authorization() {...} - static handle_etags() {...} - static handle_validations() {...} - static handle_temporal_data() {...} - static handle_localized_data() {...} - static handle_managed_data() {...} - static handle_paging() {...} + async handle(req) { + // called directly per request: + if (this.handle_authorization) await this.handle_authorization(req) + if (this.handle_etags) await this.handle_etags(req) + if (this.handle_validations) await this.handle_validations(req) + if (req.event === 'READ') { + this.handle_temporal_data?.(req) + this.handle_paging?.(req) + this.handle_sorting?.(req) + } else if (req.event === 'UPDATE') { + this.handle_media_type?.(req) + } + return super.handle(req) + } + // registered as before/on handlers during init(): static handle_fiori() {...} static handle_crud() {...} } ``` -> The reason we used `static` methods was to **(a)** give you an easy way of overriding and adding new generic handlers / features, and **(b)** without getting into conflicts with instance methods of subclasses. - - -### _static_ handle_authorization() {.method} -This method is adding request handlers for initial authorization checks, as documented in the [Authorization guide](../guides/security/authorization.md). +### handle_authorization() {.method} +Called per request to perform authorization checks, as documented in the [Authorization guide](../guides/security/authorization.md). -### _static_ handle_etags() {.method} -This method is adding request handlers for out-of-the-box concurrency control using ETags, as documented in the [Providing Services guide](../guides/services/served-ootb#concurrency-control). +### handle_etags() {.method} +Called per request to perform concurrency control using ETags, as documented in the [Providing Services guide](../guides/services/served-ootb#concurrency-control). -### _static_ handle_validations() {.method} -This method is adding request handlers for input validation based in `@assert` annotations, and other, as documented in the [Providing Services guide](../guides/services/constraints). +### handle_validations() {.method} +Called per request to perform input validation based on `@assert` annotations, as documented in the [Providing Services guide](../guides/services/constraints). -### _static_ handle_temporal_data() {.method} +### handle_media_type() {.method} -This method is adding request handlers for handling temporal data, as documented in the [Temporal Data guide](../guides/domain/temporal-data.md). +Called per `UPDATE` request to handle media type / streaming concerns. +### handle_temporal_data() {.method} -### _static_ handle_localized_data() {.method} +Called per `READ` request to handle temporal data, as documented in the [Temporal Data guide](../guides/domain/temporal-data.md). -This method is adding request handlers for handling localized data, as documented in the [Localized Data guide](../guides/uis/localized-data.md). +### handle_paging() {.method} +Called per `READ` request to apply paging, as documented in the [Providing Services guide](../guides/services/served-ootb#pagination-sorting). -### _static_ handle_managed_data() {.method} -This method is adding request handlers for handling managed data, as documented in the [Providing Services guide](../guides/domain/index#managed-data). +### handle_sorting() {.method} - -### _static_ handle_paging() {.method} - -This method is adding request handlers for paging & implicit sorting, as documented in the [Providing Services guide](../guides/services/served-ootb#pagination-sorting). +Called per `READ` request to apply implicit sorting, as documented in the [Providing Services guide](../guides/services/served-ootb#pagination-sorting). ### _static_ handle_fiori() {.method} -This method is adding request handlers for handling Fiori Drafts and other Fiori-specifics, as documented in the [Serving Fiori guide](../guides/uis/fiori.md). +Registers before/on handlers for Fiori Drafts and other Fiori-specifics during `init()`, as documented in the [Serving Fiori guide](../guides/uis/fiori.md). ### _static_ handle_crud() {.method} -This method is adding request handlers for all CRUD operations including *deep* CRUD, as documented in the [Providing Services guide](../guides/services/served-ootb). +Registers on handlers for all CRUD operations including *deep* CRUD during `init()`, as documented in the [Providing Services guide](../guides/services/served-ootb). ## Overriding Generic Handlers -You can override some of these methods in subclasses, for example to skip certain generic features, or to add additional ones. For example like that: +The per-request handlers (`handle_authorization`, `handle_etags`, `handle_validations`, `handle_media_type`, `handle_temporal_data`, `handle_paging`, `handle_sorting`) are instance properties. You can replace or disable one by assigning to it in `init()`: + +```js +class YourService extends cds.ApplicationService { + init() { + const result = super.init() + // replace with a no-op to skip validations entirely: + this.handle_validations = null + // or wrap the default: + const orig = this.handle_validations + this.handle_validations = async (req) => { + // run before default validations + if (req.data.someField === 'forbidden') req.reject(400, 'Not allowed') + return orig?.(req) + } + return result + } +} +``` + +Alternatively, define a **static** `handle_*` on your subclass. If found, the built-in instance function is skipped entirely, and your static method is called during `init()` instead (with `this` bound to the instance), where you can register `before`/`on` handlers as usual: ```js class YourService extends cds.ApplicationService { static handle_validations() { - // Note: this is an instance of YourService here: - this.on('CREATE','*', req => {...}) - return super.handle_validations() + // 'this' is the service instance — register handlers directly + this.before('CREATE', '*', req => { /*...*/ }) } } ``` -> +For `handle_fiori` and `handle_crud`, which remain `static` and register event handlers during `init()`, override them the same way: + +```js +class YourService extends cds.ApplicationService { + static handle_fiori() { + super.handle_fiori.call(this) // keep standard Fiori support + this.before('SAVE', '*', req => { /*...*/ }) + } +} +``` ## Adding Generic Handlers -You can also add own sets of generic handlers to all instances of `cds.ApplicationService`, and subclasses thereof, by simply adding a new class method prefixed with `handle_` like so: +You can add your own sets of generic handlers to all instances of `cds.ApplicationService` by adding a new **static** method prefixed with `handle_`: ```js const cds = require('@sap/cds') -cds.ApplicationService.handle_log_events = cds.service.impl (function(){ +cds.ApplicationService.handle_log_events = function() { this.on('*', req => console.log(req.event)) -}) +} ``` + +Static `handle_*` methods are called during `init()` with `this` bound to the service instance, so you can register event handlers directly. From 612001ae692dcfcabcdeb9328c7b2b7fb85c1b67 Mon Sep 17 00:00:00 2001 From: D050513 Date: Mon, 22 Jun 2026 14:02:25 +0200 Subject: [PATCH 2/2] rm minors --- node.js/app-services.md | 46 +++++++---------------------------------- 1 file changed, 8 insertions(+), 38 deletions(-) diff --git a/node.js/app-services.md b/node.js/app-services.md index f38ebc5d51..41012b66d9 100644 --- a/node.js/app-services.md +++ b/node.js/app-services.md @@ -55,13 +55,12 @@ class cds.ApplicationService extends cds.Service { const generics = //... all static methods with prefix 'handle_' for (let each of generics) this.constructor[each].call(this) // per-request handlers — assigned unless a subclass already defined a static handle_* for it - this.handle_authorization ??= get_handle_authorization.call(this) - this.handle_etags ??= get_handle_etags.call(this) - this.handle_validations ??= get_handle_validations.call(this) - this.handle_media_type ??= get_handle_media_type.call(this) - this.handle_temporal_data ??= get_handle_temporal_data.call(this) - this.handle_paging ??= get_handle_paging.call(this) - this.handle_sorting ??= get_handle_sorting.call(this) + if (!generics.has('handle_authorization')) + this.handle_authorization ??= get_handle_authorization.call(this) + if (!generics.has('handle_etags')) + this.handle_etags ??= get_handle_etags.call(this) + if (!generics.has('handle_validations')) + this.handle_validations ??= get_handle_validations.call(this) return super.init() } async handle(req) { @@ -69,13 +68,7 @@ class cds.ApplicationService extends cds.Service { if (this.handle_authorization) await this.handle_authorization(req) if (this.handle_etags) await this.handle_etags(req) if (this.handle_validations) await this.handle_validations(req) - if (req.event === 'READ') { - this.handle_temporal_data?.(req) - this.handle_paging?.(req) - this.handle_sorting?.(req) - } else if (req.event === 'UPDATE') { - this.handle_media_type?.(req) - } + // handed over to cds.Service's handle return super.handle(req) } // registered as before/on handlers during init(): @@ -104,29 +97,6 @@ Called per request to perform input validation based on `@assert` annotations, a -### handle_media_type() {.method} - -Called per `UPDATE` request to handle media type / streaming concerns. - - - -### handle_temporal_data() {.method} - -Called per `READ` request to handle temporal data, as documented in the [Temporal Data guide](../guides/domain/temporal-data.md). - - - -### handle_paging() {.method} - -Called per `READ` request to apply paging, as documented in the [Providing Services guide](../guides/services/served-ootb#pagination-sorting). - - - -### handle_sorting() {.method} - -Called per `READ` request to apply implicit sorting, as documented in the [Providing Services guide](../guides/services/served-ootb#pagination-sorting). - - ### _static_ handle_fiori() {.method} @@ -142,7 +112,7 @@ Registers on handlers for all CRUD operations including *deep* CRUD during `init ## Overriding Generic Handlers -The per-request handlers (`handle_authorization`, `handle_etags`, `handle_validations`, `handle_media_type`, `handle_temporal_data`, `handle_paging`, `handle_sorting`) are instance properties. You can replace or disable one by assigning to it in `init()`: +The per-request handlers (`handle_authorization`, `handle_etags`, `handle_validations`) are instance properties. You can replace or disable one by assigning to it in `init()`: ```js class YourService extends cds.ApplicationService {