@rxap/nest-amqp
Version:
@rxap/nest-amqp
178 lines (177 loc) • 9.03 kB
JavaScript
"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);