TypeScript patterns
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.
Showing artifacts, examples and content with confirmed Node.js applicability. View All CAP
Overview
No core answer is documented.
capire documents TypeScript support for the Node.js runtime, which is the flavor this concern exists in.
Artifacts
Tasks
Handle a draft's own events apart from the active entity's
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.
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.
cap-handler-frameworkGives 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.
@dxfrontier/cds-ts-dispatcherCarries a second set of decorators for the draft version of an entity's events beside the active-entity ones, so a method is marked as a draft handler where it is written rather than registered against a drafts entity by hand.
Conditions: The entity is annotated @odata.draft.enabled: true
Remaining work: The method bodies are the application's. The draft decorators are offered for an entity whose model says drafts are enabled; the README frames them that way rather than as a general set.
cds-routing-handlersThe 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.
Keep data access out of a CAP service's handlers
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.
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.
@dxfrontier/cds-ts-dispatcherNames 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.
@dxfrontier/cds-ts-repositoryA 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.
cds-plugin-handlersAnswers 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.
Split one CAP service implementation into a class per entity
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.
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.
@dxfrontier/cds-ts-dispatcherA 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.
cap-handler-frameworkA 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.
cds-plugin-handlersWhere 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.
cds-routing-handlers@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.
Conditions: reflect-metadata is installed and imported before the package is used emitDecoratorMetadata and experimentalDecorators are set in the project's tsconfig.json
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.
cds-hyper-app-serviceThe 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.
Content
Seen working
It calls the OpenAI API directly with a key in the environment. It was written in early 2023 and last touched in May 2023, which is before SAP AI Core and the CAP LLM plugins existed — so read it for the shape of the application, not for how a CAP service should reach a model today.
Details
Coverage
Comparison properties
These are the questions used to compare what the artifacts do. Values come from their documentation and may apply to a particular ecosystem or runtime. Each recorded value in the comparison opens its statement and sources.
For your agent
- Ref
capability:typescript-patterns- JSON
- /capabilities/typescript-patterns.json
- MCP
- Connecting an agent