@solidjs/signals 2.0.0-beta.9 → 2.0.0-rc.0
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/README.md +13 -9
- package/dist/dev.js +5971 -1426
- package/dist/node.cjs +7681 -3293
- package/dist/prod/affects.js +126 -0
- package/dist/prod/boundaries.js +572 -0
- package/dist/prod/core/action.js +139 -0
- package/dist/prod/core/async.js +553 -0
- package/dist/prod/core/constants.js +92 -0
- package/dist/prod/core/context.js +67 -0
- package/dist/prod/core/core.js +817 -0
- package/dist/prod/core/dev.js +3 -0
- package/dist/prod/core/effect.js +145 -0
- package/dist/prod/core/error.js +68 -0
- package/dist/prod/core/external.js +98 -0
- package/dist/prod/core/graph.js +104 -0
- package/dist/prod/core/heap.js +140 -0
- package/dist/prod/core/invariants.js +42 -0
- package/dist/prod/core/lanes.js +140 -0
- package/dist/prod/core/optimistic.js +245 -0
- package/dist/prod/core/owner.js +302 -0
- package/dist/prod/core/scheduler.js +749 -0
- package/dist/prod/core/verdict.js +369 -0
- package/dist/prod/index.js +45 -0
- package/dist/prod/map.js +317 -0
- package/dist/prod/signals.js +375 -0
- package/dist/prod/store/optimistic.js +217 -0
- package/dist/prod/store/projection.js +231 -0
- package/dist/prod/store/reconcile.js +707 -0
- package/dist/prod/store/store.js +1081 -0
- package/dist/prod/store/storePath.js +103 -0
- package/dist/prod/store/utils.js +328 -0
- package/dist/types/affects.d.ts +47 -0
- package/dist/types/boundaries.d.ts +60 -13
- package/dist/types/core/action.d.ts +35 -6
- package/dist/types/core/async.d.ts +16 -1
- package/dist/types/core/constants.d.ts +35 -6
- package/dist/types/core/context.d.ts +10 -2
- package/dist/types/core/core.d.ts +58 -63
- package/dist/types/core/dev.d.ts +17 -2
- package/dist/types/core/effect.d.ts +1 -2
- package/dist/types/core/error.d.ts +33 -0
- package/dist/types/core/external.d.ts +0 -11
- package/dist/types/core/graph.d.ts +2 -1
- package/dist/types/core/heap.d.ts +8 -0
- package/dist/types/core/index.d.ts +3 -2
- package/dist/types/core/invariants.d.ts +59 -0
- package/dist/types/core/lanes.d.ts +2 -0
- package/dist/types/core/optimistic.d.ts +6 -0
- package/dist/types/core/owner.d.ts +50 -3
- package/dist/types/core/scheduler.d.ts +82 -10
- package/dist/types/core/types.d.ts +66 -1
- package/dist/types/core/verdict.d.ts +2 -0
- package/dist/types/index.d.ts +3 -2
- package/dist/types/map.d.ts +21 -5
- package/dist/types/signals.d.ts +164 -12
- package/dist/types/store/index.d.ts +1 -1
- package/dist/types/store/optimistic.d.ts +3 -3
- package/dist/types/store/projection.d.ts +10 -6
- package/dist/types/store/reconcile.d.ts +31 -8
- package/dist/types/store/store.d.ts +101 -6
- package/dist/types-cjs/affects.d.cts +47 -0
- package/dist/types-cjs/boundaries.d.cts +60 -13
- package/dist/types-cjs/core/action.d.cts +35 -6
- package/dist/types-cjs/core/async.d.cts +16 -1
- package/dist/types-cjs/core/constants.d.cts +35 -6
- package/dist/types-cjs/core/context.d.cts +10 -2
- package/dist/types-cjs/core/core.d.cts +58 -63
- package/dist/types-cjs/core/dev.d.cts +17 -2
- package/dist/types-cjs/core/effect.d.cts +1 -2
- package/dist/types-cjs/core/error.d.cts +33 -0
- package/dist/types-cjs/core/external.d.cts +0 -11
- package/dist/types-cjs/core/graph.d.cts +2 -1
- package/dist/types-cjs/core/heap.d.cts +8 -0
- package/dist/types-cjs/core/index.d.cts +3 -2
- package/dist/types-cjs/core/invariants.d.cts +59 -0
- package/dist/types-cjs/core/lanes.d.cts +2 -0
- package/dist/types-cjs/core/optimistic.d.cts +6 -0
- package/dist/types-cjs/core/owner.d.cts +50 -3
- package/dist/types-cjs/core/scheduler.d.cts +82 -10
- package/dist/types-cjs/core/types.d.cts +66 -1
- package/dist/types-cjs/core/verdict.d.cts +2 -0
- package/dist/types-cjs/index.d.cts +3 -2
- package/dist/types-cjs/map.d.cts +21 -5
- package/dist/types-cjs/signals.d.cts +164 -12
- package/dist/types-cjs/store/index.d.cts +1 -1
- package/dist/types-cjs/store/optimistic.d.cts +3 -3
- package/dist/types-cjs/store/projection.d.cts +10 -6
- package/dist/types-cjs/store/reconcile.d.cts +31 -8
- package/dist/types-cjs/store/store.d.cts +101 -6
- package/package.json +11 -9
- package/dist/prod.js +0 -3627
|
@@ -0,0 +1,749 @@
|
|
|
1
|
+
import { EFFECT_RENDER, EFFECT_USER, STATUS_PENDING, REACTIVE_IN_HEAP_HEIGHT, REACTIVE_DISPOSED, REACTIVE_ZOMBIE, NOT_PENDING, EFFECT_TRACKED, CONFIG_IN_SNAPSHOT_SCOPE, REACTIVE_SNAPSHOT_STALE, REACTIVE_OPTIMISTIC_DIRTY, REACTIVE_REASK, REACTIVE_MANUAL_WRITE, STATUS_UNINITIALIZED } from "./constants.js";
|
|
2
|
+
|
|
3
|
+
import { currentOptimisticLane } from "./core.js";
|
|
4
|
+
|
|
5
|
+
import { DEV } from "./dev.js";
|
|
6
|
+
|
|
7
|
+
import { NotReadyError } from "./error.js";
|
|
8
|
+
|
|
9
|
+
import { runHeap, deleteFromHeap, enqueueSub } from "./heap.js";
|
|
10
|
+
|
|
11
|
+
import { activeLanes, assignOrMergeLane, findLane } from "./lanes.js";
|
|
12
|
+
|
|
13
|
+
export { getOrCreateLane, hasActiveOverride, mergeLanes, resolveLane } from "./lanes.js";
|
|
14
|
+
|
|
15
|
+
import { devCheckFlushStart, devCheckActiveOverrides, devCensusCompanions, devCheckQuiescent } from "./invariants.js";
|
|
16
|
+
|
|
17
|
+
const transitions = new Set;
|
|
18
|
+
|
|
19
|
+
const dirtyQueue = {
|
|
20
|
+
eE: new Array(2e3).fill(undefined),
|
|
21
|
+
tE: false,
|
|
22
|
+
Ve: 0,
|
|
23
|
+
EE: 0
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const zombieQueue = {
|
|
27
|
+
eE: new Array(2e3).fill(undefined),
|
|
28
|
+
tE: false,
|
|
29
|
+
Ve: 0,
|
|
30
|
+
EE: 0
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** runHeap callback that discards a queued zombie recompute instead of running
|
|
34
|
+
* it: unlink pure recompute entries; strip just the recompute bit from dirtied
|
|
35
|
+
* height-adjust entries so their height work still happens. */ function cancelZombieRecompute(e) {
|
|
36
|
+
if (e.se & REACTIVE_IN_HEAP_HEIGHT) e.se &= -12; else {
|
|
37
|
+
deleteFromHeap(e, zombieQueue);
|
|
38
|
+
e.se &= -4;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let clock = 0;
|
|
43
|
+
|
|
44
|
+
let activeTransition = null;
|
|
45
|
+
|
|
46
|
+
let scheduled = false;
|
|
47
|
+
|
|
48
|
+
let halted = false;
|
|
49
|
+
|
|
50
|
+
let haltNotified = false;
|
|
51
|
+
|
|
52
|
+
let syncDepth = 0;
|
|
53
|
+
|
|
54
|
+
let projectionWriteActive = false;
|
|
55
|
+
|
|
56
|
+
// Store property nodes that were created solely to carry a pending write (no
|
|
57
|
+
// subscribers at write time). Swept after each flush that commits pending
|
|
58
|
+
// values — any still without subs get disposed via their `_unobserved` hook,
|
|
59
|
+
// releasing the slot in the parent store's node map.
|
|
60
|
+
const transientStoreNodes = new Set;
|
|
61
|
+
|
|
62
|
+
function registerTransientStoreNode(e) {
|
|
63
|
+
transientStoreNodes.add(e);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function canUseSimpleSyncFlush(e) {
|
|
67
|
+
const t = e.m;
|
|
68
|
+
return transitions.size === 0 && activeLanes.size === 0 && e.vt.length === 0 && t.Me.length === 0 && t.A.length === 0 && t.cn.size === 0 && transientStoreNodes.size === 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function sweepTransientStoreNodes() {
|
|
72
|
+
if (transientStoreNodes.size === 0) return;
|
|
73
|
+
for (const e of transientStoreNodes) {
|
|
74
|
+
if (e.o !== null) {
|
|
75
|
+
transientStoreNodes.delete(e);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (e.De !== NOT_PENDING) continue;
|
|
79
|
+
if (e._e !== undefined && e._e !== NOT_PENDING) continue;
|
|
80
|
+
// A live affects() mark keeps the node addressable: sweeping it would
|
|
81
|
+
// detach the refcount from the slot (a fresh probe would upsert a new,
|
|
82
|
+
// unmarked node for the same property).
|
|
83
|
+
if (e.t) continue;
|
|
84
|
+
transientStoreNodes.delete(e);
|
|
85
|
+
e.ut?.();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Toggles the dev-mode "must be inside a `<Loading>` boundary" enforcement
|
|
91
|
+
* window. Only `render()` calls this — wrapping the initial mount so that a
|
|
92
|
+
* top-level uncaught async read surfaces the diagnostic. Not part of the
|
|
93
|
+
* user-facing API.
|
|
94
|
+
*
|
|
95
|
+
* @internal
|
|
96
|
+
*/ function enforceLoadingBoundary(e) {}
|
|
97
|
+
|
|
98
|
+
function setProjectionWriteActive(e) {
|
|
99
|
+
projectionWriteActive = e;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Ambient work IS a transaction: the global queue always carries one
|
|
104
|
+
* current-transaction-shaped batch (`globalQueue._batch`). With no transition
|
|
105
|
+
* active, registrations (pending commits, optimistic nodes, affects marks,
|
|
106
|
+
* optimistic stores) land in a plain ambient batch that the plain flush
|
|
107
|
+
* finalizes; when a transition initializes it adopts the ambient batch's
|
|
108
|
+
* contents and `_batch` becomes the transition itself, so later registrations
|
|
109
|
+
* land there directly — no per-field aliasing.
|
|
110
|
+
*/ function createBatch() {
|
|
111
|
+
return {
|
|
112
|
+
de: clock,
|
|
113
|
+
Qt: [],
|
|
114
|
+
Ie: new Map,
|
|
115
|
+
Me: [],
|
|
116
|
+
A: [],
|
|
117
|
+
cn: new Set,
|
|
118
|
+
ie: [],
|
|
119
|
+
yt: {
|
|
120
|
+
bt: [ [], [] ],
|
|
121
|
+
vt: []
|
|
122
|
+
},
|
|
123
|
+
fn: false,
|
|
124
|
+
ln: new Set
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function mergeTransitionState(e, t) {
|
|
129
|
+
t.fn = e;
|
|
130
|
+
e.ie.push(...t.ie);
|
|
131
|
+
for (const i of activeLanes) if (i.Ne === t) i.Ne = e;
|
|
132
|
+
if (t.Me.length) {
|
|
133
|
+
// Move (don't copy): the global queue's batch may still be the outgoing
|
|
134
|
+
// transition, and the adoption pass in initTransition would re-push its
|
|
135
|
+
// contents into the target — duplicating every entry.
|
|
136
|
+
e.Me.push(...t.Me);
|
|
137
|
+
t.Me.length = 0;
|
|
138
|
+
}
|
|
139
|
+
if (t.A.length) {
|
|
140
|
+
// Move (don't copy): the global queue's batch may still be the outgoing
|
|
141
|
+
// transition, and the adoption pass in initTransition would re-push its
|
|
142
|
+
// contents into the target — double-releasing every mark.
|
|
143
|
+
e.A.push(...t.A);
|
|
144
|
+
t.A.length = 0;
|
|
145
|
+
}
|
|
146
|
+
for (const i of t.cn) e.cn.add(i);
|
|
147
|
+
for (const [i, n] of t.Ie) {
|
|
148
|
+
let t = e.Ie.get(i);
|
|
149
|
+
if (!t) e.Ie.set(i, t = new Set);
|
|
150
|
+
for (const e of n) t.add(e);
|
|
151
|
+
}
|
|
152
|
+
for (const i of t.ln) e.ln.add(i);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function schedule() {
|
|
156
|
+
if (halted) {
|
|
157
|
+
notifyHalted();
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (scheduled) return;
|
|
161
|
+
scheduled = true;
|
|
162
|
+
if (!syncDepth && !globalQueue.gt && !projectionWriteActive) queueMicrotask(flush);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Permanently halts the reactive system. Called when a user error escapes
|
|
167
|
+
* every boundary — app state is undefined at that point, so scheduling stops
|
|
168
|
+
* entirely rather than limping along with a half-applied update.
|
|
169
|
+
*/ function haltReactivity(e) {
|
|
170
|
+
if (halted) return;
|
|
171
|
+
halted = true;
|
|
172
|
+
let t = "[REACTIVITY_HALTED]";
|
|
173
|
+
// Log the cause here too: callers rethrow it, but a creation-time throw
|
|
174
|
+
// unwinds through ancestor recomputes that convert it to status instead of
|
|
175
|
+
// surfacing it (#2884), so the rethrow alone cannot guarantee visibility.
|
|
176
|
+
e === undefined ? console.error(t) : console.error(t, e);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Logs on the first write after a halt so a frozen interaction is traceable.
|
|
180
|
+
function notifyHalted() {
|
|
181
|
+
if (haltNotified) return;
|
|
182
|
+
haltNotified = true;
|
|
183
|
+
console.error("[REACTIVITY_HALTED]");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** @internal Test/dev-reload hook. Revives scheduling after a halt. */ function resetErrorHalt() {
|
|
187
|
+
halted = false;
|
|
188
|
+
haltNotified = false;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Identifies one child-traversal pass in `Queue.run` so a rescan after the
|
|
192
|
+
// child list shifts can tell "already run this pass" from "still pending".
|
|
193
|
+
let queueRunToken = 0;
|
|
194
|
+
|
|
195
|
+
class Queue {
|
|
196
|
+
ke=null;
|
|
197
|
+
bt=[ [], [] ];
|
|
198
|
+
vt=[];
|
|
199
|
+
kt=0;
|
|
200
|
+
created=clock;
|
|
201
|
+
addChild(e) {
|
|
202
|
+
this.vt.push(e);
|
|
203
|
+
e.ke = this;
|
|
204
|
+
}
|
|
205
|
+
removeChild(e) {
|
|
206
|
+
const t = this.vt.indexOf(e);
|
|
207
|
+
if (t >= 0) {
|
|
208
|
+
this.vt.splice(t, 1);
|
|
209
|
+
e.ke = null;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
notify(e, t, i, n) {
|
|
213
|
+
if (this.ke) return this.ke.notify(e, t, i, n);
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
run(e) {
|
|
217
|
+
if (this.bt[e - 1].length) {
|
|
218
|
+
const t = this.bt[e - 1];
|
|
219
|
+
this.bt[e - 1] = [];
|
|
220
|
+
runQueue(t, e);
|
|
221
|
+
}
|
|
222
|
+
// Effects run here can dispose owners, and disposal removes queues from
|
|
223
|
+
// this list — the running child itself, an earlier sibling, or several at
|
|
224
|
+
// once. A plain index walk then skips whatever shifted into the cursor.
|
|
225
|
+
// Stamping each child before it runs makes the pass idempotent, so a shift
|
|
226
|
+
// can be recovered by rescanning from the front and every child still runs
|
|
227
|
+
// exactly once. Children appended mid-pass carry a stale stamp and run,
|
|
228
|
+
// matching the previous live-array behaviour.
|
|
229
|
+
const t = this.vt;
|
|
230
|
+
const i = ++queueRunToken;
|
|
231
|
+
for (let n = 0; n < t.length; ) {
|
|
232
|
+
const s = t[n];
|
|
233
|
+
if (s.kt !== i) {
|
|
234
|
+
s.kt = i;
|
|
235
|
+
s.run?.(e);
|
|
236
|
+
if (t[n] !== s) {
|
|
237
|
+
n = 0;
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
n++;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
enqueue(e, t) {
|
|
245
|
+
if (e) {
|
|
246
|
+
// Route to lane's effect queue if we're in an optimistic recomputation
|
|
247
|
+
if (currentOptimisticLane) {
|
|
248
|
+
const i = findLane(currentOptimisticLane);
|
|
249
|
+
i.rn[e - 1].push(t);
|
|
250
|
+
} else {
|
|
251
|
+
this.bt[e - 1].push(t);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
schedule();
|
|
255
|
+
}
|
|
256
|
+
stashQueues(e) {
|
|
257
|
+
e.bt[0].push(...this.bt[0]);
|
|
258
|
+
e.bt[1].push(...this.bt[1]);
|
|
259
|
+
this.bt = [ [], [] ];
|
|
260
|
+
for (let t = 0; t < this.vt.length; t++) {
|
|
261
|
+
let i = this.vt[t];
|
|
262
|
+
let n = e.vt[t];
|
|
263
|
+
if (!n) {
|
|
264
|
+
n = {
|
|
265
|
+
bt: [ [], [] ],
|
|
266
|
+
vt: []
|
|
267
|
+
};
|
|
268
|
+
e.vt[t] = n;
|
|
269
|
+
}
|
|
270
|
+
i.stashQueues(n);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
restoreQueues(e) {
|
|
274
|
+
this.bt[0].push(...e.bt[0]);
|
|
275
|
+
this.bt[1].push(...e.bt[1]);
|
|
276
|
+
for (let t = 0; t < e.vt.length; t++) {
|
|
277
|
+
const i = e.vt[t];
|
|
278
|
+
let n = this.vt[t];
|
|
279
|
+
if (n) n.restoreQueues(i);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
class GlobalQueue extends Queue {
|
|
285
|
+
gt=false;
|
|
286
|
+
// The current transaction-shaped batch: a plain ambient batch while no
|
|
287
|
+
// transition is active, the active transition itself after initTransition.
|
|
288
|
+
m=createBatch();
|
|
289
|
+
static Ce;
|
|
290
|
+
static me;
|
|
291
|
+
static et;
|
|
292
|
+
static Dt=null;
|
|
293
|
+
// Store-side hook: drops a keyless affects() mark's identity scope when the
|
|
294
|
+
// carrier node's last registration releases (wired by store.ts, mirroring
|
|
295
|
+
// _clearOptimisticStore).
|
|
296
|
+
static p=null;
|
|
297
|
+
// affects()-side hooks (wired by affects.ts, mirroring _update): the mark
|
|
298
|
+
// engine — count/register/release — lives with the feature. Every call site
|
|
299
|
+
// is gated by state only that module creates, so `!` invocations are safe
|
|
300
|
+
// once the gate holds.
|
|
301
|
+
static G=null;
|
|
302
|
+
static M=null;
|
|
303
|
+
static h=null;
|
|
304
|
+
// External-source bridge (wired by enableExternalSource(); null while no
|
|
305
|
+
// config is active — including after _resetExternalSourceConfig()).
|
|
306
|
+
static It=null;
|
|
307
|
+
static St=null;
|
|
308
|
+
// Verdict-layer hooks (wired by verdict.ts when isPending()/latest() are
|
|
309
|
+
// imported; null in apps that never use them). Call sites either guard for
|
|
310
|
+
// null or sit behind state only the verdict layer can create (`!` is safe
|
|
311
|
+
// there: `_pendingSignal`/`_latestValueComputed` are only ever assigned by
|
|
312
|
+
// verdict.ts, and `pendingCheckActive`/`latestReadActive` only flip inside
|
|
313
|
+
// isPending()/latest()).
|
|
314
|
+
static Pe=null;
|
|
315
|
+
static Se=null;
|
|
316
|
+
static Oe=null;
|
|
317
|
+
static un=null;
|
|
318
|
+
static At=null;
|
|
319
|
+
static Ct=null;
|
|
320
|
+
static ht=null;
|
|
321
|
+
static Be=null;
|
|
322
|
+
static k=null;
|
|
323
|
+
static Lt=null;
|
|
324
|
+
// Optimistic-engine hooks (wired by core/optimistic.ts via
|
|
325
|
+
// installOptimisticEngine(), called from verdict.ts / createOptimistic /
|
|
326
|
+
// createOptimisticStore — every module that can create optimistic state).
|
|
327
|
+
// Call sites are gated by state only the engine can create: an
|
|
328
|
+
// `_overrideValue` slot, a lane in `activeLanes`, an `_optimisticNodes`
|
|
329
|
+
// entry, or a non-null `currentOptimisticLane`, so `!` invocations are safe
|
|
330
|
+
// once the gate holds.
|
|
331
|
+
static Gt=null;
|
|
332
|
+
static En=null;
|
|
333
|
+
static Tn=null;
|
|
334
|
+
static dn=null;
|
|
335
|
+
static In=null;
|
|
336
|
+
static Rt=null;
|
|
337
|
+
static Ot=null;
|
|
338
|
+
static Pt=null;
|
|
339
|
+
static je=null;
|
|
340
|
+
static $e=null;
|
|
341
|
+
static ze=null;
|
|
342
|
+
static Nn=null;
|
|
343
|
+
flush() {
|
|
344
|
+
if (this.gt) return;
|
|
345
|
+
this.gt = true;
|
|
346
|
+
try {
|
|
347
|
+
if (false) ;
|
|
348
|
+
runHeap(dirtyQueue, GlobalQueue.Ce);
|
|
349
|
+
if (activeTransition) {
|
|
350
|
+
const e = transitionComplete(activeTransition);
|
|
351
|
+
if (!e) {
|
|
352
|
+
const e = activeTransition;
|
|
353
|
+
// When the parking batch IS the transition, all of its writes commit
|
|
354
|
+
// only with it — every zombie recompute they queued would run against
|
|
355
|
+
// a world the zombie never displays (zombies render mainline until
|
|
356
|
+
// commit), so cancel them instead of running them. Only an ambient
|
|
357
|
+
// batch's mainline writes (the #2916 shape below) legitimately reach
|
|
358
|
+
// zombies here. Height-adjust entries still process normally: a
|
|
359
|
+
// dirtied one keeps its height flag and falls through to runHeap's
|
|
360
|
+
// adjustHeight path on the next pass of the bucket.
|
|
361
|
+
runHeap(zombieQueue, this.m === e ? cancelZombieRecompute : GlobalQueue.Ce);
|
|
362
|
+
// Detach: the stashed transition keeps its batch; ambient work that
|
|
363
|
+
// follows lands in a fresh one. If the batch is already a separate
|
|
364
|
+
// ambient one — action done() restored activeTransition without
|
|
365
|
+
// adopting the batch, and an ordinary write landed there before
|
|
366
|
+
// the scheduled flush (#2916) — keep it: replacing it would strand
|
|
367
|
+
// its queued pending nodes with held _pendingValues forever.
|
|
368
|
+
if (this.m === e) currentBatch = this.m = createBatch();
|
|
369
|
+
// Run lane effects immediately (before stashing) - lanes with no pending async
|
|
370
|
+
if (activeLanes.size) {
|
|
371
|
+
GlobalQueue.In(EFFECT_RENDER);
|
|
372
|
+
GlobalQueue.In(EFFECT_USER);
|
|
373
|
+
}
|
|
374
|
+
this.stashQueues(e.yt);
|
|
375
|
+
clock++;
|
|
376
|
+
// A kept ambient batch may hold pending nodes (#2916): stay
|
|
377
|
+
// scheduled so the outer drain loop commits them via the plain
|
|
378
|
+
// flush path instead of leaving them until the next natural flush.
|
|
379
|
+
scheduled = dirtyQueue.EE >= dirtyQueue.Ve || this.m.Qt.length > 0;
|
|
380
|
+
reassignPendingTransition(e.Qt);
|
|
381
|
+
activeTransition = null;
|
|
382
|
+
finalizePureQueue(null, true);
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
const t = activeTransition;
|
|
386
|
+
const i = this.m;
|
|
387
|
+
i !== t && i.Qt.push(...t.Qt);
|
|
388
|
+
this.restoreQueues(t.yt);
|
|
389
|
+
transitions.delete(t);
|
|
390
|
+
activeTransition = null;
|
|
391
|
+
reassignPendingTransition(i.Qt);
|
|
392
|
+
finalizePureQueue(t);
|
|
393
|
+
if (i === t) {
|
|
394
|
+
// Drop the dead Transition wrapper but keep its (drained) containers
|
|
395
|
+
// as the ambient batch — late registrations during finalization live
|
|
396
|
+
// there and must survive to the next flush.
|
|
397
|
+
const e = createBatch();
|
|
398
|
+
e.Qt = i.Qt;
|
|
399
|
+
e.Me = i.Me;
|
|
400
|
+
e.A = i.A;
|
|
401
|
+
e.cn = i.cn;
|
|
402
|
+
currentBatch = this.m = e;
|
|
403
|
+
}
|
|
404
|
+
} else {
|
|
405
|
+
if (canUseSimpleSyncFlush(this)) {
|
|
406
|
+
commitPendingNodes();
|
|
407
|
+
if (dirtyQueue.EE >= dirtyQueue.Ve) {
|
|
408
|
+
runHeap(dirtyQueue, GlobalQueue.Ce);
|
|
409
|
+
commitPendingNodes();
|
|
410
|
+
}
|
|
411
|
+
} else {
|
|
412
|
+
if (transitions.size) runHeap(zombieQueue, GlobalQueue.Ce);
|
|
413
|
+
finalizePureQueue();
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
clock++;
|
|
417
|
+
// Check if finalization added items to the heap (from optimistic reversion)
|
|
418
|
+
scheduled = dirtyQueue.EE >= dirtyQueue.Ve;
|
|
419
|
+
// Run lane effects first (for ready lanes), then regular effects
|
|
420
|
+
activeLanes.size && GlobalQueue.In(EFFECT_RENDER);
|
|
421
|
+
this.run(EFFECT_RENDER);
|
|
422
|
+
activeLanes.size && GlobalQueue.In(EFFECT_USER);
|
|
423
|
+
this.run(EFFECT_USER);
|
|
424
|
+
if (false) ;
|
|
425
|
+
if (false && !scheduled && !activeTransition && transitions.size === 0 && activeLanes.size === 0) ;
|
|
426
|
+
if (false) ;
|
|
427
|
+
} finally {
|
|
428
|
+
this.gt = false;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
notify(e, t, i, n) {
|
|
432
|
+
// Only track async if the boundary is propagating STATUS_PENDING (not caught by boundary)
|
|
433
|
+
if (t & STATUS_PENDING) {
|
|
434
|
+
if (i & STATUS_PENDING) {
|
|
435
|
+
const t = n !== undefined ? n : e._;
|
|
436
|
+
// A visibility-only mark notification (the affects() boundary
|
|
437
|
+
// channel) updates display state on its way up but must be invisible
|
|
438
|
+
// to completion accounting BY CONSTRUCTION: it never registers a
|
|
439
|
+
// reporter and never counts toward the loading-boundary diagnostic.
|
|
440
|
+
if (t?.l) return true;
|
|
441
|
+
if (activeTransition && t) {
|
|
442
|
+
const i = t.source;
|
|
443
|
+
let n = activeTransition.Ie.get(i);
|
|
444
|
+
if (!n) activeTransition.Ie.set(i, n = new Set);
|
|
445
|
+
const s = n.size;
|
|
446
|
+
n.add(e);
|
|
447
|
+
if (n.size !== s) schedule();
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return true;
|
|
451
|
+
}
|
|
452
|
+
return false;
|
|
453
|
+
}
|
|
454
|
+
initTransition(e) {
|
|
455
|
+
if (e) e = currentTransition(e);
|
|
456
|
+
if (e && e === activeTransition) return;
|
|
457
|
+
if (!e && activeTransition && activeTransition.de === clock) return;
|
|
458
|
+
if (!activeTransition) {
|
|
459
|
+
activeTransition = e ?? createBatch();
|
|
460
|
+
} else if (e) {
|
|
461
|
+
const t = activeTransition;
|
|
462
|
+
mergeTransitionState(e, t);
|
|
463
|
+
transitions.delete(t);
|
|
464
|
+
activeTransition = e;
|
|
465
|
+
}
|
|
466
|
+
transitions.add(activeTransition);
|
|
467
|
+
activeTransition.de = clock;
|
|
468
|
+
const t = this.m;
|
|
469
|
+
if (t !== activeTransition) {
|
|
470
|
+
// Adopt the ambient batch into the transaction, then make the
|
|
471
|
+
// transaction the batch so later registrations land there directly.
|
|
472
|
+
// Pending and optimistic nodes are re-stamped as the transaction's;
|
|
473
|
+
// marks don't hijack the node's _transition — a mark on a plain signal
|
|
474
|
+
// must not entangle unrelated writes to it; the same rule holds one hop
|
|
475
|
+
// downstream: propagation never queues pended subscribers as pending
|
|
476
|
+
// nodes, see propagateAffectsMark, #2893.
|
|
477
|
+
for (let e = 0; e < t.Qt.length; e++) {
|
|
478
|
+
const i = t.Qt[e];
|
|
479
|
+
i.Ne = activeTransition;
|
|
480
|
+
activeTransition.Qt.push(i);
|
|
481
|
+
}
|
|
482
|
+
for (let e = 0; e < t.Me.length; e++) {
|
|
483
|
+
const i = t.Me[e];
|
|
484
|
+
i.Ne = activeTransition;
|
|
485
|
+
activeTransition.Me.push(i);
|
|
486
|
+
}
|
|
487
|
+
if (t.A.length) activeTransition.A.push(...t.A);
|
|
488
|
+
for (const e of t.cn) activeTransition.cn.add(e);
|
|
489
|
+
// Gated readers recorded against the ambient batch move with it: their
|
|
490
|
+
// replay-at-commit now happens at the transaction's completion.
|
|
491
|
+
if (t.ln.size) {
|
|
492
|
+
for (const e of t.ln) activeTransition.ln.add(e);
|
|
493
|
+
t.ln.clear();
|
|
494
|
+
}
|
|
495
|
+
currentBatch = this.m = activeTransition;
|
|
496
|
+
}
|
|
497
|
+
for (const e of activeLanes) {
|
|
498
|
+
if (!e.Ne) e.Ne = activeTransition;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function queuePendingNode(e) {
|
|
504
|
+
currentBatch.Qt.push(e);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Sticky: flips true on the first refresh() ever (the only setter of
|
|
508
|
+
// REACTIVE_REASK) so the hot notification loop skips the per-subscriber flag
|
|
509
|
+
// clear entirely in apps that never refresh.
|
|
510
|
+
let reaskArmed = false;
|
|
511
|
+
|
|
512
|
+
function armReaskClear() {
|
|
513
|
+
reaskArmed = true;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function insertSubs(e, t = false) {
|
|
517
|
+
// Get source lane: prefer node's own lane over current context
|
|
518
|
+
// This is important for isPending signals which need their own lane to flush immediately
|
|
519
|
+
const i = e.Ke || currentOptimisticLane;
|
|
520
|
+
const n = e.Le !== undefined;
|
|
521
|
+
const s = reaskArmed;
|
|
522
|
+
for (let r = e.o; r !== null; r = r.le) {
|
|
523
|
+
// A value-change notification is a new question for the subscriber: any
|
|
524
|
+
// pending re-ask mark (refresh) it carried is superseded.
|
|
525
|
+
if (s) r.fe.se &= ~REACTIVE_REASK;
|
|
526
|
+
if (n && r.fe.T & CONFIG_IN_SNAPSHOT_SCOPE) {
|
|
527
|
+
r.fe.se |= REACTIVE_SNAPSHOT_STALE;
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
if (t && i) {
|
|
531
|
+
r.fe.se |= REACTIVE_OPTIMISTIC_DIRTY;
|
|
532
|
+
assignOrMergeLane(r.fe, i);
|
|
533
|
+
} else if (t) {
|
|
534
|
+
r.fe.se |= REACTIVE_OPTIMISTIC_DIRTY;
|
|
535
|
+
// No source lane means reversion - clear subscriber's lane so effects go to regular queue
|
|
536
|
+
r.fe.Ke = undefined;
|
|
537
|
+
}
|
|
538
|
+
enqueueSub(r.fe);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function commitPendingNode(e) {
|
|
543
|
+
const t = e;
|
|
544
|
+
if (!t.ce) {
|
|
545
|
+
if (e.De !== NOT_PENDING) {
|
|
546
|
+
e.Ue = e.De;
|
|
547
|
+
e.De = NOT_PENDING;
|
|
548
|
+
}
|
|
549
|
+
if (e.ge || e.pe) GlobalQueue.un(e);
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
if (e.De !== NOT_PENDING) {
|
|
553
|
+
e.Ue = e.De;
|
|
554
|
+
e.De = NOT_PENDING;
|
|
555
|
+
// Set _modified for effects, but not for tracked effects (they handle their own scheduling)
|
|
556
|
+
if (e.Re && e.Re !== EFFECT_TRACKED) e.Je = true;
|
|
557
|
+
}
|
|
558
|
+
// The committed hold is the first observable answer for a loading-window
|
|
559
|
+
// node — the window closes here, not at compute time (#2990). Unconditional
|
|
560
|
+
// store to an always-present computed slot.
|
|
561
|
+
t.Ee = false;
|
|
562
|
+
t.se &= ~REACTIVE_MANUAL_WRITE;
|
|
563
|
+
if (!(t.S & STATUS_PENDING)) t.S &= ~STATUS_UNINITIALIZED;
|
|
564
|
+
if (t.We !== null || t.Qe !== null) GlobalQueue.me(t, false, true);
|
|
565
|
+
if (e.ge || e.pe) GlobalQueue.un(e);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function commitPendingNodes() {
|
|
569
|
+
const e = currentBatch.Qt;
|
|
570
|
+
for (let t = 0; t < e.length; t++) {
|
|
571
|
+
commitPendingNode(e[t]);
|
|
572
|
+
}
|
|
573
|
+
e.length = 0;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function finalizePureQueue(e = null, t = false) {
|
|
577
|
+
// For incomplete transitions, skip pending resolution and optimistic reversion
|
|
578
|
+
// For completing transitions or no-transition, resolve pending and revert optimistic
|
|
579
|
+
const i = !t;
|
|
580
|
+
if (i) commitPendingNodes();
|
|
581
|
+
if (!t && globalQueue.vt.length) checkBoundaryChildren(globalQueue);
|
|
582
|
+
const n = dirtyQueue.EE >= dirtyQueue.Ve;
|
|
583
|
+
if (n) runHeap(dirtyQueue, GlobalQueue.Ce);
|
|
584
|
+
if (i) {
|
|
585
|
+
if (n) commitPendingNodes();
|
|
586
|
+
// The settling batch: the completing transaction's, or the ambient one.
|
|
587
|
+
const t = e ?? globalQueue.m;
|
|
588
|
+
// Optimistic reversion: a non-empty batch means _optimisticWrite ran,
|
|
589
|
+
// which installed the engine's hooks.
|
|
590
|
+
if (t.Me.length) GlobalQueue.En(t.Me);
|
|
591
|
+
// Replay entanglement: subs recorded by the read-time gate get rescheduled
|
|
592
|
+
// so they re-run with the now-committed values visible. The ambient batch
|
|
593
|
+
// replays too — laneReadsCommitted records readers whose committed-view
|
|
594
|
+
// read hid a same-tick plain write that just committed above (#2963).
|
|
595
|
+
if (t.ln.size) {
|
|
596
|
+
for (const e of t.ln) {
|
|
597
|
+
if (e.se & REACTIVE_DISPOSED) continue;
|
|
598
|
+
enqueueSub(e);
|
|
599
|
+
}
|
|
600
|
+
t.ln.clear();
|
|
601
|
+
// A completing transition keeps the outer flush loop alive by itself;
|
|
602
|
+
// the ambient batch needs the re-arm or the replay sits in the heap
|
|
603
|
+
// until the next unrelated write.
|
|
604
|
+
schedule();
|
|
605
|
+
}
|
|
606
|
+
// Declared motion ends with the transaction: settle (or plain flush end
|
|
607
|
+
// for ambient marks) releases each registration's refcount. A non-empty
|
|
608
|
+
// batch means registerAffectsMark ran, which installed the hook. Marks
|
|
609
|
+
// held boundary display state through the visual channel, and their
|
|
610
|
+
// release is the display-state update point — re-run the boundary sweep
|
|
611
|
+
// (the earlier sweep above ran while the marks were still live).
|
|
612
|
+
if (t.A.length) {
|
|
613
|
+
GlobalQueue.G(t.A);
|
|
614
|
+
if (globalQueue.vt.length) checkBoundaryChildren(globalQueue);
|
|
615
|
+
}
|
|
616
|
+
// A non-empty set means trackOptimisticStore ran, which installed the
|
|
617
|
+
// hook; the hook iterates, clears, and schedules (keeping the loop out of
|
|
618
|
+
// core lets esbuild shake it — rollup already folds the null guard). The
|
|
619
|
+
// completing transition scopes the clear to its own layer keys (#2899).
|
|
620
|
+
if (t.cn.size) GlobalQueue.Dt(t.cn, e);
|
|
621
|
+
sweepTransientStoreNodes();
|
|
622
|
+
// Lanes only enter activeLanes through the engine's getOrCreateLane.
|
|
623
|
+
if (activeLanes.size) GlobalQueue.dn(e);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function checkBoundaryChildren(e) {
|
|
628
|
+
for (const t of e.vt) {
|
|
629
|
+
t.ne?.();
|
|
630
|
+
checkBoundaryChildren(t);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Count of live `affects()` registrations across the system (including
|
|
636
|
+
* store-scope inherited marks). Gates the read-path mark check in `read()` so
|
|
637
|
+
* graphs that never use the feature pay one integer compare.
|
|
638
|
+
*/ let activeAffectsMarks = 0;
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Counter mutation seam for the mark engine in affects.ts: an imported `let`
|
|
642
|
+
* binding is read-only, and the read-path gate above must stay a plain module
|
|
643
|
+
* variable so `read()` pays one integer compare, not a function call.
|
|
644
|
+
*
|
|
645
|
+
* @internal
|
|
646
|
+
*/ function shiftAffectsMarks(e) {
|
|
647
|
+
activeAffectsMarks += e;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function reassignPendingTransition(e) {
|
|
651
|
+
for (let t = 0; t < e.length; t++) {
|
|
652
|
+
e[t].Ne = activeTransition;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
const globalQueue = new GlobalQueue;
|
|
657
|
+
|
|
658
|
+
// Hot-path mirror of `globalQueue._batch`: `queuePendingNode` runs once per
|
|
659
|
+
// staged write and `commitPendingNodes` once per flush, and the extra
|
|
660
|
+
// property hop through `_batch` was a measured instruction-count regression
|
|
661
|
+
// (CodSpeed update1to1, PR #2905). The field stays authoritative for
|
|
662
|
+
// cross-module readers; every `_batch` assignment updates both.
|
|
663
|
+
let currentBatch = globalQueue.m;
|
|
664
|
+
|
|
665
|
+
function flush(e) {
|
|
666
|
+
if (e) {
|
|
667
|
+
syncDepth++;
|
|
668
|
+
try {
|
|
669
|
+
return e();
|
|
670
|
+
} finally {
|
|
671
|
+
// Decrement even if the drain throws (a throwing effect): a leaked
|
|
672
|
+
// syncDepth would stop `schedule()` from ever queuing a microtask again.
|
|
673
|
+
try {
|
|
674
|
+
flush();
|
|
675
|
+
} finally {
|
|
676
|
+
syncDepth--;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
if (globalQueue.gt) {
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
if (halted) return;
|
|
684
|
+
// `flush()` is an explicit drain point, so it must also process an active
|
|
685
|
+
// transition even if no microtask was scheduled for it yet.
|
|
686
|
+
while (scheduled || activeTransition) {
|
|
687
|
+
globalQueue.flush();
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
function runQueue(e, t) {
|
|
692
|
+
for (let i = 0; i < e.length; i++) e[i](t);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function reporterBlocksSource(e, t) {
|
|
696
|
+
if (e.se & (REACTIVE_ZOMBIE | REACTIVE_DISPOSED)) return false;
|
|
697
|
+
if (e.oe?.has(t)) return true;
|
|
698
|
+
for (let i = e.tt; i; i = i.nt) {
|
|
699
|
+
let e = i.it;
|
|
700
|
+
while (e) {
|
|
701
|
+
if (e === t || e.lt === t) return true;
|
|
702
|
+
e = e.nn;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
return !!(e.S & STATUS_PENDING && e._ instanceof NotReadyError && e._.source === t);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
function transitionComplete(e) {
|
|
709
|
+
if (e.fn) return true;
|
|
710
|
+
if (e.ie.length) return false;
|
|
711
|
+
let t = true;
|
|
712
|
+
for (const [i, n] of e.Ie) {
|
|
713
|
+
let s = false;
|
|
714
|
+
for (const e of n) {
|
|
715
|
+
if (reporterBlocksSource(e, i)) {
|
|
716
|
+
s = true;
|
|
717
|
+
break;
|
|
718
|
+
}
|
|
719
|
+
n.delete(e);
|
|
720
|
+
}
|
|
721
|
+
if (!s) e.Ie.delete(i); else if (i.S & STATUS_PENDING && i._?.source === i) {
|
|
722
|
+
t = false;
|
|
723
|
+
break;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
// Override blockage lives with the engine (absent hook = "no optimistic
|
|
727
|
+
// blockage"); the hook's loops over _optimisticNodes/_optimisticStores are
|
|
728
|
+
// no-ops when the transition holds neither, so no pre-check is needed.
|
|
729
|
+
if (t && GlobalQueue.Tn?.(e)) t = false;
|
|
730
|
+
t && (e.fn = true);
|
|
731
|
+
return t;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
function currentTransition(e) {
|
|
735
|
+
while (e.fn && typeof e.fn === "object") e = e.fn;
|
|
736
|
+
return e;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
function runInTransition(e, t) {
|
|
740
|
+
const i = activeTransition;
|
|
741
|
+
try {
|
|
742
|
+
activeTransition = currentTransition(e);
|
|
743
|
+
return t();
|
|
744
|
+
} finally {
|
|
745
|
+
activeTransition = i;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
export { GlobalQueue, Queue, activeAffectsMarks, activeLanes, activeTransition, armReaskClear, assignOrMergeLane, clock, currentTransition, dirtyQueue, enforceLoadingBoundary, finalizePureQueue, findLane, flush, globalQueue, haltReactivity, insertSubs, projectionWriteActive, queuePendingNode, registerTransientStoreNode, resetErrorHalt, runInTransition, schedule, setProjectionWriteActive, shiftAffectsMarks, zombieQueue };
|