npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ts-micro-mediator

v2.0.0

Published

Edge-optimized Mediator Pattern for Cloudflare Workers, Node.js, Bun, and Deno with CQRS support

Readme

ts-micro-mediator

npm version npm bundle size License: MIT TypeScript

A lightweight, edge-optimized Mediator Pattern implementation for TypeScript. Designed for Cloudflare Workers, Node.js, Bun, Deno, and serverless/edge environments.

Features

  • Edge-optimized, minimal memory footprint
  • CQRS: Command, Query, Notification separation
  • TypeScript-first, fully type-safe
  • Framework-agnostic (Hono, Express, Fastify, Elysia, ...)
  • Optimized tree shaking with subpath imports
  • Multiple bundle sizes for different use cases
  • Only 4.3KB minified (full), 1.2KB (registry only)
  • 84.2KB unpacked (37% smaller than mediatr-ts, 45% smaller than @nestjs/cqrs)

Installation

npm install ts-micro-mediator

Tree Shaking & Bundle Optimization

This library is optimized for tree shaking with multiple entry points:

Bundle Sizes (Minified)

  • Full Library: ts-micro-mediator - 4.3KB
  • Lite Version: ts-micro-mediator/lite - 3.5KB (essential features only)
  • Core Only: ts-micro-mediator/core - 2.9KB (Mediator + Registry classes)
  • Registry Only: ts-micro-mediator/registry - 1.2KB (handler management)
  • Middleware Only: ts-micro-mediator/middleware - 3.5KB (framework integration)
  • Advanced Only: ts-micro-mediator/advanced - 3.5KB (batch operations)

Import Strategies

Full Library (Convenience):

import { Mediator, sendCommand, registerCommandHandler } from 'ts-micro-mediator'

Lite Version (Essential Features):

import { Mediator, sendCommand, registerCommandHandler } from 'ts-micro-mediator/lite'

Specific Modules (Optimal Tree Shaking):

// Only core classes
import { Mediator, Registry } from 'ts-micro-mediator/core'

// Only registry management
import { registerCommandHandler, registerQueryHandler } from 'ts-micro-mediator/registry'

// Only middleware functions
import { sendCommand, sendQuery, mediatorMiddleware } from 'ts-micro-mediator/middleware'

// Only advanced features
import { sendBatch, createRequestFromData } from 'ts-micro-mediator/advanced'

Bundler Optimization: Modern bundlers (Webpack, Rollup, Vite) automatically tree-shake unused code even with root imports:

import { sendCommand } from 'ts-micro-mediator' // Only includes what you use

Why CQRS Pattern?

This library uses the CQRS (Command Query Responsibility Segregation) pattern:

  • Commands � Write operations (Create, Update, Delete)
  • Queries � Read operations (Get, List, Search)
  • Notifications � Events (UserCreated, OrderPlaced)

Benefits:

  • Fully type-safe - No type checking errors when registering handlers
  • Clear separation - Easy to understand what modifies data vs what reads data
  • Better IDE support - Enhanced autocomplete and type inference
  • Maintainable - Scales well as your application grows

Quick Start

1. Define Query (Read Operation)

import { IQuery, QueryHandler, registerQueryHandler, sendQuery } from 'ts-micro-mediator';
import { ok } from 'ts-micro-result';

interface User { id: string; name: string; }

class GetUserQuery implements IQuery<User> {
  constructor(public userId: string) {}
}

const getUserHandler: QueryHandler<GetUserQuery, User> = async (query) => {
  const user = { id: query.userId, name: 'John Doe' };
  return ok(user);
};

registerQueryHandler('GetUserQuery', getUserHandler);

// Execute
const result = await sendQuery(new GetUserQuery('123'));
if (result.isOk()) {
  console.log(result.data); // { id: '123', name: 'John Doe' }
}

2. Define Command (Write Operation)

import { ICommand, CommandHandler, registerCommandHandler, sendCommand } from 'ts-micro-mediator';
import { ok } from 'ts-micro-result';

class CreateUserCommand implements ICommand<User> {
  constructor(public name: string, public email: string) {}
}

const createUserHandler: CommandHandler<CreateUserCommand, User> = async (cmd) => {
  const user = { id: 'new-id', name: cmd.name, email: cmd.email };
  return ok(user);
};

registerCommandHandler('CreateUserCommand', createUserHandler);

