Lifecycle
A node has two layers of state. Readiness is whether the node has loaded data. Operations (action, refresh, args reconciliation) run on a ready node without changing its readiness. The runtime owns every transition; callers request them through the handle.
handle.read() returns the current state as a tagged union:
const read = runtime.client.node(ProfileNode, { userId: "u_42" }).read();
read._tag; // "Unwired" | "Idle" | "Pending" | "Ready" | "Error"
States
_tag | Meaning | Has result |
|---|---|---|
Unwired | Not in the graph. | No |
Idle | Wired, no readiness attempt in flight, no ready data. | No |
Pending | Readiness attempt in flight. | No |
Ready | Result and deps available for consumer reads. | Yes |
Error | Readiness attempt failed. | No |
Idle, Pending, Ready, and Error also carry operation, busy, and operationFailure. Ready carries resultValidity.
Internal states such as Booting, Expired, Invalid, and Unavailable are kept on raw diagnostic surfaces: unsafe reads, snapshots, diagnostics, tests, and devtools. Product code should use the smaller public read union above.
Note: A
resultofundefinedis valid data. Readiness is determined by the state tag, not by whetherresultis present.
Readiness
ensureReady() crosses the readiness barrier. It resolves with the ready read when the node becomes Ready, and rejects when readiness fails.
await handle.ensureReady();
Idle + ensureReady -> Pending -> success: Ready
-> failure: Error
Pending + ensureReady -> joins the in-flight attempt
Error + ensureReady -> Pending (retry)
Readiness waits for dependency readiness first. A node cannot become Ready until every node it depends on is Ready. If a dependency fails, the dependent enters Error with that failure. See Dependencies.
Operations
Operations run against a Ready node and keep the current result visible while they run. While an operation runs, busy is true and operation is { _tag: "Running", kind }.
| Operation | Trigger | Kind |
|---|---|---|
| Action | handle.runAction(action, input) | "action" |
| Refresh | handle.refresh() | "refresh" |
| Args reconciliation | handle.updateArgs(nextArgs) | "args" |
Ready + operation -> running (result stays visible)
-> success: Ready with committed result
-> failure: Ready with operationFailure
Operation failure is not readiness failure. A failed action or refresh leaves the node Ready with operationFailure set; it does not move the node to Error.
refresh() requires displayable ready data. It returns a Failure result when the node is idle, pending, errored, invalid, or expired. On failure it rolls back to the previous result. A runtime-managed action result commit is applied only when the action succeeds; direct MobX mutations inside a failing action are author-owned.
updateArgs() reconciles args only when they resolve to the same node id. Args that change the id are rejected; request the other node instead. See Identity and keys.
Expiry
Result validity is Current, Stale, or Expired, set by the node’s validity policy.
| Validity | Meaning |
|---|---|
Current | Within the fresh window. |
Stale | Past the stale threshold, still displayable. |
Expired | Past the expiry threshold; not exposed as an ordinary result. |
Stale data still reads as Ready. Expired is a validity state, not a renderable Ready result. Expired data is hidden from ordinary reads and re-acquires when an active consumer needs the node. Expiry does not remove the node from the graph. See Result validity.
Release and eviction
| Action | Method | Effect |
|---|---|---|
| Release | handle.releaseResources(reason) | Stops resources, runs disposers, returns to Idle. Keeps graph wiring. |
| Eviction | handle.evict(mode?, reason?) | Interrupts active work, cleans up, removes the graph record and edges. |
Release keeps the node in the graph; eviction removes it. See Eviction and release.
Observing in React
useNodeState exposes the same fields for render:
const state = FrondReact.useNodeState(ProfileNode, { userId });
state.busy; // operation in flight
state.operation; // { _tag: "Idle" } | { _tag: "Running", kind }
state.operationFailure; // last operation failure, if any
state.resultValidity; // { _tag: "Current" | "Stale" }
See Wire to React.
Next: Dependencies — how readiness flows across edges.