feathers-offline-realtime
Version:
Offline-first realtime replication with optimistic updates.
196 lines (152 loc) • 6.36 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
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 _componentEmitter = require('component-emitter');
var _componentEmitter2 = _interopRequireDefault(_componentEmitter);
var _debug = require('debug');
var _debug2 = _interopRequireDefault(_debug);
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"); } }
var debug = (0, _debug2.default)('base-engine');
var BaseEngine = function () {
function BaseEngine(service) {
var _this = this;
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
_classCallCheck(this, BaseEngine);
debug('constructor entered');
this._service = service;
this._publication = options.publication;
this._subscriber = options.subscriber || function () {};
this._sorter = options.sort;
this._eventEmitter = new _componentEmitter2.default();
this._listener = function (eventName) {
return function (remoteRecord) {
return _this._mutateStore(eventName, remoteRecord, 0);
};
};
this._eventListeners = {
created: this._listener('created'),
updated: this._listener('updated'),
patched: this._listener('patched'),
removed: this._listener('removed')
};
this.useUuid = options.uuid;
this.emit = this._eventEmitter.emit;
this.on = this._eventEmitter.on;
this.listening = false;
this.store = {
last: { eventName: '', action: '', record: {} },
records: []
};
}
_createClass(BaseEngine, [{
key: 'snapshot',
value: function snapshot(records) {
debug('snapshot entered');
this.store.last = { action: 'snapshot' };
this.store.records = records;
if (this._sorter) {
records.sort(this._sorter);
}
this.emit('events', this.store.records, this.store.last);
this._subscriber(this.store.records, this.store.last);
}
}, {
key: 'addListeners',
value: function addListeners() {
debug('addListeners entered');
var service = this._service;
var eventListeners = this._eventListeners;
service.on('created', eventListeners.created);
service.on('updated', eventListeners.updated);
service.on('patched', eventListeners.patched);
service.on('removed', eventListeners.removed);
this.listening = true;
this.emit('events', this.store.records, { action: 'add-listeners' });
this._subscriber(this.store.records, { action: 'add-listeners' });
}
}, {
key: 'removeListeners',
value: function removeListeners() {
debug('removeListeners entered');
if (this.listening) {
var service = this._service;
var eventListeners = this._eventListeners;
service.removeListener('created', eventListeners.created);
service.removeListener('updated', eventListeners.updated);
service.removeListener('patched', eventListeners.patched);
service.removeListener('removed', eventListeners.removed);
this.listening = false;
this.emit('events', this.store.records, { action: 'remove-listeners' });
this._subscriber(this.store.records, { action: 'remove-listeners' });
}
}
}, {
key: '_mutateStore',
value: function _mutateStore(eventName, remoteRecord, source) {
debug('_mutateStore started: ' + eventName);
var that = this;
var idName = this._useUuid ? 'uuid' : 'id' in remoteRecord ? 'id' : '_id';
var store = this.store;
var records = store.records;
var index = this._findIndex(records, function (record) {
return record[idName] === remoteRecord[idName];
});
if (index >= 0) {
records.splice(index, 1);
}
if (eventName === 'removed') {
if (index >= 0) {
broadcast('remove');
} else if (source === 0 && (!this._publication || this._publication(remoteRecord))) {
// Emit service event if it corresponds to a previous optimistic remove
broadcast('remove');
}
return; // index >= 0 ? broadcast('remove') : undefined;
}
if (this._publication && !this._publication(remoteRecord)) {
return index >= 0 ? broadcast('left-pub') : undefined;
}
records[records.length] = remoteRecord;
if (this._sorter) {
records.sort(this._sorter);
}
return broadcast('mutated');
function broadcast(action) {
debug('emitted ' + index + ' ' + eventName + ' ' + action);
store.last = { source: source, action: action, eventName: eventName, record: remoteRecord };
that.emit('events', records, store.last);
that._subscriber(records, store.last);
}
}
}, {
key: 'changeSort',
value: function changeSort(sort) {
this._sorter = sort;
if (this._sorter) {
this.store.records.sort(this._sorter);
}
this.emit('events', this.store.records, { action: 'change-sort' });
this._subscriber(this.store.records, { action: 'change-sort' });
}
}, {
key: '_findIndex',
value: function _findIndex(array) {
var predicate = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {
return true;
};
var fromIndex = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
for (var i = fromIndex, len = array.length; i < len; i++) {
if (predicate(array[i])) {
return i;
}
}
return -1;
}
}]);
return BaseEngine;
}();
exports.default = BaseEngine;
module.exports = exports['default'];