UNPKG

@rxap/nest-amqp

Version:
220 lines (219 loc) 9.91 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 { options; logger; static instances = []; transportId = exports.TRANSPORT_ID; server = null; channel = null; connectionAttempts = 0; queue; errorSerializer = new error_serializer_1.ErrorSerializer(); constructor(options, logger = new common_1.Logger(microservices_1.Server.name)) { super(); this.options = options; this.logger = logger; ServerRMQ.instances.push(this); this.initializeSerializer(options); this.initializeDeserializer(options); } async listen(callback) { try { await this.start(callback); } catch (err) { callback(err); } } close() { this.channel?.close(); 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')); const maxConnectionAttempts = this.getOptionsProp(this.options, 'maxConnectionAttempts', INFINITE_CONNECTION_ATTEMPTS); this.server.on(constants_1.CONNECT_EVENT, () => { this.logger.log('Connected to RMQ server', 'ServerRMQ'); if (this.channel) { return; } this.logger.log('Creating RMQ channel', 'ServerRMQ'); this.channel = this.server.createChannel({ json: false, setup: (channel) => this.setupChannel(channel, callback), }); }); this.server.on(constants_1.DISCONNECT_EVENT, (err) => { this.logger.verbose?.('Disconnected from RMQ with error %JSON', err, 'ServerRMQ'); this.logger.error('Disconnected from RMQ with error: %JSON ', err.message, undefined, 'ServerRMQ'); }); this.server.on(constants_1.CONNECT_FAILED_EVENT, (error) => { this.logger.verbose?.('Connection failed error %JSON', error, 'ServerRMQ'); if (error?.['err']) { this.logger.error('Connection to RMQ failed with error: %JSON', error['err'], undefined, 'ServerRMQ'); } else { this.logger.error('Connection to RMQ failed with no error', 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'); this.logger.verbose?.(`Creating RMQ client with options: %JSON`, socketOptions, 'ServerRMQ'); 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'); if (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.logger.verbose?.(`Sending response to '${replyTo}': %JSON`, outgoingResponse, 'ServerRMQ'); 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;