StateWalker FSM Orchestrator Overview

The StateWalker FSM Orchestrator provides a process-oriented architecture that systematically addresses application complexity through hierarchical finite state machines, structured component interactions, and modular application composition.

The Complexity Challenge

Modern applications face exponential growth of complexity as functionality increases linearly. This complexity stems primarily from two sources: external dependencies that create unpredictable interaction points with APIs, databases, and files, and internal connections that multiply rapidly as components are added, leading to tangled dependencies and implicit control flows.

The core problem is that logical links and dependencies multiply rapidly as new functionality is added, creating increasingly complex systems that become difficult to maintain, test, and debug.

For detailed analysis, see Application Complexity.

Application Fragment Architecture

StateWalker addresses complexity through a modular application architecture built from application fragments. Each application consists of one or multiple fragments, where each fragment represents a plain JavaScript module that exports fields recognized by the framework.

type ApplicationFragment = {
  name: string;                    // Mandatory application identifier
  config?: StateConfig;            // Optional state machine configuration
  init?: (context: ApplicationContext) => ApplicationContext | Promise<ApplicationContext>;
} & (
  | { handlers?: StateHandlers; }  // State handlers
  | { default?: StateHandlers; }   // Default export handlers
);

Application fragments serve as building blocks for constructing modular applications. The framework automatically recognizes exported fields and uses them to compose the final application. This modular approach allows developers to create, develop, and load different fragments independently.

When multiple fragments share the same name, the framework treats them as providing additional functionality to the same process. This enables layered applications where business logic, control, and presentation concerns can be developed and deployed separately.

Complete fragment implementation details are covered in Applications section.

FSM-Based Solution: Reducing Process Connections

StateWalker uses Hierarchical Finite State Machines (FSMs) to formalize and dramatically reduce the number of possible connections between components. FSMs provide explicit control flow through states that represent distinct phases of application behavior, transitions that define allowed movements between states, and events that trigger state changes in a predictable manner.

{  
  key: "CoffeeMachine",
  transitions: [
    ["", "*", "WaitForSelection"],     // Initial state
    ["WaitForSelection", "select", "PrepareDrink"],
    ["PrepareDrink", "done", "DispenseDrink"],
    ["DispenseDrink", "taken", "WaitForSelection"]
  ]
}

This approach transforms unpredictable application flows into explicit, trackable state transitions, making the entire system behavior visible and controllable.

Learn more about FSM in the Core Concepts section.

Layered Handler Architecture: Reducing Dependencies

To further reduce component dependencies, StateWalker employs a layered architecture where each state can have multiple specialized handlers, each with a distinct responsibility.

Controllers manage business logic and external integrations:

async function PrepareDrinkController(context: ProcessContext) {
  const api = getCoffeeMachineApi(context);
  const model = getCoffeeMachineModel(context);
  
  model.preparingCoffee = true;
  await api.heatWater();
  await api.brewCoffee();
  model.preparingCoffee = false;
}

Views handle data presentation and user interactions:

async function PrepareDrinkView(context: ProcessContext) {
  const model = getCoffeeMachineModel(context);
  const element = document.querySelector("#status");
  
  const cleanup = model.onChange(() => {
    element.innerHTML = model.preparingCoffee 
      ? "Preparing coffee..." 
      : "Ready";
  });
  
  return cleanup;
}

Triggers monitor data changes and generate state transition events:

async function* PrepareDrinkTrigger(context: ProcessContext) {
  const model = getCoffeeMachineModel(context);
  
  yield* newAsyncGenerator((notify) => {
    return model.onChange(() => {
      if (!model.preparingCoffee) {
        notify("done");
      }
    });
  });
}

This separation ensures that each handler focuses on a single concern, eliminating tight coupling between data management, external API interactions, visualization, and state transitions. Handlers can be distributed across multiple application fragments, enabling teams to work on different aspects of the same application independently.

Explore all handler types in State Handlers.

Modular Application Composition

Application fragments enable two primary composition patterns. Single-module applications export multiple handlers for layered functionality within a single file. Multi-module applications distribute handlers across separate fragments that share the same application name, allowing core business logic to be developed independently from user interface components.

// Core module - business logic
export const name = "app.coffeeMachine";
export default {
  CoffeeMachineController: (context) => manageBrewing(context),
  CoffeeMachineTrigger: async function* () {
    // Monitor brewing process
  }
};

