wabbit
Version:
A library to simplify working with RabbitMQ - built on top of Rabbot.
522 lines (478 loc) • 15.3 kB
JavaScript
'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;