nesquirk
Version:
Ties Nes + minimongo together for gloryful reactive apps.
295 lines (238 loc) • 9.94 kB
JavaScript
'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;