FSM Orchestrator: Getting Started
This guide demonstrates how to define and launch a layered application using the @statewalker/fsm
orchestrator. The example application models the behavior of a light bulb that alternates between “On” and “Off” states, with transitions occurring automatically every 3 seconds.
Applications executed by the StateWalker Orchestrator are JavaScript modules that are dynamically assembled and executed at runtime. Each module contributes specific functionality while sharing a common application context:
- Application Logic - Define states and transitions using declarative configuration
- Handlers and Triggers - Implement state behavior and automatic event generation
- Loggers - Add observability with state transition tracking and lifecycle logging
- Views - Create visual representations and DOM manipulation for browser interfaces
- Server Deployment - Run applications in Node.js, Bun, or Deno environments
- Browser Integration - Deploy with rich user interfaces and interactive elements
Application Logic
The core module establishes the fundamental behavior of the light bulb through state definitions and transition rules. This fragment contains no executable code, serving instead as a declarative specification of the application’s finite state machine.
// File `./core/index.js`
// Mandatory application name
export const name = "MyTestApplication";
// Configuration defining the application logic:
export const config = {
// The root application state:
key: "LightBulb",
// Transitions between sub-states:
transitions: [
// The initial transition in the Off state:
["", "*", "Off"],
// A toggle will switch the light on
["Off", "toggle", "On"],
// A toggle will switch the light off
["On", "toggle", "Off"],
],
};
The configuration establishes a simple state machine where the light bulb begins in the “Off” state and responds to “toggle” events by switching between states. This declarative approach separates the logical structure from the implementation details.
Handlers and Triggers
The handlers fragment implements the operational behavior associated with each state. This module demonstrates the layered architecture by organizing functionality into distinct concerns: state management and event generation.
// File `./handlers/index.js`
// The application name.
// Note that it have to be the same name as in the core module.
export const name = "MyTestApplication";
// Configuration defining the application logic:
export default [
// ----------------------------
// First layer - pure logic.
// Named handlers associated with states by their name:
{
// The top-level handler. It is activated when
// the process starts.
LightBulb: (context) => {
// Resets the counters for On and Off states:
context.onCounter = 0;
context.offCounter = 0;
},
// Increases the counter each time when the bulb is activated.
On: (context) => {
context.onCounter++;
},
Off: (context) => {
context.offCounter++;
},
},
// ----------------------------
{
// This trigger emits events each 3 seconds.
// Note that the suffix "Trigger" is recognized automatically.
LightBulbTrigger: async function* () {
while (true) {
yield "toggle"; // Emit "toggle" event
await new Promise((resolve) => setTimeout(resolve, 3000));
}
},
},
];
The first layer manages application state by maintaining counters that track how many times each state has been entered. The second layer provides the automation mechanism through an asynchronous generator that emits toggle events at regular intervals. This separation allows for independent testing and modification of business logic and timing concerns.
Loggers
The logging fragment provides observability into state transitions and application behavior. The handlers return cleanup functions that execute when the application exits each state, enabling comprehensive lifecycle tracking.
// File `./logger/index.js`
export const name = "MyTestApplication";
export default {
LightBulb: () => {
console.log("<LightBulb>");
return () => console.log("</LightBulb>");
},
On: (context) => {
console.log(` <On onCounter="${context.onCounter}">`);
return () => console.log(" </On>");
},
Off: (context) => {
console.log(` <Off offCounter="${context.offCounter}">`);
return () => console.log(" </Off>");
},
};
The logger employs a hierarchical output format that reflects the nested nature of state machines. Each state entry and exit is logged with appropriate indentation and context information, creating a clear audit trail of application execution.
Views
The views fragment handles browser-specific presentation logic, translating internal state changes into visual updates. This module demonstrates how platform-specific concerns can be isolated while maintaining integration with the core application logic.
// File `./views/index.js`
export const name = "MyTestApplication";
export default {
LightBulb: (context) => {
// We select the root element and set in the context.
// So all sub-states handlers (On and Off) will
// have it available.
context.element = document.querySelector("#light-bulb-example");
},
On: (context) => {
// Add a CSS class when the "On" state is activated
context.element.classList.add("active");
},
Off: (context) => {
// Reset CSS when the bulb is switched off
context.element.classList.remove("active");
},
};
The parent handler establishes the DOM connection by selecting the target element and storing it in the shared context. Child state handlers then manipulate this element through CSS classes, creating a visual representation of the current application state.
Server Deployment
Server-side execution provides the foundation for testing application logic without browser dependencies. The orchestrator supports multiple deployment approaches, from command-line tools to programmatic integration.
The command-line interface offers immediate execution through npx:
npx @statewalker/fsm \
./core/index.js \
./handlers/index.js \
./logger/index.js
Programmatic control requires installing the library and importing the necessary modules:
> npm install "@statewalker/fsm"
The programmatic approach enables integration with existing server applications:
import startProcesses from "@statewalker/fsm";
// Fragment A - the application logic
import * as Core from "./core/index.js";
// Fragment B - handlers bound to each state
import * as Handlers from "./handlers/index.js";
// Fragment C - states loggers
import * as Logger from "./logger/index.js";
startProcesses({
modules : [
Core,
Handlers,
Logger,
]
});
This configuration works consistently across Node.js, Bun, and Deno environments, demonstrating the platform-agnostic nature of the orchestrator architecture.
Browser Integration
Browser deployment extends the server configuration with visual components and user interaction capabilities. The HTML structure provides both the module loading mechanism and the target elements for state visualization.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Counter App</title>
</head>
<body>
<script type="module">
// import startProcesses from "@statewalker/fsm";
// Or
import { startProcesses } from "https://unpkg.com/@statewalker/fsm";
// Or
// import startProcesses from "https://cdn.jsdelivr.net/npm/@statewalker/fsm";
// Fragment A - the application logic
import * as Core from "./core/index.js";
// Fragment B - handlers bound to each state
import * as Handlers from "./handlers/index.js";
// Fragment C - states loggers
import * as Logger from "./logger/index.js";
// Fragment D - visualization
import * as Views from "./views/index.js";
startProcesses({
modules: [
Core,
Handlers,
Logger,
Views
]
});
</script>
<div id="light-bulb-example">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-lightbulb">
<path d="M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5" />
<path d="M9 18h6" />
<path d="M10 22h4" />
</svg>
</div>
<style>
#light-bulb-example {
color: gray;
}
#light-bulb-example.active {
color: red;
}
</style>
</body>
</html>
The browser configuration includes the views fragment alongside the core application modules. The SVG icon provides a visual representation of the light bulb, while CSS classes create the visual distinction between on and off states. This approach demonstrates how the same core logic can be enhanced with rich user interfaces without affecting the underlying state machine behavior.