Skip to content

Getting started

Terminal window
pnpm add @cqrs-ddd/pipeline

The packages require Node.js 22.12 or later, or another runtime with AsyncLocalStorage (Bun, Deno). They are published as ES modules; a CommonJS application loads them with require().

A pipeline holds the behavior instances and the behaviors that apply to every operation. Create it once, in a module of its own:

pipeline.ts
import { createPipeline, LoggingBehavior, logging } from '@cqrs-ddd/pipeline';
export const pipeline = createPipeline({
behaviors: [new LoggingBehavior(console)],
globalBehaviors: { before: [logging({ requestResponseLogLevel: 'log' })] },
});

behaviors are the instances, one per behavior class; a behavior placed without an instance is constructed with no arguments. globalBehaviors places behaviors around every wrapped operation.

pipeline.wrap(options, ...entries) returns a wrapper. Applied to a function, it returns a new async function that runs the behaviors around it:

import { pipeline } from './pipeline.js';
export const getPrice = pipeline.wrap({ name: 'getPrice', kind: 'query' })(
async (sku: string) => prices.find(sku),
);
await getPrice('apple');

A plain function needs a name, which keys and logs use to tell operations apart, and a kind: a query reads, a command changes state, an event reacts. Behaviors use the kind to decide what applies; idempotency, for example, applies to commands.

The same wrapper decorates a method, with TypeScript’s standard decorators or with experimentalDecorators:

class Prices {
@pipeline.wrap({ kind: 'query' })
async find(sku: string) {
return this.store.get(sku);
}
}

A method is named Class.method (here Prices.find) unless the options name it.

Each concern is a package. Install it, give the pipeline an instance, and add its entry where it applies:

Terminal window
pnpm add @cqrs-ddd/pipeline-zod zod
import { validated } from '@cqrs-ddd/pipeline-zod';
import { z } from 'zod';
export const placeOrder = pipeline.wrap(
{ name: 'placeOrder', kind: 'command' },
validated(z.object({ sku: z.string(), qty: z.coerce.number().int().positive() })),
)(async (order) => orders.place(order));