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

SAP SHIPS

No core answer is documented.

capire documents TypeScript support for the Node.js runtime, which is the flavor this concern exists in.

SourceTypeScript — capirechecked

Artifacts8

8 of 8 artifacts
ArtifactSignals
Build tools1
cds-plugin-typescriptCDS build task compiling TypeScript sources during cds build
Signals
none
CDS plugins2
cap-handler-frameworkConvention-based handler layer for CAP with draft lifecycle hooks
Signals
none
cds-plugin-handlersOne handler file per entity in CAP services
Signals
none
CLI tools1
cds2typesCLI generating TypeScript interfaces from a CDS model
Signals
none
Packages4
@dxfrontier/cds-ts-dispatcherDecorator-driven handler registration for CAP in TypeScript
Signals
none
@dxfrontier/cds-ts-repositoryTyped BaseRepository over CAP entities
Signals
none
cds-hyper-app-serviceTyped class-based service layer over CAP ApplicationService
Signals
Functional boundary
cds-routing-handlersDecorator-based routing of CAP service handlers in TypeScript
Signals
Quiet

Tasks3Beta

CoversCovers, under conditionsAssessed, not knownNot assessed
Handle a draft's own events apart from the active entity's1 covers, 1 under conditions, 1 not known

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.

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

Open artifact →

Covers, under conditions@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.

Open artifact →

Assessed, not knowncds-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.

Open artifact →

Keep data access out of a CAP service's handlers3 covers

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.

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

Open artifact →

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

Open artifact →

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

Open artifact →

Split one CAP service implementation into a class per entity3 covers, 1 under conditions, 1 not known

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.

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

Open artifact →

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

Open artifact →

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

Open artifact →

Covers, under conditionscds-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.

Open artifact →

Assessed, not knowncds-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.

Open artifact →

Content4

Group by
Piece, what it offers
SAP CAP: Controller - Service - Repository architecture
SAP Community, Daniel Dragolea, BlogThe reasoningRunnable code
New majors
CDS-TS-Repository: Simplify SAP CAP Entity persistance with BaseRepository
SAP Community, Daniel Dragolea, BlogThe reasoningRunnable code
New majors
CDS-TS-Dispatcher: Simplifying SAP CAP TypeScript Development
SAP Community, Daniel Dragolea, BlogThe reasoningRunnable code
New majors
Taking CAP to the next level - with TypeScript
SAP Community, mrbandler, BlogThe reasoningRunnable code
Gone quiet
SAP CAP: Controller - Service - Repository architecture
SAP Community, Daniel Dragolea, BlogThe reasoningRunnable code
New majors
CDS-TS-Dispatcher: Simplifying SAP CAP TypeScript Development
SAP Community, Daniel Dragolea, BlogThe reasoningRunnable code
New majors
SAP CAP: Controller - Service - Repository architecture
SAP Community, Daniel Dragolea, BlogThe reasoningRunnable code
New majors
CDS-TS-Repository: Simplify SAP CAP Entity persistance with BaseRepository
SAP Community, Daniel Dragolea, BlogThe reasoningRunnable code
New majors
Taking CAP to the next level - with TypeScript
SAP Community, mrbandler, BlogThe reasoningRunnable code
Gone quiet
Taking CAP to the next level - with TypeScript
SAP Community, mrbandler, BlogThe reasoningRunnable code
Gone quiet

Seen working1

ArtifactSignals
Samples1
cap-ui5-gpt-chatChat app on CAP and SAPUI5 against the OpenAI API
Functional boundary

Details

Coverage
statecommunity only, CAP, Node.js
assessed
SourceTypeScript — capirechecked
Comparison properties4

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.

How handlers bindLayers it namesDependency injectionDraft events
For your agent
Ref
capability:typescript-patterns
JSON
/capabilities/typescript-patterns.json
MCP
Connecting an agent