// UI module - presentation layer  
export const name = "app.coffeeMachine";
export default {
  CoffeeMachineView: (context) => renderInterface(context)
};

This modular architecture enables dynamic loading of fragments at runtime, allowing applications to extend functionality without modifying core code. Fragments can also override existing functionality by providing new handlers for existing states, with the loading order determining execution precedence.

Process Context: Shared Resource Management

All handlers receive a process context that serves as a shared container for application resources including data models for reactive state management, APIs for external system integration, services for dynamic functionality extension, and configurations for application settings.

type ProcessContext = Record<string, unknown>;

// Context usage across handlers
async function PrepareDrinkController(context: ProcessContext) {
  const api = getCoffeeMachineApi(context);      // API access
  const model = getCoffeeMachineModel(context);  // Model access
  const config = getAppConfig(context);          // Configuration access
}

The process context enables resource sharing without creating direct dependencies between handlers, supporting both loose coupling and efficient resource management across application fragments.

Reactive Models: Data Containers and Connectors

Reactive models serve as the primary data containers and communication mechanism between handlers. These self-contained classes encapsulate runtime data and notify subscribers when state changes occur.

class CoffeeMachineModel {
  private _preparingCoffee = false;
  private _temperature = 20;
  
  onChange: (listener: () => void) => () => void;
  protected notify: () => void;
  
  constructor() {
    [this.onChange, this.notify] = newListeners<[]>();
  }
  
  get preparingCoffee(): boolean { 
    return this._preparingCoffee; 
  }
  
  set preparingCoffee(value: boolean) {
    if (this._preparingCoffee !== value) {
      this._preparingCoffee = value;
      this.notify(); // Notify all subscribers
    }
  }
}

Input/Output Model Pattern

StateWalker applications follow a fundamental pattern that separates concerns through two distinct model types: Input Models that capture user interactions, form state, and UI focus, and Output Models that hold computed data, API responses, and presentation state.

This separation creates clear boundaries between user interaction data and presentation data, enabling independent development and testing of different application aspects.

Detailed model patterns are covered in Data Models.

Adapter Pattern: Structured Resource Access

The adapter pattern provides a structured, type-safe method for accessing shared resources from the process context, acting as a lightweight alternative to dependency injection.

import { newContextAdapter, getContextAdapter } from "@/utils/adapters.js";

// Define adapters for type-safe resource access
const [getCoffeeMachineApi, setCoffeeMachineApi] = 
  newContextAdapter<CoffeeMachineApi>("api:coffee-machine");

const [getCoffeeMachineModel, removeCoffeeMachineModel] = 
  getContextAdapter<CoffeeMachineModel>("model:coffee-machine", () => new CoffeeMachineModel());

// Usage in handlers
async function PrepareDrinkController(context: ProcessContext) {
  const api = getCoffeeMachineApi(context);
  const model = getCoffeeMachineModel(context);
  // Handler logic using structured resource access
}

The adapter pattern provides lazy initialization where resources are created only when needed, type safety with full TypeScript support for resource access, scoped reuse where multiple handlers share the same resource instances, and easy testing through simple mock substitution during test execution.

Complete adapter implementation details are in Adapter Pattern.

Architectural Benefits

This orchestrated approach delivers significant advantages for complex application development through complexity management where adding features doesn’t exponentially increase system complexity, explicit control flow where state transitions are visible and trackable, and isolated concerns where each handler manages a specific responsibility.

Development benefits include independent testing where handlers can be tested in isolation with mocked dependencies, parallel development where teams can work on different handler types simultaneously, and progressive enhancement where features can be added incrementally without system-wide changes.

Maintenance advantages encompass clear responsibility boundaries where changes are contained within specific handler types, predictable debugging where application state is always known and inspectable, and flexible deployment where functionality can be enabled or disabled through handler selection.

Foundation Patterns

The orchestrator builds upon several foundational patterns that can be used independently including the Listeners Pattern for reactive subscriptions, Services Pattern for dynamic functionality extension, Extensions Pattern for modular UI composition, and Generator Pattern for async event handling.

Getting Started

Ready to implement StateWalker FSM orchestration? Begin with these resources:

Core Concepts — Master FSM fundamentals
Handler Development — Learn to create controllers, views, and triggers
Applications — Understand modular application composition through fragments

The StateWalker FSM Orchestrator transforms complex application development into a manageable, predictable process through systematic complexity reduction, structured component interactions, and modular application architecture.