@solidjs/signals 2.0.0-rc.7 → 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 (117) hide show
  1. package/dist/dev-shared.js +5243 -0
  2. package/dist/dev.attribution.js +2333 -0
  3. package/dist/dev.js +1229 -6834
  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} +129 -73
  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} +295 -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/core/action.js +52 -32
  40. package/dist/prod/core/async.js +171 -127
  41. package/dist/prod/core/constants.js +24 -1
  42. package/dist/prod/core/context.js +3 -3
  43. package/dist/prod/core/core.js +434 -291
  44. package/dist/prod/core/effect.js +54 -34
  45. package/dist/prod/core/external.js +2 -2
  46. package/dist/prod/core/graph.js +32 -32
  47. package/dist/prod/core/heap.js +49 -44
  48. package/dist/prod/core/lanes.js +45 -38
  49. package/dist/prod/core/optimistic.js +207 -73
  50. package/dist/prod/core/owner.js +43 -39
  51. package/dist/prod/core/scheduler.js +319 -190
  52. package/dist/prod/core/verdict.js +67 -67
  53. package/dist/prod/index.js +9 -3
  54. package/dist/prod/map.js +292 -141
  55. package/dist/prod/signals.js +7 -10
  56. package/dist/prod/store/next/optimistic.js +140 -125
  57. package/dist/prod/store/next/projection.js +3 -3
  58. package/dist/prod/store/next/reconcile.js +87 -84
  59. package/dist/prod/store/next/store.js +524 -280
  60. package/dist/prod/store/next/target.js +32 -10
  61. package/dist/prod/store/store.js +12 -14
  62. package/dist/prod/store/utils.js +36 -27
  63. package/dist/types/attribution.d.ts +15 -0
  64. package/dist/types/attribution.prod.d.ts +13 -0
  65. package/dist/types/core/async.d.ts +1 -1
  66. package/dist/types/core/attribution-hooks.d.ts +119 -6
  67. package/dist/types/core/attribution.d.ts +208 -53
  68. package/dist/types/core/constants.d.ts +23 -0
  69. package/dist/types/core/core.d.ts +7 -0
  70. package/dist/types/core/dev.d.ts +124 -26
  71. package/dist/types/core/effect.d.ts +8 -1
  72. package/dist/types/core/index.d.ts +2 -1
  73. package/dist/types/core/lanes.d.ts +8 -1
  74. package/dist/types/core/scheduler.d.ts +60 -0
  75. package/dist/types/core/types.d.ts +29 -9
  76. package/dist/types/index.d.ts +9 -2
  77. package/dist/types/map.d.ts +2 -0
  78. package/dist/types/store/index.d.ts +1 -0
  79. package/dist/types/store/next/store.d.ts +5 -0
  80. package/dist/types/store/next/target.d.ts +36 -9
  81. package/dist/types/store/utils.d.ts +7 -0
  82. package/package.json +21 -17
  83. package/dist/node.cjs +0 -10541
  84. package/dist/node.dev.cjs +0 -13724
  85. package/dist/types-cjs/affects.d.cts +0 -47
  86. package/dist/types-cjs/boundaries.d.cts +0 -175
  87. package/dist/types-cjs/core/action.d.cts +0 -64
  88. package/dist/types-cjs/core/async.d.cts +0 -23
  89. package/dist/types-cjs/core/attribution-hooks.d.cts +0 -127
  90. package/dist/types-cjs/core/attribution.d.cts +0 -495
  91. package/dist/types-cjs/core/core.d.cts +0 -185
  92. package/dist/types-cjs/core/dev.d.cts +0 -136
  93. package/dist/types-cjs/core/effect.d.cts +0 -30
  94. package/dist/types-cjs/core/error.d.cts +0 -56
  95. package/dist/types-cjs/core/external.d.cts +0 -15
  96. package/dist/types-cjs/core/graph.d.cts +0 -28
  97. package/dist/types-cjs/core/heap.d.cts +0 -24
  98. package/dist/types-cjs/core/index.d.cts +0 -13
  99. package/dist/types-cjs/core/invariants.d.cts +0 -59
  100. package/dist/types-cjs/core/lanes.d.cts +0 -54
  101. package/dist/types-cjs/core/optimistic.d.cts +0 -6
  102. package/dist/types-cjs/core/owner.d.cts +0 -124
  103. package/dist/types-cjs/core/scheduler.d.cts +0 -236
  104. package/dist/types-cjs/core/types.d.cts +0 -204
  105. package/dist/types-cjs/core/verdict.d.cts +0 -2
  106. package/dist/types-cjs/index.d.cts +0 -10
  107. package/dist/types-cjs/map.d.cts +0 -69
  108. package/dist/types-cjs/package.json +0 -3
  109. package/dist/types-cjs/store/index.d.cts +0 -18
  110. package/dist/types-cjs/store/next/optimistic.d.cts +0 -25
  111. package/dist/types-cjs/store/next/projection.d.cts +0 -8
  112. package/dist/types-cjs/store/next/reconcile.d.cts +0 -10
  113. package/dist/types-cjs/store/next/store.d.cts +0 -121
  114. package/dist/types-cjs/store/next/target.d.cts +0 -166
  115. package/dist/types-cjs/store/store.d.cts +0 -143
  116. package/dist/types-cjs/store/storePath.d.cts +0 -58
  117. package/dist/types-cjs/store/utils.d.cts +0 -74
