UNPKG

@fanckler/processing-utils

Version:

This library contains all common solutions for system microservices

297 lines (286 loc) 7.82 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.emitEvent = emitEvent; exports.onEventListeners = onEventListeners; exports.sendEventMessage = sendEventMessage; exports.sendResponse = sendResponse; var _amqplib = _interopRequireDefault(require("amqplib")); var _lodash = _interopRequireDefault(require("lodash")); var _events = _interopRequireDefault(require("events")); var _uuid = require("uuid"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } let instance; class RmqClient { constructor() { this.queues = {}; } /** * * @returns {Promise<RmqClient>} */ async init(url) { try { this.connection = await _amqplib.default.connect(url); this.channel = await this.connection.createChannel(); this.channel.responseEmitter = new _events.default(); this.channel.responseEmitter.setMaxListeners(0); this.channel.consume('amq.rabbitmq.reply-to', msg => { this.channel.responseEmitter.emit(msg.properties.correlationId, JSON.parse(msg.content.toString())); }, { noAck: true }); console.log('>>> RmqClient connected!'); return this; } catch (e) { console.log('>>> RmqClient ERROR!', e.message); throw e; } } /** * @param ex * @param routingKey * @param msg * @returns {Promise<void>} */ async publish({ ex, routingKey }, msg) { const queue = `${ex}.${routingKey}`; await this.channel.assertQueue(queue, { durable: true }); this.channel.bindQueue(queue, ex, routingKey); this.channel.publish(ex, routingKey, Buffer.from(msg), { messageId: (0, _uuid.v4)() }); } /** * * @param ex * @param routingKey * @param messageId * @param msg * @param resolve */ send({ ex, routingKey, messageId = (0, _uuid.v4)(), expiration }, msg, resolve, reject) { const queue = `${ex}.${routingKey}`; const correlationId = (0, _uuid.v4)(); let isRead = false; const rejectTimeout = expiration && setTimeout(() => { if (!isRead) { reject(new Error('Microservice does not answer!')); } return clearTimeout(rejectTimeout); }, expiration); this.channel.responseEmitter.once(correlationId, data => { resolve(data); isRead = true; clearTimeout(rejectTimeout); }); this.channel.sendToQueue(queue, Buffer.from(msg), { correlationId, replyTo: 'amq.rabbitmq.reply-to', messageId, expiration }); } /** * @param exchange * @param bindingKey * @param handler * @returns {Promise<function(): Promise<void>>} */ async subscribe({ exchange, bindingKey }, handler) { const queue = `${exchange}.${bindingKey}`; if (!this.connection) { await this.init(); } await this.channel.assertExchange(exchange, 'direct', { durable: true }); if (this.queues[queue]) { const existingHandler = _lodash.default.find(this.queues[queue], h => h === handler); if (existingHandler) { return () => this.unsubscribe(queue, existingHandler); } this.queues[queue].push(handler); return () => this.unsubscribe(queue, handler); } await this.channel.assertQueue(queue, { durable: true }); this.channel.bindQueue(queue, exchange, bindingKey); this.queues[queue] = [handler]; this.channel.consume(queue, async msg => { const ack = _lodash.default.once(() => this.channel.ack(msg)); this.queues[queue].forEach(h => h(msg, ack)); }); return () => this.unsubscribe(queue, handler); } /** * @param queue * @param handler * @returns {Promise<void>} */ async unsubscribe(queue, handler) { _lodash.default.pull(this.queues[queue], handler); } } /** * Singleton * @returns {Promise<RmqClient>} */ RmqClient.getInstance = async function (url) { if (!instance) { const broker = new RmqClient(); instance = broker.init(url); } return instance; }; /** * * @param url RMQ URL CONNECTION * @param events * * events = [ * { * queue: exchange который слушаем; * event: тип ивента; * callback: (payload, closeQueue) => void; * payload = { * data: сообщение, любой тим данных; * properties: если сообщение ожидает обратного ответа - присылаем replyTo и correlationId любо undefined; * }, * closeQueue: коллбек закрытия очереди по завершению логики или ошибки; * } * ] */ function onEventListeners(url, events) { RmqClient.getInstance(url).then(broker => { function onEventListen(queue, event, callback, log) { let msgId; broker.subscribe({ exchange: queue, bindingKey: event }, (data, closeQueue) => { console.log('broker subscribe', data, queue, event); try { const { replyTo, correlationId, messageId } = data.properties; console.log(`>>> Subscription RMQ ${queue}.${event} ${log ? JSON.stringify(JSON.parse(data.content)) : '*** hidden ***'}\n`, JSON.stringify({ replyTo, correlationId, messageId })); if (msgId === messageId) { console.log('>>> Message with this id has already been processed'); return closeQueue(); } msgId = messageId; callback({ data: JSON.parse(data.content), properties: replyTo && correlationId && { replyTo, correlationId } }, closeQueue); } catch (e) { console.log('broker subscribe error', e); closeQueue(); } }); } if (!Array.isArray(events)) { throw new Error('onEventListeners expects an array of rabbit connections'); } events.forEach(item => { const { queue, event, callback, log = true } = item; onEventListen(queue, event, callback, log); }); }); } /** * * @param url * @param queue * @param event * @param payload * @param log * @returns {Promise<void>} */ async function emitEvent(url, { queue, event, payload }, log = true) { const broker = await RmqClient.getInstance(url); await broker.publish({ ex: queue, routingKey: event }, JSON.stringify(payload)); console.log(`>>> Emit RMQ ${queue}.${event} \n`, log ? JSON.stringify(payload) : '*** hidden ***'); } /** * * @param url * @param queue * @param event * @param payload * @param messageId * @param expiration * @param log * @returns {Promise<void>} */ function sendEventMessage(url, { queue, event, payload, messageId, expiration }, log = true) { return new Promise((resolve, reject) => { RmqClient.getInstance(url).then(broker => { broker.send({ ex: queue, routingKey: event, messageId, expiration }, JSON.stringify(payload), resolve, reject); console.log(`>>> Send message RMQ ${queue}.${event} \n`, log ? JSON.stringify(payload) : '*** hidden ***'); }); }); } /** * * @param replyTo * @param correlationId * @param response * @param log * @returns {Promise<void>} */ async function sendResponse(replyTo, correlationId, response, log = true) { const broker = await RmqClient.getInstance(); broker.channel.sendToQueue(replyTo, Buffer.from(JSON.stringify(response)), { correlationId }); console.log(`>>> Send response RMQ to ${correlationId} -> ${replyTo} \n`, log ? JSON.stringify(response) : '*** hidden ***'); }