UNPKG

lever-ui-eventbus

Version:

A minimal TypeScript event bus: subscribe(Class, handler), async delivery, dead events, and polymorphic dispatch.

264 lines (257 loc) 8.29 kB
/** * A constructor type that can create instances of T. * @template T The type that this constructor creates */ type Constructor<T = any> = new (...args: any[]) => T; /** * Context information provided when a subscriber throws an exception. * @template T The event type */ interface SubscriberExceptionContext<T = unknown> { /** The event that was being processed */ event: T; /** The constructor/class of the event type */ eventType: Constructor<any>; /** The handler function that threw the exception */ handler: (e: T) => void; /** The EventBus instance that was processing the event */ eventBus: any; } /** * Handler function for exceptions thrown by event subscribers. * * @param error The exception that was thrown * @param ctx Context information about the failed event delivery * * @example * ```ts * const errorHandler: SubscriberExceptionHandler = (error, ctx) => { * logger.error(`Event ${ctx.eventType.name} failed:`, error); * }; * const bus = new EventBus(errorHandler); * ``` */ type SubscriberExceptionHandler = (error: unknown, ctx: SubscriberExceptionContext) => void; /** * Represents an active subscription that can be cancelled. */ interface Subscription { /** * Unsubscribe this specific handler from the event bus. * After calling this, the handler will no longer receive events. */ unsubscribe(): void; } /** * Internal handler record structure. * @internal */ interface HandlerRec<T = unknown> { fn: (e: T) => void; } /** * Event emitted when no handler is found for a posted event. * This helps detect unhandled events and avoid silent failures. * * @example * ```ts * bus.subscribe(DeadEvent, (e) => { * console.warn('Unhandled event:', e.event); * }); * ``` */ declare class DeadEvent { source: any; event: unknown; /** * @param source The EventBus that could not deliver the event * @param event The original event that was unhandled */ constructor(source: any, event: unknown); } /** * Resolves the prototype chain for an event to determine all applicable types. * Uses WeakMap caching for performance optimization. * @internal */ declare class TypeResolver { private typeCache; /** * Get all constructor types in the prototype chain for an event. * Results are cached for performance. */ getTypesFor(event: unknown, hasObjectHandler: boolean): Constructor[]; /** * Clear the type cache (useful when clearing the event bus). */ clearCache(): void; } /** * A synchronous event bus that delivers events to registered handlers. * * Features: * - Type-safe event subscription * - Polymorphic dispatch (handlers for base classes receive subclass events) * - Dead event handling for undelivered events * - Exception handling for subscriber errors * * @example * ```ts * class OrderCreated { constructor(public id: string) {} } * * const bus = new EventBus(); * const subscription = bus.subscribe(OrderCreated, (event) => { * console.log('Order:', event.id); * }); * * bus.post(new OrderCreated('123')); // logs: "Order: 123" * subscription.unsubscribe(); * ``` */ declare class EventBus { protected exceptionHandler: SubscriberExceptionHandler; protected registry: Map<Constructor, Set<HandlerRec<unknown>>>; protected typeResolver: TypeResolver; /** * Create a new EventBus. * * @param exceptionHandler Handler for exceptions thrown by subscribers. * Defaults to logging to console.error. */ constructor(exceptionHandler?: SubscriberExceptionHandler); /** * Subscribe to events of a specific type. * * @template T The event type to subscribe to * @param type The constructor/class of events to listen for * @param handler Function to call when events of this type are posted * @returns Subscription object that can be used to unsubscribe * * @example * ```ts * class UserLoggedIn { constructor(public userId: string) {} } * * const sub = bus.subscribe(UserLoggedIn, (event) => { * console.log('User logged in:', event.userId); * }); * ``` */ subscribe<T>(type: Constructor<T>, handler: (e: T) => void): Subscription; /** * Remove all handlers for a specific event type. * * @param type The constructor/class to remove all handlers for * @returns The number of handlers that were removed * * @example * ```ts * const removed = bus.unsubscribeAll(UserLoggedIn); * console.log(`Removed ${removed} handlers`); * ``` */ unsubscribeAll(type: Constructor): number; /** * Get the number of active subscriptions for a specific event type. * * @param type The constructor/class to count handlers for * @returns The number of active handlers for this type * * @example * ```ts * const count = bus.getSubscriptionCount(UserLoggedIn); * console.log(`${count} handlers for UserLoggedIn`); * ``` */ getSubscriptionCount(type: Constructor): number; /** * Get all event types that have active subscriptions. * * @returns Array of constructor functions that have handlers * * @example * ```ts * const activeTypes = bus.getActiveEventTypes(); * console.log('Subscribed types:', activeTypes.map(t => t.name)); * ``` */ getActiveEventTypes(): Constructor[]; /** * Remove all subscriptions from the event bus. * * @returns The total number of handlers that were removed * * @example * ```ts * const totalRemoved = bus.clear(); * console.log(`Cleared ${totalRemoved} handlers`); * ``` */ clear(): number; /** * Post an event to all registered handlers. * * Handlers are called for the exact type and all parent types in the prototype chain. * If no handlers are found, a DeadEvent is posted instead. * * @template T The type of event being posted * @param event The event instance to deliver * @returns The number of handlers that received the event * * @example * ```ts * const delivered = bus.post(new UserLoggedIn('user123')); * console.log(`Event delivered to ${delivered} handlers`); * ``` */ post<T>(event: T): number; /** * Deliver an event to a specific handler with exception handling. * @internal */ protected deliver(fn: (e: unknown) => void, type: Constructor, event: unknown): void; } /** * An asynchronous event bus that delivers events via a pluggable executor. * * All event delivery is deferred through the executor function, allowing for * custom scheduling strategies like setTimeout, process.nextTick, or custom queues. * * @example * ```ts * // Using setTimeout for delivery * const asyncBus = new AsyncEventBus((task) => setTimeout(task, 0)); * * // Using custom executor * const customBus = new AsyncEventBus((task) => { * myCustomScheduler.schedule(task); * }); * * asyncBus.subscribe(OrderCreated, (event) => { * console.log('Async order:', event.id); // called asynchronously * }); * * asyncBus.post(new OrderCreated('123')); * console.log('Posted'); // this logs first * ``` */ declare class AsyncEventBus extends EventBus { private executor; /** * Create a new AsyncEventBus. * * @param executor Function that schedules task execution. Defaults to queueMicrotask. * @param exceptionHandler Handler for exceptions thrown by subscribers. */ constructor(executor?: (task: () => void) => void, exceptionHandler?: SubscriberExceptionHandler); /** * Deliver an event asynchronously using the configured executor. * @internal */ protected deliver(fn: (e: unknown) => void, type: Constructor, event: unknown): void; } /** * Default exception handler that logs errors to console. * @internal */ declare const defaultExceptionHandler: SubscriberExceptionHandler; export { AsyncEventBus, type Constructor, DeadEvent, EventBus, type HandlerRec, type SubscriberExceptionContext, type SubscriberExceptionHandler, type Subscription, TypeResolver, defaultExceptionHandler };