import { Signal, signal } from '@angular/core'; /** * A tiny "Elm-style" store. The whole idea: all state lives in ONE value * (the Model). The only way to change it is to send a message (Msg) to a PURE * function `update(model, msg)` that returns the next Model. Nothing else * mutates state, so to understand the app you only read the update function. * * Side effects (HTTP, timers) do NOT go in `update` — that stays pure and easy * to test. Instead, effectful "command" functions call the network and then * `dispatch` a message describing what happened (e.g. Loaded / Failed). */ export interface Store { /** The current state, as a read-only Angular signal. */ readonly model: Signal; /** Send a message; the model becomes update(model, msg). */ dispatch(msg: Msg): void; } export function createStore( init: Model, update: (model: Model, msg: Msg) => Model, ): Store { const model = signal(init); return { model: model.asReadonly(), // Use `.update` (raw current value, no tracked read) not `set(update(model(), …))`: // dispatch is a command and must never subscribe its caller to `model`. Reading // `model()` here inside an effect that also dispatches makes the effect depend on // its own write and livelock the main thread (crashed the upload wizards). dispatch: (msg) => model.update((m) => update(m, msg)), }; }