UNPKG

wabbit

Version:

A library to simplify working with RabbitMQ - built on top of Rabbot.

522 lines (478 loc) 15.3 kB
'use strict'; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var Rabbot = require('rabbot'), _ = require('lodash'), EventEmitter = require('events').EventEmitter, ee = new EventEmitter(), prefix = '[WABBIT]'; var instance = null; var Wabbit = function () { function Wabbit() { _classCallCheck(this, Wabbit); instance = instance || this; return instance; } _createClass(Wabbit, [{ key: 'rejectUnhandled', value: function rejectUnhandled() { Rabbot.rejectUnhandled(); } }, { key: 'nackOnError', value: function nackOnError() { Rabbot.nackOnError(); } }, { key: 'configure', value: function configure(config) { var _this = this; // NOTE // we will return a promise that will resolve // after we have fully configured Rabbot AND Wabbit // and registered all of the queues and exchanges return new Promise(function (resolve, reject) { Rabbot.configure(config).done(function () { if (_this.debug) { console.log(prefix, "Rabbot configured."); } var bindings = config.bindings; if (!bindings || !(bindings instanceof Array) || bindings.length < 1) { var err = "Wabbit.configure must be passed an [Object]"; if (_this.debug) { console.warn(prefix, err); } reject(new Error(err)); } else { bindings.map(function (config) { if (config.exchange) { var ex = new _this.Exchange(config.exchange); if (config.target) { var q = new _this.Queue({ name: config.target, keys: config.keys }); ex.registerQueue(q); } _this.registerExchange(ex); } }); // now set up our listener just in case any more exchanges // get added after this point ee.on('register:exchange', _this.runExchange); ee.on('register:queue', _this.runQueue); if (_this.debug) { console.log(prefix, "Wabbit configured."); } _this.ready = true; resolve(); } }); }); } }, { key: 'dump', value: function dump() { _.values(this.exchanges).map(function (exchange) { console.log(JSON.stringify(exchange, true, 2)); _.values(exchange.queues).map(function (queue) { console.log(JSON.stringify(queue, true, 2)); }); }); } }, { key: 'run', value: function run() { var _this2 = this; if (!this.ready) { var err = 'Wabbit has not been configured! Please make sure you run Wabbit.configure() first.'; if (this.debug) { console.warn(prefix, err); } throw new Error(err); } // ensure that all queues handlers are registered with rabbitmq! // map all queues if (this.debug) { console.log(prefix, "Running exchanges..."); } _.values(this.exchanges).map(function (exchange) { _this2.runExchange(exchange); }); // everything is registered (trickle-down...) // try to empty our messages in memory this.messages if (this.debug) { if (_.isArray(this.messages) && this.messages.length) { console.log(prefix, "Publishing stored messages."); } } var msg = null; while (msg = this.messages.shift()) { if (msg.type == 'request') { this.request(msg.key, msg.msg); } else { this.publish(msg.key, msg.msg); } } } }, { key: 'runExchange', value: function runExchange(exchange) { var _this3 = this; if (this.debug) { console.log(prefix, 'Running queues...'); } _.values(exchange.queues).map(function (queue) { _this3.runQueue(queue, exchange); }); } }, { key: 'runQueue', value: function runQueue(queue, exchange) { var _this4 = this; var startSubscription = _.isArray(queue.handlers) && queue.handlers.length; if (!startSubscription) { var key = void 0; while (key = queue.keys.shift()) { var route = { key: key, exchange: exchange.name, queue: queue.name }; this.createRouteMap(route); } } else { queue.handlers.map(function (_handler) { if (!_handler || !_handler.key || !_handler.handler) { return; } var route = { key: _handler.key, exchange: exchange.name, queue: queue.name }; _this4.createRouteMap(route); Rabbot.handle({ type: _handler.key, handler: function handler(msg) { _handler.handler(msg, function (result) { if (msg.properties.headers.reply) { var reply = _.isUndefined(result) || _.isNull(result) ? { result: null } : result; msg.reply(reply); } else { msg.ack(); } }); } }).catch(function (err, msg) { if (_this4.debug) { console.log(prefix, err); console.log(prefix, msg); } if (_this4.rejectOnError) { msg.reject(); } }); }); // all handlers have been initialized for this queue // we can safely start the subscription if (this.debug) { console.log(prefix, 'Starting subscription on:', queue.name); } Rabbot.startSubscription(queue.name); } } }, { key: 'registerExchange', value: function registerExchange(exchange) { var _this5 = this; if (!exchange) { return null; } if (this.exchanges[exchange.name]) { // we have already registered this exchange... // let's do ourselves a solid and register // any queues that are registered to this incoming exchange _.values(exchange.queues).map(function (queue) { _this5.exchanges[exchange.name].registerQueue(queue); }); } else { this.exchanges[exchange.name] = exchange; ee.emit('register:exchange', this.exchanges[exchange.name]); } } }, { key: 'request', value: function request(key, msg) { var _this6 = this; if (this.debug) { console.log(prefix, 'requested:', key, msg); } if (!Rabbot) { console.warn('Queueing request for delivery when Rabbot is available.'); this.messages.push(Object.assign({}, { type: 'request' }, { key: key, msg: msg })); return; } var route = this.routeMap[key]; if (_.isNull(route) || _.isUndefined(route)) { if (this.debug) { console.log(prefix, 'no route mapped for:', key, route); } return; } var type = route.type; var routingKey = route.routingKey; var exchange = route.exchange; var queue = route.queue; var options = Object.assign({}, { routingKey: routingKey, type: type, body: msg, headers: { reply: true } }); if (this.debug) { console.log(prefix, 'requesting w/options:', JSON.stringify(options, true, 2)); } return Rabbot.request(exchange, options).then(function (response) { response.ack(); return _this6.replyWithBody ? response.body : response; }); } }, { key: 'publish', value: function publish(key, msg) { if (this.debug) { console.log(prefix, 'published:', key, msg); } if (!Rabbot) { console.warn('Queueing request for delivery when Rabbot is available.'); this.messages.push(Object.assign({}, { type: 'publish' }, { key: key, msg: msg })); return; } var route = this.routeMap[key]; if (_.isNull(route) || _.isUndefined(route)) { if (this.debug) { console.log(prefix, 'no route mapped for:', key, route); } return; } var type = route.type; var routingKey = route.routingKey; var exchange = route.exchange; var queue = route.queue; var options = Object.assign({}, { routingKey: routingKey, type: type, body: msg }); if (this.debug) { console.log(prefix, 'publishing w/options:', JSON.stringify(options, true, 2)); } return Rabbot.publish(exchange, options); } }, { key: 'createRouteMap', value: function createRouteMap(_ref) { var key = _ref.key; var queue = _ref.queue; var exchange = _ref.exchange; this.routeMap[key] = { queue: queue, exchange: exchange, routingKey: key, type: key }; } }, { key: 'debug', get: function get() { return this._debug || false; }, set: function set(value) { this._debug = _.isBoolean(value) ? value : value; } }, { key: 'replyWithBody', get: function get() { return this._replyWithBody || false; }, set: function set(value) { this._replyWithBody = _.isBoolean(value) ? value : value; } }, { key: 'rejectOnError', get: function get() { return this._rejectOnError || false; }, set: function set(value) { this._rejectOnError = _.isBoolean(value) ? value : value; } }, { key: 'routeMap', get: function get() { if (!this._routeMap) { this.routeMap = {}; } return this._routeMap; }, set: function set(value) { this._routeMap = _.isObject(value) ? value : { value: value }; } }, { key: 'exchanges', get: function get() { if (!this._exchanges) { this.exchanges = {}; } return this._exchanges; }, set: function set(value) { this._exchanges = _.isObject(value) ? value : { value: value }; } }, { key: 'messages', get: function get() { return this._messages || []; }, set: function set(value) { this._messages = _.isArray(value) ? value : [value]; } }, { key: 'ready', get: function get() { return this._ready || false; }, set: function set(value) { this._ready = _.isBoolean(value) ? value : value; } /////////////////////////////////////////// // // a class within a class! // }, { key: 'Exchange', get: function get() { return function () { function _class(name) { _classCallCheck(this, _class); this.name = name; } _createClass(_class, [{ key: 'registerQueue', value: function registerQueue(queue) { if (!queue) { return null; } // we have already registered this queue... // let's do ourselves a solid and add any // routing keys and handlers that are listed in the incoming queue if (this.queues[queue.name]) { // keys var keys = this.queues[queue.name].keys.concat(queue.keys); keys.sort(); this.queues[queue.name].keys = _.uniq(keys, true); // handlers Array.prototype.push.apply(this.queues[queue.name].handlers, queue.handlers); } else { this.queues[queue.name] = queue; ee.emit('register:queue', this.queues[queue.name]); } } }, { key: 'getQueue', value: function getQueue(name) { return this.queues[name]; } }, { key: 'name', get: function get() { return this._name; }, set: function set(value) { this._name = _.isString(value) ? value : value.toString(); } }, { key: 'queues', get: function get() { if (!this._queues) { this.queues = {}; } return this._queues; }, set: function set(value) { this._queues = _.isObject(value) ? value : { value: value }; } }]); return _class; }(); } /////////////////////////////////////////// // // a class within a class! // }, { key: 'Queue', get: function get() { return function () { function _class2(opts) { _classCallCheck(this, _class2); this.name = opts.name; this.keys = opts.keys; } _createClass(_class2, [{ key: 'registerHandler', value: function registerHandler(opts) { if (!opts || !_.isObject(opts)) { throw new Error(500, 'Queue.handler options must be an [Object] with properties {key, handler}'); } var key = opts.key; var handler = opts.handler; if (!key || !this.hasKey(key)) { throw new Error(501, 'Queue.handler routing key [' + key + '] is not available on this queue'); } if (!handler || !_.isFunction(handler)) { throw new Error(502, 'Queue.handler handler function must be of type [Function]'); } this.handlers.push({ key: key, handler: handler }); } }, { key: 'hasKey', value: function hasKey(key) { return this.keys.indexOf(key) > -1; } }, { key: 'name', get: function get() { return this._name; }, set: function set(value) { this._name = _.isString(value) ? value : value.toString(); } }, { key: 'keys', get: function get() { if (!this._keys) { this.keys = []; } return this._keys; }, set: function set(value) { this._keys = _.isArray(value) ? value : [value]; } }, { key: 'handlers', get: function get() { if (!this._handlers) { this.handlers = []; } return this._handlers; }, set: function set(value) { this._handlers = _.isArray(value) ? value : [value]; } }]); return _class2; }(); } }]); return Wabbit; }(); var WabbitInstance = new Wabbit(); module.exports = WabbitInstance;