UNPKG

@rxap/nest-rabbitmq

Version:

This package provides a NestJS module for integrating with RabbitMQ using exchanges. It offers a client and server implementation for message queuing and supports features like health checks and error serialization. It simplifies the process of setting up

202 lines (201 loc) 9.24 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ServerRMQ = exports.TRANSPORT_ID = void 0; const common_1 = require("@nestjs/common"); const shared_utils_1 = require("@nestjs/common/utils/shared.utils"); const microservices_1 = require("@nestjs/microservices"); const constants_1 = require("@nestjs/microservices/constants"); const serializers_1 = require("@nestjs/microservices/serializers"); const utilities_1 = require("@rxap/utilities"); const amqp_connection_manager_1 = require("amqp-connection-manager"); const rxjs_1 = require("rxjs"); const error_serializer_1 = require("./error.serializer"); const incoming_request_deserializer_1 = require("./incoming-request.deserializer"); const INFINITE_CONNECTION_ATTEMPTS = -1; exports.TRANSPORT_ID = Symbol('RxAP_RMQ'); class ServerRMQ extends microservices_1.Server { constructor(options, logger = new common_1.Logger(microservices_1.Server.name)) { super(); this.options = options; this.logger = logger; this.transportId = exports.TRANSPORT_ID; this.server = null; this.channel = null; this.connectionAttempts = 0; this.errorSerializer = new error_serializer_1.ErrorSerializer(); this.initializeSerializer(options); this.initializeDeserializer(options); } async listen(callback) { try { await this.start(callback); } catch (err) { callback(err); } } close() { this.channel && this.channel.close(); this.server && this.server.close(); } bindQueue(exchange, routingKey) { this.logger.verbose?.(`Binding queue to exchange '${exchange}' with routing key '${routingKey}'`, 'ServerRMQ'); return this.channel.bindQueue(this.queue, exchange, routingKey); } async start(callback) { this.logger.verbose?.('Connecting to RMQ server...', 'ServerRMQ'); this.server = this.createClient(); this.server.addListener(constants_1.ERROR_EVENT, (err) => this.logger.error(err, undefined, 'ServerRMQ')); this.server.on(constants_1.CONNECT_EVENT, () => { if (this.channel) { return; } this.channel = this.server.createChannel({ json: false, setup: (channel) => this.setupChannel(channel, callback), }); }); const maxConnectionAttempts = this.getOptionsProp(this.options, 'maxConnectionAttempts', INFINITE_CONNECTION_ATTEMPTS); this.server.on(constants_1.DISCONNECT_EVENT, (err) => { this.logger.error(constants_1.DISCONNECTED_RMQ_MESSAGE + ': ' + err.message, undefined, 'ServerRMQ'); }); this.server.on(constants_1.CONNECT_FAILED_EVENT, (error) => { this.logger.error(constants_1.CONNECTION_FAILED_MESSAGE); if (error?.['err']) { this.logger.error(constants_1.CONNECTION_FAILED_MESSAGE + ': ' + error['err'], undefined, 'ServerRMQ'); } const isReconnecting = !!this.channel; if (maxConnectionAttempts === INFINITE_CONNECTION_ATTEMPTS || isReconnecting) { return; } if (++this.connectionAttempts === maxConnectionAttempts) { this.close(); callback?.(error?.['err'] ?? new Error(constants_1.CONNECTION_FAILED_MESSAGE)); } }); } send(stream$, respond) { let dataBuffer = null; const scheduleOnNextTick = (data) => { if (!dataBuffer) { dataBuffer = [data]; process.nextTick(async () => { for (const item of dataBuffer) { await respond(item); } dataBuffer = null; }); } else if (!data.isDisposed) { dataBuffer = dataBuffer.concat(data); } else { dataBuffer[dataBuffer.length - 1].isDisposed = data.isDisposed; } }; return stream$ .pipe((0, rxjs_1.catchError)((err) => { scheduleOnNextTick({ err: this.errorSerializer.serialize(err) }); return rxjs_1.EMPTY; }), (0, rxjs_1.finalize)(() => scheduleOnNextTick({ isDisposed: true }))) .subscribe((response) => scheduleOnNextTick({ response })); } createClient() { const socketOptions = this.getOptionsProp(this.options, 'socketOptions'); return (0, amqp_connection_manager_1.connect)(this.options.urls, socketOptions); } async setupChannel(channel, callback) { if (!this.options.noAssert) { for (const exchange of (0, utilities_1.coerceArray)(this.options.exchange)) { await channel.assertExchange(exchange.name, exchange.type, exchange.options); } const { queue } = await channel.assertQueue(this.options.queue ?? '', this.options.queueOptions); this.queue = queue; } else { this.queue = this.options.queue ?? ''; } const r = await channel.prefetch(this.options.prefetchCount ?? 0, this.options.isGlobalPrefetchCount); channel.consume(this.queue, (msg) => this.handleMessage(msg, channel), { prefetch: this.options.prefetchCount ?? 0, noAck: this.options.noAck ?? true, consumerTag: this.getOptionsProp(this.options, 'consumerTag', undefined), }); callback?.(); } async handleMessage(message, channel) { this.logger.verbose?.('Message received', 'ServerRMQ'); if ((0, shared_utils_1.isNil)(message)) { return; } const { content, properties, fields, } = message; const rawMessage = this.parseMessageContent(content); this.logger.verbose?.('Message content: %JSON', rawMessage, 'ServerRMQ'); this.logger.verbose?.('Message properties: %JSON', properties, 'ServerRMQ'); this.logger.verbose?.('Message fields: %JSON', fields, 'ServerRMQ'); const packet = await this.deserializer.deserialize(rawMessage, { fields, properties, }); this.logger.debug?.('Extracted packet message content: %JSON', packet, 'ServerRMQ'); const pattern = ((0, shared_utils_1.isString)(packet.pattern) ? packet.pattern : JSON.stringify(packet.pattern)); const rmqContext = new microservices_1.RmqContext([message, channel, pattern]); if ((0, shared_utils_1.isUndefined)(packet.id)) { this.logger.debug?.('Message without correlation id', 'ServerRMQ'); return this.handleEvent(pattern, packet, rmqContext); } const handler = this.getHandlerByPattern(pattern); if (!handler) { if (!(this.options.noAck ?? true)) { this.logger.warn((0, constants_1.RQM_NO_MESSAGE_HANDLER) `${pattern}`); this.channel.nack(rmqContext.getMessage(), false, false); } const status = 'error'; const noHandlerPacket = { id: packet.id, err: constants_1.NO_MESSAGE_HANDLER, status, }; return this.sendMessage(noHandlerPacket, properties.replyTo, properties.correlationId); } const response$ = this.transformToObservable(handler(packet.data, rmqContext)); const publish = (data) => this.sendMessage(data, properties.replyTo, properties.correlationId); this.logger.verbose?.('Handling event and sending response', 'ServerRMQ'); response$ && this.send(response$, publish); } async handleEvent(pattern, packet, context) { const handler = this.getHandlerByPattern(pattern); if (!handler && !(this.options.noAck ?? true)) { this.channel.nack(context.getMessage(), false, false); return this.logger.warn((0, constants_1.RQM_NO_EVENT_HANDLER) `${pattern}`); } return super.handleEvent(pattern, packet, context); } sendMessage(message, replyTo, correlationId) { const outgoingResponse = this.serializer.serialize(message); const options = outgoingResponse.options; delete outgoingResponse.options; const buffer = Buffer.from(JSON.stringify(outgoingResponse)); this.channel.sendToQueue(replyTo, buffer, { correlationId, ...options }); } addHandler(pattern, callback, isEventHandler, extras) { this.logger.log(`Adding message handler for pattern '${pattern}'`, 'ServerRMQ'); super.addHandler(pattern, callback, isEventHandler, extras); } initializeDeserializer(options) { this.deserializer = options?.deserializer ?? new incoming_request_deserializer_1.RabbitMqIncomingRequestDeserializer(); } initializeSerializer(options) { this.serializer = options?.serializer ?? new serializers_1.RmqRecordSerializer(); } parseMessageContent(content) { try { return JSON.parse(content.toString()); } catch { return content.toString(); } } } exports.ServerRMQ = ServerRMQ;