UNPKG

@fabiospampinato/fsm

Version:

Finite State Machine implementation, with support for guards and enter/exit events.

158 lines 12.8 kB
"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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