01 · Problem
What is it for?
Unrestricted mutable objects hide who changed a value and when listeners should run.
02 · Mental model
How should you think about it?
A store owns its state. Updates pass through a deliberate pipeline and subscriptions have explicit disposal.
Owns state snapshots and exposes explicit updates and disposable subscriptions. State values must be structured-cloneable. Nested changes require an explicit set; proxy-reserved names cannot be state keys.
03 · Control flow
What happens in which order?
- Own an initial state copy.
- Transform the proposed change through interceptors.
- Run middleware.
- Commit the change.
- Notify a snapshot of listeners.
04 · Building blocks
The important contracts
createStateStore / StateStoreConstruct a store that owns its state.set / snapshotUpdate explicitly and read independent copies.subscribeObserve one property and return an unsubscribe function.05 · Build it yourself
Implement a small core
Start with this small independent implementation, then use the steps below to make its contract explicit.
- Own an initial state copy.
- Transform the proposed change through interceptors.
- Run middleware.
- Commit the change.
- Notify a snapshot of listeners.
class Store<S extends object> {
private listeners = new Set<(state: Readonly<S>) => void>();
constructor(private state: S) {}
get() { return this.state as Readonly<S>; }
set(update: Partial<S>) {
this.state = { ...this.state, ...update };
for (const listener of this.listeners) listener(this.state);
}
subscribe(listener: (state: Readonly<S>) => void) {
this.listeners.add(listener);
return () => this.listeners.delete(listener);
}
}06 · Verify
What should you test?
- Ensure methods remain reachable through the proxy.
- Unsubscribe one listener without affecting others.
- Prevent consumers from mutating internal snapshots.
Try it yourself
Turn the first verification claim into a failing test, implement the smallest fix, then add a boundary case.
Ensure methods remain reachable through the proxy.
Compare your approach
Your check should fail when the stated invariant is deliberately broken, and pass again after the fix. Keep setup deterministic and assert an observable result.
07 · Limits
Trade-offs and failure modes
- Shallow state needs explicit nested-update rules.
- Function-valued data must not be confused with subscription registration.
- Middleware must call next at most once.
State values must be structured-cloneable. Nested changes require an explicit set; proxy-reserved names cannot be state keys.
08 · Keep exploring
Source code and related ideas
yarn tsx packages/state-store/examples/basic.ts