fluorine-orchestra
Version:
A data orchestration layer for Fluorine
307 lines (231 loc) • 9.57 kB
JavaScript
import _getIterator from 'babel-runtime/core-js/get-iterator';
import _extends from 'babel-runtime/helpers/extends';
import _Object$keys from 'babel-runtime/core-js/object/keys';
import _classCallCheck from 'babel-runtime/helpers/classCallCheck';
import _Symbol from 'babel-runtime/core-js/symbol';
import invariant from 'invariant';
import { Observable } from 'rxjs';
import { Collection } from './Collection';
import { Store } from './Store';
import combineStores from './util/combineStores';
import isDispatcher from 'fluorine-lib/lib/util/isDispatcher';
import { Iterable, Set } from 'immutable';
var resultCache = _Symbol('resultCache');
function _ref(acc, store) {
var identifier = store.identifier;
if (acc[identifier]) {
throw new Error('Orchestra: The identifier `' + identifier + '` is not unique.');
}
acc[identifier] = store;
return acc;
}
function _ref2(debounceTime) {
invariant(typeof debounceTime === 'number' && debounceTime > 0, 'Orchestra: Expected `debounceTime` to be a number and > 0.');
this.opts.debounceTime = debounceTime;
}
function _ref3(identifier, reducer) {
invariant(typeof identifier === 'string', 'Orchestra: `identifier` is expected to be a string.');
invariant(typeof reducer === 'function', 'Orchestra: `reducer` is expected to be a reducer function.');
var externals = this.externals;
var stores = this.stores;
invariant(!stores.hasOwnProperty(identifier), 'Orchesta: The identifier `' + identifier + '` is already taken by a Store.');
invariant(!externals.hasOwnProperty(identifier), 'Orchesta: The identifier `' + identifier + '` is not unique.');
externals[identifier] = reducer;
return this;
}
function _ref4(dispatcher) {
invariant(isDispatcher(dispatcher), 'Orchestra: `dispatcher` is expected to be a Fluorine dispatcher.');
if (this[resultCache][dispatcher.identifier]) {
return this[resultCache][dispatcher.identifier];
}
var stores = this.stores;
var externals = this.externals;
var connections = this.connections;
var debounceTime = this.opts.debounceTime;
var _externals = _Object$keys(externals).reduce(function (acc, key) {
var external = dispatcher.reduce(externals[key]);
if (debounceTime) {
external = external.debounceTime(debounceTime);
}
acc[key] = external;
return acc;
}, {});
var _stores = {};
var resolveStore = function resolveStore(store) {
var visited = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var identifier = store.identifier;
if (visited[identifier]) {
throw new Error('Orchestra: Failed to resolve circular dependency for identifier `' + identifier + '`.');
}
visited[identifier] = true;
if (_stores[identifier]) {
return _stores[identifier];
}
var reducer = store.getReducer();
var dependencies = store.getDependencies();
var dependencyIdentifiers = _Object$keys(dependencies);
var post = store.getPost();
var _dependencies = dependencyIdentifiers.reduce(function (acc, dependency) {
var _dependency = void 0;
if (_externals[dependency]) {
_dependency = _externals[dependency];
} else if (_stores[dependency]) {
_dependency = _stores[dependency];
} else if (stores[dependency]) {
_dependency = resolveStore(stores[dependency], visited);
} else {
throw new Error('Orchestra: Failed to resolve dependency for identifier `' + dependency + '`.');
}
acc[dependency] = _dependency;
return acc;
}, {});
var _store = dispatcher.reduce(reducer);
if (debounceTime) {
_store = _store.debounceTime(debounceTime);
}
// The resulting observable store
var res = void 0;
function _ref5(state, dependencyState, dependencyIdentifier) {
var _dependencies$depende = dependencies[dependencyIdentifier];
var getter = _dependencies$depende.getter;
var setter = _dependencies$depende.setter;
// If getter exists we need to resolve dependencies per ids
if (getter && typeof getter === 'function') {
var _ret2 = function () {
var missingIds = [];
var nextState = state.map(function (x) {
var ids = getter(x);
var result = undefined;
if (typeof ids === 'string') {
result = dependencyState.get(ids);
if (result === undefined) {
missingIds = missingIds.concat(ids);
return x;
}
} else if (Iterable.isIterable(ids) || Array.isArray(ids)) {
invariant(typeof ids.forEach === 'function', 'Orchestra: `ids` is expected to have a method `forEach`.');
var _store2 = stores[dependencyIdentifier];
var collection = _store2 ? _store2.createCollection() : new Collection();
result = collection.withMutations(function (map) {
ids.forEach(function (id) {
var item = dependencyState.get(id);
if (item === undefined) {
missingIds.push(id);
}
map.set(id, item);
});
});
}
return setter(x, result);
});
// Report missing items for ids
var dependencyStore = stores[dependencyIdentifier];
if (dependencyStore) {
dependencyStore._missing(new Set(missingIds), identifier);
}
return {
v: nextState
};
}();
if (typeof _ret2 === "object") return _ret2.v;
}
// If there is no getter we just map over the items with the setter only
return state.map(function (x) {
return setter(x, dependencyState);
});
}
if (dependencies.length === 0) {
// Bail early if there are no dependencies for the store
res = _store;
} else {
(function () {
var _extends2;
var applyDependency = _ref5;
var lastDeps = null;
res = combineStores(_extends({}, _dependencies, (_extends2 = {}, _extends2[identifier] = _store, _extends2))).scan(function (pastResult, deps) {
// Update lastDeps and copy it to _lastDeps
var _lastDeps = lastDeps;
lastDeps = deps;
var currentResult = deps[identifier];
if (_lastDeps && pastResult && pastResult === currentResult) {
// There can only be one changed dependency, since we're just combining their outputs.
// Therefore we can find the changed one and process it exclusively on the last resolved
// state that we had here.
var result = currentResult;
for (var _iterator = dependencyIdentifiers, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _getIterator(_iterator);;) {
var _ref6;
if (_isArray) {
if (_i >= _iterator.length) break;
_ref6 = _iterator[_i++];
} else {
_i = _iterator.next();
if (_i.done) break;
_ref6 = _i.value;
}
var dependencyIdentifier = _ref6;
var current = deps[dependencyIdentifier];
var last = _lastDeps[dependencyIdentifier];
if (current !== last) {
result = applyDependency(result, current, dependencyIdentifier);
}
}
return result;
}
// Fallback to processing everything, since either this is the first iteration, or the original
// store changed.
return dependencyIdentifiers.reduce(function (acc, dependencyIdentifier) {
var dependencyState = deps[dependencyIdentifier];
return applyDependency(acc, dependencyState, dependencyIdentifier);
}, currentResult);
}, null);
})();
}
// Apply post-hook, if it's defined
function _ref7(x) {
return x.map(post);
}
if (post) {
res = res.map(_ref7);
}
res = res.publishReplay(1);
_stores[identifier] = res;
connections[identifier] = res.connect();
return res;
};
for (var identifier in stores) {
if (stores.hasOwnProperty(identifier)) {
var store = stores[identifier];
_stores[identifier] = resolveStore(store);
}
}
this[resultCache][dispatcher.identifier] = _stores;
return _stores;
}
export var Orchestra = function () {
Orchestra.isOrchestra = function isOrchestra(obj) {
return typeof obj === 'object' && obj instanceof Orchestra;
};
function Orchestra() {
_classCallCheck(this, Orchestra);
for (var _len = arguments.length, stores = Array(_len), _key = 0; _key < _len; _key++) {
stores[_key] = arguments[_key];
}
invariant(stores.length > 0 && stores.every(Store.isStore), 'Orchestra: constructor expects to receive Stores.');
var _stores = stores.reduce(_ref, {});
this[resultCache] = {};
this.stores = _stores;
this.connections = {};
this.externals = {};
this.opts = {};
}
Orchestra.prototype.addDebounce = _ref2;
Orchestra.prototype.addReducer = _ref3;
Orchestra.prototype.reduce = _ref4;
return Orchestra;
}();
export default function createOrchestra() {
for (var _len2 = arguments.length, stores = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
stores[_key2] = arguments[_key2];
}
return new (Function.prototype.bind.apply(Orchestra, [null].concat(stores)))();
}