@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.
- package/dist/dev-shared.js +5243 -0
- package/dist/dev.attribution.js +2333 -0
- package/dist/dev.js +1758 -6621
- package/dist/observe/affects.js +127 -0
- package/dist/observe/attribution.js +1 -0
- package/dist/observe/boundaries.js +586 -0
- package/dist/observe/core/action.js +167 -0
- package/dist/observe/core/async.js +736 -0
- package/dist/observe/core/attribution-hooks.js +60 -0
- package/dist/observe/core/attribution.js +2318 -0
- package/dist/{types-cjs/core/constants.d.cts → observe/core/constants.js} +135 -71
- package/dist/{types-cjs/core/context.d.cts → observe/core/context.js} +37 -12
- package/dist/observe/core/core.js +1289 -0
- package/dist/observe/core/dev.js +232 -0
- package/dist/observe/core/effect.js +179 -0
- package/dist/observe/core/error.js +80 -0
- package/dist/observe/core/external.js +98 -0
- package/dist/observe/core/graph.js +155 -0
- package/dist/observe/core/heap.js +147 -0
- package/dist/observe/core/invariants.js +43 -0
- package/dist/observe/core/lanes.js +168 -0
- package/dist/observe/core/optimistic.js +438 -0
- package/dist/observe/core/owner.js +308 -0
- package/dist/observe/core/scheduler.js +1190 -0
- package/dist/observe/core/verdict.js +551 -0
- package/dist/observe/index.js +55 -0
- package/dist/observe/map.js +482 -0
- package/dist/{types-cjs/signals.d.cts → observe/signals.js} +305 -317
- package/dist/observe/store/index.js +38 -0
- package/dist/observe/store/next/optimistic.js +651 -0
- package/dist/observe/store/next/projection.js +242 -0
- package/dist/observe/store/next/reconcile.js +373 -0
- package/dist/observe/store/next/store.js +2213 -0
- package/dist/observe/store/next/target.js +51 -0
- package/dist/observe/store/store.js +323 -0
- package/dist/observe/store/storePath.js +103 -0
- package/dist/observe/store/utils.js +210 -0
- package/dist/prod/attribution.js +32 -0
- package/dist/prod/boundaries.js +3 -1
- package/dist/prod/core/action.js +52 -32
- package/dist/prod/core/async.js +177 -128
- package/dist/prod/core/attribution-hooks.js +3 -0
- package/dist/prod/core/constants.js +32 -1
- package/dist/prod/core/context.js +10 -16
- package/dist/prod/core/core.js +505 -286
- package/dist/prod/core/dev.js +2 -0
- package/dist/prod/core/effect.js +72 -43
- package/dist/prod/core/external.js +2 -2
- package/dist/prod/core/graph.js +40 -36
- package/dist/prod/core/heap.js +50 -51
- package/dist/prod/core/lanes.js +43 -22
- package/dist/prod/core/optimistic.js +224 -80
- package/dist/prod/core/owner.js +43 -39
- package/dist/prod/core/scheduler.js +347 -232
- package/dist/prod/core/verdict.js +71 -74
- package/dist/prod/index.js +9 -5
- package/dist/prod/map.js +311 -150
- package/dist/prod/signals.js +52 -38
- package/dist/prod/store/next/optimistic.js +127 -131
- package/dist/prod/store/next/projection.js +3 -3
- package/dist/prod/store/next/reconcile.js +149 -294
- package/dist/prod/store/next/store.js +694 -380
- package/dist/prod/store/next/target.js +32 -10
- package/dist/prod/store/store.js +12 -14
- package/dist/prod/store/utils.js +36 -27
- package/dist/types/attribution.d.ts +15 -0
- package/dist/types/attribution.prod.d.ts +13 -0
- package/dist/types/core/async.d.ts +1 -1
- package/dist/types/core/attribution-hooks.d.ts +183 -6
- package/dist/types/core/attribution.d.ts +435 -24
- package/dist/types/core/constants.d.ts +31 -0
- package/dist/types/core/core.d.ts +19 -3
- package/dist/types/core/dev.d.ts +165 -26
- package/dist/types/core/effect.d.ts +8 -1
- package/dist/types/core/heap.d.ts +5 -3
- package/dist/types/core/index.d.ts +2 -1
- package/dist/types/core/invariants.d.ts +1 -1
- package/dist/types/core/lanes.d.ts +17 -0
- package/dist/types/core/scheduler.d.ts +69 -4
- package/dist/types/core/types.d.ts +29 -9
- package/dist/types/index.d.ts +9 -2
- package/dist/types/map.d.ts +2 -0
- package/dist/types/signals.d.ts +10 -0
- package/dist/types/store/index.d.ts +4 -6
- package/dist/types/store/next/optimistic.d.ts +4 -2
- package/dist/types/store/next/reconcile.d.ts +5 -12
- package/dist/types/store/next/store.d.ts +8 -9
- package/dist/types/store/next/target.d.ts +56 -55
- package/dist/types/store/store.d.ts +14 -9
- package/dist/types/store/utils.d.ts +7 -0
- package/package.json +21 -16
- package/dist/node.cjs +0 -11121
- package/dist/prod/store/next/patch-hooks.js +0 -13
- package/dist/prod/store/next/patch.js +0 -614
- package/dist/types/store/next/patch-hooks.d.ts +0 -41
- package/dist/types/store/next/patch.d.ts +0 -91
- package/dist/types-cjs/affects.d.cts +0 -47
- package/dist/types-cjs/boundaries.d.cts +0 -175
- package/dist/types-cjs/core/action.d.cts +0 -64
- package/dist/types-cjs/core/async.d.cts +0 -23
- package/dist/types-cjs/core/attribution-hooks.d.cts +0 -63
- package/dist/types-cjs/core/attribution.d.cts +0 -239
- package/dist/types-cjs/core/core.d.cts +0 -176
- package/dist/types-cjs/core/dev.d.cts +0 -95
- package/dist/types-cjs/core/effect.d.cts +0 -30
- package/dist/types-cjs/core/error.d.cts +0 -56
- package/dist/types-cjs/core/external.d.cts +0 -15
- package/dist/types-cjs/core/graph.d.cts +0 -28
- package/dist/types-cjs/core/heap.d.cts +0 -22
- package/dist/types-cjs/core/index.d.cts +0 -13
- package/dist/types-cjs/core/invariants.d.cts +0 -59
- package/dist/types-cjs/core/lanes.d.cts +0 -44
- package/dist/types-cjs/core/optimistic.d.cts +0 -6
- package/dist/types-cjs/core/owner.d.cts +0 -124
- package/dist/types-cjs/core/scheduler.d.cts +0 -231
- package/dist/types-cjs/core/types.d.cts +0 -204
- package/dist/types-cjs/core/verdict.d.cts +0 -2
- package/dist/types-cjs/index.d.cts +0 -10
- package/dist/types-cjs/map.d.cts +0 -69
- package/dist/types-cjs/package.json +0 -3
- package/dist/types-cjs/store/index.d.cts +0 -21
- package/dist/types-cjs/store/next/optimistic.d.cts +0 -23
- package/dist/types-cjs/store/next/patch-hooks.d.cts +0 -41
- package/dist/types-cjs/store/next/patch.d.cts +0 -91
- package/dist/types-cjs/store/next/projection.d.cts +0 -8
- package/dist/types-cjs/store/next/reconcile.d.cts +0 -17
- package/dist/types-cjs/store/next/store.d.cts +0 -127
- package/dist/types-cjs/store/next/target.d.cts +0 -192
- package/dist/types-cjs/store/store.d.cts +0 -138
- package/dist/types-cjs/store/storePath.d.cts +0 -58
- 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 };
|