The powerhouse of your events
A tiny, fully-typed event emitter for TypeScript with built-in error handling. Zero dependencies, under 2KB.
- ✅ Full type safety — Event names and payloads checked at compile time
- ✅ Tiny — Under 2KB minified
- ✅ Zero dependencies
- ✅ Universal — Works in browser and Node.js
- ✅ Async support —
emitAsyncawaits all handlers - ✅ Wildcard listeners —
onAnyfor debugging/logging - ✅ Error handling — Configurable error handling for resilient apps
- ✅ Memory leak detection — Warns when too many listeners are added
- ✅ Inspection tools — Debug your emitter with introspection methods
npm install emitochondriaimport { createEmitochondria } from 'emitochondria';
// Define your events
type MyEvents = {
'user:login': { userId: string; email: string };
'user:logout': { userId: string };
'app:ready': void;
};
// Create emitter
const events = createEmitochondria<MyEvents>();
// Subscribe (with full autocomplete!)
events.on('user:login', (data) => {
console.log(`${data.email} logged in`);
});
// Emit
events.emit('user:login', { userId: '123', email: 'pablo@example.com' });
// Void events need no payload
events.emit('app:ready');Create a new typed emitter with optional configuration.
const events = createEmitochondria<MyEvents>({
// Custom error handler (default: logs in dev, silent in production)
onError: (error, event, handler) => {
console.error(`Error in ${event}:`, error);
},
// Or preserve throwing behavior
// onError: 'throw',
// Max listeners before warning (default: 10, 0 to disable)
maxListeners: 20,
// Custom warning handler
onMaxListenersExceeded: (event, count, max) => {
console.warn(`Too many listeners on ${event}: ${count}/${max}`);
}
});Subscribe to an event. Returns an unsubscribe function.
const unsubscribe = events.on('user:login', (data) => {
console.log(data);
});
// Later...
unsubscribe();Remove a specific handler.
Subscribe for a single emission only.
events.once('app:ready', () => {
console.log('This only runs once');
});Emit an event synchronously.
Emit and await all handlers (parallel execution).
events.on('save', async (data) => {
await saveToDatabase(data);
});
await events.emitAsync('save', { id: '123' });
console.log('All handlers complete');Subscribe to all events. Great for logging.
events.onAny((event, payload) => {
console.log(`[${event}]`, payload);
});Remove a wildcard handler.
Clear handlers for an event, or all handlers if no event specified.
Get the number of listeners for an event.
Change the error handler at runtime.
events.setErrorHandler((error, event) => {
logger.error(`Event ${event} failed:`, error);
});Adjust or check the max listener warning threshold.
events.setMaxListeners(50); // Increase limit
console.log(events.getMaxListeners()); // 50Get all event names that currently have registered listeners.
events.on('user:login', handler1);
events.on('user:logout', handler2);
console.log(events.eventNames()); // ['user:login', 'user:logout']Get all handlers registered for a specific event.
const handlers = events.listeners('user:login');
console.log(handlers.length); // Number of handlersGet all wildcard handlers.
const wildcards = events.wildcardListeners();
console.log(wildcards.length);Check if a specific handler is registered for an event.
if (events.hasListener('user:login', myHandler)) {
console.log('Handler is registered');
}By default, errors thrown by event handlers are caught and logged in development (silent in production). This prevents one failing handler from breaking others:
events.on('save', () => { throw new Error('DB error'); });
events.on('save', () => console.log('This still runs!')); // ✅ Executes
events.emit('save');
// Console (dev): [Emitochondria] Error in handler for event "save": Error: DB error
// Output: "This still runs!"Custom error handling:
const events = createEmitochondria<MyEvents>({
onError: (error, event, handler) => {
// Send to your error tracking service
Sentry.captureException(error, { tags: { event } });
}
});Preserve throwing behavior (v1.0 compatibility):
const events = createEmitochondria<MyEvents>({
onError: 'throw' // Errors will throw like before
});Emitochondria warns you when adding too many listeners to a single event (default: 10), which often indicates a bug:
// This might indicate a leak (handler not cleaned up in a loop)
for (let i = 0; i < 15; i++) {
events.on('tick', handler); // Warning after 10th iteration
}
// Console: [Emitochondria] Possible memory leak detected: 11 listeners...Disable or customize:
const events = createEmitochondria<MyEvents>({
maxListeners: 0 // Disable warnings
});The type system prevents mistakes at compile time:
// ❌ Compile error: event doesn't exist
events.emit('user:logni', {});
// ❌ Compile error: missing required fields
events.emit('user:login', {});
// ❌ Compile error: wrong payload type
events.emit('user:login', { userId: 123 }); // should be string
// ✅ All good
events.emit('user:login', { userId: '123', email: 'test@example.com' });Check out the examples/ folder for more comprehensive usage examples including:
- Basic event subscription and emission
- Async event handlers
- Wildcard listeners
- Biological API usage
Contributions are welcome! Please feel free to submit a Pull Request.
MIT © Pablo Díaz