You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Port the C# Computed invalidation contracts into the TS kernel:
- K4+K17: Computed.invalidate() never throws. Own onInvalidated handlers
fire first (each isolated via EventHandlerSet.triggerSafe + error log),
then dependants propagate unconditionally (per-dependant try/catch),
then unregister — all in a finally so the cascade always completes. This
also keeps a ComputedState update loop alive when a handler throws.
- K12: replace the public onInvalidated EventHandlerSet field with an
onInvalidated(handler) method that invokes the handler immediately when
the computed is already invalidated (C# add-accessor parity); the handler
set is now private. Updated call sites in fusion-rpc/fusion-hub.ts and the
fusion tests.
- K10: whenInvalidated(abortSignal) stores the abort listener so both sides
clean each other up (invalidation removes the abort listener; abort
removes the invalidation handler); pre-checks abortSignal.aborted and
rejects immediately with signal.reason ?? new Error('Operation cancelled.').
- K9: Computed.update() runs the renewer with the compute context cleared
(computeContextKey = undefined), the TS analog of C# BeginIsolation, so
update()/state.update()/recompute() never records a dependency edge.
Adds kernel2-invalidation.test.ts covering all five items.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SDiBQM5rnhyBG1asuP1kDi
- TS: `computed.ts:184-193` — dependant propagation and `onInvalidated.trigger()` run with no try/catch (`events.ts:19-21` doesn't isolate handlers either). A throwing handler propagates up: remaining dependants are never invalidated, `_unregister` is skipped (an Invalidated computed stays registered), and the exception surfaces to whoever called `invalidate()` — e.g. out of `MutableState.set()`, or inside `ComputedState._updateCycle`, whose outer catch (`computed-state.ts:150-154`) logs "UpdateCycle failed and stopped" and **exits the loop permanently**.
@@ -132,6 +134,8 @@ Confidence: confirmed.
132
134
133
135
### K9. `update()` is not isolated — renewal registers a dependency in the ambient compute context
- TS: `Computed.update()` → `_renewer()` (`computed.ts:106-114`) → `ComputeFunction.invoke`, which falls back to `AsyncContext.current` (`compute-function.ts:55-58`) and captures the produced computed into that ambient context (`compute-function.ts:116`).
@@ -142,6 +146,8 @@ Confidence: confirmed.
142
146
143
147
### K10. `whenInvalidated(abortSignal)` leaks one abort listener per call on long-lived signals; never settles on an already-aborted signal
- TS: `computed.ts:196-216` — the `'abort'` listener (added `{ once: true }`) is never removed when the promise resolves via invalidation. `ComputedState._updateCycle` passes the same `disposeSignal` every iteration (`computed-state.ts:139`), so listeners accumulate one per update. Also: when the signal is *already aborted*, the listener is skipped and the returned promise may never settle (feeds S5); and `ps.reject(abortSignal.reason)` can reject with `undefined`.
@@ -162,6 +168,8 @@ Confidence: confirmed.
162
168
163
169
### K12. `onInvalidated` is a public raw handler set — a handler added after invalidation never fires (C#: fires immediately)
164
170
171
+
Status: **closed** — fixed 2026-07-15 (batch kernel2; `onInvalidated(handler)` is now a method with immediate fire on already-invalidated computeds).
172
+
165
173
Confidence: confirmed.
166
174
167
175
- TS: `computed.ts:51, 190-191` — after `trigger()` the set is cleared; `EventHandlerSet.add` on an already-invalidated computed stores a handler that can never fire. Only `whenInvalidated` has the state pre-check.
@@ -208,6 +216,8 @@ Confidence: confirmed. `computed-registry.ts:25-29` vs C# `ComputedRegistry.cs:1
208
216
209
217
### K17. Invalidation ordering differs (low)
210
218
219
+
Status: **closed** — fixed 2026-07-15 (batch kernel2, folded into the K4 rework as planned).
220
+
211
221
Confidence: confirmed. TS notifies dependants before the computed's own `onInvalidated` handlers (`computed.ts:184-191`); C# fires own handlers first, dependants in `finally` (`Computed.cs:303-318`). Observable to handlers that inspect dependants' state.
212
222
213
223
-**Recommended:** fold into the K4 rework (its recommended shape already fires own handlers first, dependants after, both exception-isolated).
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors -- reason falls back to an Error; abortSignal.reason mirrors throwIfAborted()
0 commit comments