@@ -0,0 +1,1190 @@
1
+ import { EFFECT_RENDER, EFFECT_USER, STATUS_PENDING, REACTIVE_IN_HEAP_HEIGHT, REACTIVE_DISPOSED, CONFIG_HELD_TRUTH, REACTIVE_ZOMBIE, CONFIG_HAS_LANE, CONFIG_HAS_SNAPSHOT, REACTIVE_RECOMPUTING_DEPS, REACTIVE_MISSED_WAKE, CONFIG_IN_SNAPSHOT_SCOPE, REACTIVE_SNAPSHOT_STALE, REACTIVE_OPTIMISTIC_DIRTY, NOT_PENDING, CONFIG_SLOT_NODE, CONFIG_HAS_COMPANIONS, EFFECT_TRACKED, CONFIG_INPUTS_PUBLISHED, REACTIVE_REASK, REACTIVE_MANUAL_WRITE, STATUS_UNINITIALIZED, CONFIG_AUTHORITATIVE_READ } from "./constants.js";
2
+
3
+ import { attrHooks } from "./attribution-hooks.js";
4
+
5
+ import { currentOptimisticLane, slotUnobservedHook } from "./core.js";
6
+
7
+ import { DEV, GRAPH_SIZE_WARN_AT, noteFanOut } from "./dev.js";
8
+
9
+ import { NotReadyError } from "./error.js";
10
+
11
+ import { sweepDormant } from "./graph.js";
12
+
13
+ import { runHeap, deleteFromHeap, enqueueSub } from "./heap.js";
14
+
15
+ import { activeLanes, assignOrMergeLane, findLane, hasActiveOverride } from "./lanes.js";
16
+
17
+ export { getOrCreateLane, mergeLanes, resolveLane } from "./lanes.js";
18
+
19
+ import { devCheckFlushStart, devCheckActiveOverrides, devCensusCompanions, devCheckQuiescent } from "./invariants.js";
20
+
21
+ const transitions = new Set;
22
+
23
+ const dirtyQueue = {
24
+ eE: new Array(2e3).fill(undefined),
25
+ tE: false,
26
+ St: 0,
27
+ EE: 0
28
+ };
29
+
30
+ const zombieQueue = {
31
+ eE: new Array(2e3).fill(undefined),
32
+ tE: false,
33
+ St: 0,
34
+ EE: 0
35
+ };
36
+
37
+ /** runHeap callback that discards a queued zombie recompute instead of running
38
+ * it: unlink pure recompute entries; strip just the recompute bit from dirtied
39
+ * height-adjust entries so their height work still happens. */ function cancelZombieRecompute(e) {
40
+ if (e.it & REACTIVE_IN_HEAP_HEIGHT) e.it &= -12; else {
41
+ deleteFromHeap(e, zombieQueue);
42
+ e.it &= -4;
43
+ }
44
+ }
45
+
46
+ let clock = 0;
47
+
48
+ let activeTransition = null;
49
+
50
+ let scheduled = false;
51
+
52
+ let halted = false;
53
+
54
+ let haltNotified = false;
55
+
56
+ let syncDepth = 0;
57
+
58
+ let projectionWriteActive = false;
59
+
60
+ /** > 0 while an action's generator body is on the stack (the synchronous
61
+ * slice between yields). Maintained by action.ts around `it.next()`. */ let actionStepDepth = 0;
62
+
63
+ function enterActionStep() {
64
+ actionStepDepth++;
65
+ }
66
+
67
+ function exitActionStep() {
68
+ actionStepDepth--;
69
+ }
70
+
71
+ // Store property nodes that were created solely to carry a pending write (no
72
+ // subscribers at write time). Swept after each flush that commits pending
73
+ // values — any still without subs get disposed via their `_unobserved` hook,
74
+ // releasing the slot in the parent store's node map.
75
+ const transientStoreNodes = new Set;
76
+
77
+ function canUseSimpleSyncFlush(e) {
78
+ const t = e.m;
79
+ return transitions.size === 0 && activeLanes.size === 0 && e.ei.length === 0 && t.en.length === 0 && t.A.length === 0 && t.nn.size === 0 && transientStoreNodes.size === 0;
80
+ }
81
+
82
+ function sweepTransientStoreNodes() {
83
+ if (transientStoreNodes.size === 0) return;
84
+ for (const e of transientStoreNodes) {
85
+ if (e.u !== null) {
86
+ transientStoreNodes.delete(e);
87
+ continue;
88
+ }
89
+ if (e.Pe !== NOT_PENDING) continue;
90
+ if (e.o?.De !== undefined && e.o?.De !== NOT_PENDING) continue;
91
+ // A live affects() mark keeps the node addressable: sweeping it would
92
+ // detach the refcount from the slot (a fresh probe would upsert a new,
93
+ // unmarked node for the same property).
94
+ if (e.o?.t) continue;
95
+ transientStoreNodes.delete(e);
96
+ if (e.T & CONFIG_SLOT_NODE) slotUnobservedHook(e); else e.o?.qt?.();
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Toggles the dev-mode "must be inside a `<Loading>` boundary" enforcement
102
+ * window. Only `render()` calls this — wrapping the initial mount so that a
103
+ * top-level uncaught async read surfaces the diagnostic. Not part of the
104
+ * user-facing API.
105
+ *
106
+ * @internal
107
+ */ function enforceLoadingBoundary(e) {}
108
+
109
+ function setProjectionWriteActive(e) {
110
+ projectionWriteActive = e;
111
+ }
112
+
113
+ /**
114
+ * Ambient work IS a transaction: the global queue always carries one
115
+ * current-transaction-shaped batch (`globalQueue._batch`). With no transition
116
+ * active, registrations (pending commits, optimistic nodes, affects marks,
117
+ * optimistic stores) land in a plain ambient batch that the plain flush
118
+ * finalizes; when a transition initializes it adopts the ambient batch's
119
+ * contents and `_batch` becomes the transition itself, so later registrations
120
+ * land there directly — no per-field aliasing.
121
+ */ function createBatch() {
122
+ return {
123
+ ae: clock,
124
+ tn: [],
125
+ Ie: new Map,
126
+ en: [],
127
+ A: [],
128
+ nn: new Set,
129
+ ut: [],
130
+ ti: {
131
+ ii: [ [], [] ],
132
+ ei: []
133
+ },
134
+ Ut: false,
135
+ ht: new Set,
136
+ xt: null
137
+ };
138
+ }
139
+
140
+ function mergeTransitionState(e, t) {
141
+ if (attrHooks !== null) attrHooks.transitionMerged(e, t);
142
+ t.Ut = e;
143
+ e.ut.push(...t.ut);
144
+ for (const i of activeLanes) if (i.Ee === t) i.Ee = e;
145
+ if (t.en.length) {
146
+ // Move (don't copy): the global queue's batch may still be the outgoing
147
+ // transition, and the adoption pass in initTransition would re-push its
148
+ // contents into the target — duplicating every entry.
149
+ e.en.push(...t.en);
150
+ t.en.length = 0;
151
+ }
152
+ if (t.A.length) {
153
+ // Move (don't copy): the global queue's batch may still be the outgoing
154
+ // transition, and the adoption pass in initTransition would re-push its
155
+ // contents into the target — double-releasing every mark.
156
+ e.A.push(...t.A);
157
+ t.A.length = 0;
158
+ }
159
+ for (const i of t.nn) e.nn.add(i);
160
+ for (const [i, n] of t.Ie) {
161
+ let t = e.Ie.get(i);
162
+ if (!t) e.Ie.set(i, t = new Set);
163
+ for (const e of n) t.add(e);
164
+ }
165
+ for (const i of t.ht) e.ht.add(i);
166
+ if (t.xt) (e.xt ??= []).push(...t.xt);
167
+ }
168
+
169
+ /**
170
+ * Flip-entanglement (#3164 follow-up): `until()` is a declaration of
171
+ * relatedness — the predicate names the condition that confirms the awaiting
172
+ * transaction. When the predicate settles truthy, every live foreign
173
+ * transition whose staged write it read IS the confirming event by the
174
+ * user's own definition, so it merges into the awaiting transaction and
175
+ * reveals at the joint settle — the cross-primitive twin of the family fold
176
+ * (a landing on an optimism-carrying family joins the retaining
177
+ * transaction). Non-flipping updates never pass through here: falsy
178
+ * evaluations don't entangle, so unrelated traffic on the watched sources
179
+ * reveals freely on its own schedule.
180
+ *
181
+ * Runs inside the predicate's compute (pure phase) — the confirming
182
+ * transition's stamps are still live and its commit decision hasn't run, so
183
+ * the merge lands before any reveal. Only the tree-shaken graphs that call
184
+ * `until()` retain this.
185
+ */ function entangleConfirmingTransitions(e, t) {
186
+ t = currentTransition(t);
187
+ if (t.Ut === true) return;
188
+ // The confirming evidence is a dep whose value is STAGED (pending,
189
+ // uncommitted) at flip evaluation — committed deps are public already and
190
+ // carry nothing to entangle. A staged dep lives in one of two carriers: a
191
+ // stamped transition, or the queue's current batch (ambient registrations
192
+ // don't stamp; "ambient work IS a transaction" — the batch is the
193
+ // carrier). The entangle STEALS the carrier's staged cargo — its pending
194
+ // nodes move (re-stamped) into the awaiting transaction and reveal at its
195
+ // settle — but never the carrier itself: its async reporters, actions,
196
+ // and stashes are its own future (a live stream's flight must not chain
197
+ // the awaiting transaction to landings that haven't happened; a merged
198
+ // reporter deadlocked exactly that way).
199
+ let i = false;
200
+ for (let n = e.te; n !== null; n = n.ne) {
201
+ const r = n.oe;
202
+ if (r.Pe !== NOT_PENDING) {
203
+ const e = r.Ee;
204
+ const n = e != null ? currentTransition(e) : null;
205
+ // Skip the awaiting transaction's own cargo (t === target: a
206
+ // fold-staged landing or a write the action itself issued — hold and
207
+ // reveal already correct) and dead carriers. Ambient-batch staging
208
+ // (t === null) must leave the batch NOW — it commits at this flush's
209
+ // end, which would reveal the confirmation under the live optimism
210
+ // it just confirmed.
211
+ const o = n === null ? currentBatch.tn : n !== t && n.Ut !== true ? n.tn : null;
212
+ if (o !== null) i = stealEntangledCargo(o, t) || i;
213
+ }
214
+ if (n === e.Dt) break;
215
+ }
216
+ // The steal never activates the awaiting transaction: the predicate can
217
+ // flip inside another transaction's finalize heap, and adopting the queue
218
+ // batch there hands the stolen cargo to that finalize's commit sweep — a
219
+ // premature reveal at a foreign settle. Subscribers that computed against
220
+ // the pre-steal world were re-dirtied by the steal itself, so this
221
+ // flush's applies paint the masked (mid-hold) view; the cargo commits at
222
+ // the awaiting transaction's own settle.
223
+ }
224
+
225
+ /** Move a confirming carrier's staged nodes into the awaiting transaction:
226
+ * re-stamp and arm the held-truth mask (override-covered nodes skip it —
227
+ * the override already hides their staged value per A17, and is usually
228
+ * the very optimism this confirmation settles); the mask's commit
229
+ * registers the settle-side post-revert wake. The carrier's array is
230
+ * emptied so its own commit point commits none of the stolen cargo.
231
+ *
232
+ * EFFECT subs of stolen nodes re-run: any that recomputed against the
233
+ * staging BEFORE the steal (the carrier's landing notified them as a plain
234
+ * write) hold a private torn result — override composed with confirming
235
+ * truth — that the next paint gate (stash-point lane run, a foreign
236
+ * flush's completion drain) would show. Re-running them under the mask
237
+ * re-derives the mid-hold view in this same heap pass. PURE computeds are
238
+ * deliberately NOT re-run: a torn staged value of theirs is itself stolen
239
+ * cargo — masked at read, so ordinary readers already serve their
240
+ * committed value — while re-running them would re-derive the OLD world
241
+ * and re-stage it over the held truth. The reveal re-notifies
242
+ * (commitPendingNodes), which is when they re-derive for real. */ function stealEntangledCargo(e, t) {
243
+ if (e === t.tn || e.length === 0) return false;
244
+ for (let i = 0; i < e.length; i++) {
245
+ const n = e[i];
246
+ n.Ee = t;
247
+ t.tn.push(n);
248
+ // Override-covered nodes stay silent AND unmasked: the override is the
249
+ // display (A17 — its staging never notified, its revert will), so their
250
+ // subs saw nothing and re-running one would break the silence with a
251
+ // duplicate fire of an unchanged view.
252
+ if (!hasActiveOverride(n)) {
253
+ n.T |= CONFIG_HELD_TRUTH;
254
+ for (let e = n.u; e !== null; e = e.se) {
255
+ const t = e.le;
256
+ if (t.Re && !(t.T & CONFIG_AUTHORITATIVE_READ)) enqueueSub(t);
257
+ }
258
+ }
259
+ }
260
+ e.length = 0;
261
+ transitions.add(t);
262
+ return true;
263
+ }
264
+
265
+ function schedule() {
266
+ if (halted) {
267
+ notifyHalted();
268
+ return;
269
+ }
270
+ if (scheduled) return;
271
+ scheduled = true;
272
+ if (!syncDepth && !globalQueue.Qn && !projectionWriteActive) queueMicrotask(flush);
273
+ }
274
+
275
+ /**
276
+ * Permanently halts the reactive system. Called when a user error escapes
277
+ * every boundary — app state is undefined at that point, so scheduling stops
278
+ * entirely rather than limping along with a half-applied update.
279
+ */ function haltReactivity(e) {
280
+ if (halted) return;
281
+ halted = true;
282
+ let t = "[REACTIVITY_HALTED]";
283
+ // Surface the cause here too: callers rethrow it, but a creation-time throw
284
+ // unwinds through ancestor recomputes that convert it to status instead of
285
+ // surfacing it (#2884), so the rethrow alone cannot guarantee visibility.
286
+ // Where the platform has one, hand the cause to its uncaught-error channel
287
+ // (`reportError` → `error` event → window.onerror / error monitoring): a
288
+ // halt that only reaches console.error leaves a page that LOOKS alive with
289
+ // nothing an app or its telemetry can act on (#3338 — an uncaught throw
290
+ // during the hydration render). The rethrow may reach the top as well in
291
+ // the non-swallowed cases; a duplicate report beats a silent one.
292
+ const i = e !== undefined && globalThis.reportError;
293
+ i || e === undefined ? console.error(t) : console.error(t, e);
294
+ i && i(e);
295
+ }
296
+
297
+ // Logs on the first write after a halt so a frozen interaction is traceable.
298
+ function notifyHalted() {
299
+ if (haltNotified) return;
300
+ haltNotified = true;
301
+ console.error("[REACTIVITY_HALTED]");
302
+ }
303
+
304
+ /** @internal Test/dev-reload hook. Revives scheduling after a halt. */ function resetErrorHalt() {
305
+ halted = false;
306
+ haltNotified = false;
307
+ }
308
+
309
+ // Identifies one child-traversal pass in `Queue.run` so a rescan after the
310
+ // child list shifts can tell "already run this pass" from "still pending".
311
+ let queueRunToken = 0;
312
+
313
+ class Queue {
314
+ Nt=null;
315
+ ii=[ [], [] ];
316
+ ei=[];
317
+ ni=0;
318
+ created=clock;
319
+ addChild(e) {
320
+ this.ei.push(e);
321
+ e.Nt = this;
322
+ }
323
+ removeChild(e) {
324
+ const t = this.ei.indexOf(e);
325
+ if (t >= 0) {
326
+ this.ei.splice(t, 1);
327
+ e.Nt = null;
328
+ }
329
+ }
330
+ notify(e, t, i, n) {
331
+ if (this.Nt) return this.Nt.notify(e, t, i, n);
332
+ return false;
333
+ }
334
+ run(e) {
335
+ if (this.ii[e - 1].length) {
336
+ const t = this.ii[e - 1];
337
+ this.ii[e - 1] = [];
338
+ runQueue(t, e);
339
+ }
340
+ // Effects run here can dispose owners, and disposal removes queues from
341
+ // this list — the running child itself, an earlier sibling, or several at
342
+ // once. A plain index walk then skips whatever shifted into the cursor.
343
+ // Stamping each child before it runs makes the pass idempotent, so a shift
344
+ // can be recovered by rescanning from the front and every child still runs
345
+ // exactly once. Children appended mid-pass carry a stale stamp and run,
346
+ // matching the previous live-array behaviour.
347
+ const t = this.ei;
348
+ const i = ++queueRunToken;
349
+ for (let n = 0; n < t.length; ) {
350
+ const r = t[n];
351
+ if (r.ni !== i) {
352
+ r.ni = i;
353
+ r.run?.(e);
354
+ if (t[n] !== r) {
355
+ n = 0;
356
+ continue;
357
+ }
358
+ }
359
+ n++;
360
+ }
361
+ }
362
+ enqueue(e, t) {
363
+ if (e) {
364
+ // Route to lane's effect queue if we're in an optimistic recomputation
365
+ if (currentOptimisticLane) {
366
+ const i = findLane(currentOptimisticLane);
367
+ i.vn[e - 1].push(t);
368
+ } else {
369
+ this.ii[e - 1].push(t);
370
+ }
371
+ }
372
+ schedule();
373
+ }
374
+ stashQueues(e) {
375
+ // Attribution hook: the parking transition's lane effects have run; its
376
+ // queues are being stashed. Root call only (children recurse below).
377
+ if (attrHooks !== null && this === globalQueue) attrHooks.holdEnd();
378
+ e.ii[0].push(...this.ii[0]);
379
+ e.ii[1].push(...this.ii[1]);
380
+ this.ii = [ [], [] ];
381
+ for (let t = 0; t < this.ei.length; t++) {
382
+ let i = this.ei[t];
383
+ let n = e.ei[t];
384
+ if (!n) {
385
+ n = {
386
+ ii: [ [], [] ],
387
+ ei: []
388
+ };
389
+ e.ei[t] = n;
390
+ }
391
+ i.stashQueues(n);
392
+ }
393
+ }
394
+ restoreQueues(e) {
395
+ this.ii[0].push(...e.ii[0]);
396
+ this.ii[1].push(...e.ii[1]);
397
+ for (let t = 0; t < e.ei.length; t++) {
398
+ const i = e.ei[t];
399
+ let n = this.ei[t];
400
+ if (n) n.restoreQueues(i);
401
+ }
402
+ }
403
+ }
404
+
405
+ class GlobalQueue extends Queue {
406
+ Qn=false;
407
+ // The current transaction-shaped batch: a plain ambient batch while no
408
+ // transition is active, the active transition itself after initTransition.
409
+ m=createBatch();
410
+ static lt;
411
+ static Et;
412
+ static bt;
413
+ static ri=null;
414
+ // Store-side hook: drops a keyless affects() mark's identity scope when the
415
+ // carrier node's last registration releases (wired by store.ts, mirroring
416
+ // _clearOptimisticStore).
417
+ static p=null;
418
+ // affects()-side hooks (wired by affects.ts, mirroring _update): the mark
419
+ // engine — count/register/release — lives with the feature. Every call site
420
+ // is gated by state only that module creates, so `!` invocations are safe
421
+ // once the gate holds.
422
+ static G=null;
423
+ static M=null;
424
+ static N=null;
425
+ // External-source bridge (wired by enableExternalSource(); null while no
426
+ // config is active — including after _resetExternalSourceConfig()).
427
+ static un=null;
428
+ static cn=null;
429
+ // Verdict-layer hooks (wired by verdict.ts when isPending()/latest() are
430
+ // imported; null in apps that never use them). Call sites either guard for
431
+ // null or sit behind state only the verdict layer can create (`!` is safe
432
+ // there: `_pendingSignal`/`_latestValueComputed` are only ever assigned by
433
+ // verdict.ts, and `pendingCheckActive`/`latestReadActive` only flip inside
434
+ // isPending()/latest()).
435
+ static Ae=null;
436
+ static fe=null;
437
+ static Ge=null;
438
+ static Dn=null;
439
+ static In=null;
440
+ static Nn=null;
441
+ static Cn=null;
442
+ static gt=null;
443
+ static k=null;
444
+ static oi=null;
445
+ // Re-asks probes whose verdict was provisionally suppressed by a fresh read
446
+ // of a held value, once the transaction gains an async blocker (#3028).
447
+ static si=null;
448
+ // Optimistic-engine hooks (wired by core/optimistic.ts via
449
+ // installOptimisticEngine(), called from verdict.ts / createOptimistic /
450
+ // createOptimisticStore — every module that can create optimistic state).
451
+ // Call sites are gated by state only the engine can create: an
452
+ // `_overrideValue` slot, a lane in `activeLanes`, an `_optimisticNodes`
453
+ // entry, or a non-null `currentOptimisticLane`, so `!` invocations are safe
454
+ // once the gate holds.
455
+ static pn=null;
456
+ static Un=null;
457
+ static Pn=null;
458
+ static Fn=null;
459
+ static Vn=null;
460
+ /** Patch-channel optimistic drain (next/patch.ts): optimistic emissions
461
+ * apply at lane-effect timing — visible in flight, unlike the regular
462
+ * effect queues an action stashes. Injected; null when unused. */
463
+ static bn=null;
464
+ static An=null;
465
+ static dn=null;
466
+ /** Is the node routed through a LIVE lane (`resolveLane`)? read()'s reveal
467
+ * carve-out asks before showing a foreign-held pending node's committed
468
+ * value: a lane-derived flight's inputs are already revealed through the
469
+ * lane (#3334). Gated on CONFIG_HAS_LANE, which only the engine sets. */
470
+ static Tn=null;
471
+ static On=null;
472
+ static Ft=null;
473
+ static vt=null;
474
+ /** Authoritative-view reader wakeup: installed by until() and refresh() before
475
+ * their first read. Call sites are gated by CONFIG_AUTHORITATIVE_OBSERVED, which
476
+ * only such a reader's carve-out read can set, so `!` invocations are safe once
477
+ * the gate holds (#3303). */
478
+ static En=null;
479
+ static Ht=null;
480
+ /** A18 supersession (#3331): own-source truth `value` landed under an active
481
+ * override. The engine decides whether the graph re-derives — the value
482
+ * differs from the override and is not a stale (older-action) answer (mark
483
+ * the node, demote its lane cascade, notify), or returns to it after an
484
+ * earlier differing arrival (clear the mark, notify) — and owns the
485
+ * authoritative-observer wake for a silent confirm. Installed with the
486
+ * optimistic engine; only reachable on a node that has an override. */
487
+ static Oe=null;
488
+ /** read()'s value for a TRACKED reader of a superseded node (#3331): the
489
+ * staged truth, unless the reader is a stale (render) reader of another
490
+ * transaction — then the displayed override, as it keeps a foreign
491
+ * transaction's committed value over its staged write. */
492
+ static Sn=null;
493
+ /** setSignal's authoritative (projection-write) landing on an override-
494
+ * covered node (#3331 store twin): stage the truth for its transaction's
495
+ * commit whatever its relation to the committed value — a landing equal to
496
+ * committed still differs from the override — then _supersedeOverride
497
+ * decides. Installed with the optimistic engine; only reachable on a node
498
+ * that has an override. */
499
+ static Rn=null;
500
+ static Hn=null;
501
+ flush() {
502
+ if (this.Qn) return;
503
+ // Fast drain: nothing in flight but plain pending commits — no dirty
504
+ // computeds, no queued effects, no child queues, no transitions/lanes/
505
+ // optimistic state. Commit and go; anything a commit hook schedules
506
+ // (companion snaps, store folds notifying subs) re-arms `scheduled`
507
+ // below and the outer drain loop takes the full spine next round.
508
+ if (activeTransition === null && dirtyQueue.EE < dirtyQueue.St && this.ii[0].length === 0 && this.ii[1].length === 0 && this.ei.length === 0 && canUseSimpleSyncFlush(this)) {
509
+ this.Qn = true;
510
+ try {
511
+ // Sweep first: unobserved() pulls swept nodes out of the dirty heap,
512
+ // so a dormant memo dirtied in the same tick is reclaimed instead of
513
+ // recomputed (matching the old inline dispose-on-read counts).
514
+ sweepDormant();
515
+ commitPendingNodes();
516
+ } finally {
517
+ this.Qn = false;
518
+ }
519
+ clock++;
520
+ scheduled = dirtyQueue.EE >= dirtyQueue.St || this.ii[0].length !== 0 || this.ii[1].length !== 0 || this.m.tn.length !== 0;
521
+ return;
522
+ }
523
+ this.Qn = true;
524
+ try {
525
+ if (false) ;
526
+ // Before runHeap for the same reason as the fast drain above; late
527
+ // subscribers (an effect reading a swept memo this flush) revive it,
528
+ // which is the pay-for-use contract.
529
+ sweepDormant();
530
+ runHeap(dirtyQueue, GlobalQueue.lt);
531
+ if (activeTransition) {
532
+ const e = transitionComplete(activeTransition);
533
+ if (!e) {
534
+ const e = activeTransition;
535
+ // When the parking batch IS the transition, all of its writes commit
536
+ // only with it — every zombie recompute they queued would run against
537
+ // a world the zombie never displays (zombies render mainline until
538
+ // commit), so cancel them instead of running them. Only an ambient
539
+ // batch's mainline writes (the #2916 shape below) legitimately reach
540
+ // zombies here. Height-adjust entries still process normally: a
541
+ // dirtied one keeps its height flag and falls through to runHeap's
542
+ // adjustHeight path on the next pass of the bucket.
543
+ runHeap(zombieQueue, this.m === e ? cancelZombieRecompute : GlobalQueue.lt);
544
+ // Detach: the stashed transition keeps its batch; ambient work that
545
+ // follows lands in a fresh one. If the batch is already a separate
546
+ // ambient one — action done() restored activeTransition without
547
+ // adopting the batch, and an ordinary write landed there before
548
+ // the scheduled flush (#2916) — keep it: replacing it would strand
549
+ // its queued pending nodes with held _pendingValues forever.
550
+ if (this.m === e) currentBatch = this.m = createBatch();
551
+ // Run lane effects immediately (before stashing) - lanes with no pending async
552
+ if (activeLanes.size) {
553
+ GlobalQueue.Vn(EFFECT_RENDER);
554
+ GlobalQueue.Vn(EFFECT_USER);
555
+ }
556
+ this.stashQueues(e.ti);
557
+ clock++;
558
+ // A kept ambient batch may hold pending nodes (#2916): stay
559
+ // scheduled so the outer drain loop commits them via the plain
560
+ // flush path instead of leaving them until the next natural flush.
561
+ scheduled = dirtyQueue.EE >= dirtyQueue.St || this.m.tn.length > 0;
562
+ reassignPendingTransition(e.tn);
563
+ activeTransition = null;
564
+ finalizePureQueue(null, true);
565
+ return;
566
+ }
567
+ const t = activeTransition;
568
+ const i = this.m;
569
+ i !== t && i.tn.push(...t.tn);
570
+ this.restoreQueues(t.ti);
571
+ transitions.delete(t);
572
+ activeTransition = null;
573
+ reassignPendingTransition(i.tn);
574
+ finalizePureQueue(t);
575
+ if (i === t) {
576
+ // Drop the dead Transition wrapper but keep its (drained) containers
577
+ // as the ambient batch — late registrations during finalization live
578
+ // there and must survive to the next flush.
579
+ const e = createBatch();
580
+ e.tn = i.tn;
581
+ e.en = i.en;
582
+ e.A = i.A;
583
+ e.nn = i.nn;
584
+ currentBatch = this.m = e;
585
+ }
586
+ } else {
587
+ if (canUseSimpleSyncFlush(this)) {
588
+ commitPendingNodes();
589
+ if (dirtyQueue.EE >= dirtyQueue.St) {
590
+ runHeap(dirtyQueue, GlobalQueue.lt);
591
+ commitPendingNodes();
592
+ }
593
+ } else {
594
+ if (transitions.size) runHeap(zombieQueue, GlobalQueue.lt);
595
+ finalizePureQueue();
596
+ }
597
+ }
598
+ clock++;
599
+ // Check if finalization added items to the heap (from optimistic reversion).
600
+ // Finalization may also have ENTERED a transaction (a commit hook, boundary
601
+ // sweep or recompute wrote a node it owns): effects computed under it
602
+ // since are its to apply, not this flush's — runEffect leaves them queued
603
+ // and the next pass parks them with it (#3319). Everything computed
604
+ // mainline applies now.
605
+ scheduled = dirtyQueue.EE >= dirtyQueue.St || activeTransition !== null;
606
+ // Run lane effects first (for ready lanes), then regular effects
607
+ activeLanes.size && GlobalQueue.Vn(EFFECT_RENDER);
608
+ this.run(EFFECT_RENDER);
609
+ activeLanes.size && GlobalQueue.Vn(EFFECT_USER);
610
+ this.run(EFFECT_USER);
611
+ if (false) ;
612
+ if (false && !scheduled && !activeTransition && transitions.size === 0 && activeLanes.size === 0) ;
613
+ if (false) ;
614
+ } finally {
615
+ this.Qn = false;
616
+ }
617
+ }
618
+ notify(e, t, i, n) {
619
+ // Only track async if the boundary is propagating STATUS_PENDING (not caught by boundary)
620
+ if (t & STATUS_PENDING) {
621
+ if (i & STATUS_PENDING) {
622
+ // Callers pass either nothing or this node's own `_x._error`, so `??`
623
+ // is exact (a null error falls back to the same null).
624
+ const t = n ?? e.o?._;
625
+ // A visibility-only mark notification (the affects() boundary
626
+ // channel) updates display state on its way up but must be invisible
627
+ // to completion accounting BY CONSTRUCTION: it never registers a
628
+ // reporter and never counts toward the loading-boundary diagnostic.
629
+ if (t?.l) return true;
630
+ if (t) {
631
+ // A reveal can discover a flight started in an earlier flush. Hold
632
+ // the staged writes with that reader (A15), even if the reader is
633
+ // new. Fresh/reset loading boundaries consume pending before it
634
+ // reaches here. A reader already parked in a transition must not
635
+ // open a second one.
636
+ if (!activeTransition && !e.Ee && currentBatch.tn.length) this.initTransition();
637
+ if (activeTransition) {
638
+ const i = t.source;
639
+ let n = activeTransition.Ie.get(i);
640
+ if (!n) activeTransition.Ie.set(i, n = new Set);
641
+ const r = n.size;
642
+ n.add(e);
643
+ if (n.size !== r) {
644
+ schedule();
645
+ GlobalQueue.si?.(activeTransition);
646
+ }
647
+ }
648
+ }
649
+ }
650
+ return true;
651
+ }
652
+ return false;
653
+ }
654
+ initTransition(e) {
655
+ if (e) {
656
+ e = currentTransition(e);
657
+ // A finished transaction cannot be re-entered: its state is committed
658
+ // or reverted, so "rejoining" it (A26) is meaningless and re-activating
659
+ // it spins the drain loop (#3140). The refusal must be a bare return —
660
+ // redirecting the caller to a fresh batch would re-arm the loop with a
661
+ // new transaction identity each pass. Stamps are cleared at commit, so
662
+ // this is a belt for paths that hand over a chased-dead reference
663
+ // (merged chains, async settles racing completion).
664
+ if (e.Ut === true || e === activeTransition) return;
665
+ }
666
+ if (!e && activeTransition && activeTransition.ae === clock) return;
667
+ if (!activeTransition) {
668
+ activeTransition = e ?? createBatch();
669
+ } else if (e) {
670
+ const t = activeTransition;
671
+ mergeTransitionState(e, t);
672
+ // Effects the outgoing transaction parked belong to the surviving one
673
+ // now: back onto the live queue, where this flush parks them under
674
+ // `transition` or runs them at its completion. The outgoing stash is
675
+ // never read again — the transaction is dead (#3310).
676
+ this.restoreQueues(t.ti);
677
+ transitions.delete(t);
678
+ activeTransition = e;
679
+ }
680
+ transitions.add(activeTransition);
681
+ activeTransition.ae = clock;
682
+ const t = this.m;
683
+ if (t !== activeTransition) {
684
+ // Adopt the ambient batch into the transaction, then make the
685
+ // transaction the batch so later registrations land there directly.
686
+ // Pending and optimistic nodes are re-stamped as the transaction's;
687
+ // marks don't hijack the node's _transition — a mark on a plain signal
688
+ // must not entangle unrelated writes to it; the same rule holds one hop
689
+ // downstream: propagation never queues pended subscribers as pending
690
+ // nodes, see propagateAffectsMark, #2893.
691
+ for (let e = 0; e < t.tn.length; e++) {
692
+ const i = t.tn[e];
693
+ i.Ee = activeTransition;
694
+ activeTransition.tn.push(i);
695
+ }
696
+ for (let e = 0; e < t.en.length; e++) {
697
+ const i = t.en[e];
698
+ i.Ee = activeTransition;
699
+ activeTransition.en.push(i);
700
+ }
701
+ if (t.A.length) activeTransition.A.push(...t.A);
702
+ for (const e of t.nn) activeTransition.nn.add(e);
703
+ // Gated readers recorded against the ambient batch move with it: their
704
+ // replay-at-commit now happens at the transaction's completion.
705
+ if (t.ht.size) {
706
+ for (const e of t.ht) activeTransition.ht.add(e);
707
+ t.ht.clear();
708
+ }
709
+ currentBatch = this.m = activeTransition;
710
+ }
711
+ for (const e of activeLanes) {
712
+ if (!e.Ee) e.Ee = activeTransition;
713
+ }
714
+ // A transaction's ambient window is one flush. Entering must therefore
715
+ // guarantee a flush: a transaction opened with no writes (an action whose
716
+ // first statements only await) otherwise leaves activeTransition and the
717
+ // adopted batch armed across the async gap, and the next unrelated work
718
+ // to arrive — an optimistic store's authoritative landing, a plain async
719
+ // settle — is adopted into a transaction it has nothing to do with
720
+ // (#3141). The scheduled flush parks the incomplete transaction through
721
+ // the normal machinery and detaches the ambient slots first.
722
+ schedule();
723
+ }
724
+ }
725
+
726
+ function queuePendingNode(e) {
727
+ currentBatch.tn.push(e);
728
+ }
729
+
730
+ // Sticky: flips true on the first refresh() ever (the only setter of
731
+ // REACTIVE_REASK) so the hot notification loop skips the per-subscriber flag
732
+ // clear entirely in apps that never refresh.
733
+ let reaskArmed = false;
734
+
735
+ /** §12d: bumped by every recompute and every new subscriber edge. A node's
736
+ * staged-rewrite skip is sound only while NOTHING recomputed or linked since
737
+ * its last notify — a mid-batch pull can clean a marked subscriber, and a
738
+ * skipped re-write would leave it stale. */ let notifyEpoch = 0;
739
+
740
+ function bumpNotifyEpoch() {
741
+ notifyEpoch++;
742
+ }
743
+
744
+ function armReaskClear() {
745
+ reaskArmed = true;
746
+ }
747
+
748
+ /** Provenance of the work currently running (A18 supersession, #3331): the
749
+ * invocation sequence of the action whose ambient window this is — set by
750
+ * action() for each slice; the flush that ends the window clears it — or,
751
+ * inside an async landing, the sequence captured when that flight was
752
+ * registered (asyncWrite sets it for the landing's synchronous propagation,
753
+ * so a sync recompute downstream of the landing — an optimistic wrapper over
754
+ * the async source — derives under the flight's provenance, and flights it
755
+ * registers inherit it). 0 is mainline: no action, always the current
756
+ * question. An override stamps this at its write (`_overrideStamp`); an
757
+ * answer whose flight an OLDER action issued is a stale question the user
758
+ * has since changed — it holds silently to commit instead of superseding. A
759
+ * slow source must not leak back in over a newer intent. Transactions merge,
760
+ * so the transition object cannot say WHICH action asked; this can. */ let origin = 0;
761
+
762
+ function setOrigin(e) {
763
+ const t = origin;
764
+ origin = e;
765
+ return t;
766
+ }
767
+
768
+ function insertSubs(e, t = false) {
769
+ // §12d: stamp before walking — setSignal's staged-rewrite fast path skips
770
+ // the next walk for this node while the epoch holds (marking is idempotent).
771
+ e.Mt = notifyEpoch;
772
+ // Get source lane: prefer node's own lane over current context
773
+ // This is important for isPending signals which need their own lane to flush immediately
774
+ // Presence bits gate the optional-slot probes (see constants.ts): one
775
+ // masked read of the always-present _config instead of missing-property
776
+ // lookups in the hottest notify loop. Bits are sticky — the field read
777
+ // stays authoritative when a bit is set.
778
+ const i = e.T;
779
+ const n = (i & CONFIG_HAS_LANE ? e.o?.Te : undefined) || currentOptimisticLane;
780
+ const r = (i & CONFIG_HAS_SNAPSHOT) !== 0 && e.o?.Ot !== undefined;
781
+ const o = reaskArmed;
782
+ // Observe-tier fan-out: this walk visits every subscriber edge anyway, so
783
+ // the graph-size count is one local increment here and no field anywhere.
784
+ let s = 0;
785
+ for (let i = e.u; i !== null; i = i.se) {
786
+ const e = i.le;
787
+ s++;
788
+ // A value-change notification is a new question for the subscriber: any
789
+ // pending re-ask mark (refresh) it carried is superseded.
790
+ if (o) e.it &= ~REACTIVE_REASK;
791
+ // Missed-wake latch (#3037): this write is landing while the subscriber
792
+ // is mid-recompute (a nested pull committing beneath its reads), and the
793
+ // heap refuses RECOMPUTING nodes. A gen-current link means the pass
794
+ // already validated this dep — the value it read is now stale — so latch
795
+ // for recompute's tail to reschedule. Untouched links need no latch (the
796
+ // pass either re-reads them fresh or trims them), and neither does the
797
+ // tail link: it is the read IN FLIGHT — read() links before it pulls, so
798
+ // this very commit is what that read returns.
799
+ if (e.it & REACTIVE_RECOMPUTING_DEPS && i.fn === e.Pt && i !== e.Dt) e.it |= REACTIVE_MISSED_WAKE;
800
+ if (r && e.T & CONFIG_IN_SNAPSHOT_SCOPE) {
801
+ e.it |= REACTIVE_SNAPSHOT_STALE;
802
+ continue;
803
+ }
804
+ if (t && n) {
805
+ e.it |= REACTIVE_OPTIMISTIC_DIRTY;
806
+ assignOrMergeLane(e, n);
807
+ } else if (t) {
808
+ e.it |= REACTIVE_OPTIMISTIC_DIRTY;
809
+ // No source lane means reversion - clear subscriber's lane so effects go to regular queue
810
+ if (e.o) e.o.Te = undefined;
811
+ }
812
+ enqueueSub(e);
813
+ }
814
+ if (s >= GRAPH_SIZE_WARN_AT) noteFanOut(e, s);
815
+ }
816
+
817
+ function commitPendingNode(e) {
818
+ const t = e;
819
+ if (!t.ot) {
820
+ if (e.Pe !== NOT_PENDING) {
821
+ e.ge = e.Pe;
822
+ e.Pe = NOT_PENDING;
823
+ }
824
+ if (e.T & CONFIG_HAS_COMPANIONS) GlobalQueue.Dn(e);
825
+ return;
826
+ }
827
+ if (e.Pe !== NOT_PENDING) {
828
+ e.ge = e.Pe;
829
+ e.Pe = NOT_PENDING;
830
+ // Set _modified for effects, but not for tracked effects (they handle their own scheduling)
831
+ if (e.Re && e.Re !== EFFECT_TRACKED) e._t = true;
832
+ // A quiet re-ask classification preserved through a held landing dies
833
+ // with the value commit — the commit IS the reveal (#3178). Gated on the
834
+ // staged value: status propagation queues pending nodes whose windows
835
+ // are still OPEN (no staged value), and their live classification must
836
+ // survive this sweep.
837
+ if (e.o) e.o.Ne = false;
838
+ }
839
+ // The committed hold is the first observable answer for a loading-window
840
+ // node — the window closes here, not at compute time (#2990). Unconditional
841
+ // store to an always-present computed slot.
842
+ t.de = false;
843
+ t.it &= ~REACTIVE_MANUAL_WRITE;
844
+ if (!(t.S & STATUS_PENDING)) t.S &= ~STATUS_UNINITIALIZED;
845
+ // A flight this commit leaves in the air (unobserved, or observed only by
846
+ // a boundary) now has PUBLISHED inputs: its committed value is stale
847
+ // against the frame. read()'s reveal carve-out keys on the mark (#3305).
848
+ else e.T |= CONFIG_INPUTS_PUBLISHED;
849
+ if (t.o != null && (t.o.Rt !== null || t.o.Ct !== null)) GlobalQueue.Et(t, false, true);
850
+ if (e.T & CONFIG_HAS_COMPANIONS) GlobalQueue.Dn(e);
851
+ }
852
+
853
+ // Store commit hook (INTERNALS-STORE-STATE.md §3): installed by the store
854
+ // module at init (same treeshakeable pattern as _resolveOptimistic /
855
+ // _clearOptimisticStores). Folds committed store-node values into their
856
+ // backing objects at the same moment pending values commit — the single
857
+ // mutation point of the owned-raw model.
858
+ let storeCommitHook = null;
859
+
860
+ function setStoreCommitHook(e) {
861
+ storeCommitHook = e;
862
+ }
863
+
864
+ /** Held truth committed this finalize, awaiting its post-revert wake (see
865
+ * finalizePureQueue): the commit IS the reveal, but subscribers must not
866
+ * re-derive until the settling transaction's optimistic overrides have
867
+ * reverted — a commit-time wake recomputes them in the window where
868
+ * confirming truth is committed and the override still displays, a torn
869
+ * frame no timeline contains. */ const heldRevealed = [];
870
+
871
+ function commitPendingNodes() {
872
+ const e = currentBatch.tn;
873
+ for (let t = 0; t < e.length; t++) {
874
+ const i = e[t];
875
+ commitPendingNode(i);
876
+ // The stamp dies with the commit (#3143) — symmetric with
877
+ // resolveOptimisticNodes clearing optimistic stamps. A stamp outliving
878
+ // its transaction let any later write (even a value-equal no-op, which
879
+ // re-opens before the equality bail) resurrect the finished transaction;
880
+ // a boundary flag rewritten every finalize pass then spun the drain loop
881
+ // forever (#3140). The held-truth mark dies the same death — the commit
882
+ // IS the reveal — but its wake defers to the post-revert pass: ordinary
883
+ // subscribers were masked to committed all hold (some re-derived against
884
+ // that old view and cached it), and commits are otherwise silent
885
+ // (staging already notified), so without a wake they'd hold the old
886
+ // world forever.
887
+ i.Ee = null;
888
+ if (i.T & CONFIG_HELD_TRUTH) {
889
+ i.T &= ~CONFIG_HELD_TRUTH;
890
+ heldRevealed.push(i);
891
+ }
892
+ }
893
+ e.length = 0;
894
+ storeCommitHook?.();
895
+ }
896
+
897
+ function finalizePureQueue(e = null, t = false) {
898
+ // For incomplete transitions, skip pending resolution and optimistic reversion
899
+ // For completing transitions or no-transition, resolve pending and revert optimistic
900
+ const i = currentBatch;
901
+ const n = !t;
902
+ if (n) commitPendingNodes();
903
+ if (!t && globalQueue.ei.length) checkBoundaryChildren(globalQueue);
904
+ // Contested effects (#3322) re-derive from the world this commit just
905
+ // produced. Ahead of the heap run — not the post-heap gated replay — so
906
+ // the recompute and the effect phase land in this same pass and the value
907
+ // the other transaction wrote into the slot is never published.
908
+ // (No clear: a completed transition is never finalized again.)
909
+ // A transaction whose settle reverts optimism re-derives them post-revert
910
+ // instead (below, with the gated replay): between commitPendingNodes and
911
+ // _resolveOptimistic the truth is committed but the overrides still
912
+ // display, and a re-derive here would compose the two — the #3164 tear,
913
+ // one window later. The slot meanwhile holds the frame that is on screen.
914
+ const r = e?.xt;
915
+ const o = n && (e ?? i).en.length !== 0;
916
+ if (r && !o) for (const e of r) if (!(e.it & REACTIVE_DISPOSED)) enqueueSub(e);
917
+ const s = dirtyQueue.EE >= dirtyQueue.St;
918
+ if (s) runHeap(dirtyQueue, GlobalQueue.lt);
919
+ if (n) {
920
+ // Boundary checks, commit hooks and recomputes can enter a transaction,
921
+ // which adopts the batch this finalize was settling: nothing batch-derived
922
+ // may be committed or reverted here — the entered transaction owns it now
923
+ // (#3319). A completing transaction's OWN containers are a different
924
+ // matter: when the ambient batch was separate from it (the #2916 shape),
925
+ // adoption never touched them and it must still settle them; when the
926
+ // batch WAS the completing transaction, adoption re-stamped its contents
927
+ // into the entered one and there is nothing left to settle.
928
+ if (currentBatch !== i) {
929
+ if (e === null || e === i) return;
930
+ } else if (s) commitPendingNodes();
931
+ // The settling batch: the completing transaction's, or the ambient one.
932
+ const t = e ?? i;
933
+ // Optimistic reversion: a non-empty batch means _optimisticWrite ran,
934
+ // which installed the engine's hooks.
935
+ if (t.en.length) GlobalQueue.Un(t.en);
936
+ if (r && o) {
937
+ for (const e of r) if (!(e.it & REACTIVE_DISPOSED)) enqueueSub(e);
938
+ schedule();
939
+ }
940
+ // Replay entanglement: subs recorded by the read-time gate get rescheduled
941
+ // so they re-run with the now-committed values visible. The ambient batch
942
+ // replays too — laneReadsCommitted records readers whose committed-view
943
+ // read hid a same-tick plain write that just committed above (#2963).
944
+ if (t.ht.size) {
945
+ for (const e of t.ht) {
946
+ if (e.it & REACTIVE_DISPOSED) continue;
947
+ enqueueSub(e);
948
+ }
949
+ t.ht.clear();
950
+ // A completing transition keeps the outer flush loop alive by itself;
951
+ // the ambient batch needs the re-arm or the replay sits in the heap
952
+ // until the next unrelated write.
953
+ schedule();
954
+ }
955
+ // Declared motion ends with the transaction: settle (or plain flush end
956
+ // for ambient marks) releases each registration's refcount. A non-empty
957
+ // batch means registerAffectsMark ran, which installed the hook. Marks
958
+ // held boundary display state through the visual channel, and their
959
+ // release is the display-state update point — re-run the boundary sweep
960
+ // (the earlier sweep above ran while the marks were still live).
961
+ if (t.A.length) {
962
+ GlobalQueue.G(t.A);
963
+ if (globalQueue.ei.length) checkBoundaryChildren(globalQueue);
964
+ }
965
+ // A non-empty set means trackOptimisticStore ran, which installed the
966
+ // hook; the hook iterates, clears, and schedules (keeping the loop out of
967
+ // core lets esbuild shake it — rollup already folds the null guard). The
968
+ // completing transition scopes the clear to its own layer keys (#2899).
969
+ if (t.nn.size) GlobalQueue.ri(t.nn, e);
970
+ // Held-truth reveal wake (#3164), post-revert by construction: this
971
+ // finalize committed confirming truth whose subscribers were masked all
972
+ // hold — some re-derived against the committed view (the staging, or a
973
+ // confirming carrier's landing, notified them as a plain write) and
974
+ // cached it, and stash-restored applies may carry those torn values.
975
+ // Waking and recomputing HERE — after _resolveOptimistic and the store
976
+ // clears above — means every apply paints the settled view; a wake at
977
+ // commit time would recompute them in the window where truth is
978
+ // committed but the settling transaction's overrides still display.
979
+ if (heldRevealed.length !== 0) {
980
+ while (heldRevealed.length) insertSubs(heldRevealed.pop());
981
+ if (dirtyQueue.EE >= dirtyQueue.St) {
982
+ runHeap(dirtyQueue, GlobalQueue.lt);
983
+ commitPendingNodes();
984
+ }
985
+ }
986
+ sweepTransientStoreNodes();
987
+ // Lanes only enter activeLanes through the engine's getOrCreateLane.
988
+ if (activeLanes.size) GlobalQueue.Fn(e);
989
+ }
990
+ }
991
+
992
+ function checkBoundaryChildren(e) {
993
+ for (const t of e.ei) {
994
+ t.st?.();
995
+ checkBoundaryChildren(t);
996
+ }
997
+ }
998
+
999
+ /**
1000
+ * Count of live `affects()` registrations across the system (including
1001
+ * store-scope inherited marks). Gates the read-path mark check in `read()` so
1002
+ * graphs that never use the feature pay one integer compare.
1003
+ */ let activeAffectsMarks = 0;
1004
+
1005
+ /**
1006
+ * Counter mutation seam for the mark engine in affects.ts: an imported `let`
1007
+ * binding is read-only, and the read-path gate above must stay a plain module
1008
+ * variable so `read()` pays one integer compare, not a function call.
1009
+ *
1010
+ * @internal
1011
+ */ function shiftAffectsMarks(e) {
1012
+ activeAffectsMarks += e;
1013
+ }
1014
+
1015
+ function reassignPendingTransition(e) {
1016
+ for (let t = 0; t < e.length; t++) {
1017
+ e[t].Ee = activeTransition;
1018
+ }
1019
+ }
1020
+
1021
+ const globalQueue = new GlobalQueue;
1022
+
1023
+ // Hot-path mirror of `globalQueue._batch`: `queuePendingNode` runs once per
1024
+ // staged write and `commitPendingNodes` once per flush, and the extra
1025
+ // property hop through `_batch` was a measured instruction-count regression
1026
+ // (CodSpeed update1to1, PR #2905). The field stays authoritative for
1027
+ // cross-module readers; every `_batch` assignment updates both.
1028
+ let currentBatch = globalQueue.m;
1029
+
1030
+ function flush(e) {
1031
+ // Inside an action body the drain is incoherent (#3333): the action's
1032
+ // writes are held by its transaction until it settles, so a drain can't
1033
+ // reveal them — and the loop below only exits once `activeTransition` is
1034
+ // null, so it would PARK the transaction mid-slice and every write after it
1035
+ // in the body would land as a plain, committed write. The reporter's
1036
+ // "leading flush()" workaround was exactly that leak. Prod: run `fn` if
1037
+ // given (its writes stay in the transaction) and skip the drain.
1038
+ if (actionStepDepth > 0) {
1039
+ return e ? e() : undefined;
1040
+ }
1041
+ if (e) {
1042
+ syncDepth++;
1043
+ try {
1044
+ return e();
1045
+ } finally {
1046
+ // Decrement even if the drain throws (a throwing effect): a leaked
1047
+ // syncDepth would stop `schedule()` from ever queuing a microtask again.
1048
+ try {
1049
+ flush();
1050
+ } finally {
1051
+ syncDepth--;
1052
+ }
1053
+ }
1054
+ }
1055
+ if (globalQueue.Qn) {
1056
+ return;
1057
+ }
1058
+ if (halted) return;
1059
+ // Attribution: whether this call drained anything, so `flushEnd` fires once
1060
+ // per real drain and never for a no-op call. The declaration is dead in
1061
+ // prod (its only write is behind true) and rollup drops it.
1062
+ let t = false;
1063
+ // `flush()` is an explicit drain point, so it must also process an active
1064
+ // transition even if no microtask was scheduled for it yet.
1065
+ while (scheduled || activeTransition) {
1066
+ globalQueue.flush();
1067
+ t = true;
1068
+ }
1069
+ // Provenance ends with the drain: every ambient window (an action's first
1070
+ // slice, a landing's propagation) runs to this flush.
1071
+ origin = 0;
1072
+ // Outside every try in this function (see the rule in attribution-hooks.ts):
1073
+ // the drain loop above is the one place all scheduled work funnels through,
1074
+ // so this is the "committed and effects ran, or parked" instant for
1075
+ // everything the loop processed.
1076
+ if (t && attrHooks !== null) attrHooks.flushEnd();
1077
+ }
1078
+
1079
+ function runQueue(e, t) {
1080
+ for (let i = 0; i < e.length; i++) e[i](t);
1081
+ }
1082
+
1083
+ function reporterBlocksSource(e, t) {
1084
+ if (e.it & (REACTIVE_ZOMBIE | REACTIVE_DISPOSED)) return false;
1085
+ if (e.o?.ee?.has(t)) return true;
1086
+ for (let i = e.te; i; i = i.ne) {
1087
+ let e = i.oe;
1088
+ while (e) {
1089
+ if (e === t || e.re === t) return true;
1090
+ e = e.o?.Qe;
1091
+ }
1092
+ }
1093
+ return !!(e.S & STATUS_PENDING && e.o?._ instanceof NotReadyError && e.o?._.source === t);
1094
+ }
1095
+
1096
+ function transitionComplete(e) {
1097
+ if (e.Ut) return true;
1098
+ if (e.ut.length) {
1099
+ // A live action parks the transaction regardless of async state.
1100
+ if (attrHooks !== null) attrHooks.holdStart(e);
1101
+ return false;
1102
+ }
1103
+ let t = true;
1104
+ for (const [i, n] of e.Ie) {
1105
+ let r = false;
1106
+ for (const e of n) {
1107
+ if (reporterBlocksSource(e, i)) {
1108
+ r = true;
1109
+ break;
1110
+ }
1111
+ n.delete(e);
1112
+ }
1113
+ if (!r) e.Ie.delete(i); else if (i.S & STATUS_PENDING && i.o?._?.source === i) {
1114
+ t = false;
1115
+ break;
1116
+ }
1117
+ }
1118
+ // Override blockage lives with the engine (absent hook = "no optimistic
1119
+ // blockage"); the hook's loops over _optimisticNodes/_optimisticStores are
1120
+ // no-ops when the transition holds neither, so no pre-check is needed.
1121
+ if (t && GlobalQueue.Pn?.(e)) t = false;
1122
+ // Attribution hook: this verdict is the fork between settling (held writes
1123
+ // commit next — `_pendingNodes` still lists them) and parking (the flush
1124
+ // runs the lane effects, then stashes; `holdEnd` fires from stashQueues).
1125
+ // Fired here rather than at flush()'s call site because that site is inside
1126
+ // a `try` (see the rule in attribution-hooks.ts).
1127
+ if (attrHooks !== null) t ? attrHooks.transitionSettled(e) : attrHooks.holdStart(e);
1128
+ t && (e.Ut = true);
1129
+ return t;
1130
+ }
1131
+
1132
+ /** A fresh, unentered transaction (#3146): the optimistic store's truth
1133
+ * flight DECLARES an owned transaction instead of relying on whatever the
1134
+ * ambient adoption machinery stamped on its firewall. Activate it with
1135
+ * initTransition; it is a plain batch until then. */ function createTransition() {
1136
+ return createBatch();
1137
+ }
1138
+
1139
+ function currentTransition(e) {
1140
+ while (e.Ut && typeof e.Ut === "object") e = e.Ut;
1141
+ return e;
1142
+ }
1143
+
1144
+ /**
1145
+ * The live transition blocked on `source` — the one whose render reader
1146
+ * observed it pending (INV-3 records the observation in whichever transaction
1147
+ * was active when the reader was notified). The observation is a fact about
1148
+ * the node, so a hold check must not assume it was recorded in the transaction
1149
+ * it happens to hold — lanes merge across transactions (#2912), and a merged
1150
+ * root's transaction knows nothing of the async its members' transactions
1151
+ * observed (#3335). Null when nobody is waiting.
1152
+ */ function waitingTransition(e) {
1153
+ for (const t of transitions) if (t.Ie.has(e)) return t;
1154
+ return null;
1155
+ }
1156
+
1157
+ function runInTransition(e, t) {
1158
+ const i = activeTransition;
1159
+ try {
1160
+ activeTransition = currentTransition(e);
1161
+ return t();
1162
+ } finally {
1163
+ activeTransition = i;
1164
+ }
1165
+ }
1166
+
1167
+ /** Run `fn` with `transition` as BOTH the ambient transaction and the
1168
+ * registration batch, restoring both after. runInTransition alone is not
1169
+ * enough for code that WRITES on behalf of a transaction from inside someone
1170
+ * else's window (optimistic replay re-arming a still-open action's edits
1171
+ * during a landing commit, #3123): registrations route through the queue's
1172
+ * batch pointer, and a bare activeTransition swap leaves them in the ambient
1173
+ * batch — a plain batch "completes" at the next flush and reverts optimistic
1174
+ * registrations that were supposed to live with the transaction.
1175
+ * initTransition is the wrong tool here: it MERGES the currently ambient
1176
+ * transaction into the target, entangling whatever the interrupted window
1177
+ * belonged to. */ function runAsTransitionBatch(e, t) {
1178
+ const i = activeTransition;
1179
+ const n = globalQueue.m;
1180
+ try {
1181
+ activeTransition = currentTransition(e);
1182
+ currentBatch = globalQueue.m = activeTransition;
1183
+ return t();
1184
+ } finally {
1185
+ activeTransition = i;
1186
+ currentBatch = globalQueue.m = n;
1187
+ }
1188
+ }
1189
+
1190
+ export { GlobalQueue, Queue, actionStepDepth, activeAffectsMarks, activeLanes, activeTransition, armReaskClear, assignOrMergeLane, bumpNotifyEpoch, clock, createTransition, currentTransition, dirtyQueue, enforceLoadingBoundary, entangleConfirmingTransitions, enterActionStep, exitActionStep, finalizePureQueue, findLane, flush, globalQueue, haltReactivity, hasActiveOverride, insertSubs, notifyEpoch, origin, projectionWriteActive, queuePendingNode, reaskArmed, resetErrorHalt, runAsTransitionBatch, runInTransition, schedule, setOrigin, setProjectionWriteActive, setStoreCommitHook, shiftAffectsMarks, storeCommitHook, waitingTransition, zombieQueue };