Atoms & Scopes
Lightweight reactive state for Streamix.
Atoms are reactive state nodes. Scopes are tree-shaped containers that own atoms and child scopes, track lifecycle, and expose a unified snapshot of state.
Design
- Atoms are reactive state nodes —
atomfor writable values,flowfor stream-backed values,derivedfor derived values. - Scopes form a tree — each scope owns atoms and nested scopes created within its factory.
- Loading state —
scope.loadingistrueuntil every tracked atom (recursively) has emitted at least once. - Implicit registration — items are tracked automatically via execution context; no manual wiring required.
- Public API is explicit — only values returned from the factory are exposed on the scope object.
Tree model
app (loading)
├── header (loading)
│ └── title = flow(titleStream, '')
├── main (loading)
│ ├── count = atom(0)
│ └── label = flow(labelStream, 'hello')
└── footer = flow(footerStream, '')Only returned values define the public shape:
return { header, main, footer };Internal tracking remains separate from public structure.
API
atom(initialValue)
Creates a writable reactive state node.
const count = atom(0);count.value; // current value
count.prior; // previous value
count.set(10); // update valueSubscribe to changes:
const sub = count.subscribe(v => console.log(v));
count.set(10);
sub.unsubscribe();Dispose:
count.dispose();asyncAtom(options?)
Creates a hot atom without an initial value, similar to a Subject. Supports optional replay to late subscribers.
const count = asyncAtom<number>(); // no replay (like Subject)
const replay = asyncAtom<number>({ capacity: 3 }); // replay last 3 values
const all = asyncAtom<number>({ capacity: Infinity }); // replay all valuescount.set(10);
count.value; // 10
count.prior; // undefined (no initial value)Subscribe to changes (late subscribers may receive replayed values based on capacity):
const sub = count.subscribe(v => console.log(v));
count.set(10);
sub.unsubscribe();Dispose:
count.dispose();flow(stream, initialValue)
Creates a reactive state node connected to a stream.
const source = createSubject<number>();
const count = flow(source, 0);count.value; // current value
count.prior; // previous valueSubscribe to changes:
const sub = count.subscribe(v => console.log(v));
source.next(10);
sub.unsubscribe();Dispose:
count.dispose();derived(factory)
Creates a derived atom with automatic dependency tracking.
const first = atom('Ada');
const last = atom('Lovelace');
const full = derived(() => `${first.value} ${last.value}`);full.value; // 'Ada Lovelace'
first.set('Grace');
full.value; // 'Grace Lovelace'Dispose:
full.dispose();iterate(atom)
Creates an async iterable from any atom. Yields the current value immediately, then yields subsequent values whenever the atom emits. Completes when the atom is disposed.
import { atom, iterate } from '@epikodelabs/streamix';
const a = atom(0);
setTimeout(() => a.set(1), 10);
setTimeout(() => a.set(2), 20);
setTimeout(() => a.dispose(), 30);
for await (const value of iterate(a)) {
console.log(value); // 0, 1, 2
}scope(factory)
Creates a scoped reactive tree. All atoms and nested scopes created inside are automatically tracked.
const app = scope(() => {
const count = atom(0);
const label = flow(labelStream, 'hello');
return { count, label };
});Access:
app.count.value;
app.dispose();Nested scopes
const root = scope(() => {
const header = scope(() => {});
const main = scope(() => {});
return { header, main };
});root.dispose(); // disposes full treeLoading flag
scope.loading is true until all atoms in the tree emit at least once.
const app = scope(() => {
const a = flow(streamA, 0);
const b = flow(streamB, '');
return { a, b };
});app.loading; // true until both emitSnapshots
snapshot() returns a deep object representing current state.
app.snapshot();Example:
{
header: { title: "" },
main: { count: 0, label: "hello" }
}What Scopes do NOT do
- No manual registration — tracking is implicit
- No query/navigation API — structure is defined at creation time
- No batching — updates are synchronous and stream-driven
- No hidden mutation of public API shape