// Execute
const result = await sendCommand(new CreateUserCommand('Jane', '[email protected]'));

3. Define Notification (Event)

import { INotification, registerNotificationHandler, publishNotification } from 'ts-micro-mediator';

class UserCreatedNotification implements INotification {
  constructor(public user: User) {}
}

registerNotificationHandler('UserCreatedNotification', async (notification) => {
  console.log('User created:', notification.user);
  // Send email, update cache, etc.
});

// Publish
await publishNotification(new UserCreatedNotification({ id: '1', name: 'John' }));

Advanced Usage

Generic Way (Without CQRS)

If you prefer a simpler approach without separating Commands and Queries:

import { registerHandler, sendRequest } from 'ts-micro-mediator';

// Register
registerHandler('GetUserQuery', getUserHandler);
registerHandler('CreateUserCommand', createUserHandler);

// Execute
const result = await sendRequest(new GetUserQuery('123'));
const createResult = await sendRequest(new CreateUserCommand('Jane', '[email protected]'));

Direct Mediator Instance

For advanced scenarios (testing, dependency injection):

import { Mediator, Registry } from 'ts-micro-mediator';

const registry = new Registry();
const mediator = new Mediator(registry);

// Register
registry.registerCommandHandler('CreateUserCommand', createUserHandler);
registry.registerQueryHandler('GetUserQuery', getUserHandler);

// Execute
const result = await mediator.sendCommand(new CreateUserCommand('John', '[email protected]'));

Batch Operations

import { sendCommandBatch, sendQueryBatch, publishBatch } from 'ts-micro-mediator';

// Execute multiple commands
const commands = [
  new CreateUserCommand('John', '[email protected]'),
  new CreateUserCommand('Jane', '[email protected]')
];
const results = await sendCommandBatch(commands);

// Execute multiple queries
const queries = [
  new GetUserQuery('123'),
  new GetUserQuery('456')
];
const queryResults = await sendQueryBatch(queries);

// Publish multiple notifications
const notifications = [
  new UserCreatedNotification(user1),
  new UserCreatedNotification(user2)
];
await publishBatch(notifications);

Error Handling

Important: Handlers can throw exceptions

Starting from v2.0.0, the mediator no longer catches exceptions thrown by handlers. You should handle errors in your own code:

// Option 1: Handle errors in the handler itself (recommended)
const createUserHandler: CommandHandler<CreateUserCommand, User> = async (cmd) => {
  try {
    const user = await database.createUser(cmd.name, cmd.email);
    return ok(user);
  } catch (error) {
    return err({ code: 'DATABASE_ERROR', message: error.message });
  }
};

// Option 2: Wrap the mediator call with try-catch
try {
  const result = await sendCommand(new CreateUserCommand('John', '[email protected]'));
  if (result.isOk()) {
    console.log('Success:', result.data);
  } else {
    console.error('Error:', result.errors[0]);
  }
} catch (error) {
  console.error('Unhandled error in handler:', error);
}

Auto-Registration

Check out examples/generate-handlers.js for a script that automatically registers handlers based on file naming conventions (*.command.ts, *.query.ts, *.handler.ts).


Framework Integration

Hono (Cloudflare Workers)

import { Hono } from 'hono';
import { sendQuery, sendCommand } from 'ts-micro-mediator';

const app = new Hono();

app.get('/users/:id', async (c) => {
  const result = await sendQuery(new GetUserQuery(c.req.param('id')));
  return result.isOk()
    ? ctx.json(result.data)
    : ctx.json({ errors: result.errors }, result.status ?? 400);
});

app.post('/users', async (c) => {
  const body = await c.req.json();
  const result = await sendCommand(new CreateUserCommand(body.name, body.email));
  return result.isOk()
    ? c.json(result.data, result.status ?? 201)
    : c.json({ errors: result.errors }, result.status ?? 400);
});

export default app;

Express.js

import express from 'express';
import { sendQuery, sendCommand } from 'ts-micro-mediator';

const app = express();
app.use(express.json());

app.get('/users/:id', async (req, res) => {
  const result = await sendQuery(new GetUserQuery(req.params.id));
  if (result.isOk()) {
    res.json(result.data);
  } else {
    res.status(result.status ?? 400).json({ errors: result.errors });
  }
});

