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

  1. TypeScript Guide
    1. Request Lifecycle
    2. Quick Start
      1. 1. Define messages
      2. 2. Define handlers
      3. 3. Wire the mediator
      4. 4. Add middleware
    3. Streaming
    4. Code Generation
    5. Full API Reference

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.