/** * @file multi.ts - Advanced operational patterns for state machine orchestration. * @description * This module provides optional, higher-level abstractions for managing machines. * They solve common ergonomic and integration challenges without compromising the * immutable core of the library. * * It introduces three patterns: * * 1. **Runner (`createRunner`):** A stateful controller for ergonomic control * of a single, immutable machine. Solves state reassignment. * * 2. **Ensemble (`createEnsemble`):** A functional pattern for coordinating * machine domains through an external, framework-agnostic state store. * * 3. **StoreMachine (`createStoreMachine`):** A proxy-backed façade combining * read-only external-store fields with methods from one class instance. */ import { Machine, Context, TransitionArgs, TransitionReturn, TransitionNames } from './index'; /** * A mapped type that creates a new object type with the same transition methods * as the machine `M`, but pre-bound to update a Runner's internal state. * * When you call a method on `BoundTransitions`, it automatically transitions the * runner's state and returns the new machine instance. This is the key mechanism * that eliminates the need for manual state reassignment in imperative code. * * @template M - The machine type, which can be a union of multiple machine states. * * @example * // If your machine has these transitions: * // increment: () => Machine * // add: (n: number) => Machine * // Then BoundTransitions provides: * // increment: () => Machine (auto-updates runner state) * // add: (n: number) => Machine (auto-updates runner state) */ export type BoundTransitions> = { [K in TransitionNames]: (...args: TransitionArgs) => TransitionReturn; }; /** * A stateful controller that wraps an immutable machine instance, providing a * stable API for imperative state transitions without manual reassignment. * * The Runner holds the "current" machine state internally and updates it whenever * an action is called. This solves the ergonomic problem of having to write: * `machine = machine.transition()` over and over. Instead, you just call * `runner.actions.transition()` and the runner manages the state for you. * * **Use Runner for:** * - Complex local component state (React, Vue, Svelte components) * - Scripts that need clean imperative state management * - Situations where you have a single, self-contained state machine * * **Don't use Runner for:** * - Global application state (use Ensemble instead) * - Multiple interconnected machines * * @template M - The machine type (can be a union of states for Type-State patterns). */ export type Runner> = { /** * The current, raw machine instance. This property is essential for * type-narrowing in Type-State Programming patterns. * * Since machines can be unions of different state types, you can narrow * the type by checking `runner.state.context` properties, and TypeScript * will automatically narrow which transitions are available. * * @example * if (runner.state.context.status === 'loggedIn') { * // runner.state is now typed as LoggedInMachine * console.log(runner.state.context.username); * runner.actions.logout(); // Only available when logged in * } */ readonly state: M; /** * A direct, readonly accessor to the context of the current machine state. * This is a convenience property equivalent to `runner.state.context`. * * @example * console.log(runner.context.count); // Same as runner.state.context.count */ readonly context: Context; /** * A stable object containing all available transition methods, pre-bound to * update the runner's state. This is the primary way to trigger transitions. * * When you call `runner.actions.someTransition()`, the runner automatically: * 1. Calls the transition on the current machine * 2. Updates `runner.state` with the new machine instance * 3. Fires the `onChange` callback (if provided to createRunner) * 4. Returns the new machine instance * * Note: For union-type machines, you must first narrow the type of `runner.state` * to ensure a given action is available at compile time. * * @example * runner.actions.increment(); // Automatically updates runner.state * runner.actions.add(5); // Returns new machine instance */ readonly actions: BoundTransitions; /** * Manually sets the runner to a new machine state. Useful for resetting state * or synchronizing with external events. * * This method bypasses the normal transition path and directly updates the * runner's internal state. The `onChange` callback will be called. * * @param newState - The new machine instance to set. * * @example * const reset = createCounterMachine({ count: 0 }); * runner.setState(reset); // Jump back to initial state */ setState(newState: M): void; }; /** * Creates a Managed State Runner by wrapping a pure, immutable machine instance * in a stateful controller. This eliminates the need for `machine = machine.transition()` * reassignment, providing a more ergonomic, imperative API for complex local state. * * **How it works:** * 1. The runner holds a reference to the current machine internally * 2. When you call `runner.actions.transition()`, it calls the transition on the * current machine and automatically updates the runner's internal state * 3. The runner exposes a stable `actions` object that always reflects what * transitions are available on the *current* machine (important for Type-State) * 4. The `onChange` callback is invoked after every state change * * **Key difference from just calling transitions directly:** * Instead of: `let machine = createMachine(...); machine = machine.increment();` * You write: `const runner = createRunner(machine); runner.actions.increment();` * * The runner *is* the state holder, so you never need to reassign variables. * * @template M - The machine type. * @param initialMachine - The starting machine instance. * @param onChange - Optional callback fired after every state transition. Receives * the new machine state, allowing you to react to changes (e.g., update a UI, * log state changes, or trigger side effects). * @returns A `Runner` instance with `state`, `context`, `actions`, and `setState()`. * * @example * // Simple counter example * const counterMachine = createCounterMachine({ count: 0 }); * const runner = createRunner(counterMachine, (newState) => { * console.log('Count is now:', newState.context.count); * }); * * runner.actions.increment(); // Logs: "Count is now: 1" * runner.actions.add(5); // Logs: "Count is now: 6" * console.log(runner.context.count); // 6 * * @example * // Type-State example with conditional narrowing * type AuthMachine = LoggedOutState | LoggedInState; * * const runner = createRunner(createLoggedOutMachine()); * * // Narrow the type to access login * if (runner.state.context.status === 'loggedOut') { * runner.actions.login('alice'); // Only works in loggedOut state * } * * // Now it's logged in, so we can call logout * if (runner.state.context.status === 'loggedIn') { * runner.actions.logout(); * } */ export declare function createRunner>(initialMachine: M, onChange?: (newState: M) => void): Runner; /** * Defines the contract for an external, user-provided state store. Ensembles use * this interface to share current context, allowing multiple machine domains to * coordinate through any state solution (React, Solid, Zustand, etc.). * * **The power of this abstraction:** * Your machine logic is completely decoupled from how or where the state is stored. * The same machine factories can work with React's `useState`, Solid's `createSignal`, * a plain object, or any custom store implementation. * * **Implementation examples:** * - React: `{ getContext: () => state, setContext: setState }` * - Solid: `{ getContext: () => store, setContext: (newCtx) => Object.assign(store, newCtx) }` * - Plain object: `{ getContext: () => context, setContext: (ctx) => Object.assign(context, ctx) }` * * @template C - The shared context object type. * * @example * // Implement a simple in-memory store * let sharedContext = { status: 'idle' }; * const store: StateStore = { * getContext: () => sharedContext, * setContext: (newCtx) => { sharedContext = newCtx; } * }; * * @example * // Implement a React-based store * function useAppStore() { * const [state, setState] = useState({ status: 'idle' }); * return { * getContext: () => state, * setContext: setState * }; * } */ export interface StateStore { /** * A function that returns the current, up-to-date context from the external store. * Called whenever the Ensemble needs the latest state. */ getContext: () => C; /** * A function that takes a new context and updates the external store. * Called by transitions to persist state changes. * * @param newContext - The new context object to persist. */ setContext: (newContext: C) => void; } /** * A mapped type that finds all unique transition names across a union of machine types. * * This type extracts the union of all methods from all possible machine states, * excluding the `context` property. This is used to create the `actions` object * on an Ensemble, which can have methods from any of the machine states. * * At runtime, the Ensemble validates that an action is valid for the current state * before executing it. * * @template AllMachines - A union of all possible machine types in an Ensemble. * * @example * type IdleState = Machine<{ status: 'idle' }> & { fetch: () => LoadingState }; * type LoadingState = Machine<{ status: 'loading' }> & { cancel: () => IdleState }; * type AllStates = IdleState | LoadingState; * * // AllTransitions = { fetch: (...) => ..., cancel: (...) => ... } * // (Both fetch and cancel are available, but each is only valid in its state) */ type UnionToIntersection = (U extends unknown ? (value: U) => void : never) extends (value: infer I) => void ? I : never; type AllTransitions> = UnionToIntersection : never> & object; /** * A stable API for a coordinated machine domain whose context is managed by an * external store. * * An ensemble selects the active machine factory from the latest shared context. * Multiple ensembles can use the same store to coordinate separate domains: * each domain remains independently defined, while every action sees changes * made by the others. Unlike a Runner, an ensemble does not own local state. * * **Key characteristics:** * - Dynamically reconstructs the current machine based on context * - Validates transitions at runtime for the current state * - Coordinates independently defined machine domains through shared context * - Integrates seamlessly with framework state managers * - Same factories can be reused across different frameworks * * **Use Ensemble for:** * - Global application state * - Multiple machine domains that must coordinate without direct references * - Framework integration (React, Solid, Vue, etc.) * - Complex workflows that span multiple components * - Decoupling business logic from UI framework * * @template AllMachines - A union type of all possible machine states. * @template C - The shared context type. */ export type Ensemble, C extends object> = { /** * A direct, readonly accessor to the context from the provided `StateStore`. * This is always up-to-date with the external store. */ readonly context: C; /** * The current, fully-typed machine instance. This is dynamically created on-demand * based on the context state. Use this for type-narrowing with Type-State patterns. * * The machine is reconstructed on every access, so it always reflects the * current state of the context. */ readonly state: AllMachines; /** * A stable object containing all possible actions from all machine states. * The Ensemble performs a runtime check to ensure an action is valid for the * current state before executing it. * * The `actions` object itself is stable (doesn't change), but the methods * available on it dynamically change based on the current state. */ readonly actions: AllTransitions; }; /** * Creates an Ensemble that coordinates a machine domain through an external * state store. Create multiple ensembles over the same store when independently * defined domains must react to one another's state changes. * * **How it works:** * 1. You provide a `StateStore` that can read and write your application's state * 2. You define factory functions that create machines for each state * 3. You provide a `getDiscriminant` accessor that tells the Ensemble which * factory to use based on the current context * 4. The Ensemble dynamically constructs the right machine and provides a stable * `actions` object to call transitions * 5. Transitions persist their next context with `store.setContext`; other * ensembles observe that context the next time they resolve state or an action * * **Why this pattern?** * Business domains remain separately defined while shared context gives them a * deliberate coordination boundary. Storage stays replaceable and testable. The * ensemble does not broadcast events or schedule parallel work; the store is the * communication channel and controls persistence and subscriptions. * * @template C - The shared context type. * @template F - An object of functions that create machine instances for each state. * Each factory receives the context and returns a Machine instance for that state. * @param store - The user-provided `StateStore` that reads/writes the context. * @param factories - An object mapping state discriminant keys to factory functions. * Each factory receives the context and returns a machine instance. * @param getDiscriminant - An accessor function that takes the context and returns * the key of the current state in the `factories` object. This provides full * refactoring safety—if you rename a property in your context, TypeScript will * catch it at the accessor function. * @returns An `Ensemble` instance with `context`, `state`, and `actions`. * * @example * // Using a simple in-memory store * let sharedContext = { status: 'idle' as const, data: null }; * const store = { * getContext: () => sharedContext, * setContext: (newCtx) => { sharedContext = newCtx; } * }; * * // Define factories for each state * const factories = { * idle: (ctx) => createMachine(ctx, { * fetch: () => store.setContext({ ...ctx, status: 'loading' }) * }), * loading: (ctx) => createMachine(ctx, { * succeed: (data: any) => store.setContext({ status: 'success', data }), * fail: (error: string) => store.setContext({ status: 'error', error }) * }), * success: (ctx) => createMachine(ctx, { * retry: () => store.setContext({ status: 'loading', data: null }) * }), * error: (ctx) => createMachine(ctx, { * retry: () => store.setContext({ status: 'loading', data: null }) * }) * }; * * // Create the ensemble with a discriminant accessor * const ensemble = createEnsemble(store, factories, (ctx) => ctx.status); * * // Use the ensemble * ensemble.actions.fetch(); * console.log(ensemble.context.status); // 'loading' * * @example * // React integration example * function useAppEnsemble() { * const [context, setContext] = useState({ status: 'idle' as const, data: null }); * * const store: StateStore = { * getContext: () => context, * setContext: (newCtx) => setContext(newCtx) * }; * * const ensemble = useMemo(() => * createEnsemble(store, factories, (ctx) => ctx.status), * [context] // Re-create ensemble if context changes * ); * * return ensemble; * } * * // In your component: * function MyComponent() { * const ensemble = useAppEnsemble(); * return ( * <> *

Status: {ensemble.context.status}

* * * ); * } */ export declare function createEnsemble Machine>>(store: StateStore, factories: F, getDiscriminant: (context: C) => keyof F): Ensemble, C>; /** * Creates a factory for building consistently configured, framework-agnostic * Ensembles. This higher-order function captures a shared state store and * state-discriminant logic in a closure. * * This allows you to define your application's state "environment" once and then * easily create multiple, consistent ensembles by only providing the behavioral logic. * * @template C The shared context type for the application. * @param store The application's state store (e.g., from React, Zustand, etc.). * @param getDiscriminant An accessor function that determines the current state from the context. * @returns A `withFactories` function that is pre-configured for your app's environment. */ export declare function createEnsembleFactory(store: StateStore, getDiscriminant: (context: C) => keyof any): Machine>>(factories: F) => Ensemble, C>; /** * Executes a generator-based workflow using a Managed State Runner. * * This provides the cleanest syntax for multi-step imperative workflows, as the * `yield` keyword is only used for control flow, not state passing. Unlike the * basic `run()` function from the core library, this works directly with a Runner, * making it perfect for complex local state orchestration. * * **Syntax benefits:** * - No need to manually thread state through a chain of transitions * - `yield` is purely for control flow, not for passing state * - Can use regular `if`/`for` statements without helpers * - Generator return value is automatically your final result * * @param flow - A generator function that receives the `Runner` instance. The * generator can yield values (returned by transitions) and use them for control * flow, or just yield for side effects. * @param initialMachine - The machine to start the flow with. A runner will be * created from this automatically. * @returns The final value returned by the generator (the `return` statement). * @typeParam M - Machine or typestate union owned by the temporary runner. * @typeParam T - Generator return type forwarded to the caller. * * @example * // Simple sequential transitions * const result = runWithRunner(function* (runner) { * yield runner.actions.increment(); * yield runner.actions.add(10); * if (runner.context.count > 5) { * yield runner.actions.reset(); * } * return runner.context; * }, createCounterMachine()); * console.log(result); // { count: 0 } * * @example * // Complex workflow with Type-State narrowing * const result = runWithRunner(function* (runner) { * // Start logged out * if (runner.state.context.status === 'loggedOut') { * yield runner.actions.login('alice'); * } * * // Now logged in, fetch profile * if (runner.state.context.status === 'loggedIn') { * yield runner.actions.fetchProfile(); * } * * // Return final context * return runner.context; * }, createAuthMachine()); */ export declare function runWithRunner, T>(flow: (runner: Runner) => Generator, initialMachine: M): T; /** * Synchronously executes a generator-based workflow using an Ensemble. * * Like `runWithRunner`, this provides imperative syntax for multi-step workflows * over an Ensemble's external store rather than internal state. The generator is * driven to completion immediately: yielded promises are not awaited and yielded * values are not passed back into the generator. * * **Key differences from runWithRunner:** * - Works with external state stores (React, Solid, etc.) * - Useful for synchronous global workflows * - Can write through framework-managed stores * - Great for testing framework-agnostic state logic * * @param flow - A generator function that receives the `Ensemble` instance. * The generator can read `ensemble.context` and call `ensemble.actions`. * @param ensemble - The `Ensemble` to run the workflow against. Its context * is shared across the entire workflow. * @returns The final value returned by the generator (the `return` statement). * @typeParam AllMachines - Union of machines reconstructed by the ensemble. * @typeParam C - Shared external context type. * @typeParam T - Generator return type forwarded to the caller. * @remarks This is not an async saga runtime. Use an actor or explicit async * orchestration when yielded work must be awaited, cancelled, or serialized. * * @example * // Multi-step workflow with an ensemble * const result = runWithEnsemble(function* (ensemble) { * // Fetch initial data * if (ensemble.context.status === 'idle') { * yield ensemble.actions.fetch(); * } * * // Process the data * if (ensemble.context.status === 'success') { * yield ensemble.actions.process(ensemble.context.data); * } * * return ensemble.context; * }, ensemble); * * @example * // Testing a workflow without a UI framework * const store: StateStore = { * getContext: () => context, * setContext: (newCtx) => Object.assign(context, newCtx) * }; * * const ensemble = createEnsemble(store, factories, (ctx) => ctx.status); * * // Run a complex workflow and assert the result * const result = runWithEnsemble(function* (e) { * yield e.actions.login('alice'); * yield e.actions.fetchProfile(); * yield e.actions.updateEmail('alice@example.com'); * return e.context; * }, ensemble); * * expect(result.userEmail).toBe('alice@example.com'); */ export declare function runWithEnsemble, C extends object, T>(flow: (ensemble: Ensemble) => Generator, ensemble: Ensemble): T; /** * Base class for the class instance wrapped by `createStoreMachine`. * Extend it to define store-backed operations as instance methods. * * This abstraction does not construct multiple machines or select state-specific * implementations. One class instance reads and replaces context through an * external `StateStore`. * * **Key features:** * - Extend this class and define transition methods as instance methods * - Protected `context` getter provides access to the current state * - Protected `setContext()` method updates the external store * - Works with `createStoreMachine()` * * @template C - The shared context type. Should typically contain a discriminant * property (like `status`) that identifies the current state. * * @example * // Define your context type * type AppContext = { status: 'idle' | 'loading' | 'error'; data?: any; error?: string }; * * // Extend StoreMachineBase and define transitions as methods * class AppMachine extends StoreMachineBase { * async fetch(url: string) { * // Notify subscribers we're loading * this.setContext({ ...this.context, status: 'loading' }); * * try { * const data = await fetch(url).then(r => r.json()); * // Update state when done * this.setContext({ ...this.context, status: 'idle', data }); * } catch (error) { * // Handle errors * this.setContext({ * ...this.context, * status: 'error', * error: error.message * }); * } * } * * reset() { * this.setContext({ status: 'idle' }); * } * } */ export declare abstract class StoreMachineBase { /** * The external state store that manages the machine's context. * @protected */ protected store: StateStore; /** * @param store - The StateStore that will manage this machine's context. */ constructor(store: StateStore); /** * Read-only access to the current context from the external store. * This getter always returns the latest context from the store. * * @protected * * @example * const currentStatus = this.context.status; * const currentData = this.context.data; */ protected get context(): C; /** * Update the shared context in the external store. * Call this method in your transition methods to update the state. * * @protected * @param newContext - The new context object. Should typically be a shallow * copy with only the properties you're changing, merged with the current * context using spread operators. * * @example * // In a transition method: * this.setContext({ ...this.context, status: 'loading' }); * * @example * // Updating nested properties: * this.setContext({ * ...this.context, * user: { ...this.context.user, name: 'Alice' } * }); */ protected setContext(newContext: C): void; } /** * Backward-compatible name for `StoreMachineBase`. * * @deprecated Use `StoreMachineBase`. `MultiMachineBase` does not represent or * coordinate multiple machines. * @typeParam C - External store context exposed to subclass methods. */ export declare abstract class MultiMachineBase extends StoreMachineBase { } type StoreMachineConstructor> = new (store: StateStore) => T; /** * Creates a live façade over an external store and one class instance. Top-level * context fields are read fresh on every access and are shallow-readonly. Public * class methods can update the store through the protected `setContext` method, * but consumers cannot assign context fields directly. * * @template C - The external context type. * @template T - The `StoreMachineBase` subclass type. * @param MachineClass - A class defining store-backed operations. * @param store - The external context store. * @returns Live read-only context fields combined with public class methods. * * @example * class Counter extends StoreMachineBase<{ count: number }> { * increment() { * this.setContext({ count: this.context.count + 1 }); * } * } * * const counter = createStoreMachine(Counter, store); * counter.increment(); * console.log(counter.count); */ export declare function createStoreMachine>(MachineClass: StoreMachineConstructor, store: StateStore): Readonly & T; /** * Creates the legacy writable, class-based façade over an external state store. * * This function constructs one `MultiMachineBase` subclass and wraps it in a * `Proxy`. The returned object exposes current context properties from the store * and methods from that class instance. It does not construct multiple machines, * choose a factory from a discriminant, or limit methods by the current state. * * **Key features:** * - Directly access context properties as if they were on the machine object * - Call transition methods to update state through the store * - Type-safe integration with TypeScript * - Seamless Proxy-based API (no special method names or API quirks) * * **How it works:** * The returned Proxy intercepts property access. For context properties, it returns * values from the store. For methods, it calls them on the MultiMachine instance. * Context properties take precedence when a field and method have the same name. * Assigning an existing context property replaces the store context with a shallow * copy. This creates the illusion of a single object that is both live data and * behavior. * * @template C - The shared context type. * @template T - The MultiMachine class type. * * @param MachineClass - The class you defined that extends `MultiMachineBase`. * @param store - The `StateStore` that will manage the machine's context. * @returns A Proxy that merges context properties with class methods, allowing * direct access to both via a unified object interface. * * @example * // Define your context type * type CounterContext = { count: number }; * * // Define your machine class * class CounterMachine extends MultiMachineBase { * increment() { * this.setContext({ count: this.context.count + 1 }); * } * * add(n: number) { * this.setContext({ count: this.context.count + n }); * } * * reset() { * this.setContext({ count: 0 }); * } * } * * // Create a store * let sharedContext = { count: 0 }; * const store = { * getContext: () => sharedContext, * setContext: (ctx) => { sharedContext = ctx; } * }; * * // Create the machine instance * const machine = createMultiMachine(CounterMachine, store); * * // Use it naturally - properties and methods seamlessly integrated * console.log(machine.count); // 0 * machine.increment(); * console.log(machine.count); // 1 * machine.add(5); * console.log(machine.count); // 6 * machine.reset(); * console.log(machine.count); // 0 * * @example * // Status-based state machine with type discrimination * type AppContext = { * status: 'idle' | 'loading' | 'success' | 'error'; * data?: any; * error?: string; * }; * * class AppMachine extends MultiMachineBase { * async fetch() { * this.setContext({ ...this.context, status: 'loading' }); * try { * const data = await fetch('/api/data').then(r => r.json()); * this.setContext({ status: 'success', data }); * } catch (error) { * this.setContext({ * status: 'error', * error: error instanceof Error ? error.message : 'Unknown error' * }); * } * } * * reset() { * this.setContext({ status: 'idle' }); * } * } * * // Set up * let context: AppContext = { status: 'idle' }; * const store = { * getContext: () => context, * setContext: (ctx) => { context = ctx; } * }; * * const app = createMultiMachine(AppMachine, store); * * // Use naturally with type discrimination * console.log(app.status); // 'idle' * * if (app.status === 'idle') { * app.fetch(); // Transition to loading * } * * // Later: app.status === 'success' * // console.log(app.data); // Access the data * * @deprecated Use `createStoreMachine`, whose context façade is read-only. */ export declare function createMultiMachine>(MachineClass: new (store: StateStore) => T, store: StateStore): C & T; /** * A mapped type that defines the shape of a Mutable Machine: an intersection * of the context `C` and all possible transitions. */ type MutableMachine> = C & AllTransitions; /** * Creates a Mutable Machine that uses a shared, mutable context. This primitive * provides a stable object reference whose properties are mutated in place, * offering a direct, imperative API. * * --- * * ### Key Characteristics & Trade-offs * * - **Stable Object Reference**: The machine is a single object. You can pass this * reference around, and it will always reflect the current state. * - **Direct Imperative API**: Transitions are called like methods directly on the * object (`machine.login('user')`), and the object's properties update immediately. * - **No State History**: Since the context is mutated, the history of previous * states is not preserved, which makes patterns like time-travel debugging impossible. * - **Not for Reactive UIs**: Most UI frameworks (React, Solid, Vue) rely on * immutable state changes to trigger updates. Mutating the context directly * will not cause components to re-render. Use the `Ensemble` primitive for UI integration. * * --- * * ### Best Suited For * * - **Backend Services & Game Logic**: Ideal for managing state in server-side * processes, game loops, or other non-UI environments where performance and a * stable state object are priorities. * - **Complex Synchronous Scripts**: Useful for orchestrating data processing * pipelines, command-line tools, or any script where state needs to be managed * imperatively without passing it through a function chain. * * @template C - The shared context type. * @template F - An object of functions that create machine instances for each state. * **Crucially, transitions inside these machines must be pure functions that * return the *next context object*, not a new machine instance.** * @param sharedContext - The initial context object. This object will be mutated. * @param factories - An object mapping state names to functions that create machine instances. * @param getDiscriminant - An accessor function that takes the context and returns the key * of the current state in the `factories` object. Provides refactoring safety. * @returns A Proxy that acts as a stable, mutable machine instance. * * @example * // ===== 1. Basic Authentication Example ===== * * type AuthContext = * | { status: 'loggedOut'; error?: string } * | { status: 'loggedIn'; username: string }; * * const authFactories = { * loggedOut: (ctx: AuthContext) => ({ * context: ctx, * // This transition is a PURE function that returns the NEXT CONTEXT * login: (username: string) => ({ status: 'loggedIn', username }), * }), * loggedIn: (ctx: AuthContext) => ({ * context: ctx, * logout: () => ({ status: 'loggedOut' }), * }), * }; * * const authUser = createMutableMachine( * { status: 'loggedOut' } as AuthContext, * authFactories, * 'status' * ); * * const userReference = authUser; // Store a reference to the object * * console.log(authUser.status); // 'loggedOut' * * authUser.login('alice'); // Mutates the object in place * * console.log(authUser.status); // 'loggedIn' * console.log(authUser.username); // 'alice' * * // The original reference points to the same, mutated object * console.log(userReference.status); // 'loggedIn' * console.log(userReference === authUser); // true * * // --- Type-safe transitions --- * // `authUser.login('bob')` would now throw a runtime error because `login` * // is not a valid action in the 'loggedIn' state. * * if (authUser.status === 'loggedIn') { * // TypeScript correctly narrows the type here, allowing a safe call. * authUser.logout(); * } * console.log(authUser.status); // 'loggedOut' * * @example * // ===== 2. Game State Loop Example ===== * * type PlayerContext = { * state: 'idle' | 'walking' | 'attacking'; * hp: number; * position: { x: number; y: number }; * }; * * const playerFactories = { * idle: (ctx: PlayerContext) => ({ * context: ctx, * walk: (dx: number, dy: number) => ({ ...ctx, state: 'walking', position: { x: ctx.position.x + dx, y: ctx.position.y + dy } }), * attack: () => ({ ...ctx, state: 'attacking' }), * }), * walking: (ctx: PlayerContext) => ({ * context: ctx, * stop: () => ({ ...ctx, state: 'idle' }), * }), * attacking: (ctx: PlayerContext) => ({ * context: ctx, * finishAttack: () => ({ ...ctx, state: 'idle' }), * }), * }; * * const player = createMutableMachine( * { state: 'idle', hp: 100, position: { x: 0, y: 0 } }, * playerFactories, * (ctx) => ctx.state * ); * * // Simulate a game loop * function processInput(input: 'move_right' | 'attack') { * if (player.state === 'idle') { * if (input === 'move_right') player.walk(1, 0); * if (input === 'attack') player.attack(); * } * console.log(`State: ${player.state}, Position: (${player.position.x}, ${player.position.y})`); * } * * processInput('move_right'); // State: walking, Position: (1, 0) * player.stop(); * processInput('attack'); // State: attacking, Position: (1, 0) */ export declare function createMutableMachine Machine>>(sharedContext: C, factories: F, getDiscriminant: (context: C) => keyof F): MutableMachine>; export {}; //# sourceMappingURL=multi.d.ts.map