@qiwi/cyclone
Version:
"State machine" for basic purposes
178 lines (177 loc) • 6.24 kB
JavaScript
"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;