import assert from "./assert.js" ;
import { log } from "./logging.js" ;
export class FSM {
constructor ( data , events ) {
// data is really only for logging/debugging
this . data = data ;
this . events = events ;
this . state = "START" ;
this . on _state = undefined ;
}
onTransition ( cb ) {
this . transition _cb = cb ;
}
transition ( next _state ) {
this . state = next _state ;
if ( this . transition _cb ) {
this . transition _cb ( this ) ;
}
}
event _names ( ) {
return Object . getOwnPropertyNames ( Object . getPrototypeOf ( this . events ) ) . filter ( k => k !== "constructor" ) ;
}
async do ( event , ... args ) {
const evhandler = this . events [ event ] ;
assert ( evhandler !== undefined , ` Invalid event ${ event } . Available ones are ' ${ this . event _names ( ) } '. ` ) ;
// NOTE: you have to use .call to pass in the this.events object or else this === undefined in the call
const next _state = await evhandler . call ( this . events , this . state , ... args ) ;
assert ( next _state , ` The event " ${ event } " returned " ${ next _state } " but must be a truthy true state. ` ) ;
if ( Array . isArray ( next _state ) ) {
assert ( next _state . length == 2 , ` Returning an array only allows 2 elements (state, func) but you returned ${ next _state } ` ) ;
let [ state , func ] = next _state ;
log . debug ( ` FSM ${ this . events . constructor . name } : ( ${ event } ) = ${ this . state } -> ${ state } ( ${ args } ) ` , "DATA:" , this . data , func ? ` THEN ${ func . name } () ` : undefined ) ;
this . transition ( state ) ;
await func ( ) ;
} else {
log . debug ( ` FSM ${ this . events . constructor . name } : ( ${ event } ) = ${ this . state } -> ${ next _state } ( ${ args } ) ` , "DATA:" , this . data ) ;
this . transition ( next _state ) ;
}
return this . state ;
}
}
export default FSM ;