app.post('/users', async (req, res) => {
  const result = await sendCommand(new CreateUserCommand(req.body.name, req.body.email));
  if (result.isOk()) {
    res.status(result.status ?? 201).json(result.data);
  } else {
    res.status(result.status ?? 400).json({ errors: result.errors });
  }
});

app.listen(3000);

Optional Middleware

import { mediatorMiddleware, attachMediator } from 'ts-micro-mediator';

// Hono
app.use('*', mediatorMiddleware());

// Express
app.use(mediatorMiddleware());

// Access mediator from context
app.get('/users/:id', async (c) => {
  const ctx = attachMediator(c);
  const result = await ctx.mediator.sendQuery(new GetUserQuery(ctx.req.param('id')));
  return result.isOk()
    ? ctx.json(result.data)
    : ctx.json({ errors: result.errors }, result.status ?? 400);
});

Use attachMediator when you need typed access to the mediator on custom context objects (workers, Express requests, etc.). It only assigns the shared mediator when the property is missing, keeping existing context decorations intact. If you just need the singleton without mutating context, call resolveMediator().


API Reference

Interfaces

// Base interfaces
interface ICommand<TResponse = void> { }
interface IQuery<TResponse> { }
interface INotification { }
interface IMediator { }
interface IRegistry { }

// Stats
interface RegistryStats {
  requestHandlers: number;
  notificationHandlers: number;
  requestClasses: number;
  notificationClasses: number;
}

Handler Types

// CQRS Pattern (Type-safe)
type CommandHandler<TCommand extends ICommand<TResponse>, TResponse = void> = 
  (command: TCommand) => Promise<Result<TResponse>>;

type QueryHandler<TQuery extends IQuery<TResponse>, TResponse = void> = 
  (query: TQuery) => Promise<Result<TResponse>>;

// Generic (Backward compatibility)
type RequestHandler<TRequest extends IRequest<TResponse>, TResponse = void> = 
  (request: TRequest) => Promise<Result<TResponse>>;

// Notifications
type NotificationHandler<TNotification extends INotification> = 
  (notification: TNotification) => Promise<void>;

Registration Functions

CQRS Pattern:

// Type-safe registration
registerCommandHandler<TCommand, TResponse>(
  type: string, 
  handler: CommandHandler<TCommand, TResponse>
): void;

registerQueryHandler<TQuery, TResponse>(
  type: string, 
  handler: QueryHandler<TQuery, TResponse>
): void;

Generic Way:

// Generic registration
registerHandler<TResponse>(
  type: string, 
  handler: RequestHandler<IRequest<TResponse>, TResponse>
): void;

Notifications:

registerNotificationHandler(
  type: string, 
  handler: NotificationHandler<INotification>
): void;

Class Registration:

registerRequestClass(requestClass: RequestClassConstructor): void;
registerNotificationClass(notificationClass: NotificationClassConstructor): void;

Execution Functions

CQRS Pattern:

sendCommand<TResponse>(command: ICommand<TResponse>): Promise<Result<TResponse>>;
sendQuery<TResponse>(query: IQuery<TResponse>): Promise<Result<TResponse>>;

Generic Way:

sendRequest<TResponse>(request: IRequest<TResponse>): Promise<Result<TResponse>>;

Notifications:

publishNotification<TNotification extends INotification>(
  notification: TNotification
): Promise<void>;

Batch Operations:

sendCommandBatch<TResponse>(commands: ICommand<TResponse>[]): Promise<Result<TResponse>[]>;
sendQueryBatch<TResponse>(queries: IQuery<TResponse>[]): Promise<Result<TResponse>[]>;
sendBatch<TResponse>(requests: IRequest<TResponse>[]): Promise<Result<TResponse>[]>;
publishBatch<TNotification extends INotification>(notifications: TNotification[]): Promise<void>;

Utility Functions:

getMediatorStats(): RegistryStats;
getRegistryStats(): RegistryStats;
resetRegistry(): void;
mediatorMiddleware(): MiddlewareFunction;
attachMediator<TContext extends Record<string, unknown>>(ctx: TContext): WithMediator<TContext>;
resolveMediator(): IMediator;
isResult(value: unknown): value is Result<unknown>;

Factory & Creation:

createRequestFromData<T extends IRequest<any>>(type: string, data: any): T | null;
createNotificationFromData<T extends INotification>(type: string, data: any): T | null;

