typescriptintermediate

TypeScript Typed Event Emitter

Create type-safe event emitters in Node.js with full TypeScript support and autocomplete.

typescript
import { EventEmitter } from 'node:events';

// Define event map
interface OrderEvents {
  created: [order: { id: string; amount: number }];
  paid: [orderId: string, paymentId: string];
  shipped: [orderId: string, trackingNumber: string];
  cancelled: [orderId: string, reason: string];
  error: [error: Error];
}

// Typed event emitter
class TypedEmitter<T extends Record<string, unknown[]>> {
  private emitter = new EventEmitter();

  on<K extends keyof T>(event: K, listener: (...args: T[K]) => void) {
    this.emitter.on(event as string, listener as any);
    return this;
  }

  off<K extends keyof T>(event: K, listener: (...args: T[K]) => void) {
    this.emitter.off(event as string, listener as any);
    return this;
  }

  emit<K extends keyof T>(event: K, ...args: T[K]) {
    return this.emitter.emit(event as string, ...args);
  }

  once<K extends keyof T>(event: K, listener: (...args: T[K]) => void) {
    this.emitter.once(event as string, listener as any);
    return this;
  }
}

// Usage
const orderBus = new TypedEmitter<OrderEvents>();

orderBus.on('created', (order) => {
  console.log(`Order ${order.id} created: $${order.amount}`);
});

orderBus.on('paid', (orderId, paymentId) => {
  console.log(`Order ${orderId} paid via ${paymentId}`);
});

orderBus.on('error', (err) => {
  console.error('Order error:', err.message);
});

// Fully typed: autocomplete works on event names and args
orderBus.emit('created', { id: 'ord_123', amount: 99.99 });
orderBus.emit('paid', 'ord_123', 'pay_456');

Use Cases

  • Type-safe pub/sub communication between modules
  • Domain event handling in business logic
  • Decoupled service communication

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.