UNPKG

seneca-servicebus-transport

Version:
138 lines (127 loc) 4.42 kB
'use strict' /** * Small utility module that groups functions * to resolve queues and topic names on listeners. * * @module lib/listener/listener-util */ const _ = require('lodash') const shortid = require('shortid') const topic = require('../common/topic') // Module API module.exports = { resolveListenQueue, resolveListenTopics, resolveSubscriptionName } /** * Listener queue names are generated from the act pattern. If said pattern * contains a '*' (wildcard) it'll be replaced by this string in the name. * For example, 'version:*' -> 'version.any'. * * @type {String} */ const WILDCARD_REPLACEMENT = 'any' /** * Builds a string based on the pins of a listener that can be used an AMQP * queue name. * * For a pin such as 'role:entity,cmd:save', it generates another string like: * 'seneca.role:entity.cmd:save' (using defaults). * * An array of pins such as ['role:entity,cmd:save', 'role:entity,cmd:list', * 'foo:*'], resolves to 'seneca.role:entity.cmd:save_list.foo:any'. * * Prefix and separator can be customized. * * @param {Array|String} pins `pin` or `pins` attribute of a listener. * @param {Object} options Listener options provided to `.listen(..)` method. * This methods looks for `opts.prefix` * and `opts.separator` attributes. * Both of them are optional. * @return {String} A generated name to be associated * to the queue of a listener. */ function resolveListenQueue (pins, { prefix = '', separator = '.' } = {}) { pins = _.castArray(pins) var composition = groupByKeys(pins) return toQueueName(composition, separator, prefix) } /** * Infers routing keys that should be used in listener queues bindings for a set * of pins. AMQP "routing keys" map to "topics" in the Seneca framework. * * @param {Object|Array} pins `pin` or `pins` a listener of client defines. * @return {Array} Routing keys (or "topics") derived from the given * `pins`. */ function resolveListenTopics (pins) { pins = _.castArray(pins) var topics = pins.map((p) => topic.resolveTopic(p)) return topics } function resolveSubscriptionName (id = '', separator = '.', prefix = '') { var sid = id || shortid.generate() return prefix && prefix.length ? `${prefix}${separator}${sid}` : sid } /** * For an array of `pins`, such as [{ role: 'entity', cmd: 'save' }, * { role: 'entity', cmd: 'list' }], groups together values of equal keys and * reduce it to a single hash object with all different keys in it to create: * * { role: 'entity', cmd: [ 'save', 'list' ]} * * @param {Array} pins Any array of map objects * @return {Object} A composition */ function groupByKeys (pins) { return _.reduce(pins, _groupByKey, {}) } /** * Constructs a name for a queue from keys and values of a `source` hash object. * Each key/value entry will be joined by a `separator` character. Keys and * values will be separated by a ':' (colon). The entire name will start * with the given `prefix`. * * Example: * * toQueueName({ role: 'entity', cmd: [ 'save', 'list' ] }, '.', 'seneca') * // 'seneca.role:entity.cmd:save_list' * * @param {Object} source Any plain flat object. * Nested objects are not supported. * @param {String} separator Character (or sequence of characters) to separate * each key/value pair with (will also follow the * prefix). Defaults to '.' (dot). * @param {String} prefix The queue name will start with this. * @return {String} A newly created queue name. */ function toQueueName (source, separator = '.', prefix = '') { return _stringify(prefix ? [prefix] : [], source).join(separator) } function _groupByKey (result, pin) { _.forOwn(pin, (value, k) => { if (result[k]) { if (_.isArray(result[k])) { result[k].push(value) } else { result[k] = [result[k], value] } result[k] = _.uniq(result[k]) } else { result[k] = value } }) return result } function _stringify (acum, o) { _.forOwn(o, (value, k) => { if (_.isArray(value)) { value = value.toString().replace(/,/g, '_') } else { value = value.toString().replace(/\*/g, WILDCARD_REPLACEMENT) } acum.push(`${k}:${value}`) }) return acum }