UNPKG

yay-machine

Version:

A modern, simple, lightweight, zero-dependency, TypeScript state-machine library

302 lines (298 loc) 9.84 kB
var __defProp = Object.defineProperty; var __getOwnPropNames = Object.getOwnPropertyNames; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __hasOwnProp = Object.prototype.hasOwnProperty; var __moduleCache = /* @__PURE__ */ new WeakMap; var __toCommonJS = (from) => { var entry = __moduleCache.get(from), desc; if (entry) return entry; entry = __defProp({}, "__esModule", { value: true }); if (from && typeof from === "object" || typeof from === "function") __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable })); __moduleCache.set(from, entry); return entry; }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true, configurable: true, set: (newValue) => all[name] = () => newValue }); }; // src/index.ts var exports_src = {}; __export(exports_src, { defineMachine: () => defineMachine, createMachine: () => createMachine, YayMachine: () => YayMachine, MDC: () => MDC }); module.exports = __toCommonJS(exports_src); // src/MachineDefinitionConfig.ts var MDC = Symbol("MDC"); // src/defineMachine.ts var defineMachine = (definitionConfig) => { if (definitionConfig.states) { for (const [name, config] of Object.entries(definitionConfig.states)) { if (config.always) { for (const transition of Array.isArray(config.always) ? config.always : [config.always]) { if ("reenter" in transition) { throw new Error(`Cannot use 'reenter' with immediate transitions in state "${name}"`); } } } if (config.on) { for (const [type, tx] of Object.entries(config.on)) { for (const transition of Array.isArray(tx) ? tx : [tx]) { if ("reenter" in transition && transition.reenter === false) { if (transition.to !== name) { throw new Error(`Cannot use \`reenter: false\` to another state "${transition.to}" for transition in state "${name}" via event "${type}"`); } if (transition.data) { throw new Error(`Cannot use \`reenter: false\` with \`data()\` for transition in state "${name}" via event "${type}"`); } } } } } } } return { newInstance(instanceConfig) { const enableCopyDataOnTransition = definitionConfig.enableCopyDataOnTransition; const initialState = instanceConfig?.initialState ?? definitionConfig.initialState; let currentState = initialState; const getEffectParams = (event) => { let disposed = false; const dispose = () => { disposed = true; }; return [ { state: currentState, event, send: (event2) => { if (!disposed) { machine.send(event2); } } }, dispose ]; }; let disposeState; const initState = (event) => { const { onEnter, onExit } = definitionConfig.states?.[currentState.name] || {}; const [enterParams, disposeEnterParams] = getEffectParams(event); const disposeEnter = onEnter?.(enterParams); disposeState = (disposeEvent) => { disposeEnterParams(); disposeEnter?.(); const [exitParams, disposeExitParams] = getEffectParams(disposeEvent); onExit?.(exitParams)?.(); disposeExitParams(); }; }; const subscribers = []; const transitionTo = (nextState, event, onTransition) => { if (disposeState) { disposeState(event); disposeState = undefined; } if (onTransition) { const [transitionParams, disposeTransitionParams] = getEffectParams(event); onTransition({ ...transitionParams, next: nextState, ...event && { event } })?.(); disposeTransitionParams(); } currentState = nextState; initState(event); for (const subscriber of subscribers) { subscriber({ state: currentState, event }); } }; const applyAlwaysTransitions = () => { const always = definitionConfig.states?.[currentState.name]?.always; if (always) { applyTransitions(undefined, always); } }; const applyTransitions = (event, transitions) => { for (const transition of Array.isArray(transitions) ? transitions : [transitions]) { if (tryTransition(event, transition)) { applyAlwaysTransitions(); return true; } } return false; }; const tryTransition = (event, transition) => { if ("when" in transition && !transition.when({ state: currentState, event })) { return false; } if (isReenterTransitionFalse(transition)) { const [transitionParams, disposeTransitionParams] = getEffectParams(event); if (transition.onTransition) { transition.onTransition({ ...transitionParams, event, next: currentState })?.(); disposeTransitionParams(); } return true; } let nextState; if (isTransitionData(transition)) { const { name, ...nextData } = transition.data({ state: currentState, event }); nextState = { name: transition.to ?? currentState.name, ...nextData }; } else if (enableCopyDataOnTransition) { const { name, ...nextData } = currentState; nextState = { name: transition.to ?? currentState.name, ...nextData }; } else { nextState = { name: transition.to ?? currentState.name }; } transitionTo(nextState, event, transition.onTransition); return true; }; let handlingEvent = false; const queuedEvents = []; const handleEvent = (event) => { if (handlingEvent) { queuedEvents.push(event); return; } handlingEvent = true; try { const { states, on } = definitionConfig; const state = states?.[currentState.name]; if (state) { const stateOnEvent = state.on?.[event.type]; if (stateOnEvent && applyTransitions(event, stateOnEvent)) { return; } } const anyStateOnEvent = on?.[event.type]; if (anyStateOnEvent && applyTransitions(event, anyStateOnEvent)) { return; } } finally { handlingEvent = false; handleNextQueuedEvent(); } }; const handleNextQueuedEvent = () => { const event = queuedEvents.shift(); if (event) { handleEvent(event); } }; let running = false; let starting = false; let stopping = false; let disposeMachine; const initMachine = () => { const { onStart, onStop } = definitionConfig; const [startParams, disposeStartParams] = getEffectParams(undefined); const disposeStart = onStart?.(startParams); disposeMachine = (disposeEvent) => { disposeStartParams(); disposeStart?.(); const [stopParams, disposeStopParams] = getEffectParams(disposeEvent); const disposeStop = onStop?.(stopParams); disposeStopParams(); disposeStop?.(); }; }; const machine = { get state() { return currentState; }, send(event) { if (!running) { throw new Error("Machine is not running"); } handleEvent(event); }, start() { if (stopping) { throw new Error("Machine is already stopping"); } if (running) { throw new Error("Machine is already running"); } starting = true; running = true; handlingEvent = true; initMachine(); initState(undefined); applyAlwaysTransitions(); starting = false; handlingEvent = false; handleNextQueuedEvent(); return machine; }, stop() { if (!running) { throw new Error("Machine is not running"); } if (starting) { throw new Error("Machine is already starting"); } stopping = true; handlingEvent = true; disposeState?.(undefined); disposeState = undefined; disposeMachine?.(undefined); disposeMachine = undefined; running = false; stopping = false; handlingEvent = false; queuedEvents.length = 0; }, subscribe(callback) { subscribers.push(callback); callback({ state: currentState, event: undefined }); return () => { const index = subscribers.indexOf(callback); if (index !== -1) { subscribers.splice(index, 1); } }; }, [MDC]: definitionConfig }; return machine; } }; }; var isTransitionData = (transition) => ("data" in transition); var isReenterTransitionFalse = (transition) => ("reenter" in transition) && transition.reenter === false; // src/createMachine.ts var createMachine = (definitionConfig) => { return defineMachine(definitionConfig).newInstance(); }; // src/YayMachine.ts var YayMachine = { subtle: { MDC } };