Data Models
StateWalker applications use reactive models – classes that encapsulate runtime data and notify subscribers when state changes. Models form the foundation of data flow, enabling decoupled updates without external frameworks.
Each model provides an onChange(listener) => () => void
method for subscription.
class UserModel {
onChange(listener: () => void): () => void {
/* ... */
}
get name(): string { return this.#name; }
set name(value: string) {
this.#name = value;
// Triggers onChange listeners
this.notify();
}
...
}
// Usage
const userModel = new UserModel();
const unsubscribe = userModel.onChange(() => {
console.log("User model updated!");
});
userModel.name = "Jane"; // Triggers notification
unsubscribe(); // Clean up subscription
The name onChange
is convention; developers can choose other names or use different reactivity mechanisms like signals.
Input/Output Pattern
StateWalker separates concerns through two model types:
Input Models capture user interactions:
- Form inputs and field values
- UI focus and selection states
- Validation states and preferences
Output Models hold computed data for presentation:
- API responses and processed results
- Loading states and error messages
- Derived calculations and formatting
This separation creates clear boundaries between user interaction data and presentation data.
// Input Model - user interactions
class SearchInputModel {
#query = "";
#filters: string[] = [];
#focusedItem = -1;
// ...existing code...
get query(): string { return this.#query; }
set query(value: string) {
this.#query = value;
this.notify();
}
}
// Output Model - display data
class SearchOutputModel {
#results: SearchResult[] = [];
#loading = false;
#error: string | null = null;
// ...existing code...
get results(): SearchResult[] { return this.#results; }
set results(value: SearchResult[]) {
this.#results = [...value];
this.notify();
}
}
Handler Integration
Models coordinate StateWalker’s unidirectional data flow:
Controllers observe input model changes, perform business logic, and update output models.
Views read from output models for display and update input models from user interactions.
Triggers monitor model changes to generate state machine events.
Access and Management
Models use the adapter pattern for type-safe access through process context:
import { getAdapter, newAdapter } from "@/utils/adapters.js";
// Simple property access
const [getCoffeeMachineModel, setCoffeeMachineModel] = newAdapter<CoffeeMachineModel>("coffee:model");
// Lazy initialization
const [getUserForm, removeUserForm] = getAdapter<UserForm>("form.user", () => new UserForm());
// Usage in handlers
async function PrepareDrinkController(context: ProcessContext) {
const model = getCoffeeMachineModel(context);
// Controller logic
}
Reactivity Implementation
Models implement reactivity using the observer pattern with newListeners utility:
import { newListeners } from "@/utils/listeners.js";
class CoffeeMachineModel {
#preparingCoffee = false;
#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();
}
}
}
This reactive foundation enables loose coupling between components while maintaining predictable data flow.