typescriptadvanced

Typed Event Emitter Class

Build a type-safe event emitter with TypeScript generics for strongly-typed event handling.

typescript
import { EventEmitter } from 'events';

// Type-safe event emitter wrapper
class TypedEmitter<Events extends Record<string, any[]>> {
  private emitter = new EventEmitter();

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

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

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

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

  removeAllListeners<K extends keyof Events & string>(event?: K): this {
    this.emitter.removeAllListeners(event);
    return this;
  }

  listenerCount<K extends keyof Events & string>(event: K): number {
    return this.emitter.listenerCount(event);
  }
}

// Define event types
type ServerEvents = {
  connection: [clientId: string, address: string];
  message: [clientId: string, data: Buffer];
  disconnect: [clientId: string, reason: string];
  error: [error: Error];
};

// Use typed emitter
const server = new TypedEmitter<ServerEvents>();

server.on('connection', (clientId, address) => {
  // TypeScript knows: clientId is string, address is string
  console.log(`Connected: ${clientId} from ${address}`);
});

server.on('message', (clientId, data) => {
  // TypeScript knows: data is Buffer
  console.log(`Message from ${clientId}: ${data.toString()}`);
});

server.on('disconnect', (clientId, reason) => {
  console.log(`Disconnected: ${clientId} (${reason})`);
});

server.on('error', (error) => {
  console.error('Error:', error.message);
});

// Emit events (type-checked!)
server.emit('connection', 'client-1', '192.168.1.1');
server.emit('message', 'client-1', Buffer.from('hello'));
server.emit('disconnect', 'client-1', 'timeout');

// This would cause a TypeScript error:
// server.emit('connection', 123);  // Error: number not assignable to string
// server.emit('unknown', 'data');  // Error: 'unknown' not in ServerEvents

// Application-level events
type AppEvents = {
  'user:created': [user: { id: string; email: string }];
  'user:deleted': [userId: string];
  'order:placed': [orderId: string, total: number];
  'cache:invalidated': [keys: string[]];
};

const app = new TypedEmitter<AppEvents>();

app.on('user:created', (user) => {
  console.log(`New user: ${user.email}`);
});

app.on('order:placed', (orderId, total) => {
  console.log(`Order ${orderId}: $${total}`);
});

app.emit('user:created', { id: '1', email: 'alice@test.com' });
app.emit('order:placed', 'ord-123', 99.99);

console.log('\nListeners:', app.listenerCount('user:created'));

Use Cases

  • Type-safe event-driven architecture
  • Application event bus
  • Domain event handling

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.