@goparrot/pubsub-event-bus
Version:
NestJS EventBus extension for RabbitMQ PubSub
141 lines • 6.74 kB
JavaScript
;
var PubSubEventBinder_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.PubSubEventBinder = void 0;
const tslib_1 = require("tslib");
const common_1 = require("@nestjs/common");
const lodash_1 = require("lodash");
const decorator_1 = require("../decorator");
const provider_1 = require("../provider");
const utils_1 = require("../utils");
const configuration_1 = require("../utils/configuration");
const Consumer_1 = require("./Consumer");
const EventBus_1 = require("./EventBus");
const PubSubReflector_1 = require("./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 = (0, utils_1.getMessageExchange)(message);
let matchedEventWrappers = eventWrappers.filter(({ event, options }) => options.exchange === messageExchange && (0, utils_1.toEventName)(event.name) === typeProperty);
if (!matchedEventWrappers.length) {
matchedEventWrappers = eventWrappers.filter((eventWrapper) => {
const bindingPattern = this.consumer.extractBindingPattern(eventWrapper);
return (eventWrapper.options.exchange === messageExchange &&
(bindingPattern === configuration_1.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) {
var _a;
const prefix = (_a = this.consumerOptions.queueNamePrefix) !== null && _a !== void 0 ? _a : (0, utils_1.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) => {
var _a;
const metadata = this.reflector.reflectHandlerMetadata(handler);
if (!metadata) {
this.logger().error(`Event handler "${handler.name}" should be decorated with "${decorator_1.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 "${decorator_1.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: (0, lodash_1.omit)(metadata, 'events'),
queue: (_a = metadata.queue) !== null && _a !== void 0 ? _a : [prefix, (0, utils_1.toSnakeCase)(handler.name)].join(':'),
});
});
return validHandlersWithEvents;
}
logger() {
return provider_1.LoggerProvider.logger;
}
static checkTypeAgainstBinding(typeProperty, bindingPattern) {
return new RegExp(`^${(0, lodash_1.escapeRegExp)(bindingPattern).replace(/\\\*/g, '\\w*')}$`).test(typeProperty);
}
};
exports.PubSubEventBinder = PubSubEventBinder;
exports.PubSubEventBinder = PubSubEventBinder = PubSubEventBinder_1 = tslib_1.__decorate([
(0, common_1.Injectable)(),
tslib_1.__param(3, (0, common_1.Inject)(configuration_1.CQRS_MODULE_CONSUMER_OPTIONS)),
tslib_1.__metadata("design:paramtypes", [Consumer_1.Consumer,
EventBus_1.EventBus,
PubSubReflector_1.PubSubReflector, Object])
], PubSubEventBinder);
//# sourceMappingURL=PubSubEventBinder.js.map