@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
JavaScript
"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