@goparrot/pubsub-event-bus
Version:
NestJS EventBus extension for RabbitMQ PubSub
136 lines • 6.39 kB
JavaScript
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