it-fsm/dest/fsm.js

144 lines
5.5 KiB
JavaScript

"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.FsmError = exports.State = exports.StateMachine = exports.StateMachineBuilder = exports._currState = exports._prevState = exports._stateTransitions = exports._states = void 0;
exports._states = Symbol("states");
exports._stateTransitions = Symbol("state transitions");
exports._prevState = Symbol("previous state");
exports._currState = Symbol("current state");
class StateMachineBuilder {
constructor() {
this[exports._states] = new Map();
}
withTransitions(transitions) {
this[exports._stateTransitions] = transitions;
return this;
}
withStates(names, actions) {
names.forEach((name) => this.addStateUnchecked(name, actions));
return this;
}
withState(name, actions) {
this.addStateUnchecked(name, actions);
return this;
}
addStateUnchecked(name, actions) {
const oldActions = this[exports._states].get(name);
return this[exports._states].set(name, Object.assign(Object.assign({}, oldActions), actions));
}
build(currentStateName) {
const states = this.buildStates();
const transitions = this.buildTransitions(states);
const currState = validStateFromName(states, currentStateName);
return new StateMachine(states, transitions, currState);
}
buildStates() {
return Array.from(this[exports._states].entries()).map((params) => new State(...params));
}
buildTransitions(states) {
const sourceTransitions = this[exports._stateTransitions] || [];
return new WeakMap(sourceTransitions.map(([from, toStates]) => [
validStateFromName(states, from),
new WeakSet(toStates.map(validStateFromName.bind(null, states))),
]));
}
}
exports.StateMachineBuilder = StateMachineBuilder;
class StateMachine {
constructor(states, transitions, currentState) {
this[exports._states] = states;
this[exports._stateTransitions] = transitions;
this[exports._currState] = currentState;
}
changeState(sourceState, context) {
return __awaiter(this, void 0, void 0, function* () {
const fromState = validState(this[exports._currState]);
const toState = validState(normalizeState(this[exports._states], sourceState));
if (!this.hasTransition(toState) ||
!fromState.exit(fromState, toState, context)) {
throw new FsmError(`cannot change state from "${fromState.name}" to "${toState.name}"`);
}
yield toState.entry(fromState, toState, context);
this[exports._currState] = toState;
this[exports._prevState] = fromState;
});
}
hasTransition(to) {
return hasTransition(this[exports._stateTransitions], this[exports._currState], validState(normalizeState(this[exports._states], to)));
}
allowedTransitionStates() {
const fromState = validState(this[exports._currState]);
return this[exports._states].filter(hasTransition.bind(null, this[exports._stateTransitions], fromState));
}
}
exports.StateMachine = StateMachine;
const _stateName = Symbol("state name");
const _stateActions = Symbol("state actions");
class State {
constructor(name, actions = {}) {
this[_stateName] = name;
this[_stateActions] = actions;
}
get name() {
return this[_stateName];
}
entry(fromState, toState, context) {
return __awaiter(this, void 0, void 0, function* () {
const action = this[_stateActions].onEntry;
if (isFn(action)) {
yield action(fromState, toState, context);
}
});
}
exit(fromState, toState, context) {
const action = this[_stateActions].beforeExit;
return isFn(action) ? action(fromState, toState, context) : true;
}
toString() {
return this.name;
}
toJSON() {
return this.toString();
}
}
exports.State = State;
function stateFromName(states, name) {
return states.find((state) => state.name === name);
}
function validStateFromName(states, name) {
return validState(stateFromName(states, name));
}
function normalizeState(states, state) {
return isStr(state) ? stateFromName(states, state) : state;
}
function validState(val) {
if (!isState(val)) {
throw new TypeError("an instance of State class is expected");
}
return val;
}
function isState(val) {
return val instanceof State;
}
function hasTransition(transitions, from, to) {
var _a;
return ((_a = transitions.get(from)) === null || _a === void 0 ? void 0 : _a.has(to)) || false;
}
function isStr(val) {
return typeof val === "string";
}
// deno-lint-ignore ban-types
function isFn(val) {
return typeof val === "function";
}
class FsmError extends Error {
}
exports.FsmError = FsmError;