UNPKG

@goparrot/pubsub-event-bus

Version:
136 lines 6.39 kB
var PubSubEventBinder_1; import { __decorate, __metadata, __param } from "tslib"; import { Inject, Injectable } from '@nestjs/common'; import { escapeRegExp, omit } from 'lodash'; import { PubsubEvent, PubsubEventHandler } from '../decorator'; import { LoggerProvider } from '../provider'; import { generateQueuePrefixFromPackageName, getMessageExchange, toEventName, toSnakeCase } from '../utils'; import { CQRS_MODULE_CONSUMER_OPTIONS, FAN_OUT_BINDING } from '../utils/configuration'; import { Consumer } from './Consumer'; import { EventBus } from './EventBus'; import { PubSubReflector } from './PubSubReflector'; let PubSubEventBinder = PubSubEventBinder_1 = class PubSubEventBinder { constructor(consumer, eventBus, reflector, consumerOptions) { this.consumer = consumer; this.eventBus = eventBus; this.reflector = reflector; this.consumerOptions = consumerOptions; } async registerPubSubEvents(handlers) { if (!handlers.length) { this.logger().log('No pub-sub event handlers found'); return; } const handlersWithEvents = this.filterValidHandlersWithEvents(handlers); for (const mappedHandler of handlersWithEvents) { this.consumer.configureAutoAck(mappedHandler); this.consumer.addHandleCatch(mappedHandler); await this.bindPubSubConsumer(mappedHandler); } await this.consumer.configureRetryInfrastructure(handlersWithEvents); } async bindPubSubConsumer(handlerWrapper) { await this.consumer.consume(handlerWrapper, (message) => { if (message) { this.emitPubSubEvent(handlerWrapper, message); } }); } emitPubSubEvent(handlerWrapper, message) { const { handler, eventWrappers } = handlerWrapper; const typeProperty = message.properties.type; const baseContext = { handler: handler.name, type: typeProperty }; if (typeof typeProperty !== 'string') { this.logger().warn(JSON.stringify({ ...baseContext, message: 'Message with invalid property "type" consumed', })); this.consumer.ack(message); return; } const messageExchange = getMessageExchange(message); let matchedEventWrappers = eventWrappers.filter(({ event, options }) => options.exchange === messageExchange && toEventName(event.name) === typeProperty); if (!matchedEventWrappers.length) { matchedEventWrappers = eventWrappers.filter((eventWrapper) => { const bindingPattern = this.consumer.extractBindingPattern(eventWrapper); return (eventWrapper.options.exchange === messageExchange && (bindingPattern === FAN_OUT_BINDING || PubSubEventBinder_1.checkTypeAgainstBinding(typeProperty, bindingPattern))); }); } if (!matchedEventWrappers.length) { this.logger().warn(JSON.stringify({ ...baseContext, events: matchedEventWrappers.map((eventWrapper) => { return { name: eventWrapper.event.name, bindingPattern: this.consumer.extractBindingPattern(eventWrapper), }; }), message: 'No event class matched. Possible reason: handler no longer listens for this type of message, so queue should be unbound', })); this.consumer.ack(message); return; } const eventClasses = matchedEventWrappers.map((eventWrapper) => eventWrapper.event); const [firstEventClass, ...unused] = eventClasses; if (unused.length) { this.logger().warn(JSON.stringify({ ...baseContext, used: firstEventClass.name, unused: unused.map((event) => event.name), message: "Handler's event intersection detected", })); } const pubSubEvent = new firstEventClass(JSON.parse(message.content.toString())).withMessage(message); this.eventBus.publisher.publishLocally(pubSubEvent); } filterValidHandlersWithEvents(handlers) { const prefix = this.consumerOptions.queueNamePrefix ?? generateQueuePrefixFromPackageName(); if (!prefix) { throw new Error('"config.consumer.queueNamePrefix" CQRS Module options parameter should be set or the application should be started using npm scripts.'); } const validHandlersWithEvents = []; handlers.forEach((handler) => { const metadata = this.reflector.reflectHandlerMetadata(handler); if (!metadata) { this.logger().error(`Event handler "${handler.name}" should be decorated with "${PubsubEventHandler.name}"`); return; } const eventWrappers = []; metadata.events.forEach((event) => { const metadata = this.reflector.reflectEventMetadata(event); if (!metadata) { this.logger().error(`Event "${event.name}" should be decorated with "${PubsubEvent.name}"`); return; } eventWrappers.push({ event, options: metadata }); }); if (!eventWrappers.length) { this.logger().error(`Handler "${handler.name}" has no valid events"`); return; } validHandlersWithEvents.push({ handler, eventWrappers, options: omit(metadata, 'events'), queue: metadata.queue ?? [prefix, toSnakeCase(handler.name)].join(':'), }); }); return validHandlersWithEvents; } logger() { return LoggerProvider.logger; } static checkTypeAgainstBinding(typeProperty, bindingPattern) { return new RegExp(`^${escapeRegExp(bindingPattern).replace(/\\\*/g, '\\w*')}$`).test(typeProperty); } }; PubSubEventBinder = PubSubEventBinder_1 = __decorate([ Injectable(), __param(3, Inject(CQRS_MODULE_CONSUMER_OPTIONS)), __metadata("design:paramtypes", [Consumer, EventBus, PubSubReflector, Object]) ], PubSubEventBinder); export { PubSubEventBinder }; //# sourceMappingURL=PubSubEventBinder.js.map