UNPKG

@empathyco/x-components

Version:
182 lines (179 loc) 6.69 kB
import { ReplaySubject } from 'rxjs'; import { map } from 'rxjs/operators'; import { BaseXPriorityQueue } from './x-priority-queue/x-priority-queue.js'; /** * A default {@link XBus} implementation using a * {@link XPriorityQueue | priority queue} as its data structure to * prioritise the emission of events. The priorities are preconfigured based on event naming. * * @public */ class XPriorityBus { /** * Creates a new instance of a {@link XPriorityBus}. * * @param config - A configuration object to initialise the bus. * - param config.queue - A {@link XPriorityQueue | priority queue} to store the events. * - param config.priorities - A Dictionary defining the priorities associated to a given string. * - param config.emitCallbacks - A list of functions to execute when an event is emitted. * - param config.defaultEventPriority - A default priority to assigned to an event. */ constructor(config = {}) { /** * A dictionary to store the created event emitters. * * @internal */ this.emitters = {}; /** * A list of pending pop operations timeout identifiers. * * @internal */ this.pendingPopsIds = []; this.queue = config.queue ?? new BaseXPriorityQueue(); this.priorities = config.priorities ?? {}; this.emitCallbacks = config.emitCallbacks ?? []; this.defaultEventPriority = config.defaultEventPriority ?? Number.MIN_SAFE_INTEGER; } /** * Emits an event. See {@link XBus.emit}. * * @param event - Event to be emitted. * @param payload - Event payload. * @param metadata - Extra event data. * * @returns A promise that is resolved the moment the event is emitted. */ // eslint-disable-next-line ts/promise-function-async emit(event, // TODO: Fix optional argument. payload, metadata = {}) { return new Promise(resolve => { this.queue.push(event, this.getEventPriority(event, metadata), { // This type assertion is done because payload is optional. eventPayload: payload, eventMetadata: metadata, replaceable: metadata.replaceable || false, // @ts-expect-error TODO: Fix type. resolve, }); this.flushQueue(); }); } /**. * Retrieves the event priority based on: * - the defined event metadata priority * - the priority associated to the matching preconfigured priority key * - the configured {@link XPriorityBus.defaultEventPriority | defaultEventPriority} is assigned (by default, the min safe integer). * * @param event - The event to get the priority from. * @param metadata - The event metadata. * * @returns The priority for the given event. * * @internal */ getEventPriority(event, metadata) { if (metadata.priority != null) { return metadata.priority; } const matchingKey = Object.keys(this.priorities).find(key => String(event).includes(key)); if (matchingKey) { return this.priorities[matchingKey]; } return this.defaultEventPriority; } /** * Processes the events stored in the * {@link XPriorityQueue | priority queue} and resolves each event * whenever it is emitted. * * @remarks If another 'flushQueue' operation is running, it is discarded and a new one is * executed. The pending popping operations are also discarded. * * @internal */ flushQueue() { clearTimeout(this.pendingFlushId); this.clearPendingPopsIds(); this.pendingFlushId = window.setTimeout(() => { for (let i = 0; i < this.queue.size(); ++i) { const popTimeoutId = window.setTimeout(() => { const { key, data: { eventPayload, eventMetadata, resolve }, } = this.queue.pop(); const emitter = this.getEmitter(key); const payloadObj = { eventPayload, metadata: eventMetadata, }; emitter.next(payloadObj); this.emitCallbacks.forEach(callback => { callback(key, payloadObj); }); resolve({ event: key, ...payloadObj }); this.pendingPopsIds = this.pendingPopsIds.filter(timeoutId => timeoutId !== popTimeoutId); }); this.pendingPopsIds.push(popTimeoutId); } }); } /** * Discards existing pending pop operations and empties the array. * * @internal */ clearPendingPopsIds() { this.pendingPopsIds.forEach(clearTimeout); this.pendingPopsIds.length = 0; } /** * Retrieves an observable for the event. See {@link XBus.on}. * * @param event - Event to retrieve the observable for. * @param withMetadata - Option to retrieve an observable with extra data about the event. * * @returns The emitter for the event passed. */ on(event, withMetadata = false) { // TODO: This type should work, but inference isn't working as expected. Check when updating ts. return withMetadata ? // @ts-expect-error Type is not assignable to type EventPayload<SomeEvents, SomeEvent this.getEmitter(event).asObservable() : this.getEmitter(event).pipe(map( // eslint-disable-next-line ts/no-unsafe-return value => value.eventPayload)); } /** * Retrieves an event {@link Emitter} for the given event. * * @param event - The event to retrieve the {@link Emitter} for. * * @returns The {@link Emitters} for the passed event. * * @internal */ getEmitter(event) { if (!this.emitters[event]) { this.createEmitter(event); } return this.emitters[event]; } /** * Creates an event {@link Emitter} for the given event. * * @remarks The emitter is implemented with a * {@Link https://www.learnrxjs.io/learn-rxjs/subjects/replaysubject | ReplaySubject} to allow any * new subscriber receive the last emitted value. * * @param event - The event to create the {@link Emitter} for. * * @internal */ createEmitter(event) { this.emitters[event] = new ReplaySubject(1); } } export { XPriorityBus }; //# sourceMappingURL=x-bus.js.map