Basic type aliases for state and event identifiers.
TransitionSpec
1
2
3
4
5
6
7
8
9
exporttypeTransitionSpec=|StateValue|{id?:string;// stable flow idtarget:StateValue;guard?:string;// named policyactions?:string[];// named side-effectscondition?:string;// optional expression (not enforced now)};
Represents a transition between states. Can be either:
Simple: Just the target state name as a string
Complex: Object with additional properties
TimerSpec
1
2
3
4
5
6
7
exportinterfaceTimerSpec{id:string;type:'DURATION'|'DATE';iso?:string;// when type === 'DURATION'at?:string;// when type === 'DATE'event:EventType;}
Defines timer-based transitions with either duration or absolute date triggers.
StateNode
1
2
3
4
5
6
7
exportinterfaceStateNode{id?:string;// stable element id (task/end)type?:'task'|'end';// we only use these two in v1on?:Record<EventType,TransitionSpec>;timers?:TimerSpec[];metadata?:Record<string,unknown>;}
Represents a single state in the process with its transitions and timers.
MachineSpec
1
2
3
4
5
6
7
8
9
10
exportinterfaceMachineSpec{id:string;// process keyversion:number;// definition versioninitial:StateValue;metadata?:{documentation?:string;lanes?:Record<string,string[]>;// lane -> [stateName,...]};states:Record<StateValue,StateNode>;}
The root interface representing a complete process definition.
functionvalidateMachineSpec(spec:MachineSpec):string[]{consterrors:string[]=[];// Check initial state existsif(!spec.states[spec.initial]){errors.push(`Initial state '${spec.initial}' not found in states`);}// Check all transition targets existObject.entries(spec.states).forEach(([stateName,state])=>{if(state.on){Object.entries(state.on).forEach(([event,transition])=>{consttarget=typeoftransition==='string'?transition:transition.target;if(!spec.states[target]){errors.push(`Transition target '${target}' not found in states`);}});}});returnerrors;}
classProcessStateMachine{privatespec:MachineSpec;privatecurrentState:StateValue;constructor(spec:MachineSpec){this.spec=spec;this.currentState=spec.initial;}transition(event:EventType):StateValue{conststate=this.spec.states[this.currentState];consttransition=state.on?.[event];if(!transition){thrownewError(`No transition for event '${event}' from state '${this.currentState}'`);}consttarget=typeoftransition==='string'?transition:transition.target;this.currentState=target;returntarget;}getCurrentState():StateValue{returnthis.currentState;}}
import{MachineSpec}from'./types/machine-spec';// Load from fileasyncfunctionloadMachineSpec(file:File):Promise<MachineSpec>{constcontent=awaitfile.text();returnJSON.parse(content)asMachineSpec;}// Save to filefunctionsaveMachineSpec(spec:MachineSpec,filename:string):void{constcontent=JSON.stringify(spec,null,2);constblob=newBlob([content],{type:'application/json'});consturl=URL.createObjectURL(blob);constlink=document.createElement('a');link.href=url;link.download=filename;link.click();URL.revokeObjectURL(url);}