@solidjs/signals 2.0.0-rc.6 → 2.0.0-rc.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/dist/dev-shared.js +5243 -0
  2. package/dist/dev.attribution.js +2333 -0
  3. package/dist/dev.js +1758 -6621
  4. package/dist/observe/affects.js +127 -0
  5. package/dist/observe/attribution.js +1 -0
  6. package/dist/observe/boundaries.js +586 -0
  7. package/dist/observe/core/action.js +167 -0
  8. package/dist/observe/core/async.js +736 -0
  9. package/dist/observe/core/attribution-hooks.js +60 -0
  10. package/dist/observe/core/attribution.js +2318 -0
  11. package/dist/{types-cjs/core/constants.d.cts → observe/core/constants.js} +135 -71
  12. package/dist/{types-cjs/core/context.d.cts → observe/core/context.js} +37 -12
  13. package/dist/observe/core/core.js +1289 -0
  14. package/dist/observe/core/dev.js +232 -0
  15. package/dist/observe/core/effect.js +179 -0
  16. package/dist/observe/core/error.js +80 -0
  17. package/dist/observe/core/external.js +98 -0
  18. package/dist/observe/core/graph.js +155 -0
  19. package/dist/observe/core/heap.js +147 -0
  20. package/dist/observe/core/invariants.js +43 -0
  21. package/dist/observe/core/lanes.js +168 -0
  22. package/dist/observe/core/optimistic.js +438 -0
  23. package/dist/observe/core/owner.js +308 -0
  24. package/dist/observe/core/scheduler.js +1190 -0
  25. package/dist/observe/core/verdict.js +551 -0
  26. package/dist/observe/index.js +55 -0
  27. package/dist/observe/map.js +482 -0
  28. package/dist/{types-cjs/signals.d.cts → observe/signals.js} +305 -317
  29. package/dist/observe/store/index.js +38 -0
  30. package/dist/observe/store/next/optimistic.js +651 -0
  31. package/dist/observe/store/next/projection.js +242 -0
  32. package/dist/observe/store/next/reconcile.js +373 -0
  33. package/dist/observe/store/next/store.js +2213 -0
  34. package/dist/observe/store/next/target.js +51 -0
  35. package/dist/observe/store/store.js +323 -0
  36. package/dist/observe/store/storePath.js +103 -0
  37. package/dist/observe/store/utils.js +210 -0
  38. package/dist/prod/attribution.js +32 -0
  39. package/dist/prod/boundaries.js +3 -1
  40. package/dist/prod/core/action.js +52 -32
  41. package/dist/prod/core/async.js +177 -128
  42. package/dist/prod/core/attribution-hooks.js +3 -0
  43. package/dist/prod/core/constants.js +32 -1
  44. package/dist/prod/core/context.js +10 -16
  45. package/dist/prod/core/core.js +505 -286
  46. package/dist/prod/core/dev.js +2 -0
  47. package/dist/prod/core/effect.js +72 -43
  48. package/dist/prod/core/external.js +2 -2
  49. package/dist/prod/core/graph.js +40 -36
  50. package/dist/prod/core/heap.js +50 -51
  51. package/dist/prod/core/lanes.js +43 -22
  52. package/dist/prod/core/optimistic.js +224 -80
  53. package/dist/prod/core/owner.js +43 -39
  54. package/dist/prod/core/scheduler.js +347 -232
  55. package/dist/prod/core/verdict.js +71 -74
  56. package/dist/prod/index.js +9 -5
  57. package/dist/prod/map.js +311 -150
  58. package/dist/prod/signals.js +52 -38
  59. package/dist/prod/store/next/optimistic.js +127 -131
  60. package/dist/prod/store/next/projection.js +3 -3
  61. package/dist/prod/store/next/reconcile.js +149 -294
  62. package/dist/prod/store/next/store.js +694 -380
  63. package/dist/prod/store/next/target.js +32 -10
  64. package/dist/prod/store/store.js +12 -14
  65. package/dist/prod/store/utils.js +36 -27
  66. package/dist/types/attribution.d.ts +15 -0
  67. package/dist/types/attribution.prod.d.ts +13 -0
  68. package/dist/types/core/async.d.ts +1 -1
  69. package/dist/types/core/attribution-hooks.d.ts +183 -6
  70. package/dist/types/core/attribution.d.ts +435 -24
  71. package/dist/types/core/constants.d.ts +31 -0
  72. package/dist/types/core/core.d.ts +19 -3
  73. package/dist/types/core/dev.d.ts +165 -26
  74. package/dist/types/core/effect.d.ts +8 -1
  75. package/dist/types/core/heap.d.ts +5 -3
  76. package/dist/types/core/index.d.ts +2 -1
  77. package/dist/types/core/invariants.d.ts +1 -1
  78. package/dist/types/core/lanes.d.ts +17 -0
  79. package/dist/types/core/scheduler.d.ts +69 -4
  80. package/dist/types/core/types.d.ts +29 -9
  81. package/dist/types/index.d.ts +9 -2
  82. package/dist/types/map.d.ts +2 -0
  83. package/dist/types/signals.d.ts +10 -0
  84. package/dist/types/store/index.d.ts +4 -6
  85. package/dist/types/store/next/optimistic.d.ts +4 -2
  86. package/dist/types/store/next/reconcile.d.ts +5 -12
  87. package/dist/types/store/next/store.d.ts +8 -9
  88. package/dist/types/store/next/target.d.ts +56 -55
  89. package/dist/types/store/store.d.ts +14 -9
  90. package/dist/types/store/utils.d.ts +7 -0
  91. package/package.json +21 -16
  92. package/dist/node.cjs +0 -11121
  93. package/dist/prod/store/next/patch-hooks.js +0 -13
  94. package/dist/prod/store/next/patch.js +0 -614
  95. package/dist/types/store/next/patch-hooks.d.ts +0 -41
  96. package/dist/types/store/next/patch.d.ts +0 -91
  97. package/dist/types-cjs/affects.d.cts +0 -47
  98. package/dist/types-cjs/boundaries.d.cts +0 -175
  99. package/dist/types-cjs/core/action.d.cts +0 -64
  100. package/dist/types-cjs/core/async.d.cts +0 -23
  101. package/dist/types-cjs/core/attribution-hooks.d.cts +0 -63
  102. package/dist/types-cjs/core/attribution.d.cts +0 -239
  103. package/dist/types-cjs/core/core.d.cts +0 -176
  104. package/dist/types-cjs/core/dev.d.cts +0 -95
  105. package/dist/types-cjs/core/effect.d.cts +0 -30
  106. package/dist/types-cjs/core/error.d.cts +0 -56
  107. package/dist/types-cjs/core/external.d.cts +0 -15
  108. package/dist/types-cjs/core/graph.d.cts +0 -28
  109. package/dist/types-cjs/core/heap.d.cts +0 -22
  110. package/dist/types-cjs/core/index.d.cts +0 -13
  111. package/dist/types-cjs/core/invariants.d.cts +0 -59
  112. package/dist/types-cjs/core/lanes.d.cts +0 -44
  113. package/dist/types-cjs/core/optimistic.d.cts +0 -6
  114. package/dist/types-cjs/core/owner.d.cts +0 -124
  115. package/dist/types-cjs/core/scheduler.d.cts +0 -231
  116. package/dist/types-cjs/core/types.d.cts +0 -204
  117. package/dist/types-cjs/core/verdict.d.cts +0 -2
  118. package/dist/types-cjs/index.d.cts +0 -10
  119. package/dist/types-cjs/map.d.cts +0 -69
  120. package/dist/types-cjs/package.json +0 -3
  121. package/dist/types-cjs/store/index.d.cts +0 -21
  122. package/dist/types-cjs/store/next/optimistic.d.cts +0 -23
  123. package/dist/types-cjs/store/next/patch-hooks.d.cts +0 -41
  124. package/dist/types-cjs/store/next/patch.d.cts +0 -91
  125. package/dist/types-cjs/store/next/projection.d.cts +0 -8
  126. package/dist/types-cjs/store/next/reconcile.d.cts +0 -17
  127. package/dist/types-cjs/store/next/store.d.cts +0 -127
  128. package/dist/types-cjs/store/next/target.d.cts +0 -192
  129. package/dist/types-cjs/store/store.d.cts +0 -138
  130. package/dist/types-cjs/store/storePath.d.cts +0 -58
  131. package/dist/types-cjs/store/utils.d.cts +0 -74
