diff --git a/src/events/bus.ts b/src/events/bus.ts new file mode 100644 index 0000000..bce73ef --- /dev/null +++ b/src/events/bus.ts @@ -0,0 +1,67 @@ +/** + * Event Emitter Bus - Adapter Implementation + * + * In-process EventBus implementation using Node.js EventEmitter. + * This is the ADAPTER for the EventBus PORT. + * + * Can be swapped for RabbitMQ, Kafka, WebSocket, etc. later + * without changing consumers. + */ + +import { EventEmitter } from 'node:events'; +import type { DomainEvent, EventBus } from './types.js'; + +/** + * EventEmitter-based implementation of the EventBus interface. + * + * Wraps Node.js EventEmitter to provide type-safe event handling + * that conforms to the EventBus port interface. + */ +export class EventEmitterBus implements EventBus { + private emitter: EventEmitter; + + constructor() { + this.emitter = new EventEmitter(); + // Allow more listeners for complex systems + this.emitter.setMaxListeners(100); + } + + /** + * Emit an event to all subscribed handlers. + * The event's type property determines which handlers receive it. + */ + emit(event: T): void { + this.emitter.emit(event.type, event); + } + + /** + * Subscribe to events of a specific type. + */ + on( + eventType: T['type'], + handler: (event: T) => void + ): void { + this.emitter.on(eventType, handler); + } + + /** + * Unsubscribe from events of a specific type. + */ + off( + eventType: T['type'], + handler: (event: T) => void + ): void { + this.emitter.off(eventType, handler); + } + + /** + * Subscribe to a single occurrence of an event type. + * Handler is automatically removed after first invocation. + */ + once( + eventType: T['type'], + handler: (event: T) => void + ): void { + this.emitter.once(eventType, handler); + } +} diff --git a/src/events/index.ts b/src/events/index.ts new file mode 100644 index 0000000..65e8f09 --- /dev/null +++ b/src/events/index.ts @@ -0,0 +1,26 @@ +/** + * Events Module - Public API + * + * Exports the EventBus port interface and EventEmitter adapter. + * All modules should import from this index file. + */ + +// Port interface (what consumers depend on) +export type { EventBus, DomainEvent } from './types.js'; + +// Adapter implementation +export { EventEmitterBus } from './bus.js'; + +// Factory function for creating event bus instances +import { EventEmitterBus } from './bus.js'; +import type { EventBus } from './types.js'; + +/** + * Create a new EventBus instance. + * + * Returns the default in-process EventEmitter adapter. + * This factory allows swapping implementations without changing call sites. + */ +export function createEventBus(): EventBus { + return new EventEmitterBus(); +} diff --git a/src/events/types.ts b/src/events/types.ts new file mode 100644 index 0000000..e1f9cc8 --- /dev/null +++ b/src/events/types.ts @@ -0,0 +1,56 @@ +/** + * Event Bus Types + * + * Port interface for the event bus - the backbone of the hexagonal architecture. + * EventBus is the PORT. Implementations (EventEmitterBus, RabbitMQ, etc.) are ADAPTERS. + */ + +/** + * Base interface for all domain events. + * Every event in the system extends this. + */ +export interface DomainEvent { + /** Event type identifier (e.g., 'process:spawned', 'server:started') */ + type: string; + /** When the event occurred */ + timestamp: Date; + /** Event-specific data */ + payload: unknown; +} + +/** + * Event Bus Port Interface + * + * All modules communicate through this interface. + * Can be swapped for external systems (RabbitMQ, WebSocket forwarding) later. + */ +export interface EventBus { + /** + * Emit an event to all subscribed handlers + */ + emit(event: T): void; + + /** + * Subscribe to events of a specific type + */ + on( + eventType: T['type'], + handler: (event: T) => void + ): void; + + /** + * Unsubscribe from events of a specific type + */ + off( + eventType: T['type'], + handler: (event: T) => void + ): void; + + /** + * Subscribe to a single occurrence of an event type + */ + once( + eventType: T['type'], + handler: (event: T) => void + ): void; +}