@bernierllc/event-emitter
v1.0.5
Published
Type-safe event bus for inter-package communication
Downloads
426
Readme
@bernierllc/event-emitter
Type-safe event bus for inter-package communication with support for wildcard listeners, async handlers, and comprehensive error handling.
Installation
npm install @bernierllc/event-emitterFeatures
- 🎯 Type-safe event handling - Full TypeScript support with generics
- 🌟 Wildcard listeners - Subscribe to all events with
* - ⚡ Async/await support - Handles both sync and async event handlers
- 🛡️ Error handling - Configurable error capture and propagation
- 🔄 One-time subscriptions -
once()support for single-use listeners - 📊 Observable - Track listener counts and event names
- 🚀 High performance - Parallel execution of async handlers
- 🔌 Zero dependencies - Lightweight core with optional logger integration
Usage
Basic Event Subscription and Publishing
import { createEventEmitter } from '@bernierllc/event-emitter';
// Create event emitter
const events = createEventEmitter();
// Subscribe to events
const subscription = events.on('user.created', (data) => {
console.log('User created:', data);
});
// Publish events
await events.emit('user.created', {
userId: '123',
email: '[email protected]'
});
// Unsubscribe
subscription.unsubscribe();Type-Safe Events
interface UserCreatedEvent {
userId: string;
email: string;
name: string;
}
// Type-safe subscription
events.on<UserCreatedEvent>('user.created', (data) => {
// data is typed as UserCreatedEvent
console.log(data.userId, data.email, data.name);
});
// Type-safe emission
await events.emit<UserCreatedEvent>('user.created', {
userId: '123',
email: '[email protected]',
name: 'John Doe'
});One-Time Subscriptions
// Subscribe once (automatically unsubscribes after first event)
events.once('app.ready', () => {
console.log('App is ready!');
});
await events.emit('app.ready', {});
await events.emit('app.ready', {}); // Handler won't be calledWildcard Listeners
// Listen to all events
events.on('*', (data) => {
console.log('Event received:', data);
});
// Both specific and wildcard listeners will be called
await events.emit('user.created', { userId: '123' });
await events.emit('order.placed', { orderId: '456' });Async Event Handlers
// Async handler
events.on('user.created', async (data) => {
await sendWelcomeEmail(data.email);
await createUserProfile(data.userId);
});
// emit() waits for all handlers to complete
await events.emit('user.created', {
userId: '123',
email: '[email protected]'
});Error Handling
// With error capture (default)
const safeEmitter = createEventEmitter({ captureErrors: true });
safeEmitter.on('process', () => {
throw new Error('Processing failed');
});
// Errors are captured and logged, other handlers continue
await safeEmitter.emit('process', {});
// Without error capture
const strictEmitter = createEventEmitter({ captureErrors: false });
strictEmitter.on('process', () => {
throw new Error('Processing failed');
});
// Errors are thrown
await strictEmitter.emit('process', {}); // Throws errorMax Listeners
// Limit listeners per event
const limitedEmitter = createEventEmitter({ maxListeners: 10 });
// Add 10 listeners
for (let i = 0; i < 10; i++) {
limitedEmitter.on('test', () => console.log(i));
}
// This will throw
limitedEmitter.on('test', () => console.log('too many')); // Error!Managing Listeners
// Get listener count
const count = events.listenerCount('user.created');
console.log(`${count} listeners for user.created`);
// Get all event names
const eventNames = events.eventNames();
console.log('Active events:', eventNames);
// Remove all listeners for specific event
events.removeAllListeners('user.created');
// Remove all listeners for all events
events.removeAllListeners();Configuration
const events = createEventEmitter({
// Maximum listeners per event (0 = unlimited)
maxListeners: 100,
// Log events (requires @bernierllc/logger)
logEvents: true,
// Capture and suppress handler errors
captureErrors: true
});Environment Variables
EVENT_EMITTER_MAX_LISTENERS=100
EVENT_EMITTER_LOG_EVENTS=true
EVENT_EMITTER_CAPTURE_ERRORS=trueAPI Reference
EventEmitter
Main class for event management.
Methods
on<T>(eventType: string, handler: EventHandler<T>): EventSubscription- Subscribe to eventonce<T>(eventType: string, handler: EventHandler<T>): EventSubscription- Subscribe onceoff(subscription: EventSubscription): void- Unsubscribe from eventemit<T>(eventType: string, data: T): Promise<void>- Publish eventremoveAllListeners(eventType?: string): void- Remove all listenerslistenerCount(eventType: string): number- Get listener counteventNames(): string[]- Get all event names
EventSubscription
Subscription object returned by on() and once().
interface EventSubscription {
id: string; // Unique subscription ID
eventType: string; // Event type
handler: EventHandler; // Handler function
once: boolean; // One-time subscription?
unsubscribe: () => void; // Unsubscribe function
}EventHandler<T>
Event handler function type.
type EventHandler<T = any> = (data: T) => void | Promise<void>;createEventEmitter(options?: EventEmitterOptions): EventEmitter
Factory function for creating event emitters.
Integration Status
Logger Integration
Status: Optional (recommended for event tracking)
Justification: While this package can function without logging, it's highly recommended to integrate @bernierllc/logger for event tracking and error monitoring. Event emitters often need to log event emissions, listener registrations, and errors to help with debugging and monitoring event-driven workflows. However, the package is designed to work without logging for environments where logging isn't available.
Pattern: Optional integration - package works without logger, but logger enhances event tracking capabilities.
Example Integration:
import { Logger } from '@bernierllc/logger';
import { EventEmitter } from '@bernierllc/event-emitter';
const logger = new Logger({ service: 'event-emitter' });
const emitter = new EventEmitter({ logger });
emitter.on('error', (error) => {
logger.error('Event emitter error', error);
});NeverHub Integration
Status: Optional (recommended for distributed event bus)
Justification: While this package can function without NeverHub, it's highly recommended to integrate @bernierllc/neverhub-adapter for distributed event bus capabilities. Event emitters often need to coordinate events across services, and NeverHub provides a service mesh for publishing events that other services can subscribe to. However, the package is designed to work without NeverHub for simpler use cases.
Pattern: Optional integration - package works without NeverHub, but NeverHub enhances distributed event capabilities.
Example Integration:
import { NeverHubAdapter } from '@bernierllc/neverhub-adapter';
import { EventEmitter } from '@bernierllc/event-emitter';
const neverhub = new NeverHubAdapter({ service: 'event-emitter' });
const emitter = new EventEmitter();
// Bridge local events to NeverHub
emitter.on('user:created', async (data) => {
await neverhub.publish('user.created', data);
});Docs-Suite Integration
Status: Ready
Format: TypeDoc-compatible JSDoc comments are included throughout the source code. All public APIs are documented with examples and type information.
Use Cases
Inter-Package Communication
// In cache-manager package
events.emit('cache.invalidated', { key: 'user:123' });
// In data-sync package
events.on('cache.invalidated', async (data) => {
await refreshData(data.key);
});State Management
// State changes
events.on('state.changed', (newState) => {
console.log('State updated:', newState);
});
await events.emit('state.changed', { status: 'active' });Task Lifecycle Events
// Task queue events
events.on('task.started', (task) => console.log('Started:', task.id));
events.on('task.completed', (task) => console.log('Completed:', task.id));
events.on('task.failed', (task) => console.log('Failed:', task.id));Workflow Orchestration
// Multi-step workflow
events.on('workflow.step.completed', async (data) => {
if (data.nextStep) {
await events.emit('workflow.step.start', { step: data.nextStep });
}
});Performance
- Parallel Execution: Async handlers execute in parallel via
Promise.all() - Memory Efficient: Automatic cleanup of empty listener sets
- Fast Lookups: O(1) listener retrieval using Map data structure
- Zero Dependencies: Minimal bundle size (~2KB gzipped)
Testing
# Run tests
npm test
# Run tests with coverage
npm run test:coverage
# Run tests in watch mode
npm run test:watchSee Also
- @bernierllc/logger - Logging integration for event tracking
- @bernierllc/cache-manager - Uses events for cache invalidation
- @bernierllc/state-machine - Uses events for state transitions
- @bernierllc/task-queue - Uses events for task lifecycle
License
Copyright (c) 2025 Bernier LLC. All rights reserved.
