redux-automata
Version:
Finite state automata for Redux.
398 lines (342 loc) • 11.9 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var _inheritsLoose = require('@babel/runtime/helpers/inheritsLoose');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var _inheritsLoose__default = /*#__PURE__*/_interopDefaultLegacy(_inheritsLoose);
var ACTION_TYPE_PREFIX = "@@AUTOMATA";
var StateOptionsEx = /*#__PURE__*/function () {
function StateOptionsEx(options, stateOptions) {
this.options = options;
this.stateOptions = stateOptions;
}
var _proto = StateOptionsEx.prototype;
_proto.in = function _in(state) {
return this.options.in(state);
};
_proto.on = function on(actionFunc) {
return this.stateOptions.on(actionFunc);
};
return StateOptionsEx;
}();
var ActionOptions = /*#__PURE__*/function () {
function ActionOptions(sourceStates, actionType, smOptions, stateOptions) {
this.sourceStates = sourceStates;
this.actionType = actionType;
this.smOptions = smOptions;
this.stateOptions = stateOptions;
this.transitions = [];
}
var _proto = ActionOptions.prototype;
_proto.goTo = function goTo(state) {
this.targetState = state.stateName;
return new StateOptionsEx(this.smOptions, this.stateOptions);
};
_proto.execute = function execute(transition) {
this.transitions.push(transition);
return this;
};
_proto.noop = function noop() {
return new StateOptionsEx(this.smOptions, this.stateOptions);
};
_proto.createArcs = function createArcs() {
var sourceStates = this.sourceStates,
actionType = this.actionType,
targetState = this.targetState,
transitions = this.transitions;
return sourceStates.map(function (sourceState) {
return {
actionType: actionType,
sourceState: sourceState,
targetState: targetState,
transitions: transitions
};
});
};
return ActionOptions;
}();
var StateOptions = /*#__PURE__*/function () {
function StateOptions(sourceStates, smOptions) {
this.sourceStates = sourceStates;
this.smOptions = smOptions;
this.builders = [];
}
var _proto = StateOptions.prototype;
_proto.on = function on(action) {
var builder = new ActionOptions(this.sourceStates, action.actionType, this.smOptions, this);
this.builders.push(builder);
return builder;
};
_proto.or = function or(state) {
this.sourceStates.push(state.stateName);
return this;
};
_proto.getArcs = function getArcs() {
return this.builders.reduce(function (a, b) {
return a.concat(b.createArcs());
}, []);
};
return StateOptions;
}();
var Automata = /*#__PURE__*/function () {
function Automata(automataName) {
this.automataName = automataName;
this.states = [];
this.options = [];
this.initial = Object.assign({});
this.current = this.initial;
}
var _proto = Automata.prototype;
_proto.in = function _in(state) {
var existingState = this.states.find(function (_) {
return _.stateName === state.stateName;
});
if (!existingState) throw new Error("State should be defined using this.state(...) method.");
var option = new StateOptions([existingState.stateName], this);
this.options.push(option);
return option;
};
_proto.inAny = function inAny() {
var option = new StateOptions(this.states.map(function (_) {
return _.stateName;
}), this);
this.options.push(option);
return option;
};
_proto.beginWith = function beginWith(state) {
this.graphCache = undefined;
var existingState = this.states.find(function (_) {
return _.stateName === state.stateName;
});
if (!existingState) throw new Error("State should be previously defined using this.state(...) method.");
this.initial = Object.assign(state({}, undefined), {
__sm_state: existingState.stateName
});
};
_proto.state = function state(name, reducer) {
this.graphCache = undefined;
if (!name) throw new Error("State name can't be empty, null or undefined.");
var duplicate = this.states.find(function (_) {
return _.stateName === name;
});
if (duplicate) throw new Error("State with the same name already exist: " + name);
var newState = Object.assign(reducer, {
stateName: name
});
this.states.push(newState);
return newState;
};
_proto.action = function action(type) {
var _this = this;
this.graphCache = undefined;
var actionType = ACTION_TYPE_PREFIX + " " + this.automataName + " / " + type;
var func = function func(payload) {
var action = {
payload: payload,
type: actionType
};
return action;
};
var isInvocable = function isInvocable(state) {
return _this.hasTransition(state.__sm_state || "", actionType);
};
return Object.assign(func, {
actionType: actionType,
isInvocable: isInvocable
});
};
_proto.getGraph = function getGraph() {
if (this.graphCache) return this.graphCache;
var arcs = this.options.reduce(function (a, b) {
return a.concat(b.getArcs());
}, new Array());
this.graphCache = this.states.map(function (entry) {
var actions = arcs.filter(function (_) {
return _.sourceState === entry.stateName;
}).map(function (_) {
return {
actionType: _.actionType,
targetState: _.targetState,
transitions: _.transitions
};
});
actions = actions.filter(function (_, i) {
return actions.findIndex(function (a) {
return a.actionType === _.actionType;
}) === i;
});
return {
actions: actions,
entry: entry
};
});
return this.graphCache;
};
_proto.hasTransition = function hasTransition(stateName, actionType) {
var graph = this.getGraph();
var node = graph.find(function (_) {
return _.entry.stateName === stateName;
});
return node !== undefined && node.actions.findIndex(function (_) {
return _.actionType === actionType;
}) > -1;
};
_proto.mergeState = function mergeState(state) {
var nextState = Object.assign({}, this.current, state);
return nextState;
};
return Automata;
}();
function automataMiddleware(api) {
return function (next) {
return function (action) {
if (!action.type.startsWith(ACTION_TYPE_PREFIX)) return next(action);
var dispatching = true;
Object.assign(action, {
dispatch: function dispatch(a) {
if (dispatching) setTimeout(function () {
return api.dispatch(a);
}, 0);else return api.dispatch(a);
return a;
}
});
var deferred = next(action);
dispatching = false;
return deferred;
};
};
}
function automataReducer(automata) {
if (automata.initial.__sm_state === undefined) throw new Error("No initial state specified. Use BeginWith() method to specify initial state.");
automata.current = automata.initial;
var nodes = automata.getGraph();
var currentNode = nodes.find(function (_) {
return _.entry.stateName === automata.current.__sm_state;
});
if (!currentNode) throw new Error("Can't find initial state.");
return function (state, action) {
if (state === void 0) {
state = automata.initial;
}
if (typeof action.type !== "string" || !action.type.startsWith(ACTION_TYPE_PREFIX)) return state;
var node = nodes.find(function (_) {
return _.entry.stateName === state.__sm_state;
});
if (!node) return state;
var stateAction = node.actions.find(function (_) {
return _.actionType === action.type;
});
if (!stateAction) return state;
var newState = state;
if (stateAction.targetState) {
var nextNode = nodes.find(function (_) {
return _.entry.stateName === stateAction.targetState;
});
if (!nextNode) throw new Error("Can't find state " + stateAction.targetState);
automata.current = state;
newState = nextNode.entry(state, action.payload);
newState.__sm_state = nextNode.entry.stateName;
automata.current = newState;
}
if (!action.dispatch) {
throw new Error("Dispatch is not defined to perform transitions. It seems `automataMiddleware` was not applied.");
}
var localStore = Object.assign(action.dispatch, {
dispatch: action.dispatch,
getState: function getState() {
return automata.current;
}
});
stateAction.transitions.forEach(function (transition) {
return transition(localStore, action.payload);
});
return newState;
};
}
var TaskAutomata = /*#__PURE__*/function (_Automata) {
_inheritsLoose__default['default'](TaskAutomata, _Automata);
function TaskAutomata(Name, Process, OnSuccess, OnFailure) {
var _this;
_this = _Automata.call(this, Name + " Automata") || this;
_this.Name = Name;
_this.Process = Process;
_this.OnSuccess = OnSuccess;
_this.OnFailure = OnFailure;
_this.Idle = _this.state("Idle", function () {
return _this.getDefaultState();
});
_this.Processing = _this.state("Processing", function () {
return _this.mergeState({
error: null,
isProcessing: true
});
});
_this.Completed = _this.state("Completed", function (state, result) {
return _this.mergeState({
error: null,
isProcessing: false,
result: result
});
});
_this.Failure = _this.state("Failure", function (state, error) {
return _this.mergeState({
error: error,
isProcessing: false
});
});
_this.Start = _this.action(_this.Name + " Start");
_this.Restart = _this.action(_this.Name + " Restart");
_this.Cancel = _this.action(_this.Name + " Cancel");
_this.End = _this.action(_this.Name + " Success");
_this.Fail = _this.action(_this.Name + " Fail");
_this.BeginProcessing = function (dispatch, input) {
return _this.Process(input).then(function (_) {
var result = dispatch(_this.End(_));
if (_this.OnSuccess) _this.OnSuccess(dispatch, _, input);
return result;
}).catch(function (_) {
var result = dispatch(_this.Fail(_));
if (_this.OnFailure) _this.OnFailure(dispatch, _, input);
return result;
});
};
return _this;
}
var _proto = TaskAutomata.prototype;
_proto.setupProcessIn = function setupProcessIn(state) {
var Idle = this.Idle,
Processing = this.Processing,
Completed = this.Completed,
Failure = this.Failure,
Start = this.Start,
Restart = this.Restart,
End = this.End,
Fail = this.Fail,
Cancel = this.Cancel,
BeginProcessing = this.BeginProcessing;
this.in(state).on(Start).execute(BeginProcessing).goTo(Processing).in(Processing).on(End).goTo(Completed).on(Fail).goTo(Failure).on(Cancel).goTo(Idle).in(Failure).on(Cancel).goTo(Idle).in(Completed).or(Failure).on(Restart).execute(BeginProcessing).goTo(Processing);
};
_proto.getDefaultState = function getDefaultState() {
return {
isProcessing: false
};
};
return TaskAutomata;
}(Automata);
function createTaskAutomation(dataName, processTask, onSuccess, onFailure) {
var automata = new TaskAutomata(dataName, processTask, onSuccess, onFailure);
automata.setupProcessIn(automata.Idle);
automata.beginWith(automata.Idle);
var reducer = automataReducer(automata);
return {
cancel: automata.Cancel,
reducer: reducer,
restart: automata.Restart,
start: automata.Start
};
}
exports.ACTION_TYPE_PREFIX = ACTION_TYPE_PREFIX;
exports.Automata = Automata;
exports.TaskAutomata = TaskAutomata;
exports.automataMiddleware = automataMiddleware;
exports.automataReducer = automataReducer;
exports.createTaskAutomation = createTaskAutomation;