Fully type-checked EventEmitter with zero runtime overhead
npx @tessl/cli install tessl/npm-tiny-typed-emitter@2.1.0Tiny Typed Emitter provides a fully type-checked EventEmitter wrapper with zero runtime overhead. It enables developers to define strongly-typed event interfaces and ensures compile-time type safety for event emission and listener registration while maintaining full compatibility with Node.js EventEmitter.
npm install tiny-typed-emitterimport { TypedEmitter } from "tiny-typed-emitter";For CommonJS:
const { TypedEmitter } = require("tiny-typed-emitter");For type definitions:
import { TypedEmitter, ListenerSignature, DefaultListener } from "tiny-typed-emitter";import { TypedEmitter } from "tiny-typed-emitter";
// Define event signatures
interface MyEvents {
'data': (payload: string, timestamp: number) => void;
'error': (error: Error) => void;
'complete': () => void;
}
// Create typed emitter
const emitter = new TypedEmitter<MyEvents>();
// Add listeners with full type safety
emitter.on('data', (payload, timestamp) => {
console.log(`Received: ${payload} at ${timestamp}`);
});
emitter.on('error', (error) => {
console.error('Error occurred:', error.message);
});
// Emit events with type checking
emitter.emit('data', 'Hello World', Date.now());
emitter.emit('error', new Error('Something went wrong'));
emitter.emit('complete');Tiny Typed Emitter is built around a single core concept:
A generic class that wraps Node.js EventEmitter with compile-time type safety for event names and listener signatures.
class TypedEmitter<L extends ListenerSignature<L> = DefaultListener> {
static defaultMaxListeners: number;
// Event listener management
addListener<U extends keyof L>(event: U, listener: L[U]): this;
prependListener<U extends keyof L>(event: U, listener: L[U]): this;
prependOnceListener<U extends keyof L>(event: U, listener: L[U]): this;
removeListener<U extends keyof L>(event: U, listener: L[U]): this;
removeAllListeners(event?: keyof L): this;
once<U extends keyof L>(event: U, listener: L[U]): this;
on<U extends keyof L>(event: U, listener: L[U]): this;
off<U extends keyof L>(event: U, listener: L[U]): this;
// Event emission
emit<U extends keyof L>(event: U, ...args: Parameters<L[U]>): boolean;
// Event introspection
eventNames<U extends keyof L>(): U[];
listenerCount(type: keyof L): number;
listeners<U extends keyof L>(type: U): L[U][];
rawListeners<U extends keyof L>(type: U): L[U][];
// Configuration
getMaxListeners(): number;
setMaxListeners(n: number): this;
}Usage Examples:
import { TypedEmitter } from "tiny-typed-emitter";
// Define events interface
interface FileWatcherEvents {
'file-changed': (filename: string, stats: { size: number; modified: Date }) => void;
'error': (error: Error) => void;
'started': () => void;
}
// Create typed emitter instance
const watcher = new TypedEmitter<FileWatcherEvents>();
// Type-safe listener registration
watcher.on('file-changed', (filename, stats) => {
// TypeScript knows filename is string and stats has size/modified properties
console.log(`File ${filename} changed. Size: ${stats.size}, Modified: ${stats.modified}`);
});
// Type-safe event emission
watcher.emit('file-changed', 'config.json', {
size: 1024,
modified: new Date()
});
// Class extension example
class FileWatcher extends TypedEmitter<FileWatcherEvents> {
constructor() {
super();
}
start() {
this.emit('started');
}
}Methods for adding, removing, and managing event listeners with full type safety.
// Add listener to end of listeners array
addListener<U extends keyof L>(event: U, listener: L[U]): this;
// Add listener to beginning of listeners array
prependListener<U extends keyof L>(event: U, listener: L[U]): this;
// Add one-time listener to beginning of listeners array
prependOnceListener<U extends keyof L>(event: U, listener: L[U]): this;
// Remove specific listener
removeListener<U extends keyof L>(event: U, listener: L[U]): this;
// Remove all listeners for event (or all events if no event specified)
removeAllListeners(event?: keyof L): this;
// Add one-time listener
once<U extends keyof L>(event: U, listener: L[U]): this;
// Add listener (alias for addListener)
on<U extends keyof L>(event: U, listener: L[U]): this;
// Remove listener (alias for removeListener)
off<U extends keyof L>(event: U, listener: L[U]): this;Emit events with type-safe arguments based on the event signature.
/**
* Emit an event with type-safe arguments
* @param event - Event name (must be key of L)
* @param args - Arguments matching the event's listener signature
* @returns true if event had listeners, false otherwise
*/
emit<U extends keyof L>(event: U, ...args: Parameters<L[U]>): boolean;Methods for inspecting current listeners and event state.
// Get array of registered event names
eventNames<U extends keyof L>(): U[];
// Get count of listeners for specific event
listenerCount(type: keyof L): number;
// Get copy of listeners array for event
listeners<U extends keyof L>(type: U): L[U][];
// Get copy of listeners array including wrappers
rawListeners<U extends keyof L>(type: U): L[U][];Methods for configuring the emitter's behavior.
// Get current maximum listeners limit
getMaxListeners(): number;
// Set maximum number of listeners (returns this for chaining)
setMaxListeners(n: number): this;
// Static property for default maximum listeners
static defaultMaxListeners: number;// Maps event names to their listener function signatures
type ListenerSignature<L> = {
[E in keyof L]: (...args: any[]) => any;
};
// Default listener signature for dynamic event names
type DefaultListener = {
[k: string]: (...args: any[]) => any;
};Use generic interfaces for reusable event patterns:
import { TypedEmitter, ListenerSignature } from "tiny-typed-emitter";
interface DataEvents<T> {
'data': (item: T) => void;
'batch': (items: T[]) => void;
'complete': () => void;
}
class DataProcessor<T> extends TypedEmitter<DataEvents<T>> {
process(item: T) {
this.emit('data', item);
}
}
// Usage with specific type
const stringProcessor = new DataProcessor<string>();
stringProcessor.on('data', (str) => console.log(str.toUpperCase()));Create compatible subclasses that introduce different events:
import { TypedEmitter, ListenerSignature } from "tiny-typed-emitter";
class Animal<E extends ListenerSignature<E> = {}> extends TypedEmitter<{spawn: () => void} & E> {
constructor() {
super();
}
}
class Frog extends Animal<{jump: () => void}> {
jump() {
this.emit('jump');
}
}
class Bird extends Animal<{fly: () => void}> {
fly() {
this.emit('fly');
}
}
// Compatible usage
const animals: Animal[] = [new Frog(), new Bird()];
animals.forEach(animal => {
animal.on('spawn', () => console.log('Animal spawned'));
});