UNPKG

@bgaldino/nestjs-rabbitmq

Version:

A different way of configuring your RabbitMQ

210 lines 10.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.RabbitMQConsumer = void 0; const common_1 = require("@nestjs/common"); const faster_stable_stringify_1 = __importDefault(require("faster-stable-stringify")); const amqp_connection_manager_1 = require("./amqp-connection-manager"); const helper_1 = require("./helper"); class RabbitMQConsumer { constructor(connection, options, publishChannelWrapper) { var _a, _b, _c; this.defaultConsumerOptions = { autoAck: true, durable: true, prefetch: 10, autoDelete: false, retryStrategy: { enabled: true, maxAttempts: 5, delay: () => 5000, }, deadLetterStrategy: { callback: async () => true, suffix: ".dlq", }, }; this.connection = connection; this.options = options; this.delayExchange = `${this.options.delayExchangeName}.delay`; this.publishChannel = publishChannelWrapper; this.logType = (_a = process.env.RABBITMQ_LOG_TYPE) !== null && _a !== void 0 ? _a : this.options.extraOptions.logType; this.logger = (_c = (_b = options === null || options === void 0 ? void 0 : options.extraOptions) === null || _b === void 0 ? void 0 : _b.loggerInstance) !== null && _c !== void 0 ? _c : new common_1.Logger(RabbitMQConsumer.name); } async createConsumer(consumer, messageHandler) { consumer = (0, helper_1.merge)(this.defaultConsumerOptions, consumer); const consumerChannel = this.connection.createChannel({ confirm: true, name: consumer.queue, setup: (channel) => { var _a, _b; return Promise.all([ channel.prefetch(consumer.prefetch), channel.assertQueue(consumer.queue, { durable: consumer.durable, autoDelete: consumer.autoDelete, deadLetterRoutingKey: `${consumer.queue}${(_b = (_a = consumer.deadLetterStrategy) === null || _a === void 0 ? void 0 : _a.suffix) !== null && _b !== void 0 ? _b : ".dlq"}`, deadLetterExchange: "", }), new Promise((resolve) => { if (typeof consumer.routingKey === "object") { for (const rk of consumer.routingKey) { channel.bindQueue(consumer.queue, consumer.exchangeName, rk); } } else { channel.bindQueue(consumer.queue, consumer.exchangeName, consumer.routingKey); } resolve(true); }), this.attachRetryAndDLQ(channel, consumer), channel.consume(consumer.queue, async (message) => { await this.processConsumerMessage(message, channel, consumer, messageHandler); }), ]); }, }); return consumerChannel; } async processConsumerMessage(message, channel, consumer, callback) { let hasErrors = null; let hasRetried = false; const start = process.hrtime.bigint(); try { await callback((0, helper_1.tryParseJson)(message.content.toString("utf8")), { message, channel, queue: consumer.queue, }); } catch (e) { hasErrors = e; hasRetried = await this.processRetry(consumer, message); } finally { if (["consumer", "all"].includes(this.logType) || hasErrors) this.inspectConsumer({ binding: { queue: consumer.queue, routingKey: message.fields.routingKey, exchange: consumer.exchangeName, }, consumeMessage: message, error: hasErrors, elapsedTime: process.hrtime.bigint() - start, isDead: hasErrors && !hasRetried, }); this.ackMessage(channel, message, consumer, hasErrors, hasRetried); } } async attachRetryAndDLQ(channel, consumer) { var _a, _b, _c; const deadletterQueue = `${consumer.queue}${(_b = (_a = consumer.deadLetterStrategy) === null || _a === void 0 ? void 0 : _a.suffix) !== null && _b !== void 0 ? _b : ".dlq"}`; await channel.assertQueue(deadletterQueue, { durable: true }); if (((_c = consumer === null || consumer === void 0 ? void 0 : consumer.retryStrategy) === null || _c === void 0 ? void 0 : _c.enabled) == false) { await channel.unbindQueue(consumer.queue, this.delayExchange, consumer.queue); } else { await channel.assertExchange(this.delayExchange, "x-delayed-message", { durable: true, arguments: { "x-delayed-type": "direct" }, }); await channel.bindQueue(consumer.queue, this.delayExchange, consumer.queue); } } async processRetry(consumer, message) { var _a, _b, _c; let isPublished = false; if (consumer.retryStrategy === undefined || consumer.retryStrategy.enabled === undefined || (consumer === null || consumer === void 0 ? void 0 : consumer.retryStrategy.enabled)) { const retryCount = (_c = (_b = (_a = message.properties) === null || _a === void 0 ? void 0 : _a.headers) === null || _b === void 0 ? void 0 : _b["x-retries-count"]) !== null && _c !== void 0 ? _c : 0; const maxRetry = consumer.retryStrategy.maxAttempts; if (retryCount < maxRetry) { const retryDelay = consumer.retryStrategy.delay(retryCount); try { isPublished = await this.publishChannel.publish(this.delayExchange, consumer.queue, (0, faster_stable_stringify_1.default)((0, helper_1.tryParseJson)(message.content.toString("utf8"))), { headers: Object.assign(Object.assign({}, message.properties.headers), { "x-retries-count": retryCount + 1, "x-delay": retryDelay }), deliveryMode: 2, //persistent message persistent: true, }); } catch (e) { this.logger.error({ message: "could_not_retry", error: e }); isPublished = false; } } } return isPublished; } inspectConsumer(args) { var _a, _b; const { binding, consumeMessage, data, error } = args; const { exchange, routingKey, queue } = binding; const { content, fields, properties } = consumeMessage; const message = `[AMQP] [CONSUMER] [${exchange}] [${routingKey}] [${queue}]`; const logLevel = error ? "error" : "log"; const logData = { logLevel, type: "consumer", duration: args.elapsedTime.toString(), correlationId: (_b = (_a = args === null || args === void 0 ? void 0 : args.consumeMessage) === null || _a === void 0 ? void 0 : _a.properties) === null || _b === void 0 ? void 0 : _b.correlationId, binding, title: message, isDead: args.isDead, consumedMessage: { fields, properties, content: data !== null && data !== void 0 ? data : (0, helper_1.tryParseJson)(content.toString("utf8")), }, }; if (error) { logData["error"] = { stack: error === null || error === void 0 ? void 0 : error.stack, message: error === null || error === void 0 ? void 0 : error.message, name: error === null || error === void 0 ? void 0 : error.name, }; } this.logger[logLevel](logData); } async ackMessage(channel, message, consumer, hasErrors, hasRetried) { var _a, _b, _c; if (!amqp_connection_manager_1.AMQPConnectionManager.consumerConn.isConnected()) { this.logger.error("Could not acknowledge message, Connection is offline"); return; } if ((!hasErrors && (consumer === null || consumer === void 0 ? void 0 : consumer.autoAck)) || (hasErrors && hasRetried)) { channel.ack(message); } else if (hasErrors && !hasRetried) { let shouldNack = true; try { shouldNack = (_c = (await ((_b = (_a = consumer.deadLetterStrategy) === null || _a === void 0 ? void 0 : _a.callback) === null || _b === void 0 ? void 0 : _b.call(_a, message.content.toString("utf8"))))) !== null && _c !== void 0 ? _c : true; } catch (e) { this.logger.error({ type: "consumer", title: `[AMQP] [DEADLETTER] ${message.fields.exchange} ${message.fields.routingKey} ${message.fields} ${consumer.queue}`, error: { stack: e === null || e === void 0 ? void 0 : e.stack, message: e === null || e === void 0 ? void 0 : e.message, name: e === null || e === void 0 ? void 0 : e.name, }, }); } if (shouldNack) { channel.nack(message, false, false); } else { channel.ack(message); } } } } exports.RabbitMQConsumer = RabbitMQConsumer; //# sourceMappingURL=rabbitmq-consumers.js.map