Batch Registration:

registerBatch(registrations: {
  handlers?: Array<{ type: string; handler: RequestHandler<any, any> }>;
  notificationHandlers?: Array<{ type: string; handler: NotificationHandler<any> }>;
  requestClasses?: RequestClassConstructor[];
  notificationClasses?: NotificationClassConstructor[];
}): void;

Mediator Class

class Mediator implements IMediator {
  constructor(registry: Registry);
  
  // CQRS Pattern
  sendCommand<TResponse>(command: ICommand<TResponse>): Promise<Result<TResponse>>;
  sendQuery<TResponse>(query: IQuery<TResponse>): Promise<Result<TResponse>>;
  
  // Generic
  send<TResponse>(request: IRequest<TResponse>): Promise<Result<TResponse>>;
  
  // Notifications
  publish<TNotification extends INotification>(notification: TNotification): Promise<void>;
  
  // Batch operations
  sendCommandBatch<TResponse>(commands: ICommand<TResponse>[]): Promise<Result<TResponse>[]>;
  sendQueryBatch<TResponse>(queries: IQuery<TResponse>[]): Promise<Result<TResponse>[]>;
  sendBatch<TResponse>(requests: IRequest<TResponse>[]): Promise<Result<TResponse>[]>;
  publishBatch<TNotification extends INotification>(notifications: TNotification[]): Promise<void>;
  
  // Utility
  getStats(): RegistryStats;
  get registry(): IRegistry;
}

Registry Class

class Registry implements IRegistry {
  // CQRS Pattern
  registerCommandHandler<TCommand, TResponse>(type: string, handler: CommandHandler<TCommand, TResponse>): void;
  registerQueryHandler<TQuery, TResponse>(type: string, handler: QueryHandler<TQuery, TResponse>): void;
  
  // Generic
  registerHandler<TResponse>(type: string, handler: RequestHandler<IRequest<TResponse>, TResponse>): void;
  
  // Notifications
  registerNotificationHandler(type: string, handler: NotificationHandler<INotification>): void;
  
  // Retrieval
  getHandler(type: string): RequestHandler<any, any> | undefined;
  getNotificationHandlers(type: string): NotificationHandler<any>[];
  
  // Class registration
  registerRequestClass(requestClass: RequestClassConstructor): void;
  registerNotificationClass(notificationClass: NotificationClassConstructor): void;
  
  // Factory
  createRequest(type: string, data: any): IRequest<any> | null;
  createNotification(type: string, data: any): INotification | null;
  
  // Utility
  reset(): void;
  getStats(): RegistryStats;
}

MediatorFactory

class MediatorFactory {
  static create(): Mediator;
  static createWithRegistry(registry: Registry): Mediator;
  static reset(): void;
  static getRegistry(): Registry | null;
}

Performance & Bundle Analysis

Bundle Size Comparison

| Entry Point | Minified Size | Use Case | |-------------|---------------|----------| | ts-micro-mediator | 4.3KB | Full library with all features | | ts-micro-mediator/lite | 3.5KB | Essential features for most apps | | ts-micro-mediator/core | 2.9KB | Just Mediator and Registry classes | | ts-micro-mediator/registry | 1.2KB | Handler management only | | ts-micro-mediator/middleware | 3.5KB | Framework integration | | ts-micro-mediator/advanced | 3.5KB | Batch operations and utilities |

Package Size Comparison

| Package | Unpacked Size | Package Size | |---------|---------------|--------------| | ts-micro-mediator | 84.2KB ✅ | 11.5KB ✅ | | mediatr-ts | 133.9KB | - | | @nestjs/cqrs | 154.8KB | - |

Tree Shaking Benefits

  • Automatic Optimization: Modern bundlers remove unused code
  • Subpath Imports: Import only what you need
  • Zero Runtime Overhead: No unused code in final bundle
  • Faster Loading: Smaller bundles = better performance

Bundle Analysis

Run npm run analyze to generate a visual bundle analysis showing:

  • Bundle composition
  • Tree shaking effectiveness
  • Gzip/Brotli compression ratios
  • Dependency breakdown

Framework Compatibility

  • Cloudflare Workers (Hono, native)
  • Node.js (Express, Fastify, Koa)
  • Bun (Elysia, native)
  • Deno (Oak, native)
  • Any HTTP framework

License

MIT — see LICENSE

Links