Type-Safe Event System
A TypeScript event emitter that provides complete type safety for event names and payloads at compile time.
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.