UNPKG

@zeix/cause-effect

Version:

Cause & Effect - reactive state management primitives library for TypeScript.

39 lines (23 loc) 2.2 kB
# ADR 0001: Reactive Task Stale Detection via Pending Node ## Status ✅ Accepted ## Context When a Task source changes, the previous design caused effects to miss the pending state transition. The propagation mechanism sent only `FLAG_CHECK` to downstream effects, and `refresh(effectNode)` called `recomputeTask()` synchronously without triggering a value change. Effects saw no `FLAG_DIRTY` flag and silently became `FLAG_CLEAN` without running — the `stale` handler never fired. The previous rationale ("effect already re-runs at both transition points") was incorrect. This was a critical bug in the reactivity system where stale state was not being properly detected and handled. ## Decision Implement `isPending()` backed by an internal `pendingNode: StateNode<boolean>` in `TaskNode`. - The `pendingNode` is set to `true` inside `recomputeTask()` before starting the async function - The promise `.then`/`.catch` handlers set it to `false` inside a `batch()` alongside any value propagation - Subscribe via `makeSubscribe(pendingNode)` when `isPending()` is called, creating a graph edge This makes the pending state reactive: `setState(pendingNode, true)` inside `recomputeTask` propagates `FLAG_DIRTY` to the effect mid-refresh, causing it to run and route to `stale`. ## Alternatives Considered - **(a) Plain boolean check**: Rejected — effect does not re-run when task goes pending, only when it resolves. This was the previous design that failed. - **(b) Dedicated flag propagated through task sinks**: Rejected — more complex implementation, less unified with the existing graph structure. ## Consequences - ✅ Effects correctly re-run on pending state transition - ✅ The `stale` handler fires as expected when tasks enter pending state - ✅ Graph edge established on the first `ok` or `stale` run; no eager-read refactor needed - ✅ The conditional read in `match()` works correctly because by the time re-fetches matter, at least one prior run through `ok` or `stale` has created the edge - ⚠️ Adds internal node overhead per Task (one additional StateNode) ## Related - Architecture: [Task stale detection in match()](ARCHITECTURE.md#key-decisions)