@@ -0,0 +1,438 @@
1
+ import { NOT_PENDING, unwrapOverride, STATUS_UNINITIALIZED, REACTIVE_DIRTY, REACTIVE_CHECK, CONFIG_HAS_LANE, OVERRIDE_UNDEFINED, STATUS_PENDING, CONFIG_OVERRIDE_SUPERSEDED, EFFECT_RENDER, CONFIG_AUTHORITATIVE_OBSERVED, REACTIVE_MANUAL_WRITE, REACTIVE_OPTIMISTIC_DIRTY, LANE_RUN, EFFECT_USER } from "./constants.js";
2
+
3
+ import { attrHooks } from "./attribution-hooks.js";
4
+
5
+ import { ext, stale, latestReadActive, currentOptimisticLane } from "./core.js";
6
+
7
+ import { NotReadyError } from "./error.js";
8
+
9
+ import "./invariants.js";
10
+
11
+ import { resolveTransition, getOrCreateLane, hasActiveOverride, activeLanes, signalLanes, laneHeld, findLane, resolveLane, assignOrMergeLane } from "./lanes.js";
12
+
13
+ import { GlobalQueue, activeTransition, globalQueue, origin, clock, insertSubs, schedule, queuePendingNode } from "./scheduler.js";
14
+
15
+ /**
16
+ * The optimistic write engine, moved out of core.ts/scheduler.ts. Everything
17
+ * here serves only optimistic overrides — createOptimistic,
18
+ * createOptimisticStore (and its store-node writes), and the verdict layer's
19
+ * companions (which are optimistic nodes). Modules that can create optimistic
20
+ * state call `installOptimisticEngine()` before creating it; apps that never
21
+ * import one of those APIs never retain any of this.
22
+ *
23
+ * Core call sites fire the hooks behind guards on state only this module can
24
+ * create (`_overrideValue !== undefined`, `currentOptimisticLane !== null`,
25
+ * `_optimisticNodes.length`, `activeLanes.size`), so `!` invocations are safe
26
+ * once the gate holds — the same late-binding contract as verdict.ts.
27
+ */
28
+ /** The optimistic half of setSignal, fired when `_overrideValue !== undefined`. */ function optimisticWrite(e, n) {
29
+ const i = e.o?.De !== NOT_PENDING;
30
+ const t = i ? unwrapOverride(e.o?.De) : e.ge;
31
+ if (typeof n === "function") n = n(t);
32
+ const u = !!(e.S & STATUS_UNINITIALIZED) ||
33
+ // A dirty node's _value is stale (its queued recompute hasn't run — e.g.
34
+ // a latest() shadow marked by the previous landing's companion snap), so
35
+ // equality against it must not swallow the write. Without this, a sync
36
+ // push returning the shadow to that stale value was dropped, the snap
37
+ // recompute then committed the parent's old value, and the banner showed
38
+ // the previous transition's target (#3041 follow-up).
39
+ !!((e.it ?? 0) & (REACTIVE_DIRTY | REACTIVE_CHECK)) || !e.Ue || !e.Ue(t, n);
40
+ if (!u) {
41
+ // Same-value write with an active override still entangles the current
42
+ // action's transition — the hold must outlast all overlapping actions —
43
+ // and renews the override's PROVENANCE: the newer action re-asks the
44
+ // question, so an older action's answer arriving later is stale to it
45
+ // too (#3331; a same-value re-prediction otherwise let the first
46
+ // action's slow source supersede and restart the downstream flight).
47
+ if (i) {
48
+ const n = resolveTransition(e);
49
+ if (n && activeTransition !== n) globalQueue.initTransition(n);
50
+ if (origin > e.o.Zt) e.o.Zt = origin;
51
+ }
52
+ return n;
53
+ }
54
+ if (i) globalQueue.initTransition(resolveTransition(e));
55
+ // No revert target is stashed: while the override is active every reader
56
+ // sees it (A17), so authoritative arrivals commit silently into _value and
57
+ // reverting is just dropping the override — _value is already correct.
58
+ else globalQueue.m.en.push(e);
59
+ // Stamp ownership on the node (post-merge, so entangled writers share the
60
+ // joint root). resolveTransition prefers this over the lane's _transition,
61
+ // which a shared subscriber can merge across transactions (#2912).
62
+ ext(e).Yt = activeTransition;
63
+ ext(e).Lt = clock;
64
+ // Provenance: the action asking. An answer an OLDER action's flight brings
65
+ // back is a stale question and holds silently to commit (#3331).
66
+ ext(e).Zt = origin;
67
+ const r = getOrCreateLane(e);
68
+ ext(e).Te = r;
69
+ // A fresh override re-masks: whatever truth is staged, this write is the
70
+ // value for the graph again until the source answers it (#3331).
71
+ e.T = (e.T | CONFIG_HAS_LANE) & ~CONFIG_OVERRIDE_SUPERSEDED;
72
+ // Literal undefined must not land raw: the slot doubles as the optimistic
73
+ // brand, and erasing it makes the write invisible and routes follow-up
74
+ // writes off the optimistic path into permanent commits (#2898).
75
+ ext(e).De = n === undefined ? OVERRIDE_UNDEFINED : n;
76
+ // syncCompanions only pokes _pendingSignal/_latestValueComputed — with
77
+ // neither companion present the call is a guaranteed no-op.
78
+ (e.o?.be !== undefined || e.o?.pe !== undefined) && GlobalQueue.Ae !== null && GlobalQueue.Ae(e, n);
79
+ if (e.ot !== undefined) e.ae = clock;
80
+ // §12e: computed-only slot
81
+ insertSubs(e, true);
82
+ schedule();
83
+ return n;
84
+ }
85
+
86
+ /**
87
+ * transitionComplete's override blockage: a settling transition stays open
88
+ * while one of its optimistic nodes holds an active override that is still
89
+ * pending on real (non-affects-sentinel) async.
90
+ */ function transitionBlocked(e) {
91
+ for (let n = 0; n < e.en.length; n++) {
92
+ const i = e.en[n];
93
+ if (hasActiveOverride(i) && "S" in i && i.S & STATUS_PENDING && i.o?._ instanceof NotReadyError) {
94
+ return true;
95
+ }
96
+ }
97
+ return false;
98
+ }
99
+
100
+ function resolveOptimisticNodes(e) {
101
+ // Settlement writes below (snapCompanionsToState → updatePendingSignal-style
102
+ // notifications) may push fresh optimistic nodes; only this batch settles
103
+ // now, so iterate a fixed window and splice it out at the end.
104
+ const n = e.length;
105
+ for (let i = 0; i < n; i++) {
106
+ const n = e[i];
107
+ if (n.o !== null) n.o.Te = undefined;
108
+ // Revert is a pure drop: there is no revert target to commit —
109
+ // override-covered authoritative values hold in _pendingValue and
110
+ // elevate on their OWN transition's schedule (A18 as re-ruled 2026-07-07).
111
+ if (!(n.S & STATUS_PENDING)) n.S &= ~STATUS_UNINITIALIZED;
112
+ const t = n.o?.De;
113
+ ext(n).De = NOT_PENDING;
114
+ // A superseded override's subscribers already re-derived from the truth
115
+ // when it arrived (#3331) — the drop changes nothing they read. Everyone
116
+ // else learns of the correction here: this drop IS their notification.
117
+ const u = (n.T & CONFIG_OVERRIDE_SUPERSEDED) !== 0;
118
+ n.T &= ~CONFIG_OVERRIDE_SUPERSEDED;
119
+ if (!u && t !== NOT_PENDING && n.ge !== unwrapOverride(t)) insertSubs(n, true);
120
+ n.Ee = null;
121
+ if (n.o !== null) n.o.Yt = null;
122
+ }
123
+ // Settlement checkpoint (#2838): companions caught in this batch (or owned
124
+ // by a node in it) re-derive from committed state, so verdicts survive the
125
+ // transition that produced them (A19 — pending is a property of the data).
126
+ for (let i = 0; i < n; i++) {
127
+ const n = e[i];
128
+ if (n.o?.be || n.o?.pe) GlobalQueue.Dn(n);
129
+ const t = n.o?.Qe;
130
+ if (t && (t.o?.be === n || t.o?.pe === n)) GlobalQueue.Dn(t);
131
+ }
132
+ e.splice(0, n);
133
+ }
134
+
135
+ /**
136
+ * A18 supersession (#3331): the node's own source arrived with a value that
137
+ * differs from its active override. "Knowing otherwise" ends the optimism for
138
+ * the graph at once: the override stays only as the DISPLAYED value (untracked
139
+ * reads, the applied frame) until the owning transaction commits, while
140
+ * tracked readers see the staged truth and re-derive from it as that
141
+ * transaction's held work — so async downstream restarts now, not at the
142
+ * revert (no waterfall).
143
+ *
144
+ * The lane's job for this node is over: a lane applies an optimistic view
145
+ * ahead of its transaction, and there is no optimistic view left — the
146
+ * corrected cascade is plain transaction-held work (staged memos, effect runs
147
+ * in the stashable queues). Demote the node and every cascade member that
148
+ * rides this lane; the caller then notifies on the plain channel. Runners the
149
+ * lane still holds for demoted effects (the optimistic frame that never got to
150
+ * apply) defer to the regular queue in runEffect. In a lane merged with a
151
+ * still-optimistic source, members shared with that source lose their lane
152
+ * too and simply wait for the transaction — less optimistic, never torn.
153
+ *
154
+ * A later arrival EQUAL to the override (an earlier action's answer superseded
155
+ * this one's; now this one's answer confirms it) ends the supersession: the
156
+ * graph re-derives from the override, which is the truth again. Notifies the
157
+ * subscribers in both cases. A plain matching confirmation (no supersession
158
+ * in force) is A17-silent for ordinary subscribers and wakes only an
159
+ * authoritative-view reader (until()'s predicate, refresh()'s waiter) that
160
+ * observed this node past its override — "authoritative arrival equal to the
161
+ * override" is exactly the acknowledgment it waits for (#3164, #3303). The
162
+ * wake hook is installed by the setters of that bit; optional here because
163
+ * the bit only implies the optimistic engine was consulted.
164
+ */ function supersedeOverride(e, n) {
165
+ const i = !e.Ue || !e.Ue(n, unwrapOverride(e.o.De));
166
+ if (!i) {
167
+ if (!(e.T & CONFIG_OVERRIDE_SUPERSEDED)) {
168
+ if (e.T & CONFIG_AUTHORITATIVE_OBSERVED) GlobalQueue.En?.(e);
169
+ return;
170
+ }
171
+ e.T &= ~CONFIG_OVERRIDE_SUPERSEDED;
172
+ } else {
173
+ // Provenance (#3331): an answer brought back by an OLDER action than the
174
+ // one that wrote this override answers a question the user has since
175
+ // changed. It is staged like any other landing and reveals if it is
176
+ // still the truth when the transaction commits, but it does not move the
177
+ // graph now — a slow source must not leak back in over a newer intent.
178
+ // 0 is mainline (no action): always the current question.
179
+ if (origin && origin < e.o.Zt) return;
180
+ e.T |= CONFIG_OVERRIDE_SUPERSEDED;
181
+ const n = e.o?.Te;
182
+ if (n) {
183
+ const i = findLane(n);
184
+ const t = [ e ];
185
+ while (t.length) {
186
+ const e = t.pop();
187
+ const n = e.o?.Te;
188
+ if (!n || findLane(n) !== i) continue;
189
+ e.o.Te = undefined;
190
+ i._e.delete(e);
191
+ for (let n = e.u; n !== null; n = n.se) t.push(n.le);
192
+ }
193
+ }
194
+ }
195
+ if (attrHooks !== null) attrHooks.asyncEnd(e, undefined, n, true);
196
+ insertSubs(e);
197
+ }
198
+
199
+ /** read()'s value for a tracked reader of a superseded node: the truth —
200
+ * staged, or already committed (a mainline landing commits at the head of
201
+ * its flush, ahead of the heap run, and the override drops only at the
202
+ * batch's end; in between the graph must not fall back to the override it
203
+ * has left) — or the displayed override for a stale (render) reader of some
204
+ * OTHER transaction, the same visibility a foreign transaction's staged
205
+ * write has. */ function supersededRead(e) {
206
+ if (stale && e.Ee && activeTransition !== e.Ee) return unwrapOverride(e.o?.De);
207
+ return e.Pe !== NOT_PENDING ? e.Pe : e.ge;
208
+ }
209
+
210
+ /**
211
+ * An authoritative store landing on an override-covered node — a derived
212
+ * optimistic store's own truth arriving over a tentative edit through the
213
+ * projection-write channel (setSignal under projectionWriteActive). The
214
+ * store twin of asyncWrite's override branch: the truth stages for its
215
+ * transaction's commit whatever its relation to the committed value (a
216
+ * landing equal to committed still differs from the override), companions
217
+ * learn of it, and supersedeOverride decides the rest — A18 supersession with
218
+ * action provenance, or an A17-silent confirmation (#3331). Before this the
219
+ * landing took setSignal's plain path: a differing truth staged silently
220
+ * under the override and the graph never moved until the commit.
221
+ */ function landOnOverride(e, n) {
222
+ const i = e.Pe === NOT_PENDING ? e.ge : e.Pe;
223
+ if (typeof n === "function") n = n(i);
224
+ if (attrHooks !== null) attrHooks.write(e, i, n);
225
+ if (e.Pe === NOT_PENDING) queuePendingNode(e);
226
+ e.Pe = n;
227
+ GlobalQueue.Ae?.(e, n);
228
+ supersedeOverride(e, n);
229
+ schedule();
230
+ return n;
231
+ }
232
+
233
+ function runQueue(e, n) {
234
+ for (let i = 0; i < e.length; i++) e[i](n | LANE_RUN);
235
+ }
236
+
237
+ /**
238
+ * Run effects from all lanes that are ready (no OBSERVED pending async — see
239
+ * laneHeld).
240
+ */ function runLaneEffects(e) {
241
+ for (const n of activeLanes) {
242
+ if (n.gn || laneHeld(n)) continue;
243
+ const i = n.vn[e - 1];
244
+ if (i.length) {
245
+ n.vn[e - 1] = [];
246
+ runQueue(i, e);
247
+ }
248
+ }
249
+ // Optimistic patch applications ride the same visibility slot as lane
250
+ // effects (in-flight DOM updates); no-op unless patches registered.
251
+ if (e === EFFECT_RENDER) GlobalQueue.bn?.();
252
+ }
253
+
254
+ function cleanupCompletedLanes(e) {
255
+ for (const n of activeLanes) {
256
+ const i = e ? n.Ee === e : !n.Ee;
257
+ if (!i) continue;
258
+ if (!n.gn) {
259
+ if (n.vn[0].length) runQueue(n.vn[0], EFFECT_RENDER);
260
+ if (n.vn[1].length) runQueue(n.vn[1], EFFECT_USER);
261
+ }
262
+ if (n.Ln.o?.Te === n) if (n.Ln.o !== null) n.Ln.o.Te = undefined;
263
+ n._e.clear();
264
+ n.vn[0].length = 0;
265
+ n.vn[1].length = 0;
266
+ activeLanes.delete(n);
267
+ signalLanes.delete(n.Ln);
268
+ }
269
+ }
270
+
271
+ /** read()'s per-lane suspension test (pending-throw path, lane context). */ function laneSuspends(e) {
272
+ // An UNINITIALIZED async source suspends regardless of lane (#3276): a
273
+ // lane mismatch preserves an already-committed stale value, but a source
274
+ // with no committed truth has nothing to serve — the cross-lane read
275
+ // surfaced a fabricated `undefined` where latest() itself suspends
276
+ // (latestRead rethrows NotReady for tracked uninitialized reads). Lives
277
+ // here rather than read()'s throw path so the floor bundles don't pay:
278
+ // this is only reachable under a lane, which implies the engine.
279
+ if (e.S & STATUS_UNINITIALIZED) return true;
280
+ // Per-lane suspension: only throw if in same lane as pending async
281
+ // AND the node doesn't have an active override (overrides are the visible value,
282
+ // downstream in the lane should read the override, not throw)
283
+ const n = e.o?.Te;
284
+ if (!n) return false;
285
+ return findLane(n) === findLane(currentOptimisticLane) && !hasActiveOverride(e);
286
+ }
287
+
288
+ /**
289
+ * read()'s reveal carve-out asks whether a pending node is routed through a
290
+ * LIVE lane: a lane-derived flight's inputs are already revealed through the
291
+ * lane (the override, or latest()'s fresh value), so a stale reader of another
292
+ * transaction must hold on the flight rather than show the node's committed
293
+ * value beside them (#3334). Exact, not sticky: `resolveLane` clears a lane
294
+ * reference the engine has since retired, so a node that was once lane-routed
295
+ * and is now pending under a plain hold is judged by that hold alone.
296
+ */ function laneLive(e) {
297
+ return resolveLane(e) !== undefined;
298
+ }
299
+
300
+ /**
301
+ * read()'s entanglement gate: a reader recomputing under an optimistic lane
302
+ * that reads a pending mid-transition write sees the committed value; the sub
303
+ * is recorded for replay at commit.
304
+ */ function gatedRead(e, n, i) {
305
+ if (latestReadActive || e.Pe === NOT_PENDING || e.ot || n !== e && !(n.it & REACTIVE_MANUAL_WRITE)) {
306
+ return false;
307
+ }
308
+ activeTransition.ht.add(i);
309
+ return true;
310
+ }
311
+
312
+ /**
313
+ * read()'s value selection under a lane: return the committed `_value` for
314
+ * optimistic/lane-assigned signals, stale-mode reads, and pending owners.
315
+ */ function laneReadsCommitted(e, n, i) {
316
+ if (e.o?.De !== undefined || !!e.o?.Te || !!(n.S & STATUS_PENDING)) {
317
+ // The committed view hides a staged in-flight value that will promote
318
+ // silently (commitPendingNode never re-notifies). gatedRead records plain
319
+ // signals for replay at commit; async memos are excluded from it by the
320
+ // `_fn` check and reach here instead — a lane-assigned source whose async
321
+ // already settled (laneAsyncSettled keeps _optimisticLane) served its
322
+ // committed value to a reader that never re-ran after the landing, so a
323
+ // pending-gated branch stayed one value behind permanently (#3041
324
+ // follow-up). Record the reader under the same replay contract — when
325
+ // the commit will actually change what it read: a staged value equal to
326
+ // the committed one (a lane recompute already published it, INV-11)
327
+ // promotes to the same view, and a replay would only re-run effects
328
+ // against an unchanged frame (#3330). An override-covered node's revert
329
+ // notifies its own subscribers when the truth differs (resolveOptimistic
330
+ // Nodes), so the reader is recorded only for the staged-vs-committed gap.
331
+ if (e.Pe !== NOT_PENDING && e.Pe !== e.ge) (activeTransition ?? globalQueue.m).ht.add(i);
332
+ return true;
333
+ }
334
+ if (n === e && stale && i.o?.Qe !== e) {
335
+ // The committed view can hide a staged write (a lane member — even just
336
+ // an isPending companion flip — puts the reader "under a lane"). The
337
+ // staged value commits with no re-delivery (commitPendingNode never
338
+ // re-notifies), so record the reader for replay at commit — the same
339
+ // contract gatedRead provides (#2963). gatedRead itself only covers
340
+ // signal reads where the reading computed differs from the source; the
341
+ // owner === el memo/self read lands here instead. With a transaction
342
+ // active the staged value promotes silently at ITS landing, so record
343
+ // into the transaction (#3041 follow-up: a pending-gated branch that
344
+ // first read its async source during the landing flush stayed one value
345
+ // behind permanently); with none, into the ambient batch.
346
+ if (e.Pe !== NOT_PENDING) (activeTransition ?? globalQueue.m).ht.add(i);
347
+ return true;
348
+ }
349
+ return false;
350
+ }
351
+
352
+ /**
353
+ * recompute()'s lane posture: resolve the node's own lane (own=true), or adopt
354
+ * a dependency's optimistic lane (own=false — parent-deeper-than-owned-child
355
+ * can run before its OPT-dirty child propagates).
356
+ */ function recomputeLane(e, n) {
357
+ if (n) {
358
+ const n = resolveLane(e);
359
+ if (!n) return null;
360
+ // Wake-only lane demotion (#3009): a plain write to a latest()-tracked
361
+ // source rides the optimistic channel only to wake verdict companions —
362
+ // its lane is sourced by the companion shadow (_parentSource set) and owns
363
+ // no transaction. When such a node is pulled mid-tick by a latest()/
364
+ // isPending() probe, lane posture would direct-commit _value, leaking the
365
+ // queued write into committed reads before the flush. Return `false` so
366
+ // recompute() runs plain: the value stages and commits with the flush.
367
+ // (el's own override slot excludes companions themselves; a lane merged
368
+ // into a real optimistic lane resolves to a non-companion source.)
369
+ if (!globalQueue.Qn && !activeTransition && !n.Ee && n.Ln.o?.Qe !== undefined && e.o?.De === undefined) {
370
+ if (e.o !== null) e.o.Te = undefined;
371
+ return false;
372
+ }
373
+ return n;
374
+ }
375
+ for (let n = e.te; n; n = n.ne) {
376
+ const i = n.oe;
377
+ if (i.it & REACTIVE_OPTIMISTIC_DIRTY) {
378
+ const n = resolveLane(i);
379
+ if (n) {
380
+ e.it |= REACTIVE_OPTIMISTIC_DIRTY;
381
+ assignOrMergeLane(e, n);
382
+ return n;
383
+ }
384
+ }
385
+ }
386
+ return null;
387
+ }
388
+
389
+ /** recompute()'s catch path: record the pending async as the current lane's
390
+ * (ownership — laneHeld decides the hold). The lane source's isPending
391
+ * companion is NOT refreshed here: its verdict never read _pendingAsync, and
392
+ * the source's own write/commit/settlement paths keep it current. */ function laneAsyncPending(e) {
393
+ const n = findLane(currentOptimisticLane);
394
+ if (n.Ln !== e) {
395
+ n._e.add(e);
396
+ ext(e).Te = n;
397
+ e.T |= CONFIG_HAS_LANE;
398
+ }
399
+ }
400
+
401
+ /** recompute()'s success path: the node's async settled, clear it from its lane. */ function laneAsyncSettled(e) {
402
+ const n = resolveLane(e);
403
+ if (n) {
404
+ n._e.delete(e);
405
+ }
406
+ }
407
+
408
+ function trackOptimisticStore(e) {
409
+ // After initTransition, globalQueue._batch IS activeTransition (same reference)
410
+ globalQueue.m.nn.add(e);
411
+ schedule();
412
+ }
413
+
414
+ /**
415
+ * Installs the engine's hooks. Idempotent; called by every module that can
416
+ * create optimistic state (verdict.ts at module top level, createOptimistic
417
+ * and createOptimisticStore at first call) BEFORE any optimistic node exists.
418
+ */ function installOptimisticEngine() {
419
+ if (GlobalQueue.pn !== null) return;
420
+ GlobalQueue.pn = optimisticWrite;
421
+ GlobalQueue.Un = resolveOptimisticNodes;
422
+ GlobalQueue.Pn = transitionBlocked;
423
+ GlobalQueue.Fn = cleanupCompletedLanes;
424
+ GlobalQueue.Vn = runLaneEffects;
425
+ GlobalQueue.Oe = supersedeOverride;
426
+ GlobalQueue.Sn = supersededRead;
427
+ GlobalQueue.Rn = landOnOverride;
428
+ GlobalQueue.An = gatedRead;
429
+ GlobalQueue.dn = laneSuspends;
430
+ GlobalQueue.Tn = laneLive;
431
+ GlobalQueue.On = laneReadsCommitted;
432
+ GlobalQueue.Ft = recomputeLane;
433
+ GlobalQueue.vt = laneAsyncPending;
434
+ GlobalQueue.Ht = laneAsyncSettled;
435
+ GlobalQueue.Hn = trackOptimisticStore;
436
+ }
437
+
438
+ export { installOptimisticEngine };