@solidjs/signals 2.0.0-rc.1 → 2.0.0-rc.3
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.js +1779 -383
- package/dist/node.cjs +2058 -1359
- package/dist/prod/affects.js +16 -16
- package/dist/prod/boundaries.js +90 -90
- package/dist/prod/core/action.js +3 -3
- package/dist/prod/core/async.js +74 -72
- package/dist/prod/core/constants.js +39 -1
- package/dist/prod/core/core.js +332 -224
- package/dist/prod/core/effect.js +56 -56
- package/dist/prod/core/external.js +4 -4
- package/dist/prod/core/graph.js +65 -54
- package/dist/prod/core/heap.js +52 -44
- package/dist/prod/core/invariants.js +3 -2
- package/dist/prod/core/lanes.js +41 -34
- package/dist/prod/core/optimistic.js +93 -68
- package/dist/prod/core/owner.js +97 -96
- package/dist/prod/core/scheduler.js +243 -194
- package/dist/prod/core/verdict.js +191 -77
- package/dist/prod/map.js +104 -104
- package/dist/prod/signals.js +1 -1
- package/dist/prod/store/next/optimistic.js +31 -23
- package/dist/prod/store/next/projection.js +109 -47
- package/dist/prod/store/next/reconcile.js +78 -74
- package/dist/prod/store/next/store.js +357 -90
- package/dist/prod/store/store.js +7 -7
- package/dist/types/core/attribution-hooks.d.ts +52 -0
- package/dist/types/core/attribution.d.ts +186 -0
- package/dist/types/core/constants.d.ts +26 -0
- package/dist/types/core/core.d.ts +8 -1
- package/dist/types/core/dev.d.ts +20 -3
- package/dist/types/core/graph.d.ts +1 -0
- package/dist/types/core/invariants.d.ts +1 -1
- package/dist/types/core/lanes.d.ts +4 -16
- package/dist/types/core/scheduler.d.ts +8 -0
- package/dist/types/core/types.d.ts +85 -41
- package/dist/types/store/next/projection.d.ts +0 -16
- package/dist/types/store/next/store.d.ts +6 -0
- package/dist/types/store/next/target.d.ts +18 -0
- package/dist/types-cjs/core/attribution-hooks.d.cts +52 -0
- package/dist/types-cjs/core/attribution.d.cts +186 -0
- package/dist/types-cjs/core/constants.d.cts +26 -0
- package/dist/types-cjs/core/core.d.cts +8 -1
- package/dist/types-cjs/core/dev.d.cts +20 -3
- package/dist/types-cjs/core/graph.d.cts +1 -0
- package/dist/types-cjs/core/invariants.d.cts +1 -1
- package/dist/types-cjs/core/lanes.d.cts +4 -16
- package/dist/types-cjs/core/scheduler.d.cts +8 -0
- package/dist/types-cjs/core/types.d.cts +85 -41
- package/dist/types-cjs/store/next/projection.d.cts +0 -16
- package/dist/types-cjs/store/next/store.d.cts +6 -0
- package/dist/types-cjs/store/next/target.d.cts +18 -0
- package/package.json +15 -15
- package/dist/types/store/optimistic.d.ts +0 -45
- package/dist/types/store/projection.d.ts +0 -70
- package/dist/types/store/reconcile.d.ts +0 -46
- package/dist/types-cjs/store/optimistic.d.cts +0 -45
- package/dist/types-cjs/store/projection.d.cts +0 -70
- package/dist/types-cjs/store/reconcile.d.cts +0 -46
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import { NOT_PENDING, REACTIVE_DISPOSED, REACTIVE_DIRTY, REACTIVE_CHECK, unwrapOverride, REACTIVE_ZOMBIE, STATUS_UNINITIALIZED, STATUS_PENDING, REACTIVE_MANUAL_WRITE, STATUS_ERROR, REACTIVE_RECOMPUTING_DEPS } from "./constants.js";
|
|
1
|
+
import { NOT_PENDING, REACTIVE_DISPOSED, REACTIVE_DIRTY, REACTIVE_CHECK, unwrapOverride, REACTIVE_ZOMBIE, STATUS_UNINITIALIZED, STATUS_PENDING, REACTIVE_OPTIMISTIC_DIRTY, REACTIVE_MANUAL_WRITE, CONFIG_HAS_COMPANIONS, CONFIG_CHILD_COMPANIONS, STATUS_ERROR, REACTIVE_RECOMPUTING_DEPS } from "./constants.js";
|
|
2
2
|
|
|
3
|
-
import { setSignal, prepareComputed, read, context, stale, currentOptimisticLane, tracking, setLatestReadActive, setContextInternal, optimisticComputed, setPendingCheckActive, latestReadActive, optimisticSignal, pendingCheckActive } from "./core.js";
|
|
3
|
+
import { setSignal, prepareComputed, read, context, stale, currentOptimisticLane, tracking, ext, setLatestReadActive, setContextInternal, optimisticComputed, setPendingCheckActive, latestReadActive, optimisticSignal, pendingCheckActive } from "./core.js";
|
|
4
4
|
|
|
5
5
|
import { NotReadyError } from "./error.js";
|
|
6
6
|
|
|
7
7
|
import { link } from "./graph.js";
|
|
8
8
|
|
|
9
|
-
import { insertIntoHeap, queueFor, markHeap } from "./heap.js";
|
|
9
|
+
import { insertIntoHeap, queueFor, markHeap, enqueueSub } from "./heap.js";
|
|
10
10
|
|
|
11
11
|
import "./invariants.js";
|
|
12
12
|
|
|
13
|
-
import { findLane, hasActiveOverride } from "./lanes.js";
|
|
13
|
+
import { findLane, hasActiveOverride, assignOrMergeLane } from "./lanes.js";
|
|
14
14
|
|
|
15
15
|
import { installOptimisticEngine } from "./optimistic.js";
|
|
16
16
|
|
|
17
|
-
import { GlobalQueue,
|
|
17
|
+
import { GlobalQueue, insertSubs, schedule, activeTransition, currentTransition, activeAffectsMarks } from "./scheduler.js";
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* The isPending()/latest() verdict layer, moved out of core.ts. Importing this
|
|
@@ -28,19 +28,45 @@ installOptimisticEngine();
|
|
|
28
28
|
|
|
29
29
|
let pendingProbe = null;
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Probes whose verdict was suppressed by the fresh-read pairing rule while
|
|
33
|
+
* the held write's fate was still undecided (see recordFreshRead /
|
|
34
|
+
* wakeSuppressedProbes): held node → the wrapper computeds that probed it.
|
|
35
|
+
* Entries die with the hold — the commit/revert snap clears them.
|
|
36
|
+
*/ const suppressedProbes = new Map;
|
|
37
|
+
|
|
31
38
|
/**
|
|
32
39
|
* Get or create the pending signal for a node (lazy).
|
|
33
40
|
* Used by isPending() to track pending state reactively.
|
|
34
|
-
*/
|
|
35
|
-
|
|
41
|
+
*/
|
|
42
|
+
/** #3038: register a companion-carrying firewall child on its firewall's
|
|
43
|
+
* companion set and arm the post-recompute snap (CONFIG_CHILD_COMPANIONS is
|
|
44
|
+
* the one-load gate at the call sites). The snap then iterates exactly the
|
|
45
|
+
* children someone asked verdicts of — O(companions) — never the full
|
|
46
|
+
* `_child` chain, which carries one node per materialized leaf (the
|
|
47
|
+
* O(all-leaves-ever-read)-per-update pathology). Entries are permanent like
|
|
48
|
+
* the companions themselves; a store with no leaf-level isPending()/latest()
|
|
49
|
+
* reads never allocates the set or pays the walk. */ function markFirewallChildCompanions(e) {
|
|
50
|
+
const t = e.lt;
|
|
51
|
+
if (!t) return;
|
|
52
|
+
t.T |= CONFIG_CHILD_COMPANIONS;
|
|
53
|
+
(ext(t).Nt ??= new Set).add(e);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function getPendingSignal(e) {
|
|
57
|
+
let t = e.o?.Ge;
|
|
58
|
+
if (!t) {
|
|
36
59
|
// Start false, write true if pending - ensures reversion returns to false
|
|
37
|
-
|
|
60
|
+
t = optimisticSignal(false, {
|
|
38
61
|
ownedWrite: true
|
|
39
62
|
});
|
|
40
|
-
e.
|
|
41
|
-
|
|
63
|
+
ext(e).Ge = t;
|
|
64
|
+
e.T |= CONFIG_HAS_COMPANIONS;
|
|
65
|
+
markFirewallChildCompanions(e);
|
|
66
|
+
ext(t).It = e;
|
|
67
|
+
if (computePendingState(e)) setSignal(t, true);
|
|
42
68
|
}
|
|
43
|
-
return
|
|
69
|
+
return t;
|
|
44
70
|
}
|
|
45
71
|
|
|
46
72
|
function collectPendingSources(e) {
|
|
@@ -70,7 +96,7 @@ function collectPendingSources(e) {
|
|
|
70
96
|
* (`_pendingObserver`) are skipped so an `isPending` wrapper memo never
|
|
71
97
|
* inherits the coverage it reports on.
|
|
72
98
|
*/ function markWalk(e, t) {
|
|
73
|
-
if (e.t) return true;
|
|
99
|
+
if (e.o?.t) return true;
|
|
74
100
|
// A real error outranks an inherited mark (A16/A24c): an errored node
|
|
75
101
|
// answers probes with its error, not a coverage verdict, and coverage does
|
|
76
102
|
// not flow through it — matching the rails' behavior, where propagation
|
|
@@ -86,10 +112,10 @@ function collectPendingSources(e) {
|
|
|
86
112
|
// pass's dependency set — walking past it would read dropped deps and
|
|
87
113
|
// latch a stale verdict on the companion.
|
|
88
114
|
const i = e;
|
|
89
|
-
const r = i.
|
|
115
|
+
const r = i.ie & REACTIVE_RECOMPUTING_DEPS ? i.Ye : undefined;
|
|
90
116
|
if (r !== null) {
|
|
91
|
-
for (let e = i.
|
|
92
|
-
if (!e.
|
|
117
|
+
for (let e = i.nt ?? null; e !== null; e = e.it) {
|
|
118
|
+
if (!e.me && markWalk(e.ut, t)) return true;
|
|
93
119
|
if (e === r) break;
|
|
94
120
|
}
|
|
95
121
|
}
|
|
@@ -101,11 +127,11 @@ function collectPendingSources(e) {
|
|
|
101
127
|
}
|
|
102
128
|
|
|
103
129
|
function quietPending(e) {
|
|
104
|
-
if (e.
|
|
105
|
-
for (const t of e.
|
|
130
|
+
if (e.o?.le) {
|
|
131
|
+
for (const t of e.o.le) if (!t.o?.pe) return false;
|
|
106
132
|
return true;
|
|
107
133
|
}
|
|
108
|
-
return e.pe;
|
|
134
|
+
return e.o?.pe ?? false;
|
|
109
135
|
}
|
|
110
136
|
|
|
111
137
|
// NOTE: a loadingValue node's open loading window (_loading) is verdict-quiet
|
|
@@ -121,47 +147,47 @@ function newQuestionInFlight(e) {
|
|
|
121
147
|
|
|
122
148
|
function computePendingState(e) {
|
|
123
149
|
const t = e;
|
|
124
|
-
if (t.
|
|
150
|
+
if (t.ie & REACTIVE_DISPOSED) return false;
|
|
125
151
|
// Mark coverage is transitive by dep-graph reachability: a latest() shadow
|
|
126
152
|
// reaches its owner (and a store leaf its firewall) through its own deps,
|
|
127
153
|
// so the one walk covers direct marks, derivation, and companion chains.
|
|
128
154
|
if (markCovered(e)) return true;
|
|
129
155
|
const n = e.lt;
|
|
130
|
-
if (e.
|
|
131
|
-
const t = e.
|
|
156
|
+
if (e.o?.It) {
|
|
157
|
+
const t = e.o?.It;
|
|
132
158
|
const n = t.lt || t;
|
|
133
159
|
return newQuestionInFlight(n);
|
|
134
160
|
}
|
|
135
|
-
if (n && e.
|
|
136
|
-
return !!(n.
|
|
161
|
+
if (n && e.Pe !== NOT_PENDING && !hasActiveOverride(e)) {
|
|
162
|
+
return !!(n.ie & REACTIVE_MANUAL_WRITE) || !n.o?.Ee && !(n.S & STATUS_PENDING) || !!(n.S & STATUS_PENDING) && quietPending(n);
|
|
137
163
|
}
|
|
138
164
|
// `!comp._loading`: a hold created while the loading window is still open is
|
|
139
165
|
// the window's own landing in flight to its commit — verdict-quiet like the
|
|
140
166
|
// rest of the window (the UNINITIALIZED check suppresses exactly this frame
|
|
141
167
|
// for windowless first loads; born-committed nodes need their own gate, #2990).
|
|
142
|
-
if (e.
|
|
143
|
-
if (hasActiveOverride(e)) return !e.Ue || !e.Ue(e.
|
|
168
|
+
if (e.Pe !== NOT_PENDING && !(t.S & STATUS_UNINITIALIZED) && !t.Ie) {
|
|
169
|
+
if (hasActiveOverride(e)) return !e.Ue || !e.Ue(e.Pe, unwrapOverride(e.o?.De));
|
|
144
170
|
return true;
|
|
145
171
|
}
|
|
146
172
|
return newQuestionInFlight(t);
|
|
147
173
|
}
|
|
148
174
|
|
|
149
175
|
function syncCompanions(e, t) {
|
|
150
|
-
if (e.
|
|
151
|
-
if (e.ge) setSignal(e.ge, t);
|
|
176
|
+
if (e.o?.Ge) updatePendingSignal(e);
|
|
177
|
+
if (e.o?.ge) setSignal(e.o?.ge, t);
|
|
152
178
|
}
|
|
153
179
|
|
|
154
180
|
function updatePendingSignal(e) {
|
|
155
|
-
if (e.
|
|
156
|
-
setSignal(e.
|
|
181
|
+
if (e.o?.Ge) {
|
|
182
|
+
setSignal(e.o?.Ge, computePendingState(e));
|
|
157
183
|
}
|
|
158
|
-
if (e.ge) updatePendingSignal(e.ge);
|
|
184
|
+
if (e.o?.ge) updatePendingSignal(e.o?.ge);
|
|
159
185
|
}
|
|
160
186
|
|
|
161
187
|
function updateChildCompanions(e) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
188
|
+
const t = e.o?.Nt;
|
|
189
|
+
if (t === undefined) return;
|
|
190
|
+
for (const e of t) updatePendingSignal(e);
|
|
165
191
|
}
|
|
166
192
|
|
|
167
193
|
/**
|
|
@@ -178,31 +204,65 @@ function updateChildCompanions(e) {
|
|
|
178
204
|
const visit = e => {
|
|
179
205
|
if (i.has(e)) return;
|
|
180
206
|
i.add(e);
|
|
181
|
-
if (e.
|
|
182
|
-
for (let t = e.
|
|
183
|
-
for (let t = e.
|
|
207
|
+
if (e.o?.Ge || e.o?.ge) n(e);
|
|
208
|
+
for (let t = e.u; t !== null; t = t.fe) visit(t.ae);
|
|
209
|
+
for (let t = e.o?.l ?? null; t !== null; t = t.ce) {
|
|
184
210
|
visit(t);
|
|
185
211
|
}
|
|
186
212
|
};
|
|
187
213
|
visit(e);
|
|
188
214
|
}
|
|
189
215
|
|
|
216
|
+
/**
|
|
217
|
+
* The correction half of the provisional fresh-read suppression (see
|
|
218
|
+
* collectPending): fired from the sanctioned async-registration site
|
|
219
|
+
* (GlobalQueue.notify) when a transaction gains an in-flight async blocker.
|
|
220
|
+
* Every probe that returned "not pending" purely because it read a held
|
|
221
|
+
* value belonging to that transaction re-runs — its re-probe now sees the
|
|
222
|
+
* live blocker through heldAwaitingAsync and lands the true verdict. The
|
|
223
|
+
* wake mirrors a companion write's own notification (optimistic-dirty on the
|
|
224
|
+
* companion's lane) so the corrected verdict commits and flushes immediately
|
|
225
|
+
* instead of being held with the transaction it reports on.
|
|
226
|
+
*/ function wakeSuppressedProbes(e) {
|
|
227
|
+
if (suppressedProbes.size === 0) return;
|
|
228
|
+
let t = false;
|
|
229
|
+
for (const [n, i] of suppressedProbes) {
|
|
230
|
+
const r = n._e;
|
|
231
|
+
const s = r ? currentTransition(r) : null;
|
|
232
|
+
if (!s) {
|
|
233
|
+
suppressedProbes.delete(n);
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
if (s !== e) continue;
|
|
237
|
+
suppressedProbes.delete(n);
|
|
238
|
+
const o = n.o?.Ge?.o?.Be;
|
|
239
|
+
for (const e of i) {
|
|
240
|
+
if (e.ie & REACTIVE_DISPOSED) continue;
|
|
241
|
+
e.ie |= REACTIVE_OPTIMISTIC_DIRTY;
|
|
242
|
+
if (o) assignOrMergeLane(e, o); else if (e.o !== null) e.o.Be = undefined;
|
|
243
|
+
enqueueSub(e);
|
|
244
|
+
t = true;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (t) schedule();
|
|
248
|
+
}
|
|
249
|
+
|
|
190
250
|
function snapCompanionsToState(e) {
|
|
191
|
-
|
|
192
|
-
|
|
251
|
+
suppressedProbes.size !== 0 && suppressedProbes.delete(e);
|
|
252
|
+
const t = e.o?.Ge;
|
|
253
|
+
if (t && (t.o?.De === undefined || t.o?.De === NOT_PENDING)) {
|
|
193
254
|
const n = computePendingState(e);
|
|
194
|
-
if (t.be !== n || t.
|
|
255
|
+
if (t.be !== n || t.Pe !== NOT_PENDING) {
|
|
195
256
|
t.be = n;
|
|
196
|
-
t.
|
|
197
|
-
t.de = clock;
|
|
257
|
+
t.Pe = NOT_PENDING;
|
|
198
258
|
insertSubs(t);
|
|
199
259
|
schedule();
|
|
200
260
|
}
|
|
201
261
|
}
|
|
202
|
-
const n = e.ge;
|
|
203
|
-
if (n && !(n.
|
|
204
|
-
if ((n.De === undefined || n.De === NOT_PENDING) && n.
|
|
205
|
-
n.
|
|
262
|
+
const n = e.o?.ge;
|
|
263
|
+
if (n && !(n.ie & REACTIVE_DISPOSED)) {
|
|
264
|
+
if ((n.o?.De === undefined || n.o?.De === NOT_PENDING) && n.Pe === NOT_PENDING && !Object.is(n.be, e.be) && !(n.ie & (REACTIVE_DIRTY | REACTIVE_CHECK))) {
|
|
265
|
+
n.ie |= REACTIVE_DIRTY;
|
|
206
266
|
insertIntoHeap(n, queueFor(n));
|
|
207
267
|
insertSubs(n);
|
|
208
268
|
schedule();
|
|
@@ -212,29 +272,45 @@ function snapCompanionsToState(e) {
|
|
|
212
272
|
}
|
|
213
273
|
|
|
214
274
|
function getLatestValueComputed(e) {
|
|
215
|
-
|
|
216
|
-
|
|
275
|
+
let t = e.o?.ge;
|
|
276
|
+
// A shadow disposed while unobserved (its gated reader unmounted at a
|
|
277
|
+
// landing) is a corpse: sync writes into it equality-swallow against its
|
|
278
|
+
// frozen _value, and a later read revives it via recompute — clearing
|
|
279
|
+
// DISPOSED and re-deriving from the committed view, so the banner showed
|
|
280
|
+
// the previous transition's target (#3041 follow-up). Treat it as absent;
|
|
281
|
+
// recreation backfills from the in-flight write below.
|
|
282
|
+
if (t && t.ie & REACTIVE_DISPOSED) t = undefined;
|
|
283
|
+
if (!t) {
|
|
284
|
+
const n = latestReadActive;
|
|
217
285
|
setLatestReadActive(false);
|
|
218
|
-
const
|
|
286
|
+
const i = pendingCheckActive;
|
|
219
287
|
setPendingCheckActive(false);
|
|
220
|
-
const
|
|
288
|
+
const r = context;
|
|
221
289
|
setContextInternal(null);
|
|
222
290
|
// Detach from owner so it isn't disposed with effects
|
|
223
|
-
|
|
224
|
-
e.ge
|
|
291
|
+
t = optimisticComputed(() => read(e));
|
|
292
|
+
ext(e).ge = t;
|
|
293
|
+
e.T |= CONFIG_HAS_COMPANIONS;
|
|
294
|
+
markFirewallChildCompanions(e);
|
|
295
|
+
ext(t).It = e;
|
|
225
296
|
// Parent-child lane relationship
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
297
|
+
// Backfill an in-flight write (mirrors getPendingSignal): the companion is
|
|
298
|
+
// created lazily, possibly after the write was processed — syncCompanions
|
|
299
|
+
// only pushes into companions that already exist, so the first latest()
|
|
300
|
+
// read inside a held transition showed the committed value (#3041).
|
|
301
|
+
if (e.Pe !== NOT_PENDING && !hasActiveOverride(e)) setSignal(t, e.Pe);
|
|
302
|
+
setContextInternal(r);
|
|
303
|
+
setPendingCheckActive(i);
|
|
304
|
+
setLatestReadActive(n);
|
|
229
305
|
}
|
|
230
|
-
return
|
|
306
|
+
return t;
|
|
231
307
|
}
|
|
232
308
|
|
|
233
309
|
/** The latest()-mode read path, installed as GlobalQueue._latestRead. */ function latestRead(e) {
|
|
234
310
|
const t = getLatestValueComputed(e);
|
|
235
311
|
const n = latestReadActive;
|
|
236
312
|
setLatestReadActive(false);
|
|
237
|
-
const i = e.De !== undefined && e.De !== NOT_PENDING ? unwrapOverride(e.De) : e.be;
|
|
313
|
+
const i = e.o?.De !== undefined && e.o?.De !== NOT_PENDING ? unwrapOverride(e.o?.De) : e.be;
|
|
238
314
|
let r;
|
|
239
315
|
try {
|
|
240
316
|
// An untracked latest() read has no reading context, so read() never
|
|
@@ -243,7 +319,7 @@ function getLatestValueComputed(e) {
|
|
|
243
319
|
// until the flush (#2922). Mirror the tracked-read pull here: mark the
|
|
244
320
|
// queued staleness through the graph, then bring the shadow up to date.
|
|
245
321
|
const e = queueFor(t);
|
|
246
|
-
if (t.Le >= e.
|
|
322
|
+
if (t.Le >= e.xe && !(t.ie & (REACTIVE_DISPOSED | REACTIVE_ZOMBIE))) {
|
|
247
323
|
markHeap(e);
|
|
248
324
|
prepareComputed(t, true);
|
|
249
325
|
}
|
|
@@ -255,8 +331,8 @@ function getLatestValueComputed(e) {
|
|
|
255
331
|
setLatestReadActive(n);
|
|
256
332
|
}
|
|
257
333
|
if (t.S & STATUS_PENDING) return i;
|
|
258
|
-
if (stale && currentOptimisticLane && t.
|
|
259
|
-
const e = findLane(t.
|
|
334
|
+
if (stale && currentOptimisticLane && t.o?.Be) {
|
|
335
|
+
const e = findLane(t.o?.Be);
|
|
260
336
|
const n = findLane(currentOptimisticLane);
|
|
261
337
|
if (e !== n && e.Ae.size > 0) {
|
|
262
338
|
return i;
|
|
@@ -266,33 +342,54 @@ function getLatestValueComputed(e) {
|
|
|
266
342
|
// speculative value in _pendingValue; a contextless read() only surfaces
|
|
267
343
|
// _value. Overrides stay authoritative (A17), and stale readers keep the
|
|
268
344
|
// other transition's committed view, matching read()'s own selection.
|
|
269
|
-
if (t.
|
|
345
|
+
if (t.Pe !== NOT_PENDING && !hasActiveOverride(t) && !(stale && t._e && activeTransition !== t._e)) return t.Pe;
|
|
270
346
|
return r;
|
|
271
347
|
}
|
|
272
348
|
|
|
273
349
|
/** The isPending()-probe read path, installed as GlobalQueue._pendingCheck. */ function pendingCheckRead(e, t, n, i) {
|
|
274
350
|
setPendingCheckActive(false);
|
|
275
|
-
if (typeof e.
|
|
351
|
+
if (typeof e.Se === "function") prepareComputed(e, true);
|
|
276
352
|
const r = n.S;
|
|
277
353
|
if (t && r & STATUS_PENDING && r & STATUS_UNINITIALIZED) {
|
|
278
354
|
if (tracking && e !== t) link(e, t);
|
|
279
355
|
setPendingCheckActive(true);
|
|
280
|
-
throw n._;
|
|
356
|
+
throw n.o?._;
|
|
281
357
|
}
|
|
282
358
|
collectPendingSources(e);
|
|
283
359
|
if (i) collectPendingSources(i);
|
|
284
360
|
setPendingCheckActive(true);
|
|
285
361
|
}
|
|
286
362
|
|
|
363
|
+
/**
|
|
364
|
+
* A held node whose transaction still has an async question in flight. The
|
|
365
|
+
* probe's fresh-read pairing rule (#2831 — "a reader that sees the fresh
|
|
366
|
+
* value must not also be told it is pending") only applies to LANDED answers
|
|
367
|
+
* awaiting reveal; while the answer is still computing, the fresh value the
|
|
368
|
+
* reader saw is an input, and pending remains the truth for every reader
|
|
369
|
+
* (#3028).
|
|
370
|
+
*/ function heldAwaitingAsync(e) {
|
|
371
|
+
const t = e._e;
|
|
372
|
+
const n = t ? currentTransition(t) : null;
|
|
373
|
+
if (!n || n.an) return false;
|
|
374
|
+
for (const [e, t] of n.Ne) {
|
|
375
|
+
if (t.size && e.S & STATUS_PENDING && e.o?._?.source === e) return true;
|
|
376
|
+
}
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
|
|
287
380
|
function recordFreshRead(e, t) {
|
|
288
|
-
if (pendingProbe !== null && e.
|
|
381
|
+
if (pendingProbe !== null && e.Pe !== NOT_PENDING && t === e.Pe) {
|
|
382
|
+
if (heldAwaitingAsync(e)) return;
|
|
383
|
+
pendingProbe.freshReads.add(e);
|
|
384
|
+
}
|
|
289
385
|
}
|
|
290
386
|
|
|
291
387
|
function applyReask(e, t) {
|
|
292
388
|
const n = !!(e.S & STATUS_PENDING);
|
|
293
|
-
const i = t && !(n && !e.pe);
|
|
294
|
-
const r = n && e.pe !== i;
|
|
295
|
-
|
|
389
|
+
const i = t && !(n && !e.o?.pe);
|
|
390
|
+
const r = n && (e.o?.pe ?? false) !== i;
|
|
391
|
+
// Allocation-free for the quiet case: false is the extension default.
|
|
392
|
+
if (i) ext(e).pe = true; else if (e.o !== null) e.o.pe = false;
|
|
296
393
|
return r;
|
|
297
394
|
}
|
|
298
395
|
|
|
@@ -313,17 +410,32 @@ function isPending(e) {
|
|
|
313
410
|
const i = pendingProbe = {
|
|
314
411
|
found: false,
|
|
315
412
|
sources: new Set,
|
|
316
|
-
freshReads: new Set
|
|
413
|
+
freshReads: new Set,
|
|
414
|
+
suppressed: []
|
|
317
415
|
};
|
|
318
416
|
const collectPending = () => {
|
|
319
417
|
setPendingCheckActive(false);
|
|
320
418
|
try {
|
|
321
419
|
i.sources.forEach(e => {
|
|
322
|
-
if (read(getPendingSignal(e))
|
|
420
|
+
if (read(getPendingSignal(e))) {
|
|
421
|
+
if (!i.freshReads.has(e)) i.found = true; else i.suppressed.push(e);
|
|
422
|
+
}
|
|
323
423
|
});
|
|
324
424
|
} finally {
|
|
325
425
|
setPendingCheckActive(true);
|
|
326
426
|
}
|
|
427
|
+
// A "not pending" verdict that exists only because this reader saw the
|
|
428
|
+
// fresh held value is provisional: if the write turns out NOT to commit
|
|
429
|
+
// this flush (a downstream async pends and holds it), the suppression was
|
|
430
|
+
// wrong and the wrapper must re-ask (#3028). Remember who to wake — the
|
|
431
|
+
// async registration (GlobalQueue.notify) triggers wakeSuppressedProbes.
|
|
432
|
+
if (!i.found && i.suppressed.length && context && typeof context.Se === "function") {
|
|
433
|
+
for (const e of i.suppressed) {
|
|
434
|
+
let t = suppressedProbes.get(e);
|
|
435
|
+
if (!t) suppressedProbes.set(e, t = new Set);
|
|
436
|
+
t.add(context);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
327
439
|
};
|
|
328
440
|
try {
|
|
329
441
|
e();
|
|
@@ -346,24 +458,26 @@ function isPending(e) {
|
|
|
346
458
|
// Hook installation (same late-binding pattern as GlobalQueue._update /
|
|
347
459
|
// _propagateAffects): core call sites fire these behind the same guards the
|
|
348
460
|
// direct calls used, so behavior is identical once this module loads.
|
|
349
|
-
GlobalQueue.
|
|
461
|
+
GlobalQueue.Oe = syncCompanions;
|
|
350
462
|
|
|
351
|
-
GlobalQueue.
|
|
463
|
+
GlobalQueue.de = updatePendingSignal;
|
|
352
464
|
|
|
353
|
-
GlobalQueue.
|
|
465
|
+
GlobalQueue.ye = updateChildCompanions;
|
|
354
466
|
|
|
355
467
|
GlobalQueue.un = snapCompanionsToState;
|
|
356
468
|
|
|
357
|
-
GlobalQueue.
|
|
469
|
+
GlobalQueue.Pt = latestRead;
|
|
470
|
+
|
|
471
|
+
GlobalQueue.Dt = pendingCheckRead;
|
|
358
472
|
|
|
359
|
-
GlobalQueue.
|
|
473
|
+
GlobalQueue.Ht = recordFreshRead;
|
|
360
474
|
|
|
361
|
-
GlobalQueue.
|
|
475
|
+
GlobalQueue.Je = applyReask;
|
|
362
476
|
|
|
363
|
-
GlobalQueue
|
|
477
|
+
GlobalQueue.p = repollDownstreamVerdicts;
|
|
364
478
|
|
|
365
|
-
GlobalQueue.
|
|
479
|
+
GlobalQueue.Bt = witnessAffects;
|
|
366
480
|
|
|
367
|
-
GlobalQueue.
|
|
481
|
+
GlobalQueue.wt = wakeSuppressedProbes;
|
|
368
482
|
|
|
369
483
|
export { isPending, latest };
|