UNPKG

@message-queue-toolkit/core

Version:

Useful utilities, interfaces and base classes for message queue handling. Supports AMQP and SQS with a common abstraction on top currently

143 lines 5.91 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DomainEventEmitter = void 0; const node_core_1 = require("@lokalise/node-core"); const HandlerSpy_1 = require("../queues/HandlerSpy"); const node_crypto_1 = require("node:crypto"); class DomainEventEmitter { eventRegistry; metadataFiller; logger; errorReporter; transactionObservabilityManager; _handlerSpy; eventHandlerMap; inProgressBackgroundHandlerByEventId; constructor(deps, options = {}) { this.eventRegistry = deps.eventRegistry; this.metadataFiller = deps.metadataFiller; this.logger = deps.logger; this.errorReporter = deps.errorReporter; this.transactionObservabilityManager = deps.transactionObservabilityManager; this._handlerSpy = (0, HandlerSpy_1.resolveHandlerSpy)(options); this.eventHandlerMap = new Map(); this.inProgressBackgroundHandlerByEventId = new Map(); } get handlerSpy() { if (!this._handlerSpy) { throw new Error('HandlerSpy was not instantiated, please pass `handlerSpy` parameter during queue service creation.'); } return this._handlerSpy; } async dispose() { await Promise.all(this.inProgressBackgroundHandlerByEventId.values()); this.inProgressBackgroundHandlerByEventId.clear(); this.eventHandlerMap.clear(); this._handlerSpy?.clear(); } async emit(supportedEvent, data, precedingMessageMetadata) { const eventTypeName = supportedEvent.publisherSchema.shape.type.value; if (!this.eventRegistry.isSupportedEvent(eventTypeName)) { throw new node_core_1.InternalError({ errorCode: 'UNKNOWN_EVENT', message: `Unknown event ${eventTypeName}`, }); } if (!data.timestamp) data.timestamp = this.metadataFiller.produceTimestamp(); if (!data.id) data.id = this.metadataFiller.produceId(); if (!data.metadata) { data.metadata = this.metadataFiller.produceMetadata( // @ts-ignore data, supportedEvent, precedingMessageMetadata ?? {}); } if (!data.metadata.correlationId) data.metadata.correlationId = this.metadataFiller.produceId(); const validatedEvent = this.eventRegistry .getEventDefinitionByTypeName(eventTypeName) .publisherSchema.parse({ type: eventTypeName, ...data }); // @ts-ignore await this.handleEvent(validatedEvent); // @ts-ignore return validatedEvent; } /** * Register handler for a specific event */ on(eventTypeName, handler, isBackgroundHandler = false) { if (!this.eventHandlerMap.has(eventTypeName)) { this.eventHandlerMap.set(eventTypeName, { foreground: [], background: [] }); } if (isBackgroundHandler) this.eventHandlerMap.get(eventTypeName)?.background.push(handler); else this.eventHandlerMap.get(eventTypeName)?.foreground.push(handler); } /** * Register handler for multiple events */ onMany(eventTypeNames, handler, isBackgroundHandler = false) { for (const eventTypeName of eventTypeNames) { this.on(eventTypeName, handler, isBackgroundHandler); } } /** * Register handler for all events supported by the emitter */ onAny(handler, isBackgroundHandler = false) { this.onMany(Array.from(this.eventRegistry.supportedEventTypes), handler, isBackgroundHandler); } async handleEvent(event) { const eventHandlers = this.eventHandlerMap.get(event.type); if (!eventHandlers) return; for (const handler of eventHandlers.foreground) { await this.executeEventHandler(event, handler, false); } const bgPromise = Promise.all(eventHandlers.background.map((handler) => this.executeEventHandler(event, handler, true))).then(() => { this.inProgressBackgroundHandlerByEventId.delete(event.id); if (!this._handlerSpy) return; this._handlerSpy.addProcessedMessage({ // @ts-ignore message: event, processingResult: { status: 'consumed' }, }, event.id); }); this.inProgressBackgroundHandlerByEventId.set(event.id, bgPromise); } async executeEventHandler(event, handler, isBackgroundHandler) { const transactionId = (0, node_crypto_1.randomUUID)(); let isSuccessful = false; try { this.transactionObservabilityManager?.startWithGroup(this.buildTransactionKey(event, handler, isBackgroundHandler), transactionId, event.type); await handler.handleEvent(event); isSuccessful = true; } catch (error) { if (!isBackgroundHandler) throw error; const context = { event: JSON.stringify(event), eventHandlerId: handler.eventHandlerId, 'x-request-id': event.metadata?.correlationId, }; this.logger.error({ ...(0, node_core_1.resolveGlobalErrorLogObject)(error), ...context, }); // biome-ignore lint/suspicious/noExplicitAny: TODO: improve error type this.errorReporter?.report({ error: error, context }); } finally { this.transactionObservabilityManager?.stop(transactionId, isSuccessful); } } buildTransactionKey(event, handler, isBackgroundHandler) { return `${isBackgroundHandler ? 'bg' : 'fg'}_event_listener:${event.type}:${handler.eventHandlerId}`; } } exports.DomainEventEmitter = DomainEventEmitter; //# sourceMappingURL=DomainEventEmitter.js.map