fluorine-lib
Version:
Reactive state and side effect management for React using a single stream of actions
149 lines (110 loc) • 4.39 kB
JavaScript
import { Subject } from 'rxjs/Subject';
import { Observable } from 'rxjs/Observable';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { of } from 'rxjs/observable/of';
import { concat } from 'rxjs/operator/concat';
import { map } from 'rxjs/operator/map';
import { mergeMap } from 'rxjs/operator/mergeMap';
import { distinctUntilChanged } from 'rxjs/operator/distinctUntilChanged';
import { publishReplay } from 'rxjs/operator/publishReplay';
import { subscribeOn } from 'rxjs/operator/subscribeOn';
import { share } from 'rxjs/operator/share';
import { filter } from 'rxjs/operator/filter';
import { _catch } from 'rxjs/operator/catch';
import { createState, filterActions } from './util/state';
import { parseOpts, logAgendas, logStore } from './util/logger';
import assert from './util/assert';
import wrapActions from './util/wrapActions';
import toObservable from './util/toObservable';
import isObservable from './util/isObservable';
var KICKSTART_ACTION = { type: '_INIT_' };
export function Dispatcher() {
var _this = this;
var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var middlewares = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
Subject.call(this);
this.keyCache = [];
this.valCache = [];
this.middlewares = [].concat(middlewares).map(function (x) {
return x(_this);
});
// Options: Logging
this.logging = parseOpts(opts.logging);
if (this.logging.agendas) {
logAgendas(this);
}
this.reduce = this.reduce.bind(this);
this.rawNext = this.rawNext.bind(this);
this.next = this.next.bind(this);
}
// Inherit from Rx.Subject
Dispatcher.prototype = Object.create(Subject.prototype);
Dispatcher.prototype.constructor = Dispatcher;
Dispatcher.prototype.reduce = function reduce(fn, init) {
var _context;
var keyCache = this.keyCache,
valCache = this.valCache,
logging = this.logging;
var index = keyCache.indexOf(fn);
if (index > -1) {
return valCache[index].store;
}
// Create cursor pointing to the state history
var cursor = createState(fn, fn(init, KICKSTART_ACTION));
// Describe states using the series of agendas
var store = (_context = (_context = (_context = of(cursor.state), concat).call(_context, mergeMap.call(this, function (agenda) {
var _context2;
// Reference agenda's root state
var anchor = cursor;
// Collect agenda's actions
var actions = [];
// Prepare agenda logger if necessary
var logger = logging.stores ? logStore(fn.name || index, agenda) : null;
// Map Agenda to consecutive states and catch errors
return (_context2 = (_context2 = map.call(agenda, function (action) {
cursor = cursor.doNext(action);
actions.push(action);
if (logger) {
logger.change(action, cursor.state); // Logging new state by action
}
return cursor.state;
}), _catch).call(_context2, function (err) {
if (!logger) {
console.error(err);
}
// Filter past actions by all of the failed agenda
var previousState = cursor.state;
filterActions(anchor, function (x) {
return actions.indexOf(x) === -1;
});
if (logger) {
logger.revert([previousState, cursor.state], err, actions); // Logging reversion
}
return of(cursor.state);
}), distinctUntilChanged).call(_context2);
})), distinctUntilChanged).call(_context), publishReplay).call(_context, 1);
var subscription = store.connect();
// Cache the store
var key = keyCache.length;
keyCache.push(fn);
valCache[key] = { store: store, subscription: subscription };
return store;
};
// Save subject's normal next method
Dispatcher.prototype.rawNext = Dispatcher.prototype.next;
Dispatcher.prototype.next = function next(arg) {
var _context3;
var middlewares = this.middlewares;
var agenda = (_context3 = toObservable(arg), share).call(_context3);
for (var i = 0; i < middlewares.length; i++) {
var middleware = middlewares[i];
agenda = middleware(agenda);
if (!isObservable(agenda)) {
return undefined;
}
}
return this.rawNext((_context3 = (_context3 = agenda, filter).call(_context3, Boolean), publishReplay).call(_context3).refCount());
};
export default function createDispatcher(opts, middlewares) {
return new Dispatcher(opts, middlewares);
}