UNPKG

vulcain-corejs

Version:
180 lines 7.01 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const amqp = require("amqplib"); const system_1 = require("./../globals/system"); const crypto_1 = require("../utils/crypto"); class RabbitAdapter { constructor(address) { this.address = address; this.eventHandlers = new Map(); this.initialized = false; this.ignoreInputMessages = false; if (!this.address) throw new Error("Address is required for RabbitAdapter"); if (!address.startsWith("amqp://")) this.address = "amqp://" + address; } open() { let self = this; return new Promise((resolve, reject) => { if (self.initialized) { return resolve(); } self.initialized = true; system_1.Service.log.info(null, () => "Open rabbitmq connection on " + system_1.Service.removePasswordFromUrl(this.address)); amqp.connect(this.address).then((conn) => { conn.createChannel().then((ch) => { self.channel = ch; resolve(); }); }) .catch(err => { system_1.Service.log.error(null, err, () => `Unable to open rabbit connection.`); reject(); }); }); } pauseReception() { this.ignoreInputMessages = true; } resumeReception() { this.ignoreInputMessages = false; } stopReception() { this.pauseReception(); this.eventHandlers.forEach(eh => { this.channel.unbindQueue(eh.queue, eh.domain, eh.args); }); this.eventHandlers.clear(); } dispose() { this.stopReception(); this.channel.close(); } /** * Send domain event (event raises by an action) * Domain events are shared by all services of any domains * * @param {string} domain * @param {EventData} event * * @memberOf RabbitAdapter */ sendEvent(domain, event) { if (!this.channel) return; domain = this.createSourceName(domain); this.channel.assertExchange(domain, 'fanout', { durable: false }); this.channel.publish(domain, '', new Buffer(JSON.stringify(event))); } createSourceName(domain) { return "vulcain_" + domain.toLowerCase() + "_events"; } createEventQueueName(domain, key) { if (!key) return ''; // Create an unique by service + handler queue name // domain, service, version + hash return ["vulcain", domain.toLowerCase(), system_1.Service.fullServiceName, crypto_1.CryptoHelper.hash(key)].join('_'); } /** * Listening for domain events * * @param {string} domain * @param {Function} handler * @param {string} queuename * * If queuename is set, event are take into account by only one instance and a ack is send if the process complete sucessfully * else event is distributed to every instance with no ack */ consumeEvents(domain, handler, distributionKey) { if (!this.channel) return; let self = this; const queueName = this.createEventQueueName(domain, distributionKey); // Since this method can be called many times for a same domain // all handlers are aggregated on only one binding domain = this.createSourceName(domain); const handlerKey = domain + queueName; let handlerInfo = this.eventHandlers.get(handlerKey); if (handlerInfo) { handlerInfo.handlers.push(handler); return; } // First time for this domain, create the binding this.channel.assertExchange(domain, 'fanout', { durable: false }); // For one event delivery: // specific queue and exclusive=false // else // empty queue name and exclusive=true let options = { exclusive: !queueName, autoDelete: !!queueName }; this.channel.assertQueue(queueName, options).then(queue => { const handlers = [handler]; this.eventHandlers.set(handlerKey, { queue: queue.queue, domain, handlers, args: '' }); self.channel.bindQueue(queue.queue, domain, ''); self.channel.consume(queue.queue, async (msg) => { if (this.ignoreInputMessages) return; let obj = JSON.parse(msg.content.toString()); let handlerInfo = self.eventHandlers.get(handlerKey); try { if (handlerInfo) { let tasks = handlerInfo.handlers.map(h => h(obj)); if (queueName) { await Promise.all(tasks); self.channel.ack(msg); } } } catch (e) { system_1.Service.log.error(null, e, () => "Event handler failed for event " + obj.metadata.eventId); } }, { noAck: !queueName }); }); } /** * Task = asynchronous action * Shared by the current service instances * * @param {string} domain * @param {string} serviceId * @param {ActionData} command * * @memberOf RabbitAdapter */ publishTask(domain, serviceId, command) { if (!this.channel) return; domain = domain.toLowerCase(); this.channel.assertExchange(domain, 'direct', { durable: false }); this.channel.publish(domain, serviceId, new Buffer(JSON.stringify(command)), { persistent: true }); } /** * Listening for asynchronous task * * @param {string} domain * @param {string} serviceId * @param {Function} handler * * @memberOf RabbitAdapter */ consumeTask(domain, serviceId, handler) { if (!this.channel) return; let self = this; domain = domain.toLowerCase(); this.channel.assertExchange(domain, 'direct', { durable: false }); this.channel.assertQueue(domain, { durable: true }).then(queue => { this.eventHandlers.set("Async:" + domain, { queue: queue.queue, domain, handlers: [handler], args: serviceId }); // Channel name = serviceId self.channel.bindQueue(queue.queue, domain, serviceId); self.channel.prefetch(1); self.channel.consume(queue.queue, async (msg) => { if (this.ignoreInputMessages) return; await handler(JSON.parse(msg.content.toString())); self.channel.ack(msg); }, { noAck: false }); }); } } exports.RabbitAdapter = RabbitAdapter; //# sourceMappingURL=rabbitAdapter.js.map