@metreeca/core - v0.9.17
    Preparing search index...

    Module state

    Type-safe immutable state management.

    Use when managing objects that change over time through named transitions - like a counter with increment and decrement operations, a shopping cart adding and removing items, or a toggle managing on/off state with enable and disable transitions.

    Concepts

    • State: An interface defining version data properties and transition methods
    • Version: The data properties of a State, excluding transition methods and observers
    • Transition: A method that takes inputs and returns a new State with updated version data
    • Update: A function that accesses current version data via this and returns partial version data to be merged into the state
    • Observer: A function called asynchronously when state transitions occur
    • Manager: Housekeeping operations (snapshots, observers) accessed via manageState(state), kept separate from State to avoid polluting user-defined interfaces

    States are created by the State factory from a Seed where transition methods are implemented as Update functions that return partial updates. These updates are merged into a new immutable state, and transition methods return the new state. Eliminates manual state spreading and ensures type-safe updates.

    Basic Usage

    Define a state interface with data properties and transition methods, then create a state by providing initial values and update functions:

    import { createState } from '@metreeca/core/state';

    interface Counter {

    readonly count: number;

    increment(): this;
    reset(): this;

    }

    const counter = createState<Counter>({

    count: 0,

    increment() {
    return { count: this.count + 1 };
    },

    reset() {
    return { count: 0 };
    }

    });

    Chaining Transitions

    Transitions return new immutable states:

    counter
    .increment() // count is 1
    .increment() // count is 2
    .reset(); // count is 0

    Parameterized Transitions

    Transitions can accept parameters, which are passed as individual arguments to the update function:

    interface Toggle {

    readonly items: readonly string[];

    toggle(item: string): this;

    }

    const toggle = createState<Toggle>({

    items: [],

    toggle(item) {
    return {
    items: this.items.includes(item)
    ? this.items.filter(i => i !== item)
    : [...this.items, item]
    };
    }

    });

    toggle
    .toggle("apple") // items is ["apple"]
    .toggle("banana") // items is ["apple", "banana"]
    .toggle("apple"); // items is ["banana"]

    Observers

    States support the observer pattern through manager metadata accessed via manageState(state). The manager's attach(observer) and detach(observer) methods create and return a new state with the observer attached or detached, preserving immutability. Observers are called asynchronously when transitions occur:

    const counter = createState<Counter>({

    count: 0,

    increment() { return { count: this.count + 1 }; }

    });

    const observer = (version: Version<Counter>) => {
    console.log("Count changed:", version.count);
    };

    // Get manager and attach observer - returns a new state with the observer attached

    const withObserver = manageState(counter).attach(observer);

    withObserver.increment(); // Logs asynchronously: "Count changed: 1"

    // Observers inherited through transitions

    withObserver.increment().increment(); // Each transition notifies observers

    // Detach observer - returns a new state without the observer

    const withoutObserver = manageState(withObserver).detach(observer);

    Observers are compared by reference equality (===), which means the same function reference can only be attached once and must be used to detach.

    Snapshots

    States support creating and restoring version snapshots for implementing features like undo/redo, checkpointing, or time-travel debugging through manager metadata accessed via manageState(state). Snapshots capture the current version data but not observers.

    Call the manager's capture() method to create a snapshot:

    const counter = createState<Counter>({
    count: 0,
    increment() { return { count: this.count + 1 }; }
    });

    const step1 = counter.increment();
    const step2 = step1.increment();

    // Create snapshot at step2 (count is 2)
    const snapshot = manageState(step2).capture();

    Restore state from a snapshot by calling the manager's restore(snapshot) method:

    const step3 = step2.increment(); // count is 3

    // Restore to step2 (count is 2)
    const restored = manageState(step3).restore(snapshot);

    console.log(restored.count); // 2

    Snapshots enable, for instance, undo/redo patterns by storing version history:

    const history: Version<Counter>[] = [];
    let current = counter;

    history.push(manageState(current).capture()); // Save initial version
    current = current.increment();

    history.push(manageState(current).capture()); // Save after increment
    current = current.increment();

    history.push(manageState(current).capture()); // Save after second increment

    // Undo to previous version
    current = manageState(current).restore(history[history.length - 2]);

    Type Aliases

    Instance

    State instance.

    Manager

    State manager.

    Version

    State version.

    Observer

    State observer.

    Seed

    State seed value.

    Transition

    State transition.

    Update

    State update.

    Interfaces

    State

    Versioned data.

    Functions

    createState

    State factory.

    manageState

    Retrieves the Manager for a state instance.