/** * Type definition for event listeners. * An event listener is a callable that receives an event object. * * Listeners can be synchronous or asynchronous. */ type EventListener = (event: T) => void | Promise; /** * An Event whose processing may be interrupted when the event has been handled. * * A Dispatcher implementation MUST check to determine if an Event * is marked as stopped after each listener is called. If it is then it should * return immediately without calling any further Listeners. */ interface StoppableEventInterface { /** * Is propagation stopped? * * This will typically only be used by the Dispatcher to determine if the * previous listener halted propagation. * * @returns True if the Event is complete and no further listeners should be called. * False to continue calling listeners. */ isPropagationStopped(): boolean; } /** * Interface for event subscribers. * A subscriber can subscribe to multiple events at once. */ interface EventSubscriberInterface { /** * Returns an object with event names as keys and the listener configuration as values. * * @example * ```typescript * getSubscribedEvents() { * return { * 'user.created': 'onUserCreated', * 'user.updated': { listener: 'onUserUpdated', priority: 10 } * }; * } * ``` */ getSubscribedEvents(): Record; } /** * Allows providing hooks on domain-specific lifecycles by dispatching events. * * Implementations of this interface are responsible for: * - Managing event listeners and their priorities * - Dispatching events to registered listeners * - Respecting event propagation stopping * * Developers can implement this interface to create custom dispatchers * optimized for their specific environment (Browser, Node.js, Worker, etc.) * * @author AGBOKOUDJO Franck */ interface EventDispatcherInterface { /** * Dispatches an event to all registered listeners. * * @template T - The event type * @param event - The event to pass to the event handlers/listeners * @param eventName - The name of the event to dispatch. If not supplied, * the class name of the event should be used instead. * @returns The passed event MUST be returned * * @example * ```typescript * const event = new UserCreatedEvent(userId); * dispatcher.dispatch(event, 'user.created'); * ``` */ dispatch(event: T, eventName?: string | null, ...otherArgs: any): T; /** * Dispatches an event and awaits each listener sequentially, * in priority order. * * Use this when subscribers perform async operations (HTTP, file I/O, DB…) * and you need to read results from the event object after dispatch. * * stopPropagation() is honoured between each awaited listener. * * @example * const event = new InitializingUploadEvent(options); * await dispatcher.dispatchAsync(event, HttpFileUploaderEvents.INITIALIZE_UPLOAD); * const mediaId = event.mediaId; // safely populated by the subscriber */ dispatchAsync(event: T, eventName?: string | null, ...otherArgs: any): Promise; /** * Adds an event listener that listens on the specified event. * * @param eventName - The event to listen on * @param listener - The listener callback * @param priority - The higher this value, the earlier an event listener * will be triggered in the chain (defaults to 0) * * @example * ```typescript * dispatcher.addListener('user.created', (event) => { * console.log('User created:', event.userId); * }, 10); * ``` */ addListener(eventName: string, listener: EventListener, priority?: number): void; /** * Adds an event subscriber. * * The subscriber is asked for all the events it is interested in * and added as a listener for these events. * * @example * ```typescript * dispatcher.addSubscriber(new UserSubscriber()); * ``` */ addSubscriber(subscriber: EventSubscriberInterface): void; /** * Removes an event listener from the specified event. * * @param eventName - The event name * @param listener - The listener to remove */ removeListener(eventName: string, listener: EventListener): void; /** * Removes an event subscriber. */ removeSubscriber(subscriber: EventSubscriberInterface): void; /** * Gets the listeners of a specific event or all listeners sorted by descending priority. * * @param eventName - The name of the event, or null to get all listeners * @returns Array of listeners for the event, or a Map of all listeners */ getListeners(eventName?: string | null): EventListener[] | Map; /** * Gets the listener priority for a specific event. * * Returns null if the event or the listener does not exist. * * @param eventName - The event name * @param listener - The listener * @returns The priority or null if not found */ getListenerPriority(eventName: string, listener: EventListener): number | null; /** * Checks whether an event has any registered listeners. * * @param eventName - The event name, or null to check if any event has listeners * @returns True if the specified event has any listeners, false otherwise */ hasListeners(eventName?: string | null): boolean; } /** * Abstract base class for all EventDispatchers. * * Philosophy (inspired by Symfony EventDispatcher): * - The internal Map is the single source of truth for dispatch. * - Native primitives (EventTarget, EventEmitter) are synchronized on * addListener/removeListener so that external code using * window.addEventListener() or emitter.on() can also react to events — * but they are NOT used to drive the dispatch loop (avoids double-call). * - dispatchAsync() is an extension beyond Symfony for async subscriber support. * * @author AGBOKOUDJO Franck */ declare abstract class AbstractEventDispatcher { protected subscriberListeners: WeakMap>; constructor(); protected isStoppableEvent(event: object): event is StoppableEventInterface; addSubscriber(subscriber: EventSubscriberInterface): void; removeSubscriber(subscriber: EventSubscriberInterface): void; abstract addListener(eventName: string, listener: EventListener, priority?: number): void; abstract removeListener(eventName: string, listener: EventListener): void; abstract hasListeners(eventName?: string | null): boolean; abstract getListeners(eventName?: string | null): EventListener[] | Map; /** * Dispatches an event synchronously to all registered listeners, * in priority order (highest first). * * Async listeners are fire-and-forget: their Promise errors are caught * and logged, but dispatch() does NOT await them. * Use dispatchAsync() when you need to await async listeners. * * This mirrors Symfony's EventDispatcher::dispatch() behaviour. */ dispatch(event: T, eventName?: string | null): T; /** * Dispatches an event and awaits each listener sequentially, * in priority order. * * Use this when subscribers perform async operations (HTTP, file I/O, DB…) * and you need to read results from the event object after dispatch. * * stopPropagation() is honoured between each awaited listener. * * @example * const event = new InitializingUploadEvent(options); * await dispatcher.dispatchAsync(event, HttpFileUploaderEvents.INITIALIZE_UPLOAD); * const mediaId = event.mediaId; // safely populated by the subscriber */ dispatchAsync(event: T, eventName?: string | null): Promise; } export { AbstractEventDispatcher as A, type EventDispatcherInterface as E, type StoppableEventInterface as S, type EventListener as a, type EventSubscriberInterface as b };