TypeScript Guide
@slck/mediator is a TypeScript-first mediator kernel for Node.js. It is fully typed, tree-shakeable, and has zero mandatory runtime dependencies.
Table of contents
Request Lifecycle
sequenceDiagram
participant C as Client
participant M as RuntimeMediator
participant P as Middleware Pipeline
participant H as Handler
C->>M: send(CreateOrderCommand)
M->>P: Logging, Validation, Retry, Timeout
P->>H: handle(cmd, ctx)
H-->>P: orderId
P-->>M: orderId
M-->>C: orderId
Quick Start
npm install @slck/mediator
1. Define messages
Extend RequestBase<TResponse> for commands and queries. Extend NotificationBase for domain events dispatched to multiple handlers.
import { RequestBase, NotificationBase } from '@slck/mediator';
class CreateOrderCommand extends RequestBase<{ orderId: string }> {
constructor(
public readonly userId: string,
public readonly amount: number,
) {
super();
}
}
class OrderCreatedEvent extends NotificationBase {
constructor(public readonly orderId: string) {
super();
}
}
2. Define handlers
import type { RequestHandler, NotificationHandler, RequestContext } from '@slck/mediator';
class CreateOrderHandler implements RequestHandler<CreateOrderCommand, { orderId: string }> {
async handle(cmd: CreateOrderCommand, ctx: RequestContext): Promise<{ orderId: string }> {
const orderId = crypto.randomUUID();
ctx.logger.info('order.created', { orderId, userId: cmd.userId });
await ctx.mediator.publish(new OrderCreatedEvent(orderId));
return { orderId };
}
}
class OrderAuditHandler implements NotificationHandler<OrderCreatedEvent> {
async handle(event: OrderCreatedEvent, ctx: RequestContext): Promise<void> {
ctx.logger.info('audit.order_created', { orderId: event.orderId });
}
}
3. Wire the mediator
import { RuntimeMediator, ConsoleLogger, InMemoryMetrics, InMemoryTracer } from '@slck/mediator';
const logger = new ConsoleLogger();
const metrics = new InMemoryMetrics();
const tracer = new InMemoryTracer('my-service', logger, metrics);
const m = new RuntimeMediator({ logger, metrics, tracer });
m.registerRequestHandler(CreateOrderCommand, new CreateOrderHandler());
m.registerNotificationHandler(OrderCreatedEvent, new OrderAuditHandler());
const { orderId } = await m.send(new CreateOrderCommand('user-1', 99.99));
4. Add middleware
Register middleware in the order you want them applied (outermost first):
import {
LoggingMiddleware,
ValidationMiddleware,
RetryMiddleware,
TimeoutMiddleware,
} from '@slck/mediator';
m.registerPipeline(new LoggingMiddleware(logger));
m.registerPipeline(new ValidationMiddleware());
m.registerPipeline(new RetryMiddleware({ maxAttempts: 3, delayMs: 100 }));
m.registerPipeline(new TimeoutMiddleware(5_000));
Streaming
Use StreamRequestBase<TItem> for requests that produce a sequence of values. The handler is an async generator; the caller receives an AsyncIterable.
import { StreamRequestBase } from '@slck/mediator';
import type { StreamHandler, RequestContext } from '@slck/mediator';
class PriceTickerQuery extends StreamRequestBase<number> {
constructor(public readonly symbol: string) { super(); }
}
class PriceTickerHandler implements StreamHandler<PriceTickerQuery, number> {
async *handle(req: PriceTickerQuery, ctx: RequestContext): AsyncIterable<number> {
for (let i = 0; i < 5; i++) {
yield Math.random() * 100;
await new Promise(r => setTimeout(r, 200));
}
}
}
m.registerStreamHandler(PriceTickerQuery, new PriceTickerHandler());
for await (const price of m.stream(new PriceTickerQuery('AAPL'))) {
console.log(price);
}
Code Generation
The build-time generator scans your handler files and emits a GeneratedMediator class with direct if/instanceof dispatch, eliminating Map lookups and enabling dead-code elimination.
flowchart LR
HF["Handler files"] -->|scan| GEN["generate-mediator.cjs"]
GEN -->|emit| GM["GeneratedMediator"]
GM -->|"direct instanceof dispatch"| H[Handler]
style GM fill:#fff9c4
Add the generate script to package.json:
{
"scripts": {
"generate": "node tools/generate-mediator.cjs"
}
}
Run before building:
npm run generate
npm run build
Never edit the generated file manually. Re-run the generator whenever you add, rename, or remove a handler or message type. The generator emits build-time diagnostics for duplicate handlers, missing handlers, and invalid signatures.
Full API Reference
See the Developer Guide for the complete API including outbox, inbox, saga, event bus, and all middleware options.