UNPKG

@rxap/nest-amqp

Version:
178 lines (177 loc) 9.03 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AmqpService = void 0; const tslib_1 = require("tslib"); const common_1 = require("@nestjs/common"); const amqp_connection_manager_1 = require("amqp-connection-manager"); const rxjs_1 = require("rxjs"); const operators_1 = require("rxjs/operators"); const uuid_1 = require("uuid"); const tokens_1 = require("./tokens"); class AmqpService { initialized = new rxjs_1.ReplaySubject(1); isReady = (0, rxjs_1.firstValueFrom)(this.initialized.pipe((0, rxjs_1.filter)(Boolean))); exchangeOptions; exchange; queueOptions; options; channelOptions; connection; isConnected = this.isReady.then(() => this.connection.isConnected()); channel; queue; replayQueue = 'amq.rabbitmq.reply-to'; responseEmitter = new rxjs_1.Subject(); handler = new Map(); logger; async onApplicationBootstrap() { await this.init(); } async init() { this.connection = (0, amqp_connection_manager_1.connect)(null, this.options); this.channel = this.connection.createChannel({ json: true, setup: async (channel) => { if (this.channelOptions.prefetchCount) { await channel.prefetch(this.channelOptions.prefetchCount, true); } if (this.queueOptions.assert || !this.queueOptions.name) { this.logger.log(`Assert queue '${this.queueOptions.name ?? '<auto>'}': %JSON`, this.queueOptions.assert, 'AMQPService'); const { queue } = await channel.assertQueue(this.queueOptions.name ?? '', this.queueOptions.assert); this.queue = queue; } else { this.queue = this.queueOptions.name; } if (this.exchangeOptions.assert) { this.logger.log(`Assert exchange '${this.exchangeOptions.name}' with type '${this.exchangeOptions.type}': %JSON`, this.exchangeOptions.assert, 'AMQPService'); const { exchange } = await channel.assertExchange(this.exchangeOptions.name, this.exchangeOptions.type, this.exchangeOptions.assert); this.exchange = exchange; } else { this.exchange = this.exchangeOptions.name; } this.logger.log('Initialization completed', 'AMQPService'); this.initialized.next(true); }, }); await this.consumeReplayQueue(); await this.consumeQueue(); } ack(message, allToUp) { this.logger.verbose(`Acknowledged message with correlationId: ${message.properties.correlationId}`, 'AmqpService'); this.channel.ack(message, allToUp); } nack(message, allUpTo, requeue) { this.logger.log(`Not acknowledged message with correlationId: ${message.properties.correlationId} (allUpTo=${allUpTo}) (requeue=${requeue})`, 'AmqpService'); this.channel.nack(message, allUpTo, requeue); } async addHandler(routingKey, handler) { if (!this.handler.has(routingKey)) { this.handler.set(routingKey, new Set()); } this.handler.get(routingKey).add(handler); } async bind(pattern) { await this.isReady; this.logger.debug(`Bind queue '${this.queue}' with pattern '${pattern}' to exchange '${this.exchange}'`, 'AMQPServer'); await this.channel.bindQueue(this.queue, this.exchange, pattern); } async unbind(pattern) { await this.isReady; this.logger.verbose(`Bind queue '${this.queue}' with pattern '${pattern}' to exchange '${this.exchange}'`, 'AMQPServer'); await this.channel.unbindQueue(this.queue, this.exchange, pattern); } async send(routingKey, data, options = {}) { options.correlationId ??= (0, uuid_1.v6)(); options.replyTo = this.replayQueue; this.logger.verbose(`Send message with routing key '${routingKey}' and correlationId '${options.correlationId}' to exchange '${this.exchange}': %JSON`, data, 'AMQPService'); const response = this.responseEmitter.asObservable().pipe((0, rxjs_1.filter)((msg) => msg.properties.correlationId === options.correlationId), (0, operators_1.map)((msg) => JSON.parse(msg.content.toString())), (0, rxjs_1.tap)((data) => this.logger.verbose(`Receive replay for '${options.correlationId}': %JSON`, data, 'AMQPService'))); await this.isReady; const done = await this.channel.publish(this.exchange, routingKey, data, options); if (!done) { throw new common_1.BadGatewayException(`A message with routing key '${routingKey}' could not be send to exchange '${this.exchange}'`); } return (0, rxjs_1.firstValueFrom)(response); } async emit(routingKey, data, options = {}) { options.correlationId ??= (0, uuid_1.v6)(); this.logger.verbose(`Emit message with routing key '${routingKey}' and correlationId '${options.correlationId}' to exchange '${this.exchange}': %JSON`, data, 'AMQPService'); await this.isReady; const done = await this.channel.publish(this.exchange, routingKey, data, options); if (!done) { throw new common_1.BadGatewayException(`A message with routing key '${routingKey}' could not be emitted to exchange '${this.exchange}'`); } } async replay(replayQueue, data, options = {}) { this.logger.debug(`Replay to '${replayQueue}': %JSON`, data, 'AMQPService'); await this.isReady; const done = await this.channel.sendToQueue(replayQueue, data, options); if (!done) { throw new common_1.BadGatewayException(`A replay message to '${replayQueue}' could not be send`); } } async consumeReplayQueue() { this.logger.debug('Setup replay queue consumption', 'AMQPService'); await this.channel.consume(this.replayQueue, (msg) => { const correlationId = msg.properties.correlationId; if (!correlationId) { this.logger.error(`A message from the replay queue '${this.replayQueue}' does not have a correlationId`, undefined, 'AMQPService'); return; } this.responseEmitter.next(msg); }, { noAck: true }); } async consumeQueue() { this.logger.debug('Setup queue consumption', 'AMQPService'); await this.channel.consume(this.queue, (msg) => { const routingKey = msg.fields.routingKey; const correlationId = msg.properties.correlationId; this.logger.verbose(`Receive message with routing key '${routingKey}' and correlation id '${correlationId}'`, 'AMQPService'); if (!correlationId) { this.logger.error(`A message from the queue '${this.queue}' with routing key '${routingKey}' does not have a correlationId`, undefined, 'AMQPService'); return; } if (this.handler.has(msg.fields.routingKey)) { for (const handler of this.handler.get(routingKey).values()) { let result = undefined; try { result = handler(msg); } catch (error) { this.logger.error(`A handler for the message rom the queue '${this.queue}' with routing key '${routingKey}' throw an error: ${error.message}`, error.stack, 'AMQPService'); } if (result) { result.catch((error) => { this.logger.error(`An async handler for the message rom the queue '${this.queue}' with routing key '${routingKey}' throw an error: ${error.message}`, error.stack, 'AMQPService'); }); } } } else { this.logger.warn(`A message from the queue '${this.queue}' with routing key '${routingKey}' does not have a handler`, 'AMQPService'); } }); } } exports.AmqpService = AmqpService; tslib_1.__decorate([ (0, common_1.Inject)(tokens_1.AMQP_EXCHANGE_OPTIONS), tslib_1.__metadata("design:type", Object) ], AmqpService.prototype, "exchangeOptions", void 0); tslib_1.__decorate([ (0, common_1.Inject)(tokens_1.AMQP_QUEUE_OPTIONS), tslib_1.__metadata("design:type", Object) ], AmqpService.prototype, "queueOptions", void 0); tslib_1.__decorate([ (0, common_1.Inject)(tokens_1.AMQP_CONNECTION_MANAGER_OPTIONS), tslib_1.__metadata("design:type", Object) ], AmqpService.prototype, "options", void 0); tslib_1.__decorate([ (0, common_1.Inject)(tokens_1.AMQP_CHANNEL_OPTIONS), tslib_1.__metadata("design:type", Object) ], AmqpService.prototype, "channelOptions", void 0); tslib_1.__decorate([ (0, common_1.Inject)(common_1.Logger), tslib_1.__metadata("design:type", common_1.Logger) ], AmqpService.prototype, "logger", void 0);