UNPKG

@nam088/nestjs-rabbitmq

Version:

A comprehensive RabbitMQ module for NestJS with decorator-based message handling

216 lines 8.1 kB
"use strict"; 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 RabbitMQService_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.RabbitMQService = void 0; const crypto_1 = require("crypto"); const common_1 = require("@nestjs/common"); let RabbitMQService = RabbitMQService_1 = class RabbitMQService { constructor(connectionManager, connectionName, logLevel = 'error') { this.connectionManager = connectionManager; this.connectionName = connectionName; this.logger = new common_1.Logger(RabbitMQService_1.name); this.rpcQueues = new Map(); this.logLevel = logLevel; } async onModuleDestroy() { await this.close(); } getChannel() { return this.channel; } getConnectionManager() { return this.connectionManager; } async getReplyQueue() { const replyQueue = 'amq.rabbitmq.reply-to'; await this.channel.consume(replyQueue, (message) => { if (!message) return; const { correlationId } = message.properties; const callbacks = this.rpcQueues.get(replyQueue); if (!callbacks) return; if (!callbacks.has(correlationId)) return; const callback = callbacks.get(correlationId); const response = this.deserializeMessage(message.content); callback(response); callbacks.delete(correlationId); }, { noAck: true }); return replyQueue; } async publish(exchange, routingKey, message, options) { try { const content = this.serializeMessage(message); const publishOptions = { persistent: true, ...options, }; await this.channel.publish(exchange, routingKey, content, publishOptions); return true; } catch (error) { this.logger.error(`Failed to publish message to ${exchange}/${routingKey}`, error.stack); throw error; } } deserializeMessage(buffer) { try { const str = buffer.toString(); return JSON.parse(str); } catch { return buffer.toString(); } } serializeMessage(message) { if (Buffer.isBuffer(message)) { return message; } if (typeof message === 'string') { return Buffer.from(message); } return Buffer.from(JSON.stringify(message)); } async assertExchange(exchange, type, options) { await this.channel.assertExchange(exchange, type, { durable: true, ...options, }); } async assertQueue(queue, options) { await this.channel.assertQueue(queue, { durable: true, ...options, }); } async bindQueue(queue, exchange, routingKey) { await this.channel.bindQueue(queue, exchange, routingKey); } async close() { this.info(`Closing RabbitMQ connection: ${this.connectionName}`); await this.channel?.close(); await this.connectionManager?.close(); } async consume(queue, onMessage, options) { await this.channel.consume(queue, async (message) => { if (!message) { return; } try { const content = this.deserializeMessage(message.content); await onMessage(content); this.channel.ack(message); } catch (error) { this.logger.error(`Error processing message from ${queue}`, error.stack); this.channel.nack(message, false, false); } }, { noAck: false, ...options, }); this.info(`Started consuming from queue: ${queue}`); } async initialize() { this.info(`Initializing RabbitMQ channel for connection: ${this.connectionName}`); this.channel = this.connectionManager.createChannel({ json: false, setup: async (channel) => { await channel.prefetch(1); this.info(`Channel setup complete for: ${this.connectionName}`); }, }); await this.channel.waitForConnect(); this.info(`RabbitMQ channel connected for: ${this.connectionName}`); } isConnected() { return this.connectionManager.isConnected(); } async request(queue, message, options = {}) { const { publishOptions = {}, timeout = 30000 } = options; const correlationId = (0, crypto_1.randomUUID)(); const replyQueue = await this.getReplyQueue(); return new Promise(async (resolve, reject) => { const timeoutId = setTimeout(() => { const callbacks = this.rpcQueues.get(replyQueue); this.logger.error(`RPC timeout after ${timeout}ms (correlationId=${correlationId})`); callbacks?.delete(correlationId); reject(new Error(`RPC timeout after ${timeout}ms`)); }, timeout); if (!this.rpcQueues.has(replyQueue)) { this.rpcQueues.set(replyQueue, new Map()); } const callbackMap = this.rpcQueues.get(replyQueue); callbackMap.set(correlationId, (response) => { clearTimeout(timeoutId); resolve(response); }); try { await this.sendToQueue(queue, message, { ...publishOptions, replyTo: replyQueue, correlationId, }); } catch (error) { clearTimeout(timeoutId); callbackMap.delete(correlationId); this.logger.error(`[RPC] Send failed: correlationId=${correlationId}, queue=${queue}, error=${String(error?.message ?? error)}`, error?.stack); reject(error instanceof Error ? error : new Error(String(error))); } }); } async sendToQueue(queue, message, options) { try { const content = this.serializeMessage(message); const sendOptions = { persistent: true, ...options, }; await this.channel.sendToQueue(queue, content, sendOptions); return true; } catch (error) { this.logger.error(`Failed to send message to queue ${queue}`, error.stack); throw error; } } debug(message) { if (this.shouldLog('debug')) this.logger.debug(message); } info(message) { if (this.shouldLog('log')) this.logger.log(message); } shouldLog(level) { const order = { debug: 3, error: 0, log: 2, none: -1, warn: 1, }; return order[level] <= order[this.logLevel]; } warn(message) { if (this.shouldLog('warn')) this.logger.warn(message); } }; exports.RabbitMQService = RabbitMQService; exports.RabbitMQService = RabbitMQService = RabbitMQService_1 = __decorate([ (0, common_1.Injectable)(), __metadata("design:paramtypes", [Object, String, String]) ], RabbitMQService); //# sourceMappingURL=rabbitmq.service.js.map