UNPKG

smqp

Version:

Synchronous message queueing package

200 lines (199 loc) 5.89 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Exchange2Exchange = Exchange2Exchange; exports.Shovel = Shovel; var _Exchange = require("./Exchange.js"); var _Errors = require("./Errors.js"); const kName = Symbol.for('name'); const kBrokerInternal = Symbol.for('brokerInternal'); const kCloneMessage = Symbol.for('cloneMessage'); const kClosed = Symbol.for('closed'); const kConsumerTag = Symbol.for('consumerTag'); const kDestinationExchange = Symbol.for('destinationExchange'); const kEventHandlers = Symbol.for('eventHandlers'); const kSourceBroker = Symbol.for('sourceBroker'); const kSourceExchange = Symbol.for('sourceExchange'); const kE2EShovel = Symbol.for('shovel'); function Shovel(name, source, destination, options) { if (!name || typeof name !== 'string') throw new TypeError('Shovel name is required and must be a string'); const { broker: sourceBroker, exchange: sourceExchangeName, pattern, queue, priority } = source; const { broker: destinationBroker, exchange: destinationExchangeName } = destination; const sourceExchange = sourceBroker.getExchange(sourceExchangeName); if (!sourceExchange) { throw new _Errors.SmqpError(`shovel ${name} source exchange <${sourceExchangeName}> not found`, _Errors.ERR_SHOVEL_SOURCE_EXCHANGE_NOT_FOUND); } const destinationExchange = destinationBroker.getExchange(destinationExchangeName); if (!destinationExchange) { throw new _Errors.SmqpError(`shovel ${name} destination exchange <${destinationExchangeName}> not found`, _Errors.ERR_SHOVEL_DESTINATION_EXCHANGE_NOT_FOUND); } if (!(this instanceof Shovel)) { return new Shovel(name, source, destination, options); } this[kBrokerInternal] = sourceBroker === destinationBroker; const routingKeyPattern = pattern || '#'; this[kName] = name; this.source = { ...source, pattern: routingKeyPattern }; this.destination = { ...destination }; this.events = new _Exchange.EventExchange('shovel__events'); const consumerTag = this[kConsumerTag] = source.consumerTag || `smq.shoveltag-${name}`; this[kClosed] = false; this[kSourceBroker] = sourceBroker; this[kSourceExchange] = sourceExchange; this[kDestinationExchange] = destinationExchange; this[kCloneMessage] = options?.cloneMessage; const boundClose = this.close.bind(this); const eventHandlers = this[kEventHandlers] = new Set([sourceExchange.on('delete', boundClose), destinationExchange.on('delete', boundClose)]); let consumer; const shovelHandler = this._onShovelMessage.bind(this); if (queue) { consumer = sourceBroker.subscribe(sourceExchangeName, routingKeyPattern, queue, shovelHandler, { consumerTag, priority }); } else { consumer = sourceBroker.subscribeTmp(sourceExchangeName, routingKeyPattern, shovelHandler, { consumerTag, priority }); this.source.queue = consumer.queue.name; } eventHandlers.add(consumer.on('cancel', boundClose)); } Object.defineProperties(Shovel.prototype, { name: { get() { return this[kName]; } }, closed: { get() { return this[kClosed]; } }, consumerTag: { get() { return this[kConsumerTag]; } } }); Shovel.prototype.emit = function emit(eventName, content) { this.events.emit(`shovel.${eventName}`, content); }; Shovel.prototype.on = function on(eventName, handler, options) { return this.events.on(`shovel.${eventName}`, handler, options); }; Shovel.prototype.off = function off(eventName, handler) { return this.events.off(`shovel.${eventName}`, handler); }; Shovel.prototype.close = function closeShovel() { if (this[kClosed]) return; this[kClosed] = true; for (const eh of this[kEventHandlers]) eh.cancel(); this[kEventHandlers].clear(); const events = this.events; this.emit('close', this); events.close(); this[kSourceBroker].cancel(this[kConsumerTag]); }; Shovel.prototype._messageHandler = function messageHandler(message) { const cloneMessage = this[kCloneMessage]; if (!cloneMessage) return message; const { fields, content, properties } = message; const { content: newContent, properties: newProperties } = cloneMessage({ fields: { ...fields }, content, properties: { ...properties } }); return { fields, content: newContent, properties: { ...properties, ...newProperties } }; }; Shovel.prototype._onShovelMessage = function onShovelMessage(routingKey, message) { const destinationExchange = this[kDestinationExchange]; if (!destinationExchange.bindingCount && !message.properties.mandatory) return message.ack(); const { content, properties } = this._messageHandler(message); const props = { ...properties, ...this.destination.publishProperties, 'source-exchange': this[kSourceExchange].name }; if (!this[kBrokerInternal]) props['shovel-name'] = this[kName]; destinationExchange.publish(this.destination.exchangeKey || routingKey, content, props); message.ack(); }; function Exchange2Exchange(shovel) { this[kE2EShovel] = shovel; } Object.defineProperties(Exchange2Exchange.prototype, { name: { get() { return this[kE2EShovel].name; } }, source: { get() { return this[kE2EShovel].source.exchange; } }, destination: { get() { return this[kE2EShovel].destination.exchange; } }, pattern: { get() { return this[kE2EShovel].source.pattern; } }, queue: { get() { return this[kE2EShovel].source.queue; } }, consumerTag: { get() { return this[kE2EShovel].consumerTag; } } }); Exchange2Exchange.prototype.on = function e2eon(...args) { return this[kE2EShovel].on(...args); }; Exchange2Exchange.prototype.close = function e2eclose() { return this[kE2EShovel].close(); };