UNPKG

@goparrot/pubsub-event-bus

Version:
152 lines 6.92 kB
import { __decorate, __metadata, __param } from "tslib"; import { Inject, Injectable } from '@nestjs/common'; import { chain } from 'lodash'; import { AutoAckEnum, RetryStrategyEnum } from '../interface'; import { HandlerBound } from '../lifecycle-event'; import { CQRS_PREPARE_HANDLER_STRATEGIES, CQRS_RETRY_STRATEGIES } from '../provider'; import { appInTestingMode, toEventName, toSnakeCase } from '../utils'; import { CQRS_BINDING_QUEUE_CONFIG, CQRS_MODULE_CONSUMER_OPTIONS, CQRS_RETRY_OPTIONS } from '../utils/configuration'; import { EventBus } from './EventBus'; import { PubsubManager } from './PubsubManager'; let Consumer = class Consumer extends 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 (appInTestingMode()) { return; } const { handler, eventWrappers, options, queue } = handlerWrapper; this.initConnectionIfRequired(); this.initChannelIfRequired(); const handlerExchanges = eventWrappers.map((event) => event.options.exchange); const exchangesToAssert = 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 HandlerBound(handler.name)); }); } extractBindingPattern(mappedEvent) { const { event, options: { customBindingPattern, customRoutingKey }, } = mappedEvent; return customBindingPattern ?? customRoutingKey ?? toEventName(event.name); } configureAutoAck(wrapper) { this.prepareHandlerStrategies[wrapper.options.autoAck ?? 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 (appInTestingMode()) { return; } this.channelWrapper.ack(message); } nack(message) { if (appInTestingMode()) { return; } this.channelWrapper.nack(message); } async publish(exchange, routingKey, content, options) { await this.channelWrapper.publish(exchange, routingKey, content, options); } async configureRetryInfrastructure(wrappers) { const wrappersWithRetryStrategy = wrappers.filter((wrapper) => wrapper.options.autoAck === AutoAckEnum.AUTO_RETRY); const maxRetryAttempts = Math.max(this.rootRetryOptions.maxRetryAttempts, ...wrappersWithRetryStrategy.map((wrapper) => wrapper.options.retryOptions?.maxRetryAttempts ?? 0)); if (!maxRetryAttempts) { this.logger().debug?.('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(chain(wrappersWithRetryStrategy) .groupBy((wrapper) => wrapper.options.retryOptions?.strategy ?? 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, 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)); }); } }; Consumer = __decorate([ Injectable(), __param(1, Inject(CQRS_RETRY_STRATEGIES)), __param(2, Inject(CQRS_MODULE_CONSUMER_OPTIONS)), __param(3, Inject(CQRS_RETRY_OPTIONS)), __param(4, Inject(CQRS_BINDING_QUEUE_CONFIG)), __param(5, Inject(CQRS_PREPARE_HANDLER_STRATEGIES)), __metadata("design:paramtypes", [EventBus, Object, Object, Object, Object, Object]) ], Consumer); export { Consumer }; //# sourceMappingURL=Consumer.js.map