UNPKG

sails-rabbitmq

Version:

sails-rabbitmq adapter for Sails / Waterline

409 lines (351 loc) 12.7 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } var _handlersPersistence = require('./handlers/persistence'); var _handlersPersistence2 = _interopRequireDefault(_handlersPersistence); var rabbit = require('rabbit.js'); var _ = require('lodash'); /** * Implementation of the sails-rabbitmq Adapter */ var Adapter = { /** * Set the primary key datatype for the persistence datastore from config/rabbit.js. */ pkFormat: _.get(global, ['sails', 'config', 'rabbitmq', 'pkFormat'], 'integer'), /** * Local connections store */ connections: new Map(), /** * Adapter default configuration */ defaults: { url: 'amqp://localhost', schema: false }, /** * This method runs when a model is initially registered * at server-start-time. This is the only required method. * * @param {[type]} connection [description] * @param {[type]} collection [description] * @param {Function} cb [description] * @return {[type]} [description] */ registerConnection: function registerConnection(connection, collections, cb) { var _this = this; if (!connection.identity) return cb(new Error('Connection is missing an identity.')); if (this.connections.get(connection.identity)) return cb(new Error('Connection is already registered.')); var context = rabbit.createContext(connection.url); var config = { identity: connection.identity, context: context, models: new Map(_.pairs(collections)), userSockets: new Set(), sockets: { publish: new Map(), push: new Map() }, persistence: connection.persistence }; this.connections.set(connection.identity, config); context.on('error', function (err) { console.error(err); }); context.once('ready', function () { _this.setupConnection(connection, collections, config).then(cb)['catch'](cb); }); }, setupConnection: function setupConnection(connection, collections, config) { var _this2 = this; return Promise.all(_.map(collections, function (model) { return _this2.setupConnectionSockets(connection, model, config); })).then(function () { if (config.persistence) { return Promise.all(_.map(collections, function (model) { new _handlersPersistence2['default'](connection, model); })); } }).then(function () { return Promise.resolve(); }); }, setupConnectionSockets: function setupConnectionSockets(connection, model, config) { return Promise.all([this.getPublishSocket(connection.identity, model.identity).then(function (pubSocket) { config.sockets.publish.set(model.identity, pubSocket); }), this.getPushSocket(connection.identity, model.identity, { name: 'persistence' }).then(function (pushSocket) { config.sockets.push.set(model.identity, pushSocket); })]); }, /** * Fired when a model is unregistered, typically when the server * is killed. Useful for tearing-down remaining open connections, * etc. * * @param {Function} cb [description] * @return {[type]} [description] */ teardown: function teardown(conn, cb) { if (_.isFunction(conn)) { cb = conn; conn = null; } var connections = conn ? [conn].values() : this.connections.values(); var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = connections[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var c = _step.value; var _iteratorNormalCompletion2 = true; var _didIteratorError2 = false; var _iteratorError2 = undefined; try { for (var _iterator2 = c.sockets.publish.values()[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { var socket = _step2.value; socket.close(); } } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2['return']) { _iterator2['return'](); } } finally { if (_didIteratorError2) { throw _iteratorError2; } } } var _iteratorNormalCompletion3 = true; var _didIteratorError3 = false; var _iteratorError3 = undefined; try { for (var _iterator3 = c.sockets.push.values()[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { var socket = _step3.value; socket.close(); } } catch (err) { _didIteratorError3 = true; _iteratorError3 = err; } finally { try { if (!_iteratorNormalCompletion3 && _iterator3['return']) { _iterator3['return'](); } } finally { if (_didIteratorError3) { throw _iteratorError3; } } } var _iteratorNormalCompletion4 = true; var _didIteratorError4 = false; var _iteratorError4 = undefined; try { for (var _iterator4 = c.userSockets.values()[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { var socket = _step4.value; socket.close(); } } catch (err) { _didIteratorError4 = true; _iteratorError4 = err; } finally { try { if (!_iteratorNormalCompletion4 && _iterator4['return']) { _iterator4['return'](); } } finally { if (_didIteratorError4) { throw _iteratorError4; } } } this.connections['delete'](c.identity); } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator['return']) { _iterator['return'](); } } finally { if (_didIteratorError) { throw _iteratorError; } } } cb(); }, /** * @override */ create: function create(connection, collection, values, cb) { this.update(connection, collection, values, values, cb); }, /** * @override */ update: function update(connection, collection, criteria, values, cb) { var config = this.connections.get(connection); var pushSocket = this.getSocket(connection, collection, 'push'); this.getSubscribeSocket(connection, collection, criteria).then(function (subSocket) { subSocket.on('data', function (data) { var model = JSON.parse(data); subSocket.close(); cb(null, model); }); pushSocket.write(JSON.stringify(values), 'utf8'); }); }, /** * Publish a message to an exchange. Accepts the same arguments as the * update() method in the "semantic" interface. * * Exchange configuration defined in the adapter's connection config object * determines how the message is handled/routed by the exchange. */ publish: function publish(connection, collection, values, cb) { var socket = this.getSocket(connection, collection, 'publish'); var routingKey = this.getRoutingKey(connection, collection, values); socket.publish(routingKey, JSON.stringify(values), 'utf8'); return Promise.resolve(socket); }, /** * Setup and connect a PUBLISH socket for the specified model */ getPublishSocket: function getPublishSocket(connection, collection) { var config = this.connections.get(connection); var context = config.context; var address = this.getExchangeName(collection); var socket = context.socket('PUBLISH', { routing: 'topic' }); return new Promise(function (resolve, reject) { socket.connect(address, function () { resolve(socket); }); }); }, /** * Setup and connect a SUBSCRIBE socket for the specified model */ getSubscribeSocket: function getSubscribeSocket(connection, collection) { var options = arguments[2] === undefined ? {} : arguments[2]; var config = this.connections.get(connection); var context = config.context; var address = this.getExchangeName(collection); var routingKey = this.getRoutingKey(connection, collection, options.where); var socket = context.socket('SUBSCRIBE', { routing: 'topic' }); socket.setEncoding('utf8'); socket.once('close', function () { config.userSockets['delete'](socket); }); return new Promise(function (resolve, reject) { socket.connect(address, routingKey, function () { config.userSockets.add(socket); resolve(socket); }); }); }, /** * Setup and connect a PUSH socket for the specified model */ getPushSocket: function getPushSocket(connection, collection) { var options = arguments[2] === undefined ? {} : arguments[2]; var config = this.connections.get(connection); var context = config.context; var address = this.getQueueName(collection, options.name); var socket = context.socket('PUSH'); return new Promise(function (resolve, reject) { socket.connect(address, function () { resolve(socket); }); }); }, /** * Setup and connect a WORKER socket for the specified model */ getWorkerSocket: function getWorkerSocket(connection, collection) { var options = arguments[2] === undefined ? {} : arguments[2]; var config = this.connections.get(connection); var context = config.context; var address = this.getQueueName(collection, options.name); var socket = context.socket('WORKER'); socket.setEncoding('utf8'); socket.once('close', function () { config.userSockets['delete'](socket); }); return new Promise(function (resolve, reject) { socket.connect(address, function () { config.userSockets.add(socket); resolve(socket); }); }); }, /** * Return an extant socket of the specific type for the specified model */ getSocket: function getSocket(connectionId, collection, type) { var connection = this.connections.get(connectionId); return connection.sockets[type].get(collection); }, /** * Return the name of the AMQP exchange that is used by the specified model */ getExchangeName: function getExchangeName(model) { return 'sails.models.' + model; }, /** * Return the name of the AMQP queue that is used by the specified model * in conjuction with a particular type of work(er) */ getQueueName: function getQueueName(model, name) { if (_.isUndefined(name)) { throw new Error('name cannot be undefined in getQueueName'); } return 'sails.models.' + model + '.' + name; }, /** * Return AMQP routing key for a given Model instance * @return dot-delimited string of model attributes which constitutes the * queue routing key */ getRoutingKey: function getRoutingKey(connection, collection, values) { var config = this.connections.get(connection); var Model = config.models.get(collection); if (_.isUndefined(values)) { return '#'; } else if (!_.isArray(Model.routingKey)) { throw new Error('The model ' + Model.identity + ' must define a routingKey\n in order to be used with the Waterline pubsub interface'); } else { return this.parseRoutingKey(Model.routingKey, values); } }, /** * @return a rabbitmq routing key derived from a list of model attributes */ parseRoutingKey: function parseRoutingKey(routingKey, values) { return routingKey.map(function (attribute) { return values[attribute]; }).join('.'); }, /** * Return a model's connection that will be used for persistence, if it * exists. */ getPersistenceConnection: function getPersistenceConnection(connection, collection) { var config = this.connections.get(connection); var Model = config.models.get(collection); var persistenceConnections = _.without(Model.connection, connection); return persistenceConnections[0]; } }; _.bindAll(Adapter); exports['default'] = Adapter; module.exports = exports['default'];