nestjs-rabbitmq-retry
Version:
NestJS RabbitMQ Retry
141 lines • 6.63 kB
JavaScript
;
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var RabbitMQLoader_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.RabbitMQLoader = void 0;
const common_1 = require("@nestjs/common");
const nestjs_discovery_1 = require("@golevelup/nestjs-discovery");
const amqplib_1 = require("amqplib");
const rabbitmq_constants_1 = require("./rabbitmq.constants");
let RabbitMQLoader = exports.RabbitMQLoader = RabbitMQLoader_1 = class RabbitMQLoader {
constructor(config, discoveryService) {
this.config = config;
this.discoveryService = discoveryService;
this.logger = new common_1.Logger(RabbitMQLoader_1.name);
this.channels = new Map();
}
async onModuleInit() {
if (this.config.channels.filter((channel) => channel.primary).length === 0) {
throw new Error('One of the channels to be created needs to be the primary channel.');
}
else {
await this.createChannels();
const channels = await this.getChannel();
await this.createQueues(channels[0]);
await this.registerListeners();
}
}
onModuleDestroy() {
Promise.all([this.closeChannels(), this.connection.close()]);
}
async getConnection() {
try {
const amqp = this.config.isAMQPS ? 'amqps' : 'amqp';
const user = this.config.username;
const password = this.config.password;
const host = this.config.host;
this.connection = await (0, amqplib_1.connect)(`${amqp}://${user}:${password}@${host}`);
this.logger.log('Connection with RabbitMQ successfully established');
return this.connection;
}
catch (error) {
this.logger.error('Connection with RabbitMQ cannot be established successfully');
throw error;
}
}
async createChannels() {
const connection = await this.getConnection();
for (const item of this.config.channels) {
const channels = [];
for (let index = 0; index < item.concurrency; index++) {
const channel = await connection.createChannel();
await channel.prefetch(item.prefetch);
channels.push(channel);
}
this.channels.set(item.name, channels);
}
this.logger.log('Channels created successfully');
}
async closeChannels() {
this.channels.forEach((channels) => {
channels.forEach(async (channel) => await channel.close());
});
}
async getChannel(name) {
const channels = this.channels.get(name);
if (channels === undefined) {
const name = this.config.channels.find((channel) => channel.primary).name;
return this.channels.get(name);
}
return channels;
}
async createQueues(channel) {
for (const queue of this.config.queues) {
await Promise.all([
channel.assertExchange(queue.exchange.name, queue.exchange.type),
channel.assertQueue(queue.name, {
...queue.options,
deadLetterExchange: queue.exchange.name,
deadLetterRoutingKey: `${queue.name}.retry`,
}),
channel.assertQueue(`${queue.name}.retry`, {
messageTtl: queue.ttl,
deadLetterExchange: queue.exchange.name,
deadLetterRoutingKey: queue.name,
}),
channel.assertQueue(`${queue.name}.dlq`),
channel.bindQueue(queue.name, queue.exchange.name, queue.routingKey),
channel.bindQueue(queue.name, queue.exchange.name, queue.name),
channel.bindQueue(`${queue.name}.retry`, queue.exchange.name, `${queue.name}.retry`),
channel.bindQueue(`${queue.name}.dlq`, queue.exchange.name, `${queue.name}.dlq`),
]);
}
this.logger.log('Queues created successfully');
}
async registerListeners() {
const methods = await this.discoveryService.providerMethodsWithMetaAtKey(rabbitmq_constants_1.LISTENER_QUEUE);
methods.forEach((method) => {
const meta = method.meta;
const handler = (...args) => method.discoveredMethod.handler.apply(method.discoveredMethod.parentClass.instance, args);
this.listener(meta.queue, meta.channelName, handler);
});
}
async listener(queue, channelName, callback) {
const channels = await this.getChannel(channelName);
for (const channel of channels) {
channel.consume(queue, async (message) => {
try {
await callback(message.content.toString('utf8'), message.fields, message.properties);
channel.ack(message);
}
catch (error) {
if (message.properties.headers['x-death'] !== undefined &&
message.properties.headers['x-death'][0].count + 1 >= this.config.retry) {
channel.sendToQueue(`${queue}.dlq`, Buffer.from(JSON.stringify(message)));
channel.ack(message, false);
}
else {
channel.nack(message, false, false);
}
}
});
}
}
};
exports.RabbitMQLoader = RabbitMQLoader = RabbitMQLoader_1 = __decorate([
(0, common_1.Injectable)(),
__param(0, (0, common_1.Inject)(rabbitmq_constants_1.CONFIG_OPTIONS)),
__metadata("design:paramtypes", [Object, nestjs_discovery_1.DiscoveryService])
], RabbitMQLoader);
//# sourceMappingURL=rabbitmq.loader.js.map