simple-machines
Version:
Simple finite state machine that enables awaiting state changes
95 lines (94 loc) • 3.33 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.StateError = void 0;
exports.fsm = fsm;
const debug_1 = __importDefault(require("debug"));
const make_error_1 = __importDefault(require("make-error"));
const prom_utils_1 = require("prom-utils");
exports.StateError = (0, make_error_1.default)('StateError');
const debug = (0, debug_1.default)('simple-machines');
/**
* Simple finite state machine with explicit allowed state transitions,
* initial state, and options around how long to wait for state transitions
* when using waitForChange as well as explicitly naming the machine for
* better debugging when using multiple machines simultaneously.
*/
function fsm(stateTransitions, initState, options = {}) {
let state = initState;
const name = options.name || 'fsm';
const onStateChange = options.onStateChange;
let startTime = new Date();
/**
* Get the current state. Prefer `is` for checking the state.
*/
const get = () => state;
/**
* Is state currently one of `states`?
*/
const is = (...states) => states.includes(state);
/**
* Can the machine be transitioned to `state`?
*/
const canChange = (newState) => stateTransitions[state]?.includes(newState);
/**
* The elapsed time in ms the FSM has been in the current state.
*/
const getElapsedTime = () => new Date().getTime() - startTime.getTime();
const _change = (newState) => {
const oldState = state;
state = newState;
const elapsedTime = getElapsedTime();
debug('%s changed state from %s to %s. Elapsed %d ms', name, oldState, newState, elapsedTime);
if (onStateChange) {
onStateChange({
name,
from: oldState,
to: newState,
elapsedTime,
});
}
startTime = new Date();
};
/**
* Transition state to `newState`. Throws if `newState` is not a valid
* transitional state for the current state.
*/
const change = (newState) => {
debug('%s changing state from %s to %s', name, state, newState);
if (!canChange(newState)) {
throw new exports.StateError(`${name} invalid state transition - ${state} to ${newState}`);
}
_change(newState);
};
/**
* Wait for state to change to one of `newStates`. Times out after 5 seconds
* by default.
*/
const waitForChange = (...newStates) => {
debug('%s waiting for state change from %s to %s', name, state, newStates.join(' or '));
return (0, prom_utils_1.waitUntil)(() => newStates.includes(state), options);
};
/**
* Change states, if valid. Returns a boolean indicating if the state was changed.
*/
const maybeChange = (newState) => {
debug('%s changing state from %s to %s', name, state, newState);
if (canChange(newState)) {
_change(newState);
return true;
}
return false;
};
return {
get,
change,
waitForChange,
is,
canChange,
maybeChange,
getElapsedTime,
};
}
;