@fabiospampinato/fsm
Version:
Finite State Machine implementation, with support for guards and enter/exit events.
158 lines • 12.8 kB
JavaScript
"use strict";
/* IMPORT */
var compact = require("lodash/compact");
var get = require("lodash/get");
var includes = require("lodash/includes");
var isFunction = require("lodash/isFunction");
var isString = require("lodash/isString");
var isUndefined = require("lodash/isUndefined");
var fifo_1 = require("@fabiospampinato/fifo");
var lockable_1 = require("@fabiospampinato/lockable");
/* FSM */
//TODO: Add support for actions on submodules, like `history.start`: 'end'
//TODO: Add support for an `action` key on the transition object, that would be the executed action
var FSM = /** @class */ (function () {
/* CONSTRUCTOR */
function FSM(model, states, initial) {
this.model = model;
this.states = states;
this.queue = new fifo_1.default();
this.initial = initial;
this.processing = new lockable_1.default();
this.set(this.initial);
}
/* UTILITIES */
FSM.prototype._isValidState = function (state) {
return this.states.hasOwnProperty(state);
};
FSM.prototype._isValidTransition = function (state, transition) {
return this._isValidState(state) && !!this._getTransitionState(state, transition) && this._isValidTransitionGuard(state, transition);
};
FSM.prototype._isValidTransitionGuard = function (state, transition) {
var guards = this._getTransitionGuard(state, transition);
if (!guards)
return true;
for (var _i = 0, _a = guards.split('|'); _i < _a.length; _i++) {
var guard = _a[_i];
var parts = guard.match(/^(!?)(\w+)(?:\.(\w+))?$/);
if (!parts)
throw new Error('[fsm] Invalid guard');
var affirmative = (parts[1] !== '!'), method = compact(parts.slice(2)).join('.');
if (!!this._callModel(method) !== affirmative)
return false;
}
return true;
};
FSM.prototype._getTransition = function (state, transition) {
var stateObj = this.states[state];
if (!stateObj.hasOwnProperty('transitions'))
return;
return stateObj.transitions[transition];
};
FSM.prototype._getTransitionState = function (state, transition) {
var transitionObj = this._getTransition(state, transition);
if (isUndefined(transitionObj) || isString(transitionObj))
return transitionObj;
return transitionObj.state;
};
FSM.prototype._getTransitionGuard = function (state, transition) {
var transitionObj = this._getTransition(state, transition);
if (isUndefined(transitionObj) || isString(transitionObj))
return;
return transitionObj.guard;
};
FSM.prototype._getExistsEnters = function (prevState, nextState) {
if (prevState === nextState)
return [[], []];
return [[prevState], [nextState]];
};
FSM.prototype._callModel = function (path, args) {
if (args === void 0) { args = []; }
var method = get(this.model, path);
if (!isFunction(method))
return;
var context = includes(path, '.') ? get(this.model, path.split('.').slice(0, -1).join('.')) : this.model;
return method.apply(context, args);
};
/* GET */
FSM.prototype.get = function () {
return this.state;
};
/* SET */
FSM.prototype.set = function (state) {
if (!this._isValidState(state))
throw new Error("[fsm] Invalid state \"" + state + "\"");
this.state = state;
return this;
};
/* RESET */
FSM.prototype.reset = function () {
return this.set(this.initial);
};
/* IS */
FSM.prototype.is = function (state) {
return this.state === state;
};
FSM.prototype.isDoable = function (transition) {
return this._isValidTransition(this.state, transition);
};
/* TRANSITION */
FSM.prototype.do = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
var _a;
return (_a = this.transition).call.apply(_a, [this].concat(args));
};
FSM.prototype.transition = function (transition) {
var args = [];
for (var _i = 1; _i < arguments.length; _i++) {
args[_i - 1] = arguments[_i];
}
this.queue.add([transition].concat(args));
if (this.processing.isLocked())
return this;
this.processing.lock();
while (true) {
var next = this.queue.next();
if (!next)
break;
this._transition.apply(this, next);
}
this.processing.unlock();
return this;
};
FSM.prototype._transition = function (transition) {
var args = [];
for (var _i = 1; _i < arguments.length; _i++) {
args[_i - 1] = arguments[_i];
}
if (!this.isDoable(transition))
throw new Error("[fsm] Invalid transition \"" + transition + "\" from state \"" + this.state + "\"");
var nextState = this._getTransitionState(this.state, transition);
if (!nextState)
throw new Error("[fsm] Invalid transition \"" + transition + "\" from state \"" + this.state + "\"");
if (nextState === '*')
nextState = this.state; // `*` states always point to the current state
var _a = this._getExistsEnters(this.state, nextState), exits = _a[0], enters = _a[1];
exits.forEach(this._exit.bind(this));
this._callModel(transition, args);
enters.forEach(this._enter.bind(this));
this.set(nextState);
};
/* EVENTS */
FSM.prototype._exit = function (state) {
this._callModel(state + "Exit");
};
FSM.prototype._enter = function (state) {
this.set(state);
this._callModel(state + "Enter");
};
return FSM;
}());
/* EXPORT */
module.exports = FSM;
module.exports.default = FSM;
Object.defineProperty(module.exports, "__esModule", { value: true });
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUNBLFlBQVk7O0FBRVosd0NBQThDO0FBQzlDLGdDQUFzQztBQUN0QywwQ0FBZ0Q7QUFDaEQsOENBQW9EO0FBQ3BELDBDQUFnRDtBQUNoRCxnREFBc0Q7QUFDdEQsOENBQStEO0FBQy9ELHNEQUEyRTtBQUczRSxTQUFTO0FBRVQsMEVBQTBFO0FBQzFFLG1HQUFtRztBQUVuRztJQVdFLGlCQUFpQjtJQUVqQixhQUFjLEtBQVksRUFBRSxNQUFpQixFQUFFLE9BQWM7UUFFM0QsSUFBSSxDQUFDLEtBQUssR0FBRyxLQUFLLENBQUM7UUFDbkIsSUFBSSxDQUFDLE1BQU0sR0FBRyxNQUFNLENBQUM7UUFDckIsSUFBSSxDQUFDLEtBQUssR0FBRyxJQUFJLGNBQUksRUFBRyxDQUFDO1FBQ3pCLElBQUksQ0FBQyxPQUFPLEdBQUcsT0FBTyxDQUFDO1FBQ3ZCLElBQUksQ0FBQyxVQUFVLEdBQUcsSUFBSSxrQkFBUSxFQUFHLENBQUM7UUFDbEMsSUFBSSxDQUFDLEdBQUcsQ0FBRyxJQUFJLENBQUMsT0FBTyxDQUFFLENBQUM7SUFFNUIsQ0FBQztJQUVELGVBQWU7SUFFZiwyQkFBYSxHQUFiLFVBQWdCLEtBQVk7UUFFMUIsT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFDLGNBQWMsQ0FBRyxLQUFLLENBQUUsQ0FBQztJQUU5QyxDQUFDO0lBRUQsZ0NBQWtCLEdBQWxCLFVBQXFCLEtBQVksRUFBRSxVQUFzQjtRQUV2RCxPQUFPLElBQUksQ0FBQyxhQUFhLENBQUcsS0FBSyxDQUFFLElBQUksQ0FBQyxDQUFDLElBQUksQ0FBQyxtQkFBbUIsQ0FBRyxLQUFLLEVBQUUsVUFBVSxDQUFFLElBQUksSUFBSSxDQUFDLHVCQUF1QixDQUFHLEtBQUssRUFBRSxVQUFVLENBQUUsQ0FBQztJQUVoSixDQUFDO0lBRUQscUNBQXVCLEdBQXZCLFVBQTBCLEtB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