UNPKG

nesquirk

Version:

Ties Nes + minimongo together for gloryful reactive apps.

295 lines (238 loc) 9.94 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 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; }; }(); var _nes = require('nes'); var _ejson = require('ejson'); var _ejson2 = _interopRequireDefault(_ejson); var _events = require('events'); var _events2 = _interopRequireDefault(_events); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } var MSG_TYPES = ['ready', 'added', 'updated', 'removed']; var Client = function (_EventEmitter) { _inherits(Client, _EventEmitter); function Client(url, opts) { _classCallCheck(this, Client); var _this = _possibleConstructorReturn(this, (Client.__proto__ || Object.getPrototypeOf(Client)).call(this)); opts = opts || {}; _this.nes = opts.client || new _nes.Client(url, opts); _this._subs = {}; _this.setMaxListeners(Infinity); return _this; } _createClass(Client, [{ key: 'connect', value: function connect() { return this.nes.connect.apply(this.nes, arguments); } }, { key: 'request', value: function request() { return this.nes.request.apply(this.nes, arguments); } }, { key: 'subscribe', value: function subscribe(path, collection, onReady) { return this._retain(path, collection, onReady); } }, { key: 'unsubscribe', value: function unsubscribe(path, collection) { return this._release(path, collection); } }, { key: 'subscriptions', value: function subscriptions() { return Object.keys(this._subs); } }, { key: '_createMessageHandler', value: function _createMessageHandler(path, collection) { var _this2 = this; return function (message, flags) { var msgType = message.msg; if (!MSG_TYPES.includes(msgType)) return console.warn('Invalid message type ' + msgType); var method = '_on' + (msgType[0].toUpperCase() + msgType.slice(1)); _this2[method](path, collection, _ejson2.default.parse(message.data)); }; } }, { key: '_createHandle', value: function _createHandle(path, collection) { var _this3 = this; var handle = new _events2.default(); handle.path = path; handle.collection = collection; handle.ready = function () { return _this3._isReady(path, collection); }; var onSubscriptionReady = function onSubscriptionReady(p, c) { if (p === path && c === collection) { _this3.removeListener('subscriptionready', onSubscriptionReady); handle.emit('ready'); } }; handle.stop = function () { _this3.removeListener('subscriptionready', onSubscriptionReady); _this3.unsubscribe(path, collection); }; this.on('subscriptionready', onSubscriptionReady); return handle; } }, { key: '_retain', value: function _retain(path, collection, onReady) { this._subs[path] = this._subs[path] || []; onReady = onReady || function () { return 0; }; var subs = this._subs[path]; var subIndex = subs.findIndex(function (s) { return s.collection === collection; }); if (subIndex === -1) { var handler = this._createMessageHandler(path, collection); subs.push({ path: path, collection: collection, handler: handler, ready: false, onReady: onReady ? [onReady] : [], count: 1, ids: new Set() }); // TODO: handle subscribe error this.nes.subscribe(path, handler, function (err) { if (err) return console.error('Failed to subscribe to ' + path, err); }); } else { var sub = _extends({}, subs[subIndex], { count: subs[subIndex].count + 1 }); if (sub.ready) { setTimeout(onReady); } else { sub.onReady = sub.onReady.concat(onReady); } subs[subIndex] = sub; } this._subs[path] = subs; return this._createHandle(path, collection); } }, { key: '_release', value: function _release(path, collection) { var subs = this._subs[path]; var subIndex = (subs || []).findIndex(function (s) { return s.collection === collection; }); if (subIndex === -1) return console.warn('Subcription not exists ' + path + ' for ' + collection.name); var sub = this._subs[path][subIndex]; // More than one reference remains if (sub.count > 1) { sub.count--; return; } var removeIds = []; // Releasing last reference - remove unused docs sub.ids.forEach(function (id) { var hasRef = subs.some(function (s) { return s !== sub && sub.ids.has(id); }); if (!hasRef) removeIds.push(id); }); if (removeIds.length) { collection.remove({ _id: { $in: removeIds } }); } // Releasing last reference - unsubscribe from server // TODO: handle unsubscribe error this.nes.unsubscribe(path, sub.handler, function (err) { if (err) return console.error('Failed to unsubscribe from ' + path, err); }); this._subs[path].splice(subIndex, 1); } }, { key: '_isReady', value: function _isReady(path, collection) { var subs = this._subs[path] || []; return subs.some(function (sub) { return sub.collection === collection && sub.ready; }); } }, { key: '_onReady', value: function _onReady(path, collection, data) { var onReadyHandlers = []; var subIndex = (this._subs[path] || []).findIndex(function (s) { return s.collection === collection; }); if (subIndex === -1) return console.warn('Subcription not exists ' + path + ' for ' + collection.name); var sub = this._subs[path][subIndex]; onReadyHandlers = sub.onReady; var ids = new Set(); if (data) { data = Array.isArray(data) ? data : [data]; data.forEach(function (d) { collection.update({ _id: d._id }, { $set: d }, { upsert: true }); ids.add(d._id); }); } this._subs[path][subIndex] = _extends({}, sub, { ready: true, onReady: [], ids: ids }); onReadyHandlers.forEach(function (onReady) { return onReady(); }); this.emit('subscriptionready', path, collection); } }, { key: '_onAdded', value: function _onAdded(path, collection, data) { if (!data) return; var subIndex = (this._subs[path] || []).findIndex(function (s) { return s.collection === collection; }); if (subIndex === -1) return console.warn('Subcription not exists ' + path + ' for ' + collection.name); var sub = this._subs[path][subIndex]; data = Array.isArray(data) ? data : [data]; data.forEach(function (d) { collection.update({ _id: d._id }, { $set: d }, { upsert: true }); sub.ids.add(d._id); }); } }, { key: '_onUpdated', value: function _onUpdated(path, collection, data) { if (!data) return; var subIndex = (this._subs[path] || []).findIndex(function (s) { return s.collection === collection; }); if (subIndex === -1) return console.warn('Subcription not exists ' + path + ' for ' + collection.name); var sub = this._subs[path][subIndex]; data = Array.isArray(data) ? data : [data]; data.forEach(function (d) { collection.update({ _id: d._id }, { $set: d }, { upsert: true }); sub.ids.add(d._id); }); } }, { key: '_onRemoved', value: function _onRemoved(path, collection, ids) { if (!ids) return; ids = Array.isArray(ids) ? ids : [ids]; if (!ids.length) return; var subIndex = (this._subs[path] || []).findIndex(function (s) { return s.collection === collection; }); if (subIndex === -1) return console.warn('Subcription not exists ' + path + ' for ' + collection.name); var sub = this._subs[path][subIndex]; collection.remove({ _id: { $in: ids } }); ids.forEach(function (id) { return sub.ids.delete(id); }); } }]); return Client; }(_events2.default); exports.default = Client;