UNPKG

thundercats

Version:
355 lines (285 loc) 11.4 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); exports['default'] = Store; function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } var _nodeUuid = require('node-uuid'); var _nodeUuid2 = _interopRequireDefault(_nodeUuid); var _stampit = require('stampit'); var _stampit2 = _interopRequireDefault(_stampit); var _rx = require('rx'); var _invariant = require('invariant'); var _invariant2 = _interopRequireDefault(_invariant); var _debug = require('debug'); var _debug2 = _interopRequireDefault(_debug); var _supermixer = require('supermixer'); var _utils = require('./utils'); var assign = Object.assign; var debug = (0, _debug2['default'])('thundercats:store'); var __DEV__ = process.env.NODE_ENV !== 'production'; function validateObservable(observable) { /* istanbul ignore else */ if (__DEV__) { (0, _invariant2['default'])((0, _utils.isObservable)(observable), 'register should get observables but was given %s', observable); } return observable; } function addOperation(observable, validateItem, map) { return validateObservable(observable).tap(validateItem).map(map); } function registerObservable(obs, actionsArr, storeName) { actionsArr = actionsArr.slice(); (0, _invariant2['default'])((0, _utils.isObservable)(obs), '%s should register observables but was given %s', storeName, obs); debug('%s registering action', storeName); actionsArr.push(obs); return actionsArr; } var Optimism = { confirm: function confirm(uid, history) { checkId(uid, history); history.get(uid).confirmed = true; history.forEach(function (operation, uid) { /* istanbul ignore else */ if (operation.confirmed) { history['delete'](uid); } }); return history; }, revert: function revert(uid, history) { checkId(uid, history); // initial value var value = history.get(uid).oldValue; var found = false; history.forEach(function (descriptor, _uid) { if (uid === _uid) { found = true; return; } if (!found) { return; } descriptor.oldValue = value; value = applyOperation(value, descriptor.operation); }); history['delete'](uid); return { history: history, value: value }; } }; exports.Optimism = Optimism; function applyOperation(oldValue, operation) { var replace = operation.replace; var transform = operation.transform; var set = operation.set; if (replace) { return replace; } else if (transform) { return transform(oldValue); } else if (set) { return assign({}, oldValue, set); } else { return oldValue; } } function notifyObservers(value, observers) { debug('starting notify cycle'); observers.forEach(function (observer, uid) { debug('notifying %s', uid); observer.onNext(value); }); } function _dispose(subscription) { if (subscription) { subscription.dispose(); } return new Map(); } function checkId(id, history) { (0, _invariant2['default'])(history.has(id), 'an unknown operation id was used that is not within its history.' + 'it may have been called outside of context'); } var methods = { register: function register(observable) { this.actions = registerObservable(observable, this.actions, (0, _utils.getName)(this)); return this; }, hasObservers: function hasObservers() { return !!this.observers.size; }, _init: function _init() { debug('initiating %s', (0, _utils.getName)(this)); this.history = _dispose(this._operationsSubscription, this.history); (0, _invariant2['default'])(this.actions.length, '%s must have at least one action to listen to but has %s', (0, _utils.getName)(this), this.actions.length); var operations = []; this.actions.forEach(function (observable) { operations.push(observable); }); (0, _invariant2['default'])((0, _utils.areObservable)(operations), '"%s" actions should be an array of observables', (0, _utils.getName)(this)); this._operationsSubscription = _rx.Observable.merge(operations).doOnNext(function (operation) { (0, _invariant2['default'])(typeof operation !== 'undefined' && !!operation, 'operation should be an object but was given %s', operation); }).filter(function (operation) { return typeof operation.replace === 'object' ? !!operation.replace : true; }).filter(function (operation) { return typeof operation.set === 'object' ? !!operation.set : true; }).doOnNext(function (operation) { (0, _invariant2['default'])(typeof operation === 'object', 'invalid operation, operations should be an object, given : %s', operation); (0, _invariant2['default'])(typeof operation.replace === 'object' || typeof operation.transform === 'function' || typeof operation.set === 'object', 'invalid operation, ' + 'operations should have a replace(an object), ' + 'transform(a function), or set(an object) property but got %s', Object.keys(operation)); if ('optimistic' in operation) { (0, _invariant2['default'])((0, _utils.isPromise)(operation.optimistic) || (0, _utils.isObservable)(operation.optimistic), 'invalid operation, optimistic should be a ' + 'promise or observable,' + 'given : %s', operation.optimistic); } }).subscribe(this._opsOnNext.bind(this), this.opsOnError.bind(this), this.opsOnCompleted.bind(this)); }, _opsOnNext: function _opsOnNext(operation) { var _this = this; var ops = assign({}, operation); debug('on next called'); var oldValue = this.value; var newValue = applyOperation(this.value, ops); if (!newValue) { debug('%s operational noop', (0, _utils.getName)(this)); // do not change value // do not update history // do not collect 200 dollars return; } // if shouldStoreNotify returns false // do not change value or update history // else continue as normal if (this.shouldStoreNotify && typeof this.shouldStoreNotify === 'function' && !this.shouldStoreNotify(oldValue, newValue)) { debug('%s will not notify', (0, _utils.getName)(this)); return; } this.value = newValue; notifyObservers(this.value, this.observers); var uid = _nodeUuid2['default'].v1(); this.history.set(uid, { operation: ops, oldValue: oldValue }); if ('optimistic' in ops) { var optimisticObs = (0, _utils.isPromise)(ops.optimistic) ? _rx.Observable.fromPromise(ops.optimistic) : ops.optimistic; optimisticObs.first().subscribe(function () {}, function (err) { debug('optimistic error. reverting changes', err); var _Optimism$revert = Optimism.revert(uid, _this.history); var value = _Optimism$revert.value; var history = _Optimism$revert.history; _this.history = history; _this.value = value; notifyObservers(value, _this.observers); }, function () { return _this.history = Optimism.confirm(uid, _this.history); }); } else { Optimism.confirm(uid, this.history); } }, opsOnError: function opsOnError(err) { throw new Error('An error has occurred in the operations observer: ' + err); }, opsOnCompleted: function opsOnCompleted() { console.warn('operations observable has terminated without error'); }, _subscribe: function _subscribe(observer) { var _this2 = this; var uid = _nodeUuid2['default'].v1(); /* istanbul ignore else */ if (!this.hasObservers()) { this._init(); } debug('adding observer %s', uid); this.observers.set(uid, observer); observer.onNext(this.value); return _rx.Disposable.create(function () { debug('Disposing obserable %s', uid); _this2.observers['delete'](uid); /* istanbul ignore else */ if (!_this2.hasObservers()) { debug('all observers cleared'); _this2.dispose(); } }); }, dispose: function dispose() { debug('disposing %s', (0, _utils.getName)(this)); this.observers = new Map(); this.history = _dispose(this._operationsSubscription, this.history); }, serialize: function serialize() { return this.value ? JSON.stringify(this.value) : ''; }, deserialize: function deserialize(stringyData) { var data = JSON.parse(stringyData); (0, _invariant2['default'])(data && typeof data === 'object', '%s deserialize must return an object but got: %s', (0, _utils.getName)(this), data); this.value = data; return this.value; } }; var staticMethods = { createRegistrar: function createRegistrar(store) { function register(observable) { store.actions = registerObservable(observable, store.actions, (0, _utils.getName)(store)); return store; } return register; }, fromMany: function fromMany() { return _rx.Observable.from(arguments).tap(validateObservable).toArray().flatMap(function (observables) { return _rx.Observable.merge(observables); }); }, replacer: function replacer(observable) { return addOperation(observable, (0, _utils.createObjectValidator)('setter should receive objects but was given %s'), function (replace) { return { replace: replace }; }); }, setter: function setter(observable) { return addOperation(observable, (0, _utils.createObjectValidator)('setter should receive objects but was given %s'), function (set) { return { set: set }; }); }, transformer: function transformer(observable) { return addOperation(observable, function (fun) { /* istanbul ignore else */ if (__DEV__) { (0, _invariant2['default'])(typeof fun === 'function', 'transform should receive functions but was given %s', fun); } }, function (transform) { return { transform: transform }; }); } }; // Store is a stamp factory // It returns a factory that creates store instances function Store() { var stampSpec = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; var _stampSpec$init = stampSpec.init; var init = _stampSpec$init === undefined ? [] : _stampSpec$init; var _stampSpec$refs = stampSpec.refs; var refs = _stampSpec$refs === undefined ? {} : _stampSpec$refs; var _stampSpec$props = stampSpec.props; var props = _stampSpec$props === undefined ? {} : _stampSpec$props; var _stampSpec$statics = stampSpec.statics; var statics = _stampSpec$statics === undefined ? {} : _stampSpec$statics; var _refs$value = refs.value; var value = _refs$value === undefined ? {} : _refs$value; var stamp = (0, _stampit2['default'])(); stamp.fixed.refs = stamp.fixed.state = (0, _supermixer.mergeChainNonFunctions)(stamp.fixed.refs, _rx.Observable.prototype); assign(stamp, assign(stamp.fixed['static'], _rx.Observable)); (0, _supermixer.mixinChainFunctions)(stamp.fixed.methods, _rx.Observable.prototype); return stamp.refs({ value: value, _operationsSubscription: null })['static'](staticMethods).methods(methods).init(function (_ref) { var instance = _ref.instance; instance.observers = new Map(); instance.history = new Map(); instance.actions = []; _rx.Observable.call(instance); return instance; }).props(props).refs(refs)['static'](statics).init(init); } // Make static methods also available on stamp factory assign(Store, staticMethods);