UNPKG

@qiwi/cyclone

Version:

"State machine" for basic purposes

178 lines (177 loc) 6.24 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Machine = exports.DEFAULT_OPTS = exports.DEFAULT_HISTORY_SIZE = exports.DEFAULT_HANDLER = exports.DELIMITER = void 0; var tslib_1 = require("tslib"); var error_1 = require("./error"); var generator_1 = require("./generator"); var log_1 = require("./log"); exports.DELIMITER = '>'; var DEFAULT_HANDLER = function () { var last = []; for (var _i = 0; _i < arguments.length; _i++) { last[_i] = arguments[_i]; } return last.pop(); }; exports.DEFAULT_HANDLER = DEFAULT_HANDLER; exports.DEFAULT_HISTORY_SIZE = 10; exports.DEFAULT_OPTS = { transitions: {}, historySize: exports.DEFAULT_HISTORY_SIZE, immutable: false }; var Machine = /** @class */ (function () { function Machine(opts) { /** * Returns the last passes argument as a result * @param {any} state * @param {any} [payload] * @return {any} */ this.DEFAULT_HANDLER = exports.DEFAULT_HANDLER; this.opts = tslib_1.__assign(tslib_1.__assign({}, exports.DEFAULT_OPTS), opts); this.history = []; this.key = null; this.id = (0, generator_1.generateId)(); this.transitions = opts.transitions; if (typeof opts.initialState === 'string') { this.history.push({ state: opts.initialState, data: opts.initialData, id: (0, generator_1.generateId)(), date: (0, generator_1.generateDate)() }); } return this; } /** * Provides next state transition. * @param state Next state name. * @param payload Any data for handler. */ Machine.prototype.next = function (state) { var payload = []; for (var _i = 1; _i < arguments.length; _i++) { payload[_i - 1] = arguments[_i]; } if (this.key) { throw new error_1.MachineError(error_1.LOCK_VIOLATION); } var handler = Machine.getHandler(state, this.history, this.transitions); var current = this.current(); var data = handler.apply(void 0, tslib_1.__spreadArray([current.data], payload, false)); var id = (0, generator_1.generateId)(); var date = (0, generator_1.generateDate)(); this.history.push({ state: state, data: data, id: id, date: date }); if (this.history.length > Machine.getHistoryLimit(this.opts.historySize)) { log_1.log.debug('history limit reached'); this.history.shift(); } return this; }; /** * Returns the machine's digest: state name and stored data. */ Machine.prototype.current = function () { return tslib_1.__assign({}, this.history[this.history.length - 1]); }; /** * Returns the last state, that satisfies the condition */ Machine.prototype.last = function (condition) { if (condition === undefined) { return this.current(); } var filter = typeof condition === 'string' ? function (_a) { var state = _a.state; return state === condition; } : condition; return tslib_1.__spreadArray([], this.history, true).reverse().find(filter); }; /** * Reverts current state to the previous. * @param state */ Machine.prototype.prev = function (state) { if (this.key) { throw new error_1.MachineError(error_1.LOCK_VIOLATION); } if (this.history.length < 2) { throw new error_1.MachineError(error_1.UNREACHABLE_STATE); } if (state === undefined) { this.history.pop(); return this; } var last = this.last(state); if (!last) { throw new error_1.MachineError(error_1.UNREACHABLE_STATE); } this.history.length = this.history.indexOf(last) + 1; return this; }; /** * Locks the machine. Any transitions are prohibited before unlocking. * @param key */ Machine.prototype.lock = function (key) { this.key = key || "lock".concat((0, generator_1.generateId)()); return this; }; /** * Unlocks the machine. * @param key */ Machine.prototype.unlock = function (key) { if (this.key !== key) { throw new error_1.MachineError(error_1.INVALID_UNLOCK_KEY); } this.key = null; return this; }; Machine.getHistoryLimit = function (historySize) { if (historySize === undefined) { return exports.DEFAULT_HISTORY_SIZE; } if (historySize === -1) { return Number.POSITIVE_INFINITY; } return historySize; }; Machine.getHandler = function (next, history, transitions) { var targetTransition = this.getTargetTransition(next, history); var nextTransition = this.getTransition(targetTransition, transitions); if (!nextTransition) { throw new error_1.MachineError(error_1.TRANSITION_VIOLATION); } var handler = transitions[nextTransition]; return typeof handler === 'function' ? handler : exports.DEFAULT_HANDLER; }; Machine.getTransition = function (targetTransition, transitions) { // TODO Support wildcards // TODO Support OR operator // TODO Generate patterns in constructor return Object.keys(transitions) .filter(function (transition) { return targetTransition.length > transition.length ? new RegExp(".*".concat(transition, "$")).test(targetTransition) : targetTransition === transition; }) .sort(function (a, b) { return b.length - a.length; })[0]; }; Machine.getTargetTransition = function (next, history) { return tslib_1.__spreadArray(tslib_1.__spreadArray([], history.map(function (_a) { var state = _a.state; return state; }), true), [next], false).join(exports.DELIMITER); }; return Machine; }()); exports.Machine = Machine;