@uniflowed/cell
function
read
export function read<T>(source: Cell<T>): T { ... }
Read a cell.
Called inside a derived or an effect, this is also what records the dependency — there is no separate subscribe step, and no way to read a value a derive depends on without depending on it, short of [peek].
Reading a failed cell re-throws what it failed with.
function
peek
export function peek<T>(source: Cell<T>): T { ... }
Read a cell without depending on it.
For the derive that wants to *look at* a value without waking when it changes — a computation that reads a configuration flag it does not want to recompute for. untracked is the same escape hatch for a whole block.
function
write
export function write<T>(source: Cell<T>, value: T): void { ... }
Replace what a cell holds.
A write of the value it already holds is dropped, so nothing downstream runs and no subscriber is woken. Derived cells refuse: their value is a function of their dependencies, and a write that stood would be silently undone by the next recompute.
function
update
export function update<T>(source: Cell<T>, reduce: (current: T) => T): void { ... }
Write the result of reduce applied to what the cell currently holds.
The read and the write are one step so that updaters compose: update(count, (n) => n + 1) twice increments twice, where write(count, read(count) + 1) twice against a value read once does not. The read is untracked — reducing a value is not depending on it.
function
subscribe
export function subscribe<T>(source: Cell<T>, listener: Listener): Unsubscribe { ... }
Be told when a cell's value changes.
Subscribing is what makes a cell *live*: it installs the links that let a write reach it, runs its onMount, and keeps the cells it derives from live too. Unsubscribing the last listener undoes all of that, so a subscription that is never returned is a subscription that never stops.
The listener is called after the graph is consistent, at most once per batch, and only when the value it would read actually changed.
function
snapshot
export function snapshot<T>(source: Cell<T>): CellSnapshot<T> { ... }
What a cell holds, and where that value belongs.
function
batch
export function batch<T>(body: () => T): T { ... }
Run body, waking subscribers once at the end instead of once per write.
Reads inside the batch still see every write immediately — batching defers notification, never consistency. Nesting is counted, so a batch inside a batch flushes with the outermost one, and the flush happens even when the body throws: the writes it made before throwing are real, and subscribers that never heard about them would render state the graph no longer holds.
function
state
export function state<T>(value: T, options?: CellOptions<T>): Cell<T> { ... }
A cell holding a value directly: state, as opposed to something derived from it.
Nothing about it is React-aware or environment-aware: the same cell is read in a server action, a worker and a component, which is why the scope it reports is "client" only in the sense of "wherever the application is".
function
derived
export function derived<T>(derive: () => T, options?: CellOptions<T>): Cell<T> { ... }
A cell derived from other cells, which discovers what those are by running.
Lazy while nothing is watching: an unread, unsubscribed derive costs nothing, and a write to something it depends on does not run it. It runs when someone asks, and — once something *is* watching — when the flush that follows a write asks on the subscriber's behalf.
The value is memoised and compared with equals before anything downstream is told, so a derive that returns the value it returned last time wakes nobody.
A derive that throws is memoised too: the failure is the node's committed state, re-thrown by every read until a dependency changes and the derive is given another chance. Retrying on every read would turn one failing derive into a failure repeated once per reader per render.
function
effect
export function effect(body: () => void | (() => void)): Unsubscribe { ... }
Run body now, and again whenever a cell it read changes.
body may return a teardown, which runs before each re-run and once more when the effect is stopped — the same contract as useEffect, for the same reason: whatever the last run started has to stop before the next run starts it again.
Stopping is what the returned function does, and it is not optional in a long-lived process: an effect holds its dependencies watched, and a watched source keeps every onMount up the chain running.
function
resource
export function resource<T>(
load: (context: LoadContext) => Promise<T>,
options?: CellOptions<?T>,
): Cell<?T> { ... }
A cell whose value arrives from a promise.
Reads as null while the load is in flight, which keeps the type one ?T rather than forcing every consumer through a status union for a state most of them render as a spinner and forget. The value it already holds survives a reload until the new one settles, so a refetch does not blank the screen.
load is tracked: resource(() => fetchUser(read(userId))) reloads when userId changes, and the load that was in flight for the previous id is discarded rather than allowed to win a race against the new one.
It is also given a [LoadContext], whose signal is aborted at that same moment — resource(({ signal }) => fetch(url, { signal })) is a load that stops rather than one that is merely ignored.
function
status
export function status<T>(source: Cell<T>): ResourceStatus { ... }
How far along a [resource]'s load is; "success" for any other cell.
A cell that holds its value always has it, which is what "success" means here. Returning null instead, and making every caller handle a state that cannot happen, buys nothing.
function
refresh
export function refresh<T>(source: Cell<T>): void { ... }
Load again, even though nothing the cell depends on changed.
The escape hatch for state uf does not model: the server knows something the client's dependency graph does not.
On a cell something is watching, the reload starts at the end of the write — and the load in flight, if any, is aborted as the new one begins. On a cell nothing is watching this only marks; the reload happens on the next read, because running it for an audience of nobody is work with no observer, and until then a load already in flight is left alone to settle.
Without a doc comment