{"ref":"capability:typescript-patterns","page":"/capabilities/typescript-patterns","json":"/capabilities/typescript-patterns.json","generated":"2026-10-26","drafted_with_ai":true,"entity":{"id":"typescript-patterns","name":"TypeScript patterns","domain":"developer-experience","ecosystems":["cap"],"universal":false,"description":"How an application is arranged once it is written in TypeScript — where handlers live, what registers them, and how business logic is kept apart from persistence. Distinct from typing, which is about the model being visible to the compiler; this is about the shape of the code around it.","short":"How an application is arranged once written in TypeScript","core_coverage":{"state":"core-and-community","assessed_at":"2026-10-15","relationship":"unassessed","what_core_does":"The shape capire gives a TypeScript service is the JavaScript one: custom logic is a subclass of cds.ApplicationService, its handlers are registered in init() with on, before and after, and the runtime finds the implementation in a file named after the service definition or in a lib/ or handlers/ subfolder beside it. The toolchain around that is official too — cds watch detects TypeScript from a tsconfig.json and runs cds-tsx, and a cds build task shipped with [@cap-js/cds-typer](artifact:cap-js-cds-typer) precompiles to JavaScript for production.","where_it_stops":"At the service's own implementation class. None of the pages read here divides a service's handlers across several classes, gives a draft's own events a place apart from the active entity's, or names a layer between a handler and the database — so a project that wants a class per entity, or business logic kept apart from persistence, writes that arrangement itself.","community_adds":"Different ways of deciding what makes a method a handler, which is the choice a reader faces here: a decorator on the class or the method, as in [cds-ts-dispatcher](artifact:dxfrontier-cds-ts-dispatcher), a method name matched by convention, as in [cap-handler-framework](artifact:mubuntu-cap-handler-framework), or a folder layout that fixes where an entity's class goes, as in [cds-plugin-handlers](artifact:lemaiwo-cds-plugin-handlers). Beside them sits the persistence half, such as [cds-ts-repository](artifact:dxfrontier-cds-ts-repository): the queries called through a class with methods instead of written as CQN inside the handler.","sources":[{"url":"https://cap.cloud.sap/docs/node.js/core-services","title":"Core Services (Node.js) — capire","checked":"2026-10-15","quote":"Implement your custom logic as a subclass of `cds.Service`, or more commonly of `cds.ApplicationService` to benefit from generic out-of-the-box implementations."},{"url":"https://cap.cloud.sap/docs/node.js/core-services","title":"Core Services (Node.js) — capire, where handlers are registered","checked":"2026-10-15","quote":"The actual implementation goes into event handlers, commonly registered in method `srv.init()`."},{"url":"https://cap.cloud.sap/docs/node.js/core-services","title":"Core Services (Node.js) — capire, where the implementation file sits","checked":"2026-10-15","quote":"The easiest way to add custom service implementations is to simply place a `.js` file with the same name next to the `.cds` file containing the respective service definition."},{"url":"https://cap.cloud.sap/docs/node.js/core-services","title":"Core Services (Node.js) — capire, the lib and handlers folders","checked":"2026-10-15","quote":"In addition to adding the implementation in a neighbouring file you can place them in nested subfolders called `lib/` or `handlers/`."},{"url":"https://cap.cloud.sap/docs/node.js/typescript","title":"TypeScript — capire","checked":"2026-10-15","quote":"Preferably use `cds watch` in a TypeScript project as if it was a JavaScript project."},{"url":"https://cap.cloud.sap/docs/node.js/typescript","title":"TypeScript — capire, what the setup covers","checked":"2026-10-15","quote":"This setup applies for service handlers, and to a custom server.ts file, or database init.ts seeding files as well."},{"url":"https://cap.cloud.sap/docs/node.js/typescript","title":"TypeScript — capire, the build task","checked":"2026-10-15","quote":"A dedicated build task for `cds build` is provided as part of the `cds-typer` package."},{"url":"https://cap.cloud.sap/docs/node.js/typescript","title":"TypeScript — capire, production","checked":"2026-10-15","quote":"For productive usage, always precompile TypeScript code to JavaScript for best performance"}],"note":"No page read for this assessment states SAP's intent about the arrangement inside srv, so the relationship between the two sides is unassessed."},"coverage_by_flavor":{"node":{"state":"community-only","assessed_at":"2026-10-15","note":"capire documents TypeScript support for the Node.js runtime, which is the flavor this concern exists in.","sources":[{"url":"https://cap.cloud.sap/docs/node.js/typescript","title":"TypeScript — capire","checked":"2026-10-15","quote":"Preferably use `cds watch` in a TypeScript project as if it was a JavaScript project."}]},"java":{"state":"not-applicable","assessed_at":"2026-10-15","note":"The CAP Java SDK develops CAP applications in Java, with Java 21 as its minimum, so a service's custom logic in this flavor is Java code and there is no TypeScript in srv to arrange. Not applicable rather than unmapped: the concern does not exist here, which is not the same as nobody having built for it.","sources":[{"url":"https://cap.cloud.sap/docs/java/getting-started","title":"Getting Started — CAP Java","checked":"2026-10-15","quote":"The CAP Java SDK enables developing CAP applications in Java."},{"url":"https://cap.cloud.sap/docs/java/getting-started","title":"Getting Started — CAP Java, the Java version","checked":"2026-10-15","quote":"Java 21 is the minimum requirement, but we recommend using Java 25."},{"url":"https://cap.cloud.sap/docs/node.js/typescript","title":"TypeScript — capire, a Node.js page","checked":"2026-10-15","quote":"Preferably use `cds watch` in a TypeScript project as if it was a JavaScript project."}]}},"facets":[{"key":"handler_binding","label":"How handlers bind","kind":"many","values":["decorators","method names","folder layout","explicit registration"]},{"key":"layers","label":"Layers it names","kind":"many","values":["handlers","service logic","persistence","external services"]},{"key":"dependency_injection","label":"Dependency injection","kind":"flag"},{"key":"draft_events","label":"Draft events","kind":"flag"}],"changed_in":[{"version":"@cap-js/cds-typer 0.5.0","date":"2023-07-25","what_changed":"Before this, deriving TypeScript types from a CDS model meant a separate community CLI run as an extra build step — cds2types was the one most people reached for, alongside hand-written declarations. In July 2023 cds-typer went open source under the cap-js GitHub org and onto npm as @cap-js/cds-typer, already supporting the array-of/many syntax and enums by the time of that announcement, giving the framework a maintained, official generator for the first time.","invalidates":"Setup guidance from 2020-2022 that reaches for cds2types, hand-written .d.ts files or another community fork to get typed entities out of a CDS model. That gap is what cds-typer closed in mid-2023; what is left open, and what this capability records, is the shape of the code around the generated types — dispatchers, repositories, controller-service-repository layering — which the core still has no opinion on.","source":{"url":"https://cap.cloud.sap/docs/releases/2023/jul23","title":"July 2023 — sap/cds — capire","checked":"2026-09-08","quote":"The package has also gone open source and is available on npm and GitHub."}},{"version":"@sap/cds-dk, December 2024","date":"2024-12-01","applies_to":{"ecosystems":["cap"]},"what_changed":"cds watch detects TypeScript mode from a tsconfig.json and runs cds-tsx itself, so a TypeScript project is started with the same command as a JavaScript one. Until then the dev loop needed its own command.","invalidates":"Setup steps that tell a reader to run cds-tsx watch or cds-ts watch to start a TypeScript project. The release note's own sentence is that this is not necessary anymore.","source":{"url":"https://cap.cloud.sap/docs/releases/2024/dec24","title":"December 2024 — capire, cds watch for TypeScript","checked":"2026-10-15","quote":"In a TypeScript project, you can now just run `cds watch` as if it was a JavaScript project."}}],"history":[{"date":"2026-10-15","field":"core_coverage.what_core_does, core_coverage.where_it_stops","from":"The core makes TypeScript work and stops there ... Dispatchers, repositories, decorators and controller-service-repository layering are all community answers to a question the core leaves open on purpose — there is no official opinion about the inside of srv.","to":"The documented class-and-init() shape and the implementation-file lookup, with the boundary at one implementation per service definition","reason":"The old sentence said the core stops at the toolchain and has no opinion about the inside of srv. capire's Core Services page states one: custom logic is a subclass of cds.ApplicationService, handlers are registered in init(), and the implementation is found in a file named after the service or in a lib/ or handlers/ subfolder. The boundary is narrower than the old text claimed and is now quoted, so a reader can see which part of the arrangement is official and which part every option here answers."},{"date":"2026-10-15","field":"core_coverage.community_adds","from":null,"to":"The three ways of binding a handler, and the persistence half","reason":"The reading named what the core does and stopped. The field says what the ecosystem answers on top, which here is a choice between three binding mechanisms rather than one missing feature; the options themselves stay derived from the provides edges."},{"date":"2026-10-15","field":"coverage_by_flavor.java","from":null,"to":"not-applicable","reason":"CAP's Java flavor runs Java, as its own getting-started page states with Java 21 as the minimum, so there is no TypeScript code in srv for this concern to arrange. Not-applicable rather than unmapped, which would say nobody had built it."},{"date":"2026-10-15","field":"coverage_by_flavor.node","from":null,"to":"community-only","reason":"Stated rather than derived, so the node map carries its own assessment date. capire documents TypeScript support under its Node.js section, which is why the concern is a node-flavor one."},{"date":"2026-10-15","field":"facets","from":null,"to":"handler_binding, layers, dependency_injection, draft_events","reason":"The options here differ on four documented facts that change a choice: what makes a method a handler, which layers the option names, whether it documents injecting anything into a handler, and whether it handles a draft's own events. Each value sits on the artifact's own provides edge with the sentence it rests on; an option whose documentation does not mention a facet states nothing, which is not a no."},{"date":"2026-10-15","field":"changed_in","from":null,"to":"@sap/cds-dk, December 2024 (cds watch for TypeScript)","reason":"Read from capire's December 2024 release notes, so the setup steps in readings published before it can be seen to predate the change. The release page names the month only; the date is its first day."},{"date":"2026-10-17","field":"description","from":"How a CAP service is arranged once it is written in TypeScript — where handlers live, what registers them, and how business logic is kept apart from persistence. Distinct from typing, which is about the model being visible to the compiler; this is about the shape of the code around it.","to":"How an application is arranged once it is written in TypeScript — where handlers live, what registers them, and how business logic is kept apart from persistence. Distinct from typing, which is about the model being visible to the compiler; this is about the shape of the code around it.","reason":"Keep the default capability wording neutral; preserve the previous wording in framing_by_ecosystem.cap. Maintainer-requested scope audit."},{"date":"2026-10-26","field":"core_coverage.state, core_coverage.relationship","from":"community-only","to":"core-and-community, relationship unassessed","reason":"Reassessed against the graph (check:capabilities): the block states what the core does with capire sources (subclass of cds.ApplicationService, init() registration, implementation file lookup, cds watch and the cds-typer build task), so the core is present beside the community providers. The relationship stays unassessed: no page read states SAP's intent for the arrangement inside srv."},{"date":"2026-10-26","field":"core_coverage.note","from":"Community-only says who is present among the artifacts: every artifact behind this capability is independently maintained. No page read for this assessment states SAP's intent about the arrangement inside srv, so the relationship between the two sides is unassessed.","to":"No page read for this assessment states SAP's intent about the arrangement inside srv, so the relationship between the two sides is unassessed.","reason":"Reassessed against the graph (check:capabilities): the first sentence explained a state the block no longer carries and said something about every provider, which moves with the corpus."},{"date":"2026-10-26","field":"core_coverage.where_it_stops","from":"At one implementation per service definition.","to":"At the service's own implementation class.","reason":"Reassessed against the graph (check:capabilities): the checker reads \"one implementation per service\" as a count; the sentence now states the boundary without one, and the rest of the field is unchanged."},{"date":"2026-10-26","field":"core_coverage.community_adds","from":"Three ways of deciding what makes a method a handler ... a decorator, a method name matched by convention, or a folder layout","to":"Different ways of deciding what makes a method a handler, each linked to the provider that shows it, and the persistence half linked to cds-ts-repository","reason":"Reassessed against the graph (check:capabilities): removes the count of ways and links the providers that define each kind (decorators, method names, folder layout, persistence), as WRITING §3 asks."}],"drafted_with_ai":true,"framing_by_ecosystem":{"cap":{"description":"How a CAP service is arranged once it is written in TypeScript — where handlers live, what registers them, and how business logic is kept apart from persistence. Distinct from typing, which is about the model being visible to the compiler; this is about the shape of the code around it."}}},"sap_delivery":{"door":"cap","all":null,"by_flavor":{"node":null,"java":null}},"edges":{"use_cases":[{"id":"handle-a-drafts-own-events-apart-from-the-active-entitys","name":"Handle a draft's own events apart from the active entity's","capability":"capability:typescript-patterns","ecosystem":"cap","flavors":["node"],"outcome":"A draft-enabled entity's own events — NEW, PATCH, EDIT, SAVE and DISCARD — are handled in places of their own in a CAP service written in TypeScript, apart from the CREATE, UPDATE and DELETE handlers of the active entity.","boundary":"Covers where a draft's events are handled and what gets a method there. The draft lifecycle itself, the drafts entity, the locking and the activation, is the runtime's and is draft-handling, not this outcome.","origin":"Editorial hypothesis, drafted for the 2026-10-15 curation of this capability in CAP. capire's own note that EDIT handlers register on the active entity rather than on the drafts entity is the boundary two of these options say they exist to hold.","requirement_dimensions":["Whether the project has draft-enabled entities at all","Whether the same class is to hold both the active and the draft handlers for one entity","The @sap/cds major version the project is on, since the draft events changed in 10.0"],"needs":[{"key":"draft_events"}],"contributions":[{"ref":"artifact:sap-cds","role":"core runtime","state":"documented","description":"capire documents each draft event and which entity it fires on: NEW when a draft is created, PATCH on every field a user edits, EDIT when an active record starts being edited, SAVE on activation, DISCARD when a draft is thrown away. It also states the part that catches people out, that EDIT handlers are registered on the active entity and not on the drafts entity.","remaining_work":"Every registration is the application's own on, before or after call against the right target. Nothing in the pages read here gives the draft events a place of their own in the code, so one entity's active and draft handlers sit in the same implementation unless the project separates them.","remaining_kinds":["application-code"],"sources":[{"url":"https://cap.cloud.sap/docs/node.js/fiori","title":"Fiori Support (Node.js) — capire, the EDIT event","checked":"2026-10-15","quote":"For logical reasons handlers for the `EDIT` event are registered on the active entity, that is, `MyEntity` in the code above, not on the `MyEntity.drafts` entity."},{"url":"https://cap.cloud.sap/docs/node.js/fiori","title":"Fiori Support (Node.js) — capire, the NEW event","checked":"2026-10-15","quote":"The `NEW` event is triggered when the user created a new draft."},{"url":"https://cap.cloud.sap/docs/node.js/fiori","title":"Fiori Support (Node.js) — capire, the PATCH event","checked":"2026-10-15","quote":"The `PATCH` event is triggered whenever the user edits a field in a draft."},{"url":"https://cap.cloud.sap/docs/node.js/fiori","title":"Fiori Support (Node.js) — capire, the SAVE event","checked":"2026-10-15","quote":"The `SAVE` event is triggered when the user saves / activates a draft."},{"url":"https://cap.cloud.sap/docs/node.js/fiori","title":"Fiori Support (Node.js) — capire, the DISCARD event","checked":"2026-10-15","quote":"The `DISCARD` event is triggered when the user discards a draft started before."}]},{"ref":"artifact:mubuntu-cap-handler-framework","role":"separate draft hooks","state":"documented","description":"Gives the draft events hooks of their own, named apart from the active-entity ones, and its README states the separation as the point: explicit hooks for NEW, PATCH, EDIT, SAVE and DISCARD, kept away from the active entity's hooks.","remaining_work":"A handler class states that it handles drafts before the draft hooks are mapped, and what each hook does is the application's code. Which hook registers on the drafts entity and which on the active one follows the runtime's own rule.","remaining_kinds":["application-code"],"sources":[{"url":"https://github.com/Mubuntu/cap-handler-framework","title":"Mubuntu/cap-handler-framework — README, Features","checked":"2026-10-15","quote":"Correct draft lifecycle — explicit hooks for NEW/PATCH/EDIT/SAVE/DISCARD, separated from active entity hooks"},{"url":"https://github.com/Mubuntu/cap-handler-framework","title":"Mubuntu/cap-handler-framework — README","checked":"2026-10-15","quote":"Handler framework for SAP CAP applications — convention-based, TypeScript-first, draft-aware."}]},{"ref":"artifact:dxfrontier-cds-ts-dispatcher","role":"draft decorators","state":"conditional","description":"Carries a second set of decorators for the draft version of an entity's events beside the active-entity ones, so a method is marked as a draft handler where it is written rather than registered against a drafts entity by hand.","remaining_work":"The method bodies are the application's. The draft decorators are offered for an entity whose model says drafts are enabled; the README frames them that way rather than as a general set.","remaining_kinds":["application-code"],"conditions":["The entity is annotated @odata.draft.enabled: true"],"sources":[{"url":"https://github.com/dxfrontier/cds-ts-dispatcher","title":"dxfrontier/cds-ts-dispatcher — README, draft decorators","checked":"2026-10-15","quote":"If `@odata.draft.enabled: true` to manage event handlers for draft version you can use"}]},{"ref":"artifact:mrbandler-cds-routing-handlers","role":"handler registration from decorators","state":"unknown","description":"The decorator reference read today covers the four CRUD events, actions and functions, and names no draft event. Nothing in it rules a draft handler out either, so what happens for a draft-enabled entity is not established.","remaining_work":"Not established from the README. A project would have to find out whether a draft event reaches an annotated method at all.","sources":[{"url":"https://github.com/mrbandler/cds-routing-handlers","title":"mrbandler/cds-routing-handlers — README, Handler Action Decorators","checked":"2026-10-15","quote":"In this table we assume that all action handlers a within a `@Handler` decorated class with the entity `Books`."}]}],"unknowns":["What cds-plugin-handlers does for a draft event. Its README states where a handler class goes and not which events that class answers, so it is left out of this record rather than counted either way.","Whether cds-routing-handlers reaches a draft event at all. Its decorator reference names none and rules none out.","No reading was re-read for this assessment, so no record here states that a reading shows one of these options doing the outcome."],"assessed_at":"2026-10-15","drafted_with_ai":true,"readings":[],"omitted_contributions":0,"assessment_kind":"documented-contributions"},{"id":"keep-data-access-out-of-a-cap-services-handlers","name":"Keep data access out of a CAP service's handlers","capability":"capability:typescript-patterns","ecosystem":"cap","flavors":["node"],"outcome":"The data access of a CAP service written in TypeScript is called through classes with methods rather than written as queries inside the event handlers, so a handler holds the business logic and the queries sit behind one object.","boundary":"Covers where the queries live and what calls them. Which database is underneath, the transaction they run in, and whether the entity types are generated are not this outcome.","origin":"Editorial hypothesis, drafted for the 2026-10-15 curation of this capability in CAP. The maintainer of the repository option documents two ways of using it, standalone and inside a dispatcher, which is the choice this record exists to show.","requirement_dimensions":["Whether the data is the project's own entities, a remote service or another database","Whether the project already binds its handlers with decorators, which decides whether the repository can be injected or has to be constructed","Whether the entities are draft-enabled, which brings the draft entity into the same layer"],"needs":[{"key":"layers","value":"persistence"}],"paths":[{"key":"repository-alone","name":"A repository class in a standard CAP TypeScript project","sources":[{"url":"https://github.com/dxfrontier/cds-ts-repository","title":"dxfrontier/cds-ts-repository — README, Option 1","checked":"2026-10-15","quote":"`Option 1` : Using `BaseRepository` with `Standard SAP CAP CDS-TS`"}]},{"key":"repository-with-dispatcher","name":"A repository class injected into a dispatcher's handler classes","sources":[{"url":"https://github.com/dxfrontier/cds-ts-repository","title":"dxfrontier/cds-ts-repository — README, Option 2","checked":"2026-10-15","quote":"`Option 2` : Using `BaseRepository` with `CDS-TS-Dispatcher`"}]}],"contributions":[{"ref":"artifact:dxfrontier-cds-ts-repository","role":"persistence layer","state":"documented","paths":["repository-alone","repository-with-dispatcher"],"description":"A class extends BaseRepository for one entity and the reads and writes are called as its methods, so the handler calls a method instead of writing a query. Its documentation sets out both ways of reaching it, in a standard CAP TypeScript project and injected into a dispatcher's handler class, and BaseRepositoryDraft extends the same class to the draft entity.","remaining_work":"Which methods a given entity needs beyond the ones the base class provides, and the queries behind them, are the application's own. The entity types the class is typed on are generated separately.","remaining_kinds":["application-code"],"sources":[{"url":"https://github.com/dxfrontier/cds-ts-repository","title":"dxfrontier/cds-ts-repository — README","checked":"2026-10-15","quote":"The goal of BaseRepository is to significantly reduce the boilerplate code required to implement data access layers for persistance entities by providing out of the box actions on the `database`."},{"url":"https://github.com/dxfrontier/cds-ts-repository","title":"dxfrontier/cds-ts-repository — README, BaseRepositoryDraft","checked":"2026-10-15","quote":"The `BaseRepositoryDraft` class extends `BaseRepository` by providing support for draft-enabled entities."}]},{"ref":"artifact:dxfrontier-cds-ts-dispatcher","role":"layering and injection","state":"documented","paths":["repository-with-dispatcher"],"description":"Names the layer and puts the object in the handler's hands: its documentation recommends Controller-Service-Repository, gives the persistence layer the @Repository class decorator, and @Inject places a repository or a service class into a field of another class.","remaining_work":"The repository class itself is the application's, or the matching package. The decorators place the object; what it does for a given entity is written either way.","sources":[{"url":"https://github.com/dxfrontier/cds-ts-dispatcher","title":"dxfrontier/cds-ts-dispatcher — README, Architecture","checked":"2026-10-15","quote":"Repository `(Repository)` - This component is dedicated to handling entity manipulation operations by leveraging the power of CDS-QL."},{"url":"https://github.com/dxfrontier/cds-ts-dispatcher","title":"dxfrontier/cds-ts-dispatcher — README, @Inject","checked":"2026-10-15","quote":"The `@Inject` decorator is utilized as a `field-level` decorator and allows you to inject dependencies into your classes."}]},{"ref":"artifact:lemaiwo-cds-plugin-handlers","role":"layer for other systems","state":"documented","description":"Answers the outcome for data that is not the project's own entities: a services folder holds a class per external service, and the requests to an external API or another database are written there rather than in the handler.","remaining_work":"The project's own entities are not covered by that layer. What the README states is where the external-service classes go; the calls inside them are the application's.","sources":[{"url":"https://github.com/lemaiwo/cds-plugin-handlers","title":"lemaiwo/cds-plugin-handlers — README, Usage","checked":"2026-10-15","quote":"All requests to external services or even to different databases can be stored in a folder \"services\"."},{"url":"https://github.com/lemaiwo/cds-plugin-handlers","title":"lemaiwo/cds-plugin-handlers — README","checked":"2026-10-15","quote":"CAP CDS Plugin that enables classes for entities in a structured way + implementing an additional layer for service classes to separate the implementation for requests to external api's."}]},{"ref":"artifact:sap-cds","role":"core runtime","state":"unknown","description":"The runtime's documented place for custom logic is the event handler itself. None of the pages read for this assessment names a layer between a handler and the database, or a place for one, so whether the core has a position on this outcome is not established either way.","remaining_work":"Not established. A project that wants the queries out of its handlers writes that separation itself with what the runtime gives it.","remaining_kinds":["application-code"],"sources":[{"url":"https://cap.cloud.sap/docs/node.js/core-services","title":"Core Services (Node.js) — capire","checked":"2026-10-15","quote":"The actual implementation goes into event handlers, commonly registered in method `srv.init()`."}]}],"readings":[{"ref":"content:cds-ts-repository-simplify-cap-entity-persistence","relevance":"The repository option's own introduction, and it shares its opening argument with the dispatcher post: the layer between business logic and persistence is the part CAP leaves open.","flavors":["node"]},{"ref":"content:sap-cap-controller-service-repository-architecture","relevance":"Sets out the whole pattern rather than one package at a time — a controller receiving the request, a service holding the logic, a repository owning persistence — which is the argument for keeping the queries out of the handler.","flavors":["node"]},{"ref":"content:dependency-injection-and-di-containers-on-cap-nodejs","relevance":"The hands-on half of a pair on wiring objects into handlers without a framework's decorators, built on the official bookshop sample with the directories flattened.","flavors":["node"]}],"unknowns":["Whether capire documents any guidance on separating persistence from handler code. The pages read here document the handler as the place custom logic goes and are silent on a layer below it; the silence is not a position.","No reading was re-read for this assessment, so no record here states that a reading shows one of these options doing the outcome."],"assessed_at":"2026-10-15","drafted_with_ai":true,"omitted_contributions":0,"assessment_kind":"documented-contributions"},{"id":"split-a-cap-service-implementation-into-a-class-per-entity","name":"Split one CAP service implementation into a class per entity","capability":"capability:typescript-patterns","ecosystem":"cap","flavors":["node"],"outcome":"The custom logic of a CAP service written in TypeScript sits in one class per entity rather than in a single service implementation file, and each class's methods still reach the framework as the before, on and after handlers for that entity.","boundary":"Covers how one service's handlers are divided up and what makes a method a handler. What the handlers then do, the generated types they use, which is typing, and how the project is transpiled or built are not this outcome.","origin":"Editorial hypothesis, drafted for the 2026-10-15 curation of this capability in CAP. Every option behind the capability answers this question and they answer it by different means, which is what makes it a decision rather than a restatement of the capability.","requirement_dimensions":["Whether the project's entities are draft-enabled, which brings events the active-entity hooks do not cover","Whether the option may install itself as a cds-plugin or has to be wired into the service implementation by hand","The @sap/cds major version the project is on","Whether the project will turn on TypeScript's decorator compiler options"],"needs":[{"any":[{"key":"handler_binding","value":"decorators"},{"key":"handler_binding","value":"method names"},{"key":"handler_binding","value":"folder layout"}]}],"contributions":[{"ref":"artifact:sap-cds","role":"core runtime","state":"documented","description":"capire documents the shape the runtime itself gives a service implementation: a subclass of cds.ApplicationService whose init() registers the handlers with on, before and after, found in a file named after the service definition or in a lib/ or handlers/ subfolder beside it. A TypeScript project is run as a JavaScript one, so this is the arrangement a TypeScript service starts from.","remaining_work":"Dividing one service's handlers across several classes, and whatever then registers them in init(), is the application's own code. The pages read here document one implementation per service definition and the folders it may sit in; none of them maps a class to an entity or finds such classes by itself.","remaining_kinds":["application-code"],"sources":[{"url":"https://cap.cloud.sap/docs/node.js/core-services","title":"Core Services (Node.js) — capire","checked":"2026-10-15","quote":"Implement your custom logic as a subclass of `cds.Service`, or more commonly of `cds.ApplicationService` to benefit from generic out-of-the-box implementations."},{"url":"https://cap.cloud.sap/docs/node.js/core-services","title":"Core Services (Node.js) — capire, srv.init","checked":"2026-10-15","quote":"Override this method in subclasses to register custom event handlers."},{"url":"https://cap.cloud.sap/docs/node.js/core-services","title":"Core Services (Node.js) — capire, where the implementation sits","checked":"2026-10-15","quote":"In addition to adding the implementation in a neighbouring file you can place them in nested subfolders called `lib/` or `handlers/`."},{"url":"https://cap.cloud.sap/docs/node.js/typescript","title":"TypeScript — capire","checked":"2026-10-15","quote":"This setup applies for service handlers, and to a custom server.ts file, or database init.ts seeding files as well."}]},{"ref":"artifact:dxfrontier-cds-ts-dispatcher","role":"handler registration from decorators","state":"documented","description":"A class decorated with @EntityHandler is the place one entity's handlers go, and the method decorators beside it, @BeforeCreate, @BeforeRead and the rest, register what srv.before and srv.on would have. A CDSDispatcher instance in the service implementation is given the handler classes.","remaining_work":"The service implementation keeps one place that lists the handler classes, the CDSDispatcher constructor, so a new class is added there as well as written. The decorator compiler options and the generated entity types the decorators take are the project's own setup.","remaining_kinds":["configuration","application-code"],"sources":[{"url":"https://github.com/dxfrontier/cds-ts-dispatcher","title":"dxfrontier/cds-ts-dispatcher — README, @EntityHandler","checked":"2026-10-15","quote":"The `@EntityHandler` decorator is utilized at the `class-level` to annotate a class with:"},{"url":"https://github.com/dxfrontier/cds-ts-dispatcher","title":"dxfrontier/cds-ts-dispatcher — README, Before","checked":"2026-10-15","quote":"Use @BeforeCreate(), @BeforeRead(), @BeforeUpdate(), @BeforeDelete() to register handlers to run before `.on` handlers, frequently used for `validating user input.`"},{"url":"https://github.com/dxfrontier/cds-ts-dispatcher","title":"dxfrontier/cds-ts-dispatcher — README, CDSDispatcher","checked":"2026-10-15","quote":"The `CDSDispatcher` constructor allows you to create an instance for dispatching and managing entities."}]},{"ref":"artifact:mubuntu-cap-handler-framework","role":"handler registration from method names","state":"documented","description":"A handler class is matched by the names of its methods, beforeCreate, onRead, afterUpdate, with nothing annotated. The CDS plugin generates the handlers/index.ts that the service's registerHandlers call reads, so a new handler file is picked up without an edit elsewhere.","remaining_work":"The service implementation still calls registerHandlers in its init(), and the entity a class belongs to is returned by a method on the class rather than declared to the framework.","remaining_kinds":["application-code"],"sources":[{"url":"https://github.com/Mubuntu/cap-handler-framework","title":"Mubuntu/cap-handler-framework — README, Features","checked":"2026-10-15","quote":"Convention-based — auto-maps methods like `beforeCreate`, `onRead`, `afterUpdate`"},{"url":"https://github.com/Mubuntu/cap-handler-framework","title":"Mubuntu/cap-handler-framework — README, Features, the generated index","checked":"2026-10-15","quote":"Auto-generation — CDS plugin generates `handlers/index.ts` automatically"}]},{"ref":"artifact:lemaiwo-cds-plugin-handlers","role":"handler classes by folder convention","state":"documented","description":"Where a class goes is the declaration: a folder per service under src/handlers, and inside it a class per entity named after that entity. As a cds-plugin it loads itself, so the service implementation holds nothing that lists the classes.","remaining_work":"The folder and file names carry the binding, so renaming a service or an entity in the model means moving or renaming files. The README read today does not state which method names a handler class uses.","sources":[{"url":"https://github.com/lemaiwo/cds-plugin-handlers","title":"lemaiwo/cds-plugin-handlers — README, Usage","checked":"2026-10-15","quote":"Inside the \"handlers\" folder you have to create a folder for each service, e.g.: \"CatalogService\" for the service CatalogService. In here, you have to create a handler class for each entity, e.g.: \"BooksHandler\" for the entity Books."},{"url":"https://github.com/lemaiwo/cds-plugin-handlers","title":"lemaiwo/cds-plugin-handlers — README","checked":"2026-10-15","quote":"CAP CDS Plugin that enables classes for entities in a structured way + implementing an additional layer for service classes to separate the implementation for requests to external api's."}]},{"ref":"artifact:mrbandler-cds-routing-handlers","role":"handler registration from decorators","state":"conditional","description":"@Handler marks a class for an entity and its annotated methods are registered as that entity's actions, the same mechanism the dispatcher uses and the earliest of these to offer it.","remaining_work":"The TypeScript decorator setup is the project's: reflect-metadata imported before anything else, and emitDecoratorMetadata and experimentalDecorators set in tsconfig.json. The README names no @sap/cds version it was built against.","remaining_kinds":["configuration"],"conditions":["reflect-metadata is installed and imported before the package is used","emitDecoratorMetadata and experimentalDecorators are set in the project's tsconfig.json"],"sources":[{"url":"https://github.com/mrbandler/cds-routing-handlers","title":"mrbandler/cds-routing-handlers — README, Handler Decorators","checked":"2026-10-15","quote":"Class that is marked with this decorator is registered as a handler and its annotated methods are registered as actions. The entity parameter is used to differentiate and register the correct actions for the corresponding entity."},{"url":"https://github.com/mrbandler/cds-routing-handlers","title":"mrbandler/cds-routing-handlers — README, Installation","checked":"2026-10-15","quote":"Before you can use it you also need to install `reflect-metadata`."},{"url":"https://github.com/mrbandler/cds-routing-handlers","title":"mrbandler/cds-routing-handlers — README, Installation, the compiler options","checked":"2026-10-15","quote":"Finally, one last step is required to use it. We need to tell the Typescript compiler to use decorators."}]},{"ref":"artifact:soontao-cds-hyper-app-service","role":"typed service implementation layer","state":"unknown","description":"The entry describes a typed layer over ApplicationService for writing handlers as class members, which is this outcome, but nothing retrieved for this assessment states how a member becomes a handler. The package declares no repository and its npm page answered 403 when it was requested on 2026-10-15.","remaining_work":"Not established. A reader would have to read the published package to find out what it binds and how."}],"readings":[{"ref":"content:cds-ts-dispatcher-simplifying-sap-cap-typescript-development","relevance":"The dispatcher's own introduction, and it states the gap it was written for: CAP derives the service from the model and leaves the communication between business logic, service and persistence to the developer.","flavors":["node"]},{"ref":"content:taking-cap-to-the-next-level-with-typescript","relevance":"The 2020 answer to the same question, by the author of cds-routing-handlers, from before the core had a TypeScript toolchain at all.","flavors":["node"]},{"ref":"content:how-we-could-potentially-have-nestjs-as-runtime-for-sap-cap","relevance":"Works through the runtime's own dispatch loop before mapping it onto another framework's controllers and providers, which is the mechanism every option here replaces; the author's own conclusion is that they do not plan to take it further.","flavors":["node"]},{"ref":"content:use-typescript-with-the-sap-cloud-application-programming-model","relevance":"Where a reader starts from: what TypeScript buys someone arriving from a typed language, ending on a worked example rather than on an arrangement.","flavors":["node"]}],"unknowns":["How cds-hyper-app-service declares a handler. Its npm page answered 403 on 2026-10-15 and the package declares no repository, so nothing was retrieved either way.","Which method names a cds-plugin-handlers handler class uses. The README states where the class goes and not what it contains; the reCAP 2023 posts it links were not read for this pass.","No reading was re-read for this assessment, so no record here states that a reading shows one of these options doing the outcome. The readings are attached for what they explain."],"assessed_at":"2026-10-15","drafted_with_ai":true,"omitted_contributions":0,"assessment_kind":"documented-contributions"}],"shared_tasks":[],"related_tasks":[],"options":[{"ref":"artifact:dxfrontier-cds-ts-dispatcher","name":"@dxfrontier/cds-ts-dispatcher","json":"/artifacts/dxfrontier-cds-ts-dispatcher.json","ecosystem":"cap","origin_ring":"independent"},{"ref":"artifact:dxfrontier-cds-ts-repository","name":"@dxfrontier/cds-ts-repository","json":"/artifacts/dxfrontier-cds-ts-repository.json","ecosystem":"cap","origin_ring":"independent"},{"ref":"artifact:mubuntu-cap-handler-framework","name":"cap-handler-framework","json":"/artifacts/mubuntu-cap-handler-framework.json","ecosystem":"cap","origin_ring":"independent"},{"ref":"artifact:soontao-cds-hyper-app-service","name":"cds-hyper-app-service","json":"/artifacts/soontao-cds-hyper-app-service.json","ecosystem":"cap","origin_ring":"independent"},{"ref":"artifact:lemaiwo-cds-plugin-handlers","name":"cds-plugin-handlers","json":"/artifacts/lemaiwo-cds-plugin-handlers.json","ecosystem":"cap","origin_ring":"independent"},{"ref":"artifact:pwasem-cds-plugin-typescript","name":"cds-plugin-typescript","json":"/artifacts/pwasem-cds-plugin-typescript.json","ecosystem":"cap","origin_ring":"independent"},{"ref":"artifact:mrbandler-cds-routing-handlers","name":"cds-routing-handlers","json":"/artifacts/mrbandler-cds-routing-handlers.json","ecosystem":"cap","origin_ring":"independent"},{"ref":"artifact:mrbandler-cds2types","name":"cds2types","json":"/artifacts/mrbandler-cds2types.json","ecosystem":"cap","origin_ring":"independent"}],"samples":[{"ref":"artifact:mikezaschka-cap-ui5-gpt-chat","name":"cap-ui5-gpt-chat","json":"/artifacts/mikezaschka-cap-ui5-gpt-chat.json"}],"explained_by":[{"ref":"content:using-cloud-sdk-for-ai-in-a-commonjs-cap-project","name":"How to use Cloud SDK for AI in an existing CAP NodeJS Project in CommonJS","json":"/content/using-cloud-sdk-for-ai-in-a-commonjs-cap-project.json"},{"ref":"content:dependency-injection-and-di-containers-on-cap-nodejs","name":"Quick Implementation of DI/DI Container on SAP CAP: Node.js Hands-on Part","json":"/content/dependency-injection-and-di-containers-on-cap-nodejs.json"},{"ref":"content:how-we-could-potentially-have-nestjs-as-runtime-for-sap-cap","name":"How we could potentially have nestjs as runtime for SAP CAP","json":"/content/how-we-could-potentially-have-nestjs-as-runtime-for-sap-cap.json"},{"ref":"content:use-typescript-with-the-sap-cloud-application-programming-model","name":"Use TypeScript with the SAP Cloud Application Programming Model","json":"/content/use-typescript-with-the-sap-cloud-application-programming-model.json"},{"ref":"content:sap-cap-controller-service-repository-architecture","name":"SAP CAP: Controller - Service - Repository architecture","json":"/content/sap-cap-controller-service-repository-architecture.json"},{"ref":"content:cds-ts-repository-simplify-cap-entity-persistence","name":"CDS-TS-Repository: Simplify SAP CAP Entity persistance with BaseRepository","json":"/content/cds-ts-repository-simplify-cap-entity-persistence.json"},{"ref":"content:cds-ts-dispatcher-simplifying-sap-cap-typescript-development","name":"CDS-TS-Dispatcher: Simplifying SAP CAP TypeScript Development","json":"/content/cds-ts-dispatcher-simplifying-sap-cap-typescript-development.json"},{"ref":"content:cap-typescript-fiori-typescript-and-live-reload-initial-setup","name":"CAP Typescript + Fiori Typescript + live reload Initial app setup","json":"/content/cap-typescript-fiori-typescript-and-live-reload-initial-setup.json"},{"ref":"content:full-stack-typescript-app-for-cloud-foundry-sample-repository","name":"Full Stack TypeScript App for Cloud Foundry - Sample Repository","json":"/content/full-stack-typescript-app-for-cloud-foundry-sample-repository.json"},{"ref":"content:taking-cap-to-the-next-level-with-typescript","name":"Taking CAP to the next level - with TypeScript","json":"/content/taking-cap-to-the-next-level-with-typescript.json"}],"normative_context":[]}}