Schmooky
← Back to Prototypes

Type-Safe Event System

A TypeScript event emitter that provides complete type safety for event names and payloads at compile time.
Library
TypeScript Events Type Safety
Year
2024
Category
Library
Type
Prototype

Type-Safe Event System

A TypeScript event emitter that eliminates runtime errors by providing complete compile-time type safety for event names and their associated payloads.

The Problem

Traditional event systems in JavaScript are stringly-typed and error-prone:

// Runtime errors waiting to happen
emitter.on('user:created', (data) => {
  console.log(data.userId); // What if it's 'id' instead?
});

emitter.emit('user:crated', userData); // Typo in event name

The Solution

A fully type-safe event system that catches these errors at compile time:

type EventMap = {
  'user:created': { userId: string; email: string; timestamp: number };
  'user:updated': { userId: string; changes: Partial<User> };
  'user:deleted': { userId: string };
};

const emitter = new TypedEventEmitter<EventMap>();

// ✅ Type-safe registration
emitter.on('user:created', (data) => {
  console.log(data.userId); // TypeScript knows the shape
});

// ❌ Compile error: 'user:crated' is not assignable to event name
emitter.emit('user:crated', userData);

// ❌ Compile error: payload doesn't match expected shape
emitter.emit('user:created', { id: '123' });

Implementation

The core uses TypeScript's advanced type features:

type EventHandler<T> = (payload: T) => void | Promise<void>;

class TypedEventEmitter<Events extends Record<string, any>> {
  private handlers = new Map<keyof Events, Set<EventHandler<any>>>();

  on<K extends keyof Events>(
    event: K,
    handler: EventHandler<Events[K]>
  ): () => void {
    // Implementation with type safety
  }

  emit<K extends keyof Events>(
    event: K,
    payload: Events[K]
  ): Promise<void> {
    // Type-safe emission
  }
}

Key Features

  • Compile-time safety for event names and payloads
  • Auto-completion in IDEs for events and payload properties
  • Zero runtime overhead compared to regular event emitters
  • Promise support for async event handlers
  • Unsubscribe helpers with proper cleanup

Usage Examples

Game Events

type GameEvents = {
  'player:move': { x: number; y: number; playerId: string };
  'enemy:spawn': { enemyType: EnemyType; position: Vector2 };
  'game:over': { score: number; duration: number };
};

const gameEmitter = new TypedEventEmitter<GameEvents>();

gameEmitter.on('player:move', ({ x, y, playerId }) => {
  // All properties are properly typed
  updatePlayerPosition(playerId, { x, y });
});

React Integration

const useGameEvents = () => {
  useEffect(() => {
    const unsubscribe = gameEmitter.on('game:over', ({ score }) => {
      setFinalScore(score);
    });
    
    return unsubscribe; // Proper cleanup
  }, []);
};

This prototype demonstrates how TypeScript's type system can eliminate entire classes of runtime errors while maintaining familiar APIs.

← Back to All Prototypes