A TypeScript implementation of a Hierarchical State Machine (HSM) that provides a robust way to manage complex state transitions and behaviors in your applications.
-
Hierarchical state management
-
Concurrent states
-
Event handling
-
Guard conditions
-
Entry/exit actions
-
Type-safe configuration
-
Function registry for handlers, guards, and actions
- Choice states
- History states (shallow and deep)
- Fork and Join states
yarn add hsm_tsimport { HSM } from './src/core/HSM';
import { parseHSMConfig } from './src/utils/parser';
import { FunctionRegistry } from './src/types/hsm';
// Create a registry with your handler functions
const registry: FunctionRegistry = {
guards: {
canTransition: () => true
},
actions: {
logTransition: () => console.log('Transitioning...')
},
handlers: {
enterState: () => {
console.log('Entering state');
return { propagate: false };
}
}
};
// Define your state machine configuration
const config = {
id: "simple",
initial: "idle",
states: {
idle: {
id: "idle",
handlerReferences: {
enter: "enterState"
},
transitions: [
{
event: "START",
target: "active",
guard: "canTransition",
action: "logTransition"
}
]
},
active: {
id: "active",
handlerReferences: {
enter: "enterState"
}
}
}
};
// Create and use the state machine
const hsm = new HSM(parseHSMConfig(config, registry));
hsm.deliverEvent({ type: "START" });const registry: FunctionRegistry = {
handlers: {
enterChild1: () => {
console.log("Entering child1");
return { propagate: false };
},
enterChild2: () => {
console.log("Entering child2");
return { propagate: false };
}
}
};
const config = {
id: "historyExample",
initial: "parent",
states: {
parent: {
id: "parent",
history: true,
initial: "child1",
states: {
child1: {
id: "child1",
handlerReferences: {
enter: "enterChild1"
},
transitions: [
{
event: "NEXT",
target: "child2"
}
]
},
child2: {
id: "child2",
handlerReferences: {
enter: "enterChild2"
}
}
}
}
}
};const registry: FunctionRegistry = {
handlers: {
enterStateA: () => {
console.log("Entering state A");
return { propagate: false };
},
enterStateB: () => {
console.log("Entering state B");
return { propagate: false };
}
}
};
const config = {
id: "concurrent",
initial: "parent",
states: {
parent: {
id: "parent",
type: "concurrent",
childMachines: [
{
id: "machineA",
initial: "stateA",
states: {
stateA: {
id: "stateA",
handlerReferences: {
enter: "enterStateA"
}
}
}
},
{
id: "machineB",
initial: "stateB",
states: {
stateB: {
id: "stateB",
handlerReferences: {
enter: "enterStateB"
}
}
}
}
]
}
}
};The HSM uses a function registry to manage all handlers, guards, and actions. This provides several benefits:
- Type safety - all functions are properly typed
- No eval-like behavior - functions are referenced directly
- Better security - no string evaluation
- Easier testing - functions can be mocked
- Better IDE support - proper code completion and type checking
interface FunctionRegistry {
guards: {
[key: string]: (event: Event) => boolean;
};
actions: {
[key: string]: (event: Event) => void;
};
handlers: {
[key: string]: (event: Event) => EventHandlingResult;
};
}- Define your functions in the registry
- Reference them by name in your state configuration
- Pass the registry to
parseHSMConfig
const registry: FunctionRegistry = {
guards: {
canTransition: (event) => event.type === "START"
},
actions: {
logTransition: (event) => console.log(`Transitioning on ${event.type}`)
},
handlers: {
enterState: (event) => {
console.log(`Entering state on ${event.type}`);
return { propagate: false };
}
}
};Functions can also be added manually to different transitions or states in the JS files as needed.
constructor(config: HSMConfig)deliverEvent(event: Event): voidgetActiveState(): StateIdgetActiveStates(): Set<StateId>
Events can be handled at any level of the state hierarchy. The propagate flag in the EventHandlingResult determines whether the event should continue propagating up the hierarchy.
interface EventHandlingResult {
propagate: boolean;
}normal- Standard stateconcurrent- State that can have multiple active child stateshistory- State that remembers its last active child state
- Fork the repository
- Create your feature branch
- Commit your changes
- Push to the branch
- Create a new Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.