@goparrot/pubsub-event-bus
Version:
NestJS EventBus extension for RabbitMQ PubSub
158 lines • 7.73 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Consumer = void 0;
const tslib_1 = require("tslib");
const common_1 = require("@nestjs/common");
const lodash_1 = require("lodash");
const interface_1 = require("../interface");
const lifecycle_event_1 = require("../lifecycle-event");
const provider_1 = require("../provider");
const utils_1 = require("../utils");
const configuration_1 = require("../utils/configuration");
const EventBus_1 = require("./EventBus");
const PubsubManager_1 = require("./PubsubManager");
let Consumer = class Consumer extends PubsubManager_1.PubsubManager {
constructor(eventBus, retryStrategies, options, rootRetryOptions, bindingQueueOptions, prepareHandlerStrategies) {
super();
this.eventBus = eventBus;
this.retryStrategies = retryStrategies;
this.options = options;
this.rootRetryOptions = rootRetryOptions;
this.bindingQueueOptions = bindingQueueOptions;
this.prepareHandlerStrategies = prepareHandlerStrategies;
this.exchanges = new Set();
}
async setupChannel(channel) {
if (this.options.prefetchPerConsumer) {
await channel.prefetch(this.options.prefetchPerConsumer, false);
}
if (this.options.prefetchPerChannel) {
await channel.prefetch(this.options.prefetchPerChannel, true);
}
}
async consume(handlerWrapper, onMessage) {
if ((0, utils_1.appInTestingMode)()) {
return;
}
const { handler, eventWrappers, options, queue } = handlerWrapper;
this.initConnectionIfRequired();
this.initChannelIfRequired();
const handlerExchanges = eventWrappers.map((event) => event.options.exchange);
const exchangesToAssert = (0, lodash_1.chain)(handlerExchanges)
.filter((exchange) => !this.exchanges.has(exchange))
.uniq()
.value();
exchangesToAssert.forEach((exchange) => this.exchanges.add(exchange));
const bindingPatterns = eventWrappers.map((event) => this.extractBindingPattern(event));
await this.channelWrapper.addSetup(async (channel) => {
await Promise.all([
...exchangesToAssert.map(async (exchange) => channel.assertExchange(exchange, 'topic', this.assertExchangeOptions)),
channel.assertQueue(queue, this.bindingOptions(options.bindingQueueOptions)),
...this.bindEvents(channel, queue, eventWrappers),
channel.consume(queue, (msg) => {
try {
onMessage(msg);
}
catch (e) {
this.logger().warn(`Message execution/acknowledge error: [${e.message}]`);
}
}),
]);
this.logger().log(`Listening for "${bindingPatterns.join(', ')}" events from [${handlerExchanges.join(', ')} <- ${queue}]`);
await this.eventBus.publish(new lifecycle_event_1.HandlerBound(handler.name));
});
}
extractBindingPattern(mappedEvent) {
var _a;
const { event, options: { customBindingPattern, customRoutingKey }, } = mappedEvent;
return (_a = customBindingPattern !== null && customBindingPattern !== void 0 ? customBindingPattern : customRoutingKey) !== null && _a !== void 0 ? _a : (0, utils_1.toEventName)(event.name);
}
configureAutoAck(wrapper) {
var _a;
this.prepareHandlerStrategies[(_a = wrapper.options.autoAck) !== null && _a !== void 0 ? _a : interface_1.AutoAckEnum.ALWAYS_ACK].process(wrapper, this);
}
addHandleCatch(handlerWrapper) {
const { handler } = handlerWrapper;
const originalMethod = handler.prototype.handle;
const logger = this.logger();
Reflect.defineProperty(handler.prototype, 'handle', {
...Reflect.getOwnPropertyDescriptor(handler.prototype, 'handle'),
async value(event) {
try {
await originalMethod.apply(this, [event]);
}
catch (error) {
logger.error(JSON.stringify({
error,
event,
}), error instanceof Error ? error.stack : undefined, handler.name);
}
},
});
}
ack(message) {
if ((0, utils_1.appInTestingMode)()) {
return;
}
this.channelWrapper.ack(message);
}
nack(message) {
if ((0, utils_1.appInTestingMode)()) {
return;
}
this.channelWrapper.nack(message);
}
async publish(exchange, routingKey, content, options) {
await this.channelWrapper.publish(exchange, routingKey, content, options);
}
async configureRetryInfrastructure(wrappers) {
var _a, _b;
const wrappersWithRetryStrategy = wrappers.filter((wrapper) => wrapper.options.autoAck === interface_1.AutoAckEnum.AUTO_RETRY);
const maxRetryAttempts = Math.max(this.rootRetryOptions.maxRetryAttempts, ...wrappersWithRetryStrategy.map((wrapper) => { var _a, _b; return (_b = (_a = wrapper.options.retryOptions) === null || _a === void 0 ? void 0 : _a.maxRetryAttempts) !== null && _b !== void 0 ? _b : 0; }));
if (!maxRetryAttempts) {
(_b = (_a = this.logger()).debug) === null || _b === void 0 ? void 0 : _b.call(_a, 'Retry infrastructure configuration skipped because no handlers with auto retry enabled found');
return;
}
if (maxRetryAttempts < 0) {
throw new Error(`Invalid max retry count value (${maxRetryAttempts})`);
}
if (maxRetryAttempts > 100) {
throw new Error(`Too great value for max retry count (${maxRetryAttempts})`);
}
await Promise.all((0, lodash_1.chain)(wrappersWithRetryStrategy)
.groupBy((wrapper) => { var _a, _b; return (_b = (_a = wrapper.options.retryOptions) === null || _a === void 0 ? void 0 : _a.strategy) !== null && _b !== void 0 ? _b : interface_1.RetryStrategyEnum.DEAD_LETTER_TTL; })
.entries()
.map(async ([strategy, wrappers]) => this.retryStrategies[strategy].setupInfrastructure(this.channelWrapper, wrappers))
.value());
}
queue(handlerWrapper) {
const pckName = process.env.npm_package_name.split('/').pop();
const platform = pckName.replace(/[_-]/gi, '.');
return [platform, (0, utils_1.toSnakeCase)(handlerWrapper.handler.name)].join(':');
}
bindingOptions(extra = {}) {
return {
...this.consumerConfiguration(),
...extra,
};
}
consumerConfiguration() {
return this.bindingQueueOptions;
}
bindEvents(channel, queueName, eventWrappers) {
return eventWrappers.map(async (event) => {
return channel.bindQueue(queueName, event.options.exchange, this.extractBindingPattern(event));
});
}
};
exports.Consumer = Consumer;
exports.Consumer = Consumer = tslib_1.__decorate([
(0, common_1.Injectable)(),
tslib_1.__param(1, (0, common_1.Inject)(provider_1.CQRS_RETRY_STRATEGIES)),
tslib_1.__param(2, (0, common_1.Inject)(configuration_1.CQRS_MODULE_CONSUMER_OPTIONS)),
tslib_1.__param(3, (0, common_1.Inject)(configuration_1.CQRS_RETRY_OPTIONS)),
tslib_1.__param(4, (0, common_1.Inject)(configuration_1.CQRS_BINDING_QUEUE_CONFIG)),
tslib_1.__param(5, (0, common_1.Inject)(provider_1.CQRS_PREPARE_HANDLER_STRATEGIES)),
tslib_1.__metadata("design:paramtypes", [EventBus_1.EventBus, Object, Object, Object, Object, Object])
], Consumer);
//# sourceMappingURL=Consumer.js.map