@solidjs/signals 2.0.0-rc.14 → 2.0.0-rc.16
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 +882 -405
- package/dist/dev.attribution.js +172 -66
- package/dist/dev.js +1529 -745
- package/dist/observe/affects-marks.js +86 -0
- package/dist/observe/affects.js +32 -80
- package/dist/observe/boundaries.js +202 -166
- package/dist/observe/core/async.js +192 -141
- package/dist/observe/core/attribution-queries.js +1 -1
- package/dist/observe/core/attribution.js +315 -214
- package/dist/observe/core/constants.js +23 -4
- package/dist/observe/core/context.js +3 -3
- package/dist/observe/core/core.js +532 -360
- package/dist/observe/core/dev.js +63 -35
- package/dist/observe/core/effect.js +47 -47
- package/dist/observe/core/error.js +5 -3
- package/dist/observe/core/external.js +5 -6
- package/dist/observe/core/graph.js +56 -42
- package/dist/observe/core/heap.js +33 -33
- package/dist/observe/core/identities.js +62 -0
- package/dist/observe/core/lanes.js +242 -229
- package/dist/observe/core/owner.js +62 -50
- package/dist/observe/core/scheduler.js +322 -168
- package/dist/observe/core/truth.js +45 -0
- package/dist/observe/core/verdict.js +114 -64
- package/dist/observe/deferred.js +184 -0
- package/dist/observe/index.js +7 -1
- package/dist/observe/map.js +339 -254
- package/dist/observe/reveal.js +77 -77
- package/dist/observe/signals.js +54 -33
- package/dist/observe/store/affects.js +76 -69
- package/dist/observe/store/edges.js +39 -0
- package/dist/observe/store/hooks.js +13 -0
- package/dist/observe/store/index.js +0 -2
- package/dist/observe/store/optimistic.js +82 -44
- package/dist/observe/store/projection.js +83 -64
- package/dist/observe/store/reconcile.js +32 -30
- package/dist/observe/store/store.js +219 -123
- package/dist/prod/affects-marks.js +86 -0
- package/dist/prod/affects.js +33 -79
- package/dist/prod/boundaries.js +185 -149
- package/dist/prod/core/async.js +176 -135
- package/dist/prod/core/constants.js +23 -4
- package/dist/prod/core/context.js +3 -3
- package/dist/prod/core/core.js +521 -353
- package/dist/prod/core/effect.js +66 -66
- package/dist/prod/core/error.js +5 -3
- package/dist/prod/core/external.js +5 -6
- package/dist/prod/core/graph.js +56 -42
- package/dist/prod/core/heap.js +38 -38
- package/dist/prod/core/identities.js +62 -0
- package/dist/prod/core/lanes.js +237 -224
- package/dist/prod/core/owner.js +62 -51
- package/dist/prod/core/scheduler.js +312 -158
- package/dist/prod/core/truth.js +45 -0
- package/dist/prod/core/verdict.js +113 -63
- package/dist/prod/deferred.js +182 -0
- package/dist/prod/index.js +7 -1
- package/dist/prod/map.js +366 -281
- package/dist/prod/reveal.js +77 -77
- package/dist/prod/signals.js +59 -38
- package/dist/prod/store/affects.js +76 -69
- package/dist/prod/store/edges.js +39 -0
- package/dist/prod/store/hooks.js +13 -0
- package/dist/prod/store/index.js +0 -2
- package/dist/prod/store/optimistic.js +121 -84
- package/dist/prod/store/projection.js +46 -34
- package/dist/prod/store/reconcile.js +32 -30
- package/dist/prod/store/store.js +205 -116
- package/dist/types/affects-marks.d.ts +14 -0
- package/dist/types/boundaries.d.ts +5 -1
- package/dist/types/core/async.d.ts +10 -0
- package/dist/types/core/attribution.d.ts +6 -2
- package/dist/types/core/constants.d.ts +18 -1
- package/dist/types/core/core.d.ts +77 -5
- package/dist/types/core/dev.d.ts +43 -13
- package/dist/types/core/graph.d.ts +11 -0
- package/dist/types/core/identities.d.ts +17 -0
- package/dist/types/core/index.d.ts +2 -1
- package/dist/types/core/lanes.d.ts +2 -4
- package/dist/types/core/owner.d.ts +5 -3
- package/dist/types/core/scheduler.d.ts +59 -34
- package/dist/types/core/truth.d.ts +16 -0
- package/dist/types/core/types.d.ts +21 -6
- package/dist/types/core/verdict.d.ts +32 -0
- package/dist/types/deferred.d.ts +28 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/map.d.ts +2 -2
- package/dist/types/signals.d.ts +28 -7
- package/dist/types/store/affects.d.ts +11 -0
- package/dist/types/store/edges.d.ts +9 -0
- package/dist/types/store/hooks.d.ts +48 -0
- package/dist/types/store/index.d.ts +10 -4
- package/dist/types/store/optimistic.d.ts +3 -2
- package/dist/types/store/projection.d.ts +4 -3
- package/dist/types/store/store.d.ts +4 -39
- package/dist/types/store/target.d.ts +16 -8
- package/dist/types/store/types.d.ts +14 -0
- package/package.json +1 -1
package/dist/dev-shared.js
CHANGED
|
@@ -23,18 +23,18 @@
|
|
|
23
23
|
* ```
|
|
24
24
|
*/
|
|
25
25
|
class NotReadyError extends Error {
|
|
26
|
-
source;
|
|
27
26
|
constructor(source) {
|
|
28
27
|
super();
|
|
29
28
|
this.source = source;
|
|
30
29
|
}
|
|
31
30
|
}
|
|
32
31
|
class StatusError extends Error {
|
|
33
|
-
source;
|
|
34
32
|
constructor(source, original) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
// Best-effort message: formatting must never replace the original (#4112).
|
|
34
|
+
try {
|
|
35
|
+
var message = String(original instanceof Error ? original.message : original);
|
|
36
|
+
} catch {}
|
|
37
|
+
super(message, { cause: original });
|
|
38
38
|
this.source = source;
|
|
39
39
|
}
|
|
40
40
|
}
|
|
@@ -149,7 +149,7 @@ const CONFIG_SYNC = 1 << 6;
|
|
|
149
149
|
// CONFIG_HELD_CHILDREN, CONFIG_INPUTS_PUBLISHED and CONFIG_ADOPTED_UNFLUSHED
|
|
150
150
|
// (transaction holds, zombies, until()'s authoritative view) went with the
|
|
151
151
|
// transactions.
|
|
152
|
-
// L2 (step 2):
|
|
152
|
+
// L2 (step 2): four bits, all maintained by the node's commit.
|
|
153
153
|
/** The node has a pass awaiting commit: its live children (and `_disposal`)
|
|
154
154
|
* are that pass's and were never shown — a re-pass tears them down on the
|
|
155
155
|
* spot, while the frame parked on `_x._pendingFirstChild` keeps waiting for
|
|
@@ -162,6 +162,11 @@ const CONFIG_STAGED = 1 << 7;
|
|
|
162
162
|
* parking flush they see the committed value and hold nothing, and are
|
|
163
163
|
* re-derived at the landing. Set at the park seam on every staged node. */
|
|
164
164
|
const CONFIG_HELD = 1 << 8;
|
|
165
|
+
/** A member of the uncommitted frame a held pass built (ruling A): set at
|
|
166
|
+
* the park seam on every computed under a staged node (`holdFrame`). Never
|
|
167
|
+
* shown, it has no screen: its reads are its transaction's, whichever flush
|
|
168
|
+
* runs it (`bornInto`, #4010). Cleared by its commit. */
|
|
169
|
+
const CONFIG_HELD_FRAME = 1 << 20;
|
|
165
170
|
/** A15's reveal corollary (#3305): a commit published this flight's inputs
|
|
166
171
|
* beneath it — the node was pending, nothing staged, when it committed — so
|
|
167
172
|
* its committed value is torn against the visible frame. A render effect
|
|
@@ -186,6 +191,9 @@ const CONFIG_SLOT_NODE = 1 << 12;
|
|
|
186
191
|
* leaf twin of REACTIVE_MANUAL_WRITE). Set by the user setter, cleared by
|
|
187
192
|
* the derive's own write to the key. */
|
|
188
193
|
const CONFIG_MANUAL_WRITE = 1 << 18;
|
|
194
|
+
/** D-41: the staged value in `_pendingValue` is an error — an
|
|
195
|
+
* answer of the held frame, published at its landing like a value. */
|
|
196
|
+
const CONFIG_STAGED_ERROR = 1 << 21;
|
|
189
197
|
/** Lanes — the node carries a written guess (`optimisticWrite`): its own
|
|
190
198
|
* source recomputing it, or a plain write landing on it, is the truth
|
|
191
199
|
* (`supersede`). A lane's derived staging is not. */
|
|
@@ -194,15 +202,21 @@ const CONFIG_GUESS = 1 << 13;
|
|
|
194
202
|
* pending is a question for its next pass — the verdict changed — so the
|
|
195
203
|
* propagation re-derives it instead of marking it pending (as a kept-tail
|
|
196
204
|
* link does, A30). Set at the window, never cleared: a reader that stopped
|
|
197
|
-
* asking re-derives once and goes pending through its own read.
|
|
198
|
-
|
|
205
|
+
* asking re-derives once and goes pending through its own read. The same
|
|
206
|
+
* bit as REACTIVE_PROBED: a pass's probe (or a read of a verdict memo,
|
|
207
|
+
* core.ts `read`) carries into the config at its end without a shift. */
|
|
208
|
+
const CONFIG_VERDICT = 1 << 15;
|
|
199
209
|
/** A dependency's status is a question for this node's pass, not a fact
|
|
200
210
|
* about its current one: the propagation re-derives it instead of marking
|
|
201
211
|
* it, for errors as for pending (as CONFIG_VERDICT). A boundary's output
|
|
202
212
|
* (show the content, or the fallback) and its `on` node (boundaries.ts); a
|
|
203
213
|
* promise over an expression (`resolve`/`until`, signals.ts: the pass reads
|
|
204
214
|
* the error and rejects, or re-asks a pending source). */
|
|
205
|
-
const CONFIG_REDERIVE = 1 <<
|
|
215
|
+
const CONFIG_REDERIVE = 1 << 14;
|
|
216
|
+
/** An answered `createDeferred` node (deferred.ts): held because a
|
|
217
|
+
* downstream hold staged its landing, its re-pass is a lagging question,
|
|
218
|
+
* not an answer, and joins no transaction (D-40). */
|
|
219
|
+
const CONFIG_LAGS = 1 << 19;
|
|
206
220
|
/** The reader wants the truth, not the lane's guess (`until`, signals.ts):
|
|
207
221
|
* a read of displayed optimism serves the base the guess covers, makes the
|
|
208
222
|
* pass no lane's, and the truth landing wakes it even when it confirms the
|
|
@@ -254,9 +268,13 @@ const CONFIG_WIDE = 1 << 26;
|
|
|
254
268
|
* creation by dev builds only (the observe artifact mangles the option name,
|
|
255
269
|
* as it does `_wide`). */
|
|
256
270
|
const CONFIG_SUPERSEDES = 1 << 27;
|
|
271
|
+
const STATUS_NONE = 0;
|
|
257
272
|
const STATUS_PENDING = 1 << 0;
|
|
258
273
|
const STATUS_ERROR = 1 << 1;
|
|
259
274
|
const STATUS_UNINITIALIZED = 1 << 2;
|
|
275
|
+
/** `catchHook`'s answer when the catcher is a boundary re-armed by
|
|
276
|
+
* `on` (truthy, as any catch): what it collects is its own, not the hold's. */
|
|
277
|
+
const CAUGHT_REARMED = 2;
|
|
260
278
|
const EFFECT_RENDER = 1;
|
|
261
279
|
const EFFECT_USER = 2;
|
|
262
280
|
const EFFECT_TRACKED = 3;
|
|
@@ -405,13 +423,21 @@ const diagnostics = {
|
|
|
405
423
|
};
|
|
406
424
|
}
|
|
407
425
|
};
|
|
426
|
+
// Flight identity → earliest declared start (see `AttributionSlot.markFlight`).
|
|
427
|
+
// Weak: a stamp must not keep a settled promise alive.
|
|
428
|
+
const flightOrigins = new WeakMap();
|
|
408
429
|
const attributionSlot = {
|
|
409
430
|
get installed() {
|
|
410
431
|
return attrHooks;
|
|
411
432
|
},
|
|
412
433
|
withInteraction,
|
|
413
434
|
withOrigin,
|
|
414
|
-
currentOrigin
|
|
435
|
+
currentOrigin,
|
|
436
|
+
markFlight(flight, startedAt = performance.now()) {
|
|
437
|
+
// Earliest wins (`undefined <= n` is false: the first mark always lands).
|
|
438
|
+
if (!(flightOrigins.get(flight) <= startedAt)) flightOrigins.set(flight, startedAt);
|
|
439
|
+
},
|
|
440
|
+
flightOrigin: flight => flightOrigins.get(flight)
|
|
415
441
|
};
|
|
416
442
|
const RECORDS = Symbol.for("@solidjs/signals/observe/records");
|
|
417
443
|
function recordsChannel() {
|
|
@@ -513,10 +539,11 @@ function isSuppressed(entry) {
|
|
|
513
539
|
return suppressedEvents.has(entry);
|
|
514
540
|
}
|
|
515
541
|
// The repair guide: the `reactivity-diagnostics` skill `solid-js` ships,
|
|
516
|
-
// one
|
|
517
|
-
//
|
|
518
|
-
|
|
519
|
-
|
|
542
|
+
// one page per code on the docs site. The slug is the code lowercased with
|
|
543
|
+
// `_` → `-` (`STRICT_READ_UNTRACKED` → `strict-read-untracked`). The host
|
|
544
|
+
// flips to docs.solidjs.com at stable (one search-and-replace, with the JSDoc
|
|
545
|
+
// links).
|
|
546
|
+
const GUIDE_URL = "https://v2.solidjs.com/reference/solid-js/diagnostics/";
|
|
520
547
|
const DEV = {
|
|
521
548
|
hooks,
|
|
522
549
|
getChildren,
|
|
@@ -526,7 +553,7 @@ const DEV = {
|
|
|
526
553
|
getObservers,
|
|
527
554
|
report: reportDiagnostic,
|
|
528
555
|
guideUrl(code) {
|
|
529
|
-
return
|
|
556
|
+
return GUIDE_URL + code.toLowerCase().replace(/_/g, "-");
|
|
530
557
|
}
|
|
531
558
|
};
|
|
532
559
|
/**
|
|
@@ -565,15 +592,29 @@ function ownerPath(subject) {
|
|
|
565
592
|
}
|
|
566
593
|
return path.length ? path.reverse() : undefined;
|
|
567
594
|
}
|
|
595
|
+
/**
|
|
596
|
+
* The innermost source location on the subject's chain: the subject's own
|
|
597
|
+
* `_loc` (a signal or computation created with `{ loc }`), else the first
|
|
598
|
+
* `_loc` up the owners — a signal hops to its registering owner, then
|
|
599
|
+
* `_parent` upward (a component root carries its JSX site). The same walk
|
|
600
|
+
* `ownerPath` makes; `undefined` when nothing on the chain was located.
|
|
601
|
+
*/
|
|
602
|
+
function locationOf(subject) {
|
|
603
|
+
// Owners have a `_parent` key (null on a root); a signal has none and hops
|
|
604
|
+
// to its registering `_owner` instead.
|
|
605
|
+
for (let node = subject; node; node = node._parent !== undefined ? node._parent : node._owner)
|
|
606
|
+
if (node._loc) return node._loc;
|
|
607
|
+
return undefined;
|
|
608
|
+
}
|
|
568
609
|
/**
|
|
569
610
|
* Records a diagnostic on the structured channel (listeners, captures) and
|
|
570
611
|
* returns the entry. `subject` locates it: the current reactive `context` by
|
|
571
612
|
* default (right for the synchronous rule checks — they fire inside the
|
|
572
613
|
* scope that misbehaved); pass the node for scheduler-time findings whose
|
|
573
614
|
* ambient context is the flush, or `null` for events that have no location
|
|
574
|
-
* by nature. An `ownerPath` already on the event wins over the
|
|
575
|
-
* (hosts whose owners are not signals' owners compute their
|
|
576
|
-
* output is a separate, dev-tier step — see `reportDiagnostic`.
|
|
615
|
+
* by nature. An `ownerPath` (or `loc`) already on the event wins over the
|
|
616
|
+
* subject walk (hosts whose owners are not signals' owners compute their
|
|
617
|
+
* own). Console output is a separate, dev-tier step — see `reportDiagnostic`.
|
|
577
618
|
*/
|
|
578
619
|
function emitDiagnostic(event, subject = context) {
|
|
579
620
|
const entry = {
|
|
@@ -590,6 +631,10 @@ function emitDiagnostic(event, subject = context) {
|
|
|
590
631
|
const path = ownerPath(subject);
|
|
591
632
|
if (path) entry.ownerPath = path;
|
|
592
633
|
}
|
|
634
|
+
if (entry.loc === undefined) {
|
|
635
|
+
const loc = locationOf(subject);
|
|
636
|
+
if (loc !== undefined) entry.loc = loc;
|
|
637
|
+
}
|
|
593
638
|
const live = subject ?? undefined;
|
|
594
639
|
if (live !== undefined) eventSubjects.set(entry, live);
|
|
595
640
|
for (const listener of diagnosticListeners) listener(entry, live);
|
|
@@ -626,12 +671,31 @@ function takeFooter(entry) {
|
|
|
626
671
|
* `reportDiagnostic(emitDiagnostic(…))`. Weak, keyed by the entry.
|
|
627
672
|
*/
|
|
628
673
|
const eventSubjects = new WeakMap();
|
|
674
|
+
/**
|
|
675
|
+
* The `at` line of a console report: the event's `loc` as the compiler wrote
|
|
676
|
+
* it (`src/App.tsx:18:3`), or — in a browser, where a dev server serves the
|
|
677
|
+
* project root at `location.origin` — `http://localhost:5173/src/App.tsx:18:3`,
|
|
678
|
+
* which Chrome and Firefox link to the Sources panel. A path that leaves the
|
|
679
|
+
* root (`../`) or is absolute is not under the origin and prints as-is.
|
|
680
|
+
*/
|
|
681
|
+
function locationLine(loc) {
|
|
682
|
+
if (
|
|
683
|
+
typeof document !== "undefined" &&
|
|
684
|
+
typeof location !== "undefined" &&
|
|
685
|
+
!loc.startsWith("..") &&
|
|
686
|
+
!loc.startsWith("/")
|
|
687
|
+
)
|
|
688
|
+
return `${location.origin}/${loc}`;
|
|
689
|
+
return loc;
|
|
690
|
+
}
|
|
629
691
|
/**
|
|
630
692
|
* The console face of a diagnostic — ONE entry per finding: the message, the
|
|
631
693
|
* owner path (`in <App> › <TodoRow> › effect`) so a human can locate it, the
|
|
632
|
-
*
|
|
633
|
-
*
|
|
634
|
-
*
|
|
694
|
+
* source location (`at src/App.tsx:18:3` — see `locationLine`) when a node on
|
|
695
|
+
* the chain was compiled with one, the once-per-code footer as trailing
|
|
696
|
+
* lines, and — when the subject is a binding effect the rendering runtime
|
|
697
|
+
* tagged — the element it writes, as a second console argument (hover
|
|
698
|
+
* highlights it, click jumps to Elements).
|
|
635
699
|
* Severity picks the console method. Call sites report the entry
|
|
636
700
|
* `emitDiagnostic` returned so the structured and console channels never
|
|
637
701
|
* disagree. Dev-tier: in an observe build this is a no-op, so wiring paths
|
|
@@ -642,6 +706,7 @@ function reportDiagnostic(entry) {
|
|
|
642
706
|
if (suppressedEvents.has(entry)) return;
|
|
643
707
|
let text = entry.message;
|
|
644
708
|
if (entry.ownerPath) text += `\n in ${entry.ownerPath.join(" › ")}`;
|
|
709
|
+
if (entry.loc) text += `\n at ${locationLine(entry.loc)}`;
|
|
645
710
|
const footer = takeFooter(entry);
|
|
646
711
|
if (footer) text += `\n${footer}`;
|
|
647
712
|
const element = eventSubjects.get(entry)?._devElement;
|
|
@@ -1047,6 +1112,101 @@ function devCheckQuiescent(isQueuedForCommit) {
|
|
|
1047
1112
|
return;
|
|
1048
1113
|
}
|
|
1049
1114
|
|
|
1115
|
+
// Optional-module hooks: module bindings, not `GlobalQueue` statics, so a
|
|
1116
|
+
// bundle that never imports the installing module never assigns them — its
|
|
1117
|
+
// minifier folds every guarded call site (and the branch behind it) away.
|
|
1118
|
+
// Each module installs its own set at evaluation (pay-for-use).
|
|
1119
|
+
//
|
|
1120
|
+
// Lanes (lanes.ts; installed when `createOptimistic` — or verdict.ts — is
|
|
1121
|
+
// imported; every call site is behind a bit or a `passLane` nothing else sets).
|
|
1122
|
+
let laneReadHook = null;
|
|
1123
|
+
let laneStageHook = null;
|
|
1124
|
+
let laneOutcomeHook = null;
|
|
1125
|
+
let laneWriteHook = null;
|
|
1126
|
+
let applyGuessesHook = null;
|
|
1127
|
+
let laneSeamsHook = null;
|
|
1128
|
+
let laneCorrectionsHook = null;
|
|
1129
|
+
let endLanesHook = null;
|
|
1130
|
+
let lanesBlockedHook = null;
|
|
1131
|
+
function installLaneHooks(
|
|
1132
|
+
read,
|
|
1133
|
+
stage,
|
|
1134
|
+
outcome,
|
|
1135
|
+
write,
|
|
1136
|
+
applyGuesses,
|
|
1137
|
+
seams,
|
|
1138
|
+
corrections,
|
|
1139
|
+
end,
|
|
1140
|
+
blocked
|
|
1141
|
+
) {
|
|
1142
|
+
laneReadHook = read;
|
|
1143
|
+
laneStageHook = stage;
|
|
1144
|
+
laneOutcomeHook = outcome;
|
|
1145
|
+
laneWriteHook = write;
|
|
1146
|
+
applyGuessesHook = applyGuesses;
|
|
1147
|
+
laneSeamsHook = seams;
|
|
1148
|
+
laneCorrectionsHook = corrections;
|
|
1149
|
+
endLanesHook = end;
|
|
1150
|
+
lanesBlockedHook = blocked;
|
|
1151
|
+
}
|
|
1152
|
+
// Boundaries (boundaries.ts): the display consumers between an observer
|
|
1153
|
+
// and the root. `catch` — status from a frame reader, nearest boundary
|
|
1154
|
+
// first (true: caught, the root never hears of it; a clear — flags 0 —
|
|
1155
|
+
// settles the reader there); `fresh` — a pass that read a hold (true: a
|
|
1156
|
+
// first pass a loading boundary that has not shown content caught,
|
|
1157
|
+
// `joinPass`; `held`: the flush's hold owns it, nothing catches it);
|
|
1158
|
+
// `hidden` — a frame reader behind a fallback (the boundary showing it)
|
|
1159
|
+
// is not on screen and holds nothing; `boundarySeam` — the seam's sweep (readers
|
|
1160
|
+
// gone or settled without a pass reveal; an `on` re-arm resolves);
|
|
1161
|
+
// `heldRun` — a queued run under a fallback-showing boundary waits for
|
|
1162
|
+
// the reveal (true: held; the boundary re-queues it by type). The
|
|
1163
|
+
// synchronous first render on creation builds the subtree, attached or
|
|
1164
|
+
// not; its updates and the user effects wait.
|
|
1165
|
+
let catchHook = null;
|
|
1166
|
+
let freshHook = null;
|
|
1167
|
+
let hiddenHook = null;
|
|
1168
|
+
let boundarySeamHook = null;
|
|
1169
|
+
let heldRunHook = null;
|
|
1170
|
+
function installBoundaryHooks(caught, fresh, hidden, seam, heldRun) {
|
|
1171
|
+
catchHook = caught;
|
|
1172
|
+
freshHook = fresh;
|
|
1173
|
+
hiddenHook = hidden;
|
|
1174
|
+
boundarySeamHook = seam;
|
|
1175
|
+
heldRunHook = heldRun;
|
|
1176
|
+
}
|
|
1177
|
+
/** Store (store/store.ts): `park` — the flush parked into `t`: the store
|
|
1178
|
+
* materializes and holds the container node of every staging that had none
|
|
1179
|
+
* (a staging needs the node as its home only when something can read the
|
|
1180
|
+
* frame through it; a hold can); `commit` — fold the pending backings whose
|
|
1181
|
+
* container nodes this flush committed (the owned-raw model's one mutation
|
|
1182
|
+
* point). */
|
|
1183
|
+
let storeParkHook = null;
|
|
1184
|
+
let storeCommitHook = null;
|
|
1185
|
+
function installStoreHooks(park, commit) {
|
|
1186
|
+
storeParkHook = park;
|
|
1187
|
+
storeCommitHook = commit;
|
|
1188
|
+
}
|
|
1189
|
+
/** `affects()` marks (affects.ts): the releases at a landing and at the seam
|
|
1190
|
+
* (ambient marks). */
|
|
1191
|
+
let releaseMarksHook = null;
|
|
1192
|
+
let releaseAmbientMarksHook = null;
|
|
1193
|
+
function installMarkHooks(release, releaseAmbient) {
|
|
1194
|
+
releaseMarksHook = release;
|
|
1195
|
+
releaseAmbientMarksHook = releaseAmbient;
|
|
1196
|
+
}
|
|
1197
|
+
/** Verdicts (verdict.ts). */
|
|
1198
|
+
let observeFlightHook = null;
|
|
1199
|
+
function installVerdictHooks(observeFlight) {
|
|
1200
|
+
observeFlightHook = observeFlight;
|
|
1201
|
+
}
|
|
1202
|
+
/** External-source bridge (wired by enableExternalSource(); undefined while
|
|
1203
|
+
* no config is active — including after _resetExternalSourceConfig()). */
|
|
1204
|
+
let wireExternalSourceHook = undefined;
|
|
1205
|
+
let externalUntrackHook = undefined;
|
|
1206
|
+
function setExternalHooks(wire, untrack) {
|
|
1207
|
+
wireExternalSourceHook = wire;
|
|
1208
|
+
externalUntrackHook = untrack;
|
|
1209
|
+
}
|
|
1050
1210
|
/** Live transactions — opened, unmerged, not landed. Scanned at every seam
|
|
1051
1211
|
* while non-empty; nothing on the plain path. */
|
|
1052
1212
|
const transactions = [];
|
|
@@ -1185,10 +1345,7 @@ function blocked(t) {
|
|
|
1185
1345
|
judge ??= t;
|
|
1186
1346
|
// An action still running in it holds it open (action.ts). Its lanes
|
|
1187
1347
|
// (lanes.ts): a lane under `t` blocks it while blocked itself.
|
|
1188
|
-
const r =
|
|
1189
|
-
t._open !== 0 ||
|
|
1190
|
-
blockedBy(t._nodes, t) ||
|
|
1191
|
-
(!!GlobalQueue._lanesBlocked && GlobalQueue._lanesBlocked(t));
|
|
1348
|
+
const r = t._open !== 0 || blockedBy(t._nodes, t) || !!lanesBlockedHook?.(t);
|
|
1192
1349
|
judge = prev;
|
|
1193
1350
|
return r;
|
|
1194
1351
|
}
|
|
@@ -1262,7 +1419,7 @@ function blockedBy(nodes, owner, own = false) {
|
|
|
1262
1419
|
// shows nothing — a mount the frame has not revealed — and holds
|
|
1263
1420
|
// nothing.
|
|
1264
1421
|
if (
|
|
1265
|
-
(r._type === EFFECT_RENDER ||
|
|
1422
|
+
((r._type === EFFECT_RENDER && r._statusFlags & STATUS_PENDING) ||
|
|
1266
1423
|
r._flags & REACTIVE_FRAME_READ ||
|
|
1267
1424
|
(r._config & CONFIG_REDERIVE && r._statusFlags === STATUS_PENDING)) &&
|
|
1268
1425
|
(s._gen === r._depGen || r._x?._error != null) &&
|
|
@@ -1283,10 +1440,7 @@ function blockedBy(nodes, owner, own = false) {
|
|
|
1283
1440
|
* everything under it, so a reader there is not on screen and holds
|
|
1284
1441
|
* nothing — boundaries.ts). */
|
|
1285
1442
|
function onScreen(r, t) {
|
|
1286
|
-
return (
|
|
1287
|
-
!(r._flags & REACTIVE_ZOMBIE && removalStagedBy(r, t)) &&
|
|
1288
|
-
!(GlobalQueue._hidden && GlobalQueue._hidden(r))
|
|
1289
|
-
);
|
|
1443
|
+
return !(r._flags & REACTIVE_ZOMBIE && removalStagedBy(r, t)) && !hiddenHook?.(r);
|
|
1290
1444
|
}
|
|
1291
1445
|
/** A15 (#3463): a reader whose removal is staged — a zombie, parked by a
|
|
1292
1446
|
* pass awaiting its commit — is still on screen and live for every hold,
|
|
@@ -1296,13 +1450,8 @@ function onScreen(r, t) {
|
|
|
1296
1450
|
* the nearest non-zombie ancestor. */
|
|
1297
1451
|
function removalStagedBy(r, t) {
|
|
1298
1452
|
let o = r;
|
|
1299
|
-
while (o
|
|
1300
|
-
return (
|
|
1301
|
-
o !== null &&
|
|
1302
|
-
(o._config & CONFIG_HELD) !== 0 &&
|
|
1303
|
-
o._x?._transaction != null &&
|
|
1304
|
-
txOf(o) === resolveTx(t)
|
|
1305
|
-
);
|
|
1453
|
+
while (o?._flags & REACTIVE_ZOMBIE) o = o._parent;
|
|
1454
|
+
return o?._config & CONFIG_HELD && o._x?._transaction != null && txOf(o) === resolveTx(t);
|
|
1306
1455
|
}
|
|
1307
1456
|
/** Unchanged passes this flush whose stale dependency tail awaits the
|
|
1308
1457
|
* flush's verdict (A30, #3469): trimmed when the flush commits, kept when
|
|
@@ -1355,10 +1504,26 @@ function list(n, t) {
|
|
|
1355
1504
|
function holdFrame(owner, t) {
|
|
1356
1505
|
for (let c = owner._firstChild; c !== null; c = c._nextSibling) {
|
|
1357
1506
|
if (c._x?._transaction?._lane) continue;
|
|
1358
|
-
if (c._fn !== undefined
|
|
1507
|
+
if (c._fn !== undefined) {
|
|
1508
|
+
if (!(c._config & CONFIG_HELD)) holdNode(c, t);
|
|
1509
|
+
c._config |= CONFIG_HELD_FRAME;
|
|
1510
|
+
}
|
|
1359
1511
|
holdFrame(c, t);
|
|
1360
1512
|
}
|
|
1361
1513
|
}
|
|
1514
|
+
/** The nodes under `owner` a transaction other than `t` holds (boundaries.ts:
|
|
1515
|
+
* what an earlier hold staged under a re-armed boundary). */
|
|
1516
|
+
function heldUnder(owner, t, fn) {
|
|
1517
|
+
for (let c = owner._firstChild; c !== null; c = c._nextSibling) {
|
|
1518
|
+
if ((c._config & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD && txOf(c) !== t) fn(c);
|
|
1519
|
+
heldUnder(c, t, fn);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
/** `n` leaves the hold it was staged in: its next pass publishes mainline. */
|
|
1523
|
+
function unhold(n) {
|
|
1524
|
+
n._x._transaction = null;
|
|
1525
|
+
n._config &= ~CONFIG_HELD;
|
|
1526
|
+
}
|
|
1362
1527
|
const dirtyQueue = {
|
|
1363
1528
|
_heap: new Array(2000).fill(undefined),
|
|
1364
1529
|
_marked: false,
|
|
@@ -1551,7 +1716,7 @@ class GlobalQueue {
|
|
|
1551
1716
|
// Lanes: an ended action body judges its guesses now that their passes
|
|
1552
1717
|
// have run (lanes.ts); a correction's re-derivations run in one more
|
|
1553
1718
|
// pure round — the parent's flights before the parent is judged below.
|
|
1554
|
-
if (
|
|
1719
|
+
if (laneCorrectionsHook?.()) runHeap(dirtyQueue, GlobalQueue._update);
|
|
1555
1720
|
const joined = flushTransaction;
|
|
1556
1721
|
flushTransaction = null;
|
|
1557
1722
|
// The frame's verdict is in: the guesses written since the last seam
|
|
@@ -1559,7 +1724,7 @@ class GlobalQueue {
|
|
|
1559
1724
|
// parents a nested lane to it), or are dropped — the frame commits, and
|
|
1560
1725
|
// there is nothing to be optimistic over. Their passes run next round;
|
|
1561
1726
|
// a parent landing at this very seam ends the lane before they do.
|
|
1562
|
-
|
|
1727
|
+
applyGuessesHook?.(joined);
|
|
1563
1728
|
// A lane's own flush (its flight landing, its reveal) parks nothing: the
|
|
1564
1729
|
// lane's work reached it per pass; the frame's plain writes commit.
|
|
1565
1730
|
// (Resolved: a guess over another transaction's guess merged them.)
|
|
@@ -1599,10 +1764,12 @@ class GlobalQueue {
|
|
|
1599
1764
|
if (
|
|
1600
1765
|
n._pendingValue !== NOT_PENDING &&
|
|
1601
1766
|
!(n._statusFlags & (STATUS_PENDING | STATUS_UNINITIALIZED)) &&
|
|
1602
|
-
n._equals &&
|
|
1603
|
-
n._equals(n._value, n._pendingValue) &&
|
|
1604
1767
|
n._x?._pendingFirstChild == null &&
|
|
1605
|
-
n._x?._pendingDisposal == null
|
|
1768
|
+
n._x?._pendingDisposal == null &&
|
|
1769
|
+
// The comparator last: a pass that created children (an object
|
|
1770
|
+
// owning a fresh async memo, #4111) must not have it read them.
|
|
1771
|
+
// A NotReady read there is a change (`isSame`).
|
|
1772
|
+
isSame(n, n._value, n._pendingValue)
|
|
1606
1773
|
) {
|
|
1607
1774
|
n._pendingValue = NOT_PENDING;
|
|
1608
1775
|
n._config &= ~CONFIG_STAGED;
|
|
@@ -1616,7 +1783,7 @@ class GlobalQueue {
|
|
|
1616
1783
|
if (n._config & CONFIG_STAGED) holdFrame(n, t);
|
|
1617
1784
|
}
|
|
1618
1785
|
pendingNodes.length = 0;
|
|
1619
|
-
|
|
1786
|
+
storeParkHook?.(t);
|
|
1620
1787
|
// (This flush's runs are stashed with `t` below — after the landings,
|
|
1621
1788
|
// so a `t` that lands at this very seam runs them first, ahead of
|
|
1622
1789
|
// the runs it held from earlier flushes.)
|
|
@@ -1626,13 +1793,17 @@ class GlobalQueue {
|
|
|
1626
1793
|
// through like the same read outside): it re-runs now, as the
|
|
1627
1794
|
// holder's verdict lane's work — before the lane seams, which reveal
|
|
1628
1795
|
// it (nothing commits this seam that could dispose it).
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1796
|
+
// (Verdict readers exist only with the verdict engine installed.)
|
|
1797
|
+
const zombies = deferredZombies.splice(0);
|
|
1798
|
+
if (observeFlightHook) {
|
|
1799
|
+
zombies.sort((a, b) => a._height - b._height);
|
|
1800
|
+
for (let i = 0; i < zombies.length; i++)
|
|
1801
|
+
if (
|
|
1802
|
+
(zombies[i]._flags & (REACTIVE_DISPOSED | REACTIVE_ZOMBIE)) === REACTIVE_ZOMBIE &&
|
|
1803
|
+
zombies[i]._config & CONFIG_VERDICT
|
|
1804
|
+
)
|
|
1805
|
+
recompute(zombies[i]);
|
|
1806
|
+
}
|
|
1636
1807
|
// The unchanged passes' tails stay linked (A30, #3469): their inputs
|
|
1637
1808
|
// are held, and the committed frame still derives from them — no trim.
|
|
1638
1809
|
} else {
|
|
@@ -1663,14 +1834,16 @@ class GlobalQueue {
|
|
|
1663
1834
|
// The passes that read this frame's stagings as the screen read held
|
|
1664
1835
|
// writes if it parked: the lane seam re-derives them on the committed
|
|
1665
1836
|
// world (a lane's runs wait this round).
|
|
1666
|
-
|
|
1667
|
-
|
|
1837
|
+
if (laneSeamsHook) {
|
|
1838
|
+
laneSeamsHook(t !== null ? stagedReaders : null);
|
|
1839
|
+
if (stagedReaders.length) stagedReaders.length = 0;
|
|
1840
|
+
}
|
|
1668
1841
|
this._queues = [[], []];
|
|
1669
1842
|
for (let k = transactions.length - 1; k >= 0; k--) {
|
|
1670
1843
|
const u = transactions[k];
|
|
1671
1844
|
if (blocked(u)) continue;
|
|
1672
1845
|
transactions.splice(k, 1);
|
|
1673
|
-
|
|
1846
|
+
endLanesHook?.(u);
|
|
1674
1847
|
land(u);
|
|
1675
1848
|
// A landing's commits can dispose a zombie a transaction judged above
|
|
1676
1849
|
// was blocked on (#3463: live "until the commit that disposes it") —
|
|
@@ -1679,12 +1852,13 @@ class GlobalQueue {
|
|
|
1679
1852
|
}
|
|
1680
1853
|
// The store folds the pending backings whose container nodes committed
|
|
1681
1854
|
// — this flush's, or a landing's (store/store.ts installs it).
|
|
1682
|
-
|
|
1855
|
+
storeCommitHook?.();
|
|
1683
1856
|
// Marks declared outside a transaction: the flush that carried them
|
|
1684
1857
|
// parked (something async below) — the window is the frame's, the marks
|
|
1685
1858
|
// are its transaction's; else they release at the seam, verdict-only,
|
|
1686
1859
|
// nothing to show (affects.ts).
|
|
1687
|
-
|
|
1860
|
+
const live = t && liveTx(t);
|
|
1861
|
+
releaseAmbientMarksHook?.(live);
|
|
1688
1862
|
// The effect phase: lane work first (displayed ahead of the frame), then
|
|
1689
1863
|
// this flush's runs, then the runs the landings held from earlier
|
|
1690
1864
|
// flushes — as one pass would have queued them (#3540: a shell
|
|
@@ -1692,18 +1866,17 @@ class GlobalQueue {
|
|
|
1692
1866
|
// write; #3528: a display-ahead swap before the count it is shown
|
|
1693
1867
|
// beside). A parked frame's runs wait with its transaction instead: the
|
|
1694
1868
|
// effect phase applies a committed frame.
|
|
1695
|
-
const
|
|
1696
|
-
const parked = u !== null;
|
|
1869
|
+
const parked = live !== null;
|
|
1697
1870
|
// Attribution hook: this flush found its transaction incomplete — its
|
|
1698
1871
|
// writes stay staged, its runs are stashed below. Before the effect
|
|
1699
1872
|
// phase (the lanes' display-ahead runs are the visible acknowledgers);
|
|
1700
1873
|
// `holdEnd` fires after it (`flush`).
|
|
1701
1874
|
if (parked && attrHooks !== null) {
|
|
1702
|
-
attrHooks.holdStart(
|
|
1875
|
+
attrHooks.holdStart(live);
|
|
1703
1876
|
holding = true;
|
|
1704
1877
|
}
|
|
1705
1878
|
for (let i = 0; i < 2; i++) {
|
|
1706
|
-
if (parked) append(
|
|
1879
|
+
if (parked) append(live._queues[i], own[i]);
|
|
1707
1880
|
// The plain flush — no reveal, no landing, not parked — has nothing to
|
|
1708
1881
|
// order around its own runs: they are the queue as they stand (the
|
|
1709
1882
|
// same array, not a copy; `run` takes it whole). Every other seam
|
|
@@ -1723,7 +1896,7 @@ class GlobalQueue {
|
|
|
1723
1896
|
}
|
|
1724
1897
|
// Boundaries: readers that settled, landed or died this flush are
|
|
1725
1898
|
// dropped; a fallback with none left reveals next round.
|
|
1726
|
-
|
|
1899
|
+
boundarySeamHook?.();
|
|
1727
1900
|
}
|
|
1728
1901
|
run(type) {
|
|
1729
1902
|
const effects = this._queues[type - 1];
|
|
@@ -1755,7 +1928,7 @@ class GlobalQueue {
|
|
|
1755
1928
|
* first: a fallback catches what is not ready under it, and the root
|
|
1756
1929
|
* never hears of it. */
|
|
1757
1930
|
notify(node, mask, flags, error) {
|
|
1758
|
-
if (
|
|
1931
|
+
if (catchHook?.(node, flags & mask, error)) return true;
|
|
1759
1932
|
if (mask & STATUS_PENDING) {
|
|
1760
1933
|
if (flags & STATUS_PENDING) {
|
|
1761
1934
|
if (_enforceLoadingBoundary) _hitUnhandledAsync = true;
|
|
@@ -1792,8 +1965,11 @@ class GlobalQueue {
|
|
|
1792
1965
|
function reruns(u) {
|
|
1793
1966
|
for (let i = 0; i < u._reruns.length; i++) {
|
|
1794
1967
|
const r = u._reruns[i];
|
|
1795
|
-
|
|
1796
|
-
|
|
1968
|
+
// An errored reader's display is its error (D-39): the landing gives it
|
|
1969
|
+
// nothing to re-derive, and a re-run would be a read that retries the
|
|
1970
|
+
// errored source (#3945).
|
|
1971
|
+
if (r._flags & REACTIVE_DISPOSED || r._statusFlags & STATUS_ERROR) continue;
|
|
1972
|
+
if (r._flags & REACTIVE_FRAME_READ || r._config & CONFIG_VERDICT) {
|
|
1797
1973
|
r._flags &= ~REACTIVE_FRAME_READ;
|
|
1798
1974
|
r._modified = false;
|
|
1799
1975
|
// Its frame is replaced by the re-derivation: a run one of its
|
|
@@ -1811,7 +1987,7 @@ function land(u) {
|
|
|
1811
1987
|
// (`_nodes` still lists them).
|
|
1812
1988
|
if (attrHooks !== null) attrHooks.transitionSettled(u);
|
|
1813
1989
|
reruns(u);
|
|
1814
|
-
if (u._marks !== null)
|
|
1990
|
+
if (u._marks !== null) releaseMarksHook(u._marks);
|
|
1815
1991
|
// Old children die in the commits (cleanups first), then the stashed
|
|
1816
1992
|
// effects run ahead of this flush's own. A node a lane took over since
|
|
1817
1993
|
// (a guess written over the staged truth) is the lane's to land.
|
|
@@ -1821,6 +1997,10 @@ function land(u) {
|
|
|
1821
1997
|
n._x._transaction = null;
|
|
1822
1998
|
commitPendingNode(n);
|
|
1823
1999
|
}
|
|
2000
|
+
// A cleanup those commits ran that wrote a node `u` still held joined
|
|
2001
|
+
// `u`, which has left `transactions`: the write is this landing's, and
|
|
2002
|
+
// the next flush must not park in it (#4089).
|
|
2003
|
+
if (flushTransaction === u) flushTransaction = null;
|
|
1824
2004
|
releaseQueues(u);
|
|
1825
2005
|
}
|
|
1826
2006
|
/** The held runs join this flush's effect phase after its own runs (the
|
|
@@ -1940,8 +2120,20 @@ function commitPendingNode(n) {
|
|
|
1940
2120
|
// commit beneath a flight publishes its inputs (A15 reveal corollary,
|
|
1941
2121
|
// #3305): a reveal of it must observe, not show the pre-flight value.
|
|
1942
2122
|
const inFlight = n._pendingValue === NOT_PENDING && (c._statusFlags & STATUS_PENDING) !== 0;
|
|
1943
|
-
|
|
1944
|
-
|
|
2123
|
+
const err = n._config & CONFIG_STAGED_ERROR;
|
|
2124
|
+
// An answer commits: the error it replaces leaves the display (D-41).
|
|
2125
|
+
if (!inFlight && c._x) c._x._shownError = undefined;
|
|
2126
|
+
n._config = inFlight ? (n._config & -1048833) | CONFIG_INPUTS_PUBLISHED : n._config & -3147137;
|
|
2127
|
+
// D-41: the staged answer is an error — published now, when a value would
|
|
2128
|
+
// show, through the ordinary error path (`stageError`). Its readers first
|
|
2129
|
+
// see it in the cycle after this seam: a tracked re-read there is not a
|
|
2130
|
+
// retry (`read`), or a boundary re-derived by it would retry it forever.
|
|
2131
|
+
if (err) {
|
|
2132
|
+
const e = n._pendingValue;
|
|
2133
|
+
n._pendingValue = NOT_PENDING;
|
|
2134
|
+
n._time = clock + 1;
|
|
2135
|
+
notifyStatus(c, STATUS_ERROR, e);
|
|
2136
|
+
} else if (n._pendingValue !== NOT_PENDING) {
|
|
1945
2137
|
n._value = n._pendingValue;
|
|
1946
2138
|
n._pendingValue = NOT_PENDING;
|
|
1947
2139
|
// A node born staged (recompute) initializes at this commit.
|
|
@@ -2432,13 +2624,15 @@ function getOwner() {
|
|
|
2432
2624
|
/**
|
|
2433
2625
|
* Low-level: registers `fn` as a disposal callback on the current owner.
|
|
2434
2626
|
* Most code should use `onCleanup()` from `solid-js`, which adds dev-mode
|
|
2435
|
-
* checks. `cleanup()` is the unchecked primitive used by internals
|
|
2627
|
+
* checks. `cleanup()` is the unchecked primitive used by internals; `owner`
|
|
2628
|
+
* (internal) targets a specific owner's disposal list instead of the ambient
|
|
2629
|
+
* one.
|
|
2436
2630
|
*/
|
|
2437
|
-
function cleanup(fn) {
|
|
2438
|
-
if (!
|
|
2439
|
-
if (!
|
|
2440
|
-
else if (Array.isArray(
|
|
2441
|
-
else
|
|
2631
|
+
function cleanup(fn, owner = context) {
|
|
2632
|
+
if (!owner) return fn;
|
|
2633
|
+
if (!owner._disposal) owner._disposal = fn;
|
|
2634
|
+
else if (Array.isArray(owner._disposal)) owner._disposal.push(fn);
|
|
2635
|
+
else owner._disposal = [owner._disposal, fn];
|
|
2442
2636
|
return fn;
|
|
2443
2637
|
}
|
|
2444
2638
|
/**
|
|
@@ -2475,12 +2669,18 @@ function createOwner(options) {
|
|
|
2475
2669
|
const parent = context;
|
|
2476
2670
|
const transparent = options?.transparent ?? false;
|
|
2477
2671
|
// Prod and observe boilerplates (see core.ts computed()). The observe
|
|
2478
|
-
// literal carries the `_name`
|
|
2479
|
-
// component label (`owner._name = "<App>"
|
|
2480
|
-
//
|
|
2672
|
+
// literal carries the `_name` and `_loc` slots the rendering layer fills
|
|
2673
|
+
// with the component label and JSX site (`owner._name = "<App>"`,
|
|
2674
|
+
// `owner._loc = "src/App.tsx:3:5"`) — slots, so labelling a root is a
|
|
2675
|
+
// plain store rather than a shape fork between labelled and plain roots.
|
|
2481
2676
|
const owner = {
|
|
2482
2677
|
id: inheritId(options, transparent, parent),
|
|
2483
2678
|
_config: transparent ? CONFIG_TRANSPARENT : 0,
|
|
2679
|
+
// Present from birth: disposeChildren marks death here on roots too, and
|
|
2680
|
+
// a first store of a missing property is a shape transition plus a
|
|
2681
|
+
// properties-store allocation per root — on a 100×4 table teardown that
|
|
2682
|
+
// was 12 KB and most of disposal's time (2026-10-17).
|
|
2683
|
+
_flags: REACTIVE_NONE,
|
|
2484
2684
|
_root: true,
|
|
2485
2685
|
_parentComputed: parent?._root ? parent._parentComputed : parent,
|
|
2486
2686
|
_firstChild: null,
|
|
@@ -2492,7 +2692,8 @@ function createOwner(options) {
|
|
|
2492
2692
|
_x: null,
|
|
2493
2693
|
_parent: parent,
|
|
2494
2694
|
dispose: disposeRootSelf,
|
|
2495
|
-
_name: undefined
|
|
2695
|
+
_name: undefined,
|
|
2696
|
+
_loc: undefined
|
|
2496
2697
|
};
|
|
2497
2698
|
if (parent && parent._config & CONFIG_CHILDREN_FORBIDDEN) {
|
|
2498
2699
|
emitDiagnostic({
|
|
@@ -2544,11 +2745,14 @@ function createOwner(options) {
|
|
|
2544
2745
|
* const detached = runWithOwner(null, () => createRoot(d => d));
|
|
2545
2746
|
* ```
|
|
2546
2747
|
*
|
|
2547
|
-
* @description https://
|
|
2748
|
+
* @description https://v2.solidjs.com/reference/solid-js/advanced/owner-introspection/create-root
|
|
2548
2749
|
*/
|
|
2549
2750
|
function createRoot(init, options) {
|
|
2550
2751
|
const owner = createOwner(options);
|
|
2551
|
-
|
|
2752
|
+
// A bound function, not an arrow: an arrow would share this scope, which
|
|
2753
|
+
// holds `init`, so keeping `dispose` would keep `init` and all it closes
|
|
2754
|
+
// over alive (#4095).
|
|
2755
|
+
return runWithOwner(owner, () => init(disposeRootSelf.bind(owner, true)));
|
|
2552
2756
|
}
|
|
2553
2757
|
|
|
2554
2758
|
/** The store's shared slot-node release handler (store/store.ts installs it
|
|
@@ -2581,12 +2785,14 @@ function unlinkSubs(link) {
|
|
|
2581
2785
|
// read. The settle path runs this same last-one-out check when that
|
|
2582
2786
|
// observer releases (the untracked-read dormancy sweep guards on
|
|
2583
2787
|
// pending identically).
|
|
2788
|
+
// During a pass's teardown of its unfinished frame, the release waits
|
|
2789
|
+
// for the pass to end (`deferredReleases`, #4142).
|
|
2584
2790
|
const c = dep;
|
|
2585
2791
|
c._fn &&
|
|
2586
2792
|
c._config & CONFIG_AUTO_DISPOSE &&
|
|
2587
2793
|
!(c._flags & REACTIVE_ZOMBIE) &&
|
|
2588
2794
|
!(c._statusFlags & STATUS_PENDING) &&
|
|
2589
|
-
unobserved(c);
|
|
2795
|
+
(deferredReleases ? deferredReleases.push(c) : unobserved(c));
|
|
2590
2796
|
}
|
|
2591
2797
|
}
|
|
2592
2798
|
return nextDep;
|
|
@@ -2615,6 +2821,17 @@ function clearDeps(el) {
|
|
|
2615
2821
|
el._deps = null;
|
|
2616
2822
|
el._depsTail = null;
|
|
2617
2823
|
}
|
|
2824
|
+
/**
|
|
2825
|
+
* Last-one-out releases held back while a pass tears down the frame of an
|
|
2826
|
+
* earlier pass that never committed (#4142). The pass is about to rebuild
|
|
2827
|
+
* the same children, which read the same lazy nodes again. Released on the
|
|
2828
|
+
* spot, a lazy async memo whose only reader was the old frame's child memo
|
|
2829
|
+
* was torn down, re-ran on the new child's read, went pending, and the cycle
|
|
2830
|
+
* repeated forever. `recompute` collects them around that teardown and
|
|
2831
|
+
* releases what is still unobserved when the pass ends.
|
|
2832
|
+
*/
|
|
2833
|
+
let deferredReleases = null;
|
|
2834
|
+
const deferReleases = list => (deferredReleases = list);
|
|
2618
2835
|
function unobserved(el) {
|
|
2619
2836
|
deleteFromHeap(el, dirtyQueue);
|
|
2620
2837
|
clearDeps(el);
|
|
@@ -2816,6 +3033,43 @@ function releaseSettledDependents(el) {
|
|
|
2816
3033
|
// (releaseSettledDependents shape): identity holders can sit below an
|
|
2817
3034
|
// intermediate whose own error state has since been scrubbed or replaced
|
|
2818
3035
|
// (e.g. an error boundary's tree node).
|
|
3036
|
+
/** D-41: an error a pass of a held node found is an answer of
|
|
3037
|
+
* that frame — it ends the wait like a value and shows when a value would.
|
|
3038
|
+
* Staged like one: `_pendingValue` carries it (CONFIG_STAGED_ERROR), readers
|
|
3039
|
+
* in the hold derive from it (`serve` throws it), and the commit publishes
|
|
3040
|
+
* it through the ordinary error path (`commitPendingNode` → `notifyStatus`),
|
|
3041
|
+
* dependents included. Not for a lane pass, nor a node with no shown value
|
|
3042
|
+
* (born held — its error has no frame to wait behind): those publish now,
|
|
3043
|
+
* as before. Returns whether the error was staged. */
|
|
3044
|
+
function stageError(el, error, inPass) {
|
|
3045
|
+
// Held, or — for a pass's own throw — derived from a staging of a flush
|
|
3046
|
+
// (REACTIVE_STAGED_READ is this pass's; a landing after the pass cannot
|
|
3047
|
+
// read it, the flush it read a staging of has committed or parked since).
|
|
3048
|
+
// Staged whether or not the flush has joined a hold yet: a pass that runs
|
|
3049
|
+
// before its async sibling joins is the seam's to decide, like a value
|
|
3050
|
+
// (#4109). A flush that parks nothing publishes it at its commit (S-38).
|
|
3051
|
+
if (
|
|
3052
|
+
!(
|
|
3053
|
+
el._config & CONFIG_HELD ||
|
|
3054
|
+
(inPass && el._flags & REACTIVE_STAGED_READ && globalQueue._running)
|
|
3055
|
+
) ||
|
|
3056
|
+
passLane
|
|
3057
|
+
)
|
|
3058
|
+
return false;
|
|
3059
|
+
// The node answered (an error ends the wait — no longer pending, nor
|
|
3060
|
+
// blocked); a shown error stays its display until the commit.
|
|
3061
|
+
clearPendingSources(el);
|
|
3062
|
+
if (el._x) el._x._blocked = false;
|
|
3063
|
+
if (!((el._statusFlags &= STATUS_ERROR | STATUS_UNINITIALIZED) & STATUS_ERROR))
|
|
3064
|
+
setPendingError(el);
|
|
3065
|
+
el._pendingValue = error instanceof StatusError ? error : new StatusError(el, error);
|
|
3066
|
+
el._config |= CONFIG_STAGED | CONFIG_STAGED_ERROR;
|
|
3067
|
+
// Readers parked on the flight re-run and derive from the staging; the
|
|
3068
|
+
// verdict readers the walk wakes need a flush (#4106).
|
|
3069
|
+
settlePendingSource(el);
|
|
3070
|
+
schedule();
|
|
3071
|
+
return true;
|
|
3072
|
+
}
|
|
2819
3073
|
function settleErroredDependents(el, error) {
|
|
2820
3074
|
let scheduled = false;
|
|
2821
3075
|
const visited = new Set();
|
|
@@ -3062,7 +3316,11 @@ function handleAsync(
|
|
|
3062
3316
|
el._time = clock;
|
|
3063
3317
|
return;
|
|
3064
3318
|
}
|
|
3065
|
-
|
|
3319
|
+
// D-41: a held node's error is staged (`stageError`). A derived store's
|
|
3320
|
+
// readers learn its error through the family, not the derive's
|
|
3321
|
+
// dependents: it publishes now.
|
|
3322
|
+
if (stillPending || setter || !stageError(el, error))
|
|
3323
|
+
notifyStatus(el, stillPending ? STATUS_PENDING : STATUS_ERROR, error);
|
|
3066
3324
|
// A NotReady rejection is a landing into another pending source. The
|
|
3067
3325
|
// rejected flight will never settle its self entry, so transfer ownership
|
|
3068
3326
|
// after notifyStatus has propagated the replacement source.
|
|
@@ -3080,7 +3338,7 @@ function handleAsync(
|
|
|
3080
3338
|
// skip this stale async result — the upcoming flush will recompute the node
|
|
3081
3339
|
// with the new value, creating a fresh Promise that supersedes this one.
|
|
3082
3340
|
if (el._flags & REACTIVE_DIRTY) return;
|
|
3083
|
-
const wasUninitialized =
|
|
3341
|
+
const wasUninitialized = el._statusFlags & STATUS_UNINITIALIZED;
|
|
3084
3342
|
landStatus(el);
|
|
3085
3343
|
// Attribution hook: lets the engine snapshot state before the landing
|
|
3086
3344
|
// branches, so it can tell whether the plain path's setSignal committed a
|
|
@@ -3093,22 +3351,27 @@ function handleAsync(
|
|
|
3093
3351
|
handleError(error);
|
|
3094
3352
|
return;
|
|
3095
3353
|
}
|
|
3354
|
+
// A store derive's landing commits through its setter: unheld, the
|
|
3355
|
+
// shown error leaves now; held, at the derive's commit.
|
|
3356
|
+
if (!(el._config & CONFIG_HELD)) el._x._shownError = undefined;
|
|
3096
3357
|
if (wasUninitialized) landStatus(el, true);
|
|
3358
|
+
// Attribution hook: this path landed through the setter (derived
|
|
3359
|
+
// stores, projections), which does not go through setSignal, so the
|
|
3360
|
+
// plain branch's asyncEnd never ran and the flight stayed open — the
|
|
3361
|
+
// next flight counted a committed answer as abandoned (#3947).
|
|
3362
|
+
// direct=true: asyncEnd finalizes either way and stamps an async write
|
|
3363
|
+
// only if the commit moved `_value`/`_time` or parked `_pendingValue`.
|
|
3364
|
+
// A store setter does not move this node's `_value`, so no stamp is
|
|
3365
|
+
// recorded and waterfall chaining through derived stores stays
|
|
3366
|
+
// unmeasured. Outside the try (#2883 — see attribution-hooks.ts).
|
|
3367
|
+
if (attrHooks !== null) attrHooks.asyncEnd(el, undefined, value, true);
|
|
3097
3368
|
} else {
|
|
3098
3369
|
// CARVE 2: the override-covered landing (hold + A18 supersession) and the
|
|
3099
3370
|
// lane-routed landing (derived override, lane effect queue) went with
|
|
3100
|
-
// the optimistic engine; every landing is the plain setSignal. A
|
|
3101
|
-
//
|
|
3102
|
-
|
|
3103
|
-
el
|
|
3104
|
-
if (wasUninitialized && t && (t = liveTx(t))) holdNode(el, t);
|
|
3105
|
-
try {
|
|
3106
|
-
setSignal(el, () => value);
|
|
3107
|
-
} catch (e) {
|
|
3108
|
-
// Same containment as above: setSignal's comparator throw is the only
|
|
3109
|
-
// pre-commit failure here, and there is no user callsite to throw to.
|
|
3110
|
-
notifyStatus(el, STATUS_ERROR, e);
|
|
3111
|
-
}
|
|
3371
|
+
// the optimistic engine; every landing is the plain setSignal. A node
|
|
3372
|
+
// already held stages its answer into that hold (`setSignal`). Its
|
|
3373
|
+
// comparator throw is contained there (`isSame`, A3).
|
|
3374
|
+
setSignal(el, () => value);
|
|
3112
3375
|
// Attribution hook: this path landed through setSignal, whose write
|
|
3113
3376
|
// hook already saw any committed change — direct=false lets the engine
|
|
3114
3377
|
// reclassify that write as an async landing iff it actually committed.
|
|
@@ -3153,15 +3416,14 @@ function handleAsync(
|
|
|
3153
3416
|
// close registration uses the ambient owner; and DEFERRED (the flattening
|
|
3154
3417
|
// path — a thenable resolved to an iterable in a later microtask), where
|
|
3155
3418
|
// there is no caller to serve and no ambient owner: sync-settled steps
|
|
3156
|
-
// write through asyncWrite, and close registration
|
|
3157
|
-
//
|
|
3158
|
-
//
|
|
3159
|
-
|
|
3160
|
-
const consumeIterator = (source, registerClose) => {
|
|
3419
|
+
// write through asyncWrite, and close registration targets `owner` (el
|
|
3420
|
+
// itself) directly. Returns whether a sync answer landed (first yield or
|
|
3421
|
+
// empty completion) — meaningful only in the live posture.
|
|
3422
|
+
const consumeIterator = (source, owner) => {
|
|
3161
3423
|
const it = source[Symbol.asyncIterator]();
|
|
3162
3424
|
let hadValue = false;
|
|
3163
3425
|
let completed = false;
|
|
3164
|
-
let initialRead = !
|
|
3426
|
+
let initialRead = !owner;
|
|
3165
3427
|
const close = () => {
|
|
3166
3428
|
if (completed) return;
|
|
3167
3429
|
completed = true;
|
|
@@ -3170,7 +3432,7 @@ function handleAsync(
|
|
|
3170
3432
|
if (isThenable(returned)) returned.then(undefined, () => {});
|
|
3171
3433
|
} catch {}
|
|
3172
3434
|
};
|
|
3173
|
-
|
|
3435
|
+
cleanup(close, owner);
|
|
3174
3436
|
// Flight-identity cancellation (#3122): the registration above is the
|
|
3175
3437
|
// owner-death backstop; the teardown slot fires at the _inFlight release
|
|
3176
3438
|
// sites so supersede stops this stream immediately.
|
|
@@ -3178,8 +3440,15 @@ function handleAsync(
|
|
|
3178
3440
|
// Release check before each next pull: an unobserved lazy node must tear
|
|
3179
3441
|
// down (its close above runs via disposal, closing the iterator) instead
|
|
3180
3442
|
// of pumping the stream forever with zero subscribers (#2935).
|
|
3443
|
+
// Later pulls run from an async continuation with no compute catch above
|
|
3444
|
+
// them: a throw invoking the protocol (next(), the step's then) settles
|
|
3445
|
+
// as this flight's error instead of escaping as a rejection (#4112).
|
|
3181
3446
|
const iterateOrRelease = () => {
|
|
3182
|
-
|
|
3447
|
+
try {
|
|
3448
|
+
if (!settleAutodispose()) iterate();
|
|
3449
|
+
} catch (e) {
|
|
3450
|
+
handleError(e);
|
|
3451
|
+
}
|
|
3183
3452
|
};
|
|
3184
3453
|
const iterate = () => {
|
|
3185
3454
|
let syncResult,
|
|
@@ -3264,7 +3533,7 @@ function handleAsync(
|
|
|
3264
3533
|
// stream itself rather than settling on the iterable object. One level
|
|
3265
3534
|
// only: A+ `then` already collapses nested thenables, so the resolved
|
|
3266
3535
|
// value is never itself a thenable.
|
|
3267
|
-
const flattenIfIterable = (value,
|
|
3536
|
+
const flattenIfIterable = (value, owner) => {
|
|
3268
3537
|
let innerIterator = false;
|
|
3269
3538
|
if (typeof value === "object" && value !== null) {
|
|
3270
3539
|
untrack(() => {
|
|
@@ -3272,8 +3541,9 @@ function handleAsync(
|
|
|
3272
3541
|
});
|
|
3273
3542
|
}
|
|
3274
3543
|
if (!innerIterator) return false;
|
|
3275
|
-
|
|
3276
|
-
|
|
3544
|
+
syncValue = undefined;
|
|
3545
|
+
const landed = consumeIterator(value, owner);
|
|
3546
|
+
if (!owner) liveLanded = landed;
|
|
3277
3547
|
return true;
|
|
3278
3548
|
};
|
|
3279
3549
|
if (thenable) {
|
|
@@ -3281,29 +3551,32 @@ function handleAsync(
|
|
|
3281
3551
|
rejected = false,
|
|
3282
3552
|
syncError,
|
|
3283
3553
|
isSync = true;
|
|
3284
|
-
// Close registration for the flattening path. Consumption starts in a
|
|
3285
|
-
// microtask where the ambient owner is gone (or worse, someone else's),
|
|
3286
|
-
// so `cleanup()` can't be used — the close targets el's disposal list
|
|
3287
|
-
// directly, exactly where a live cleanup() during this recompute would
|
|
3288
|
-
// have put it. Only a flight that actually flattens becomes
|
|
3289
|
-
// disposal-bearing.
|
|
3290
|
-
const registerDeferredClose = fn => {
|
|
3291
|
-
if (!el._disposal) el._disposal = fn;
|
|
3292
|
-
else if (Array.isArray(el._disposal)) el._disposal.push(fn);
|
|
3293
|
-
else el._disposal = [el._disposal, fn];
|
|
3294
|
-
};
|
|
3295
3554
|
watchAsyncTail(el, result).then(
|
|
3296
3555
|
v => {
|
|
3297
3556
|
if (isSync) {
|
|
3298
3557
|
syncValue = v;
|
|
3299
3558
|
resolved = true;
|
|
3300
|
-
} else
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3559
|
+
} else {
|
|
3560
|
+
// Flattening consumes the stream as the value: each yield lands
|
|
3561
|
+
// through asyncWrite under this flight's identity; the first one
|
|
3562
|
+
// clears pending exactly like a plain promise resolution would.
|
|
3563
|
+
// Disposed nodes never start a pump (their disposal list already
|
|
3564
|
+
// ran, so nothing could close the iterator). Consumption starts in
|
|
3565
|
+
// a microtask where the ambient owner is gone, so the close
|
|
3566
|
+
// registers on el's own disposal list. Iterator setup here has no
|
|
3567
|
+
// compute catch above it: a protocol throw settles as this
|
|
3568
|
+
// flight's error (#4112).
|
|
3569
|
+
try {
|
|
3570
|
+
if (
|
|
3571
|
+
el._x?._inFlight === result &&
|
|
3572
|
+
!(el._flags & REACTIVE_DISPOSED) &&
|
|
3573
|
+
flattenIfIterable(v, el)
|
|
3574
|
+
)
|
|
3575
|
+
return;
|
|
3576
|
+
asyncWrite(v);
|
|
3577
|
+
} catch (e) {
|
|
3578
|
+
handleError(e);
|
|
3579
|
+
}
|
|
3307
3580
|
settleAutodispose();
|
|
3308
3581
|
}
|
|
3309
3582
|
},
|
|
@@ -3407,6 +3680,8 @@ function notifyStatus(el, status, error, blockStatus) {
|
|
|
3407
3680
|
// (CARVE 2: the lane assignment went with the engine.)
|
|
3408
3681
|
{
|
|
3409
3682
|
if (status === STATUS_PENDING && pendingSource) {
|
|
3683
|
+
// D-41: a re-ask of a shown error keeps it the display (`_shownError`).
|
|
3684
|
+
if (el._statusFlags & STATUS_ERROR) el._x._shownError = el._x._error;
|
|
3410
3685
|
addPendingSource(el, pendingSource);
|
|
3411
3686
|
el._statusFlags = STATUS_PENDING | (el._statusFlags & STATUS_UNINITIALIZED);
|
|
3412
3687
|
// Preserve the current source on this propagation so readers park on
|
|
@@ -3486,7 +3761,8 @@ function propagateStatus(el, status, error, downstreamBlockStatus) {
|
|
|
3486
3761
|
if (
|
|
3487
3762
|
passLane === null &&
|
|
3488
3763
|
(sub._config & (CONFIG_HELD | CONFIG_OVERRIDE)) === CONFIG_HELD &&
|
|
3489
|
-
globalQueue._running
|
|
3764
|
+
globalQueue._running &&
|
|
3765
|
+
!freshHook?.(sub, pendingSource._time === clock)
|
|
3490
3766
|
)
|
|
3491
3767
|
joinFuture(txOf(sub));
|
|
3492
3768
|
return;
|
|
@@ -3501,7 +3777,7 @@ function propagateStatus(el, status, error, downstreamBlockStatus) {
|
|
|
3501
3777
|
// the dependent's pending is the lane's own flight (`laneStage`).
|
|
3502
3778
|
{
|
|
3503
3779
|
// (No answer: a pending propagation leaves a written guess a guess.)
|
|
3504
|
-
if (passLane !== null)
|
|
3780
|
+
if (laneStageHook && passLane !== null) laneStageHook(sub, passLane, true, el);
|
|
3505
3781
|
else {
|
|
3506
3782
|
queuePendingNode(sub);
|
|
3507
3783
|
// A15 (#3443): pending propagates onto a held memo without
|
|
@@ -3543,8 +3819,29 @@ GlobalQueue._update = el => {
|
|
|
3543
3819
|
// (REACTIVE_LANE_DIRTY — a lane shows ahead of any park): it runs now, as
|
|
3544
3820
|
// the lane's work, for the reveal this seam (#3463).
|
|
3545
3821
|
else if (el._flags & REACTIVE_ZOMBIE && !(el._flags & REACTIVE_LANE_DIRTY)) deferZombie(el);
|
|
3546
|
-
|
|
3822
|
+
// A lane frame's member (parked by a lane pass, #3698) follows mainline
|
|
3823
|
+
// writes only: a lane's dirty on it is cancelled — the lane's apply retires
|
|
3824
|
+
// it, and the pass that replaced it already decided what that screen shows
|
|
3825
|
+
// (#4137: a `<Show>` branch withdrawn by its own lane re-ran on a falsy
|
|
3826
|
+
// condition).
|
|
3827
|
+
// (Lanes exist only with the lane engine installed: folded away without.)
|
|
3828
|
+
else if (laneStageHook && el._flags & REACTIVE_ZOMBIE && laneFrameMember(el)) {
|
|
3829
|
+
deleteFromHeap(el, dirtyQueue);
|
|
3830
|
+
el._flags &= -65540;
|
|
3831
|
+
} else recompute(el);
|
|
3547
3832
|
};
|
|
3833
|
+
/** The zombie's frame was parked by lane work: the nearest non-zombie owner
|
|
3834
|
+
* (the one whose pass parked it) carries a lane's derived value — or, an
|
|
3835
|
+
* effect (no lane value: `laneStage`), is listed in a lane (an `insert()`
|
|
3836
|
+
* hole's render effect). */
|
|
3837
|
+
function laneFrameMember(el) {
|
|
3838
|
+
let o = el._parent;
|
|
3839
|
+
while (o._flags & REACTIVE_ZOMBIE) o = o._parent;
|
|
3840
|
+
return (
|
|
3841
|
+
(o._config & (CONFIG_OVERRIDE | CONFIG_GUESS)) === CONFIG_OVERRIDE ||
|
|
3842
|
+
(o._type && o._x?._transaction?._lane)
|
|
3843
|
+
);
|
|
3844
|
+
}
|
|
3548
3845
|
GlobalQueue._dispose = disposeChildren;
|
|
3549
3846
|
const PRIMITIVE_IN_FORBIDDEN_SCOPE_MESSAGE =
|
|
3550
3847
|
"[PRIMITIVE_IN_FORBIDDEN_SCOPE] Cannot create reactive primitives inside createTrackedEffect or owner-backed onSettled";
|
|
@@ -3579,6 +3876,10 @@ const REACTIVE_WRITE_IN_OWNED_SCOPE_REFRESH_MESSAGE =
|
|
|
3579
3876
|
let tracking = false;
|
|
3580
3877
|
let context = null;
|
|
3581
3878
|
let snapshotCaptureActive = false;
|
|
3879
|
+
/** The lane at the root of the running chain of first passes: the lane whose
|
|
3880
|
+
* pass created the chain's first node (a mount), inherited by every first
|
|
3881
|
+
* pass nested under it, whose seats are mainline (`recompute`, #4166). */
|
|
3882
|
+
let laneRoot;
|
|
3582
3883
|
let snapshotSources = null;
|
|
3583
3884
|
function ownerInSnapshotScope(owner) {
|
|
3584
3885
|
while (owner) {
|
|
@@ -3638,6 +3939,7 @@ function recompute(el, create = false) {
|
|
|
3638
3939
|
// §12d: any recompute can clean a marked subscriber — invalidate skips.
|
|
3639
3940
|
bumpNotifyEpoch();
|
|
3640
3941
|
const isEffect = el._type;
|
|
3942
|
+
let released;
|
|
3641
3943
|
// Lanes (plan sec. 28): the seat of a pass is its node's. A node carrying a lane's
|
|
3642
3944
|
// derived value runs as the lane's whoever dirtied it — a sync write, a
|
|
3643
3945
|
// boundary reset, a frame rerun — so its children and its result are the
|
|
@@ -3653,18 +3955,27 @@ function recompute(el, create = false) {
|
|
|
3653
3955
|
// derivation's pass into the lane (`read`); a leaf's never moves.
|
|
3654
3956
|
// Restored at the end, after this pass's staging and runs have been
|
|
3655
3957
|
// routed.
|
|
3958
|
+
// Lanes exist only with the lane engine installed (`laneStageHook`): a
|
|
3959
|
+
// bundle without it seats every pass on mainline, and folds the seat away.
|
|
3656
3960
|
const prevLane = passLane;
|
|
3961
|
+
// The mount a first pass belongs to: its creator's lane, or the one its
|
|
3962
|
+
// creator's own mount came from — a render effect a verdict lane's mount's
|
|
3963
|
+
// content creates is that mount's too (#4166). Lanes only, like the seat.
|
|
3964
|
+
const prevMount = laneRoot;
|
|
3965
|
+
const mount = laneStageHook && (laneRoot = create && (prevLane || prevMount));
|
|
3657
3966
|
setPassLane(
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
el.
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3967
|
+
!laneStageHook
|
|
3968
|
+
? null
|
|
3969
|
+
: (el._flags & REACTIVE_LANE_DIRTY ||
|
|
3970
|
+
(el._config & (CONFIG_OVERRIDE | CONFIG_GUESS)) === CONFIG_OVERRIDE) &&
|
|
3971
|
+
el._x?._transaction?._lane
|
|
3972
|
+
? el._x._transaction
|
|
3973
|
+
: // Loose `!=`: false with no creator lane (undefined == null).
|
|
3974
|
+
create &&
|
|
3975
|
+
prevLane?._parent._verdict != prevLane &&
|
|
3976
|
+
creatorPass(context)?._flags & REACTIVE_RECOMPUTING_DEPS
|
|
3977
|
+
? prevLane
|
|
3978
|
+
: null
|
|
3668
3979
|
);
|
|
3669
3980
|
// Attribution hook: fired before this run touches the dep list — `_deps`
|
|
3670
3981
|
// still holds the previous run's links (the subscriptions that could have
|
|
@@ -3691,17 +4002,30 @@ function recompute(el, create = false) {
|
|
|
3691
4002
|
// joins all the same. Not a render effect (rule 3): it is the frame.
|
|
3692
4003
|
// Re-run outside its transaction's flush it publishes mainline — the
|
|
3693
4004
|
// value it staged there is superseded (`frameRead` remembers it for the
|
|
3694
|
-
// landing). A frame born into the transaction (
|
|
3695
|
-
// A29
|
|
3696
|
-
// swap (ruling
|
|
4005
|
+
// landing). A frame born into the transaction (`bornInto`: never run,
|
|
4006
|
+
// A29, or mounted by a pass of it still awaiting its commit, #4010) keeps
|
|
4007
|
+
// staging: live writes keep it current, it effects at the swap (ruling
|
|
4008
|
+
// A).
|
|
4009
|
+
// A pending flight is the transition's own work (#3884). A probe on an
|
|
4010
|
+
// earlier pass left CONFIG_VERDICT, and a frame read here would serve the
|
|
4011
|
+
// committed screen, settle the flight and drop the hold — a superseding
|
|
4012
|
+
// write's verdict would depend on that probe. Cleared for this pass; a
|
|
4013
|
+
// probe in the body re-stamps it after the plain reads.
|
|
4014
|
+
if (el._statusFlags & STATUS_PENDING) el._config &= ~CONFIG_VERDICT;
|
|
3697
4015
|
if (el._config & CONFIG_HELD) {
|
|
3698
4016
|
const tx = txOf(el);
|
|
3699
4017
|
// Held by a blocked lane: the frame joins nothing — a lane never holds
|
|
3700
4018
|
// a sync write (#3460). The pass is the lane's if it reads the lane's
|
|
3701
4019
|
// world (`read`), and has left it otherwise.
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
4020
|
+
// An answered deferred node's re-pass is a lagging question (D-40):
|
|
4021
|
+
// it joins nothing, so the write that reached it commits mainline.
|
|
4022
|
+
if (tx._lane || el._config & CONFIG_LAGS);
|
|
4023
|
+
else if (isEffect !== EFFECT_RENDER && !(el._config & CONFIG_VERDICT))
|
|
4024
|
+
joinPassTx(tx, freshHook?.(el, el._statusFlags & STATUS_UNINITIALIZED));
|
|
4025
|
+
else if (
|
|
4026
|
+
tx !== flushTransaction &&
|
|
4027
|
+
!(el._statusFlags & STATUS_UNINITIALIZED || el._config & CONFIG_HELD_FRAME)
|
|
4028
|
+
) {
|
|
3705
4029
|
// Published mainline, it is not held: the frame this pass builds is
|
|
3706
4030
|
// replaced on the spot (#3404) — this flush's commit retires the
|
|
3707
4031
|
// committed frame the held pass had parked (`commitPendingNode`),
|
|
@@ -3717,8 +4041,20 @@ function recompute(el, create = false) {
|
|
|
3717
4041
|
// frame parked before it stays parked. Otherwise they ARE the frame:
|
|
3718
4042
|
// park them until this pass commits. Tracked effects run after the seam
|
|
3719
4043
|
// and own nothing to park.
|
|
3720
|
-
if (isEffect === EFFECT_TRACKED || el._config & CONFIG_STAGED)
|
|
3721
|
-
|
|
4044
|
+
if (isEffect === EFFECT_TRACKED || el._config & CONFIG_STAGED) {
|
|
4045
|
+
// A lazy node only those children read is released when this pass
|
|
4046
|
+
// ends, if the pass doesn't read it again (#4142).
|
|
4047
|
+
const prev = deferredReleases;
|
|
4048
|
+
deferReleases((released = []));
|
|
4049
|
+
disposeChildren(el);
|
|
4050
|
+
deferReleases(prev);
|
|
4051
|
+
} else if (el._firstChild !== null || el._disposal !== null) parkChildren(el);
|
|
4052
|
+
// An error a pass staged (D-41) is replaced by this pass's answer: a
|
|
4053
|
+
// value, a flight, or an error of its own (#4109, #4145).
|
|
4054
|
+
if (el._config & CONFIG_STAGED_ERROR) {
|
|
4055
|
+
el._pendingValue = NOT_PENDING;
|
|
4056
|
+
el._config ^= CONFIG_STAGED_ERROR;
|
|
4057
|
+
}
|
|
3722
4058
|
clearSignals(el);
|
|
3723
4059
|
}
|
|
3724
4060
|
const wasUninitialized = !!(el._statusFlags & STATUS_UNINITIALIZED);
|
|
@@ -3735,6 +4071,8 @@ function recompute(el, create = false) {
|
|
|
3735
4071
|
// settlePendingSource walk never runs for a landing that was preempted
|
|
3736
4072
|
// (#3181).
|
|
3737
4073
|
const wasPendingSource = el._x?._pendingSources?.has(el);
|
|
4074
|
+
// D-41: this pass's error is staged, not published (`stageError`).
|
|
4075
|
+
let stagedErr = false;
|
|
3738
4076
|
// A19 exc. 2: a `refresh()` re-asks the question already answered — quiet
|
|
3739
4077
|
// for the verdict — unless the question in flight is a new one, which the
|
|
3740
4078
|
// re-ask does not launder (a poll during a quiet confirm stays quiet).
|
|
@@ -3749,19 +4087,20 @@ function recompute(el, create = false) {
|
|
|
3749
4087
|
// frame, and the splice/reawaken paths key off it. A zombie reruns for
|
|
3750
4088
|
// live writes until the commit that disposes it, so every per-pass wipe
|
|
3751
4089
|
// — here, the finally below, updateIfNecessary — carries it (#3543).
|
|
3752
|
-
el._flags = REACTIVE_RECOMPUTING_DEPS | (el._flags & REACTIVE_ZOMBIE);
|
|
4090
|
+
el._flags = REACTIVE_RECOMPUTING_DEPS | (el._flags & (REACTIVE_ZOMBIE | REACTIVE_REASK));
|
|
3753
4091
|
el._time = clock;
|
|
3754
|
-
// The pass's previous value: its staging, else
|
|
3755
|
-
//
|
|
4092
|
+
// The pass's previous value: its staging, else a lane node's lane value
|
|
4093
|
+
// when it has one (lanes.ts), else the committed one.
|
|
3756
4094
|
let value =
|
|
3757
4095
|
el._pendingValue !== NOT_PENDING
|
|
3758
4096
|
? el._pendingValue
|
|
3759
|
-
: el._config & CONFIG_OVERRIDE
|
|
4097
|
+
: el._config & CONFIG_OVERRIDE && el._x._lane !== NOT_PENDING
|
|
3760
4098
|
? el._x._lane
|
|
3761
4099
|
: el._value;
|
|
3762
4100
|
let oldHeight = el._height;
|
|
3763
4101
|
let missedWake = false;
|
|
3764
|
-
// L2: did this pass read the future (REACTIVE_JOINED, set by read)
|
|
4102
|
+
// L2: did this pass read the future (REACTIVE_JOINED, set by read) — or,
|
|
4103
|
+
// a lane's mount, a staging of this flush (#3851, #4166)?
|
|
3765
4104
|
let joined = false;
|
|
3766
4105
|
let prevTracking = tracking;
|
|
3767
4106
|
let prevStrictRead = false;
|
|
@@ -3806,11 +4145,15 @@ function recompute(el, create = false) {
|
|
|
3806
4145
|
// retry can actually run.
|
|
3807
4146
|
parkLoadingWindow(el, e);
|
|
3808
4147
|
} else {
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
4148
|
+
// D-41: a pass of a held node, or one that read a staging of this
|
|
4149
|
+
// flush, stages its error (`stageError`).
|
|
4150
|
+
if (notReady || !(stagedErr = stageError(el, e, true))) {
|
|
4151
|
+
if (notReady) ext(el)._blocked = true;
|
|
4152
|
+
notifyStatus(el, notReady ? STATUS_PENDING : STATUS_ERROR, e);
|
|
4153
|
+
// The replacement source is fully propagated now. If no new flight
|
|
4154
|
+
// re-owned self, retire the superseded flight and its dependent copies.
|
|
4155
|
+
if (notReady && wasPendingSource && !el._x?._inFlight) settlePendingSource(el);
|
|
4156
|
+
}
|
|
3814
4157
|
// A re-park drops what the earlier pass carried (#3456): a source this
|
|
3815
4158
|
// pass no longer reaches — its branch switched, or a fresh flight
|
|
3816
4159
|
// replaced the inputs' pending with its own — stays copied onto
|
|
@@ -3818,14 +4161,16 @@ function recompute(el, create = false) {
|
|
|
3818
4161
|
// stops at this node (nothing left to retire) before it finds them. A
|
|
3819
4162
|
// dependent then waits forever on a flight it has no path to. The
|
|
3820
4163
|
// re-park twin of the unchanged-value recovery sweep below; dependents
|
|
3821
|
-
// with another path keep the source (retryReaches).
|
|
3822
|
-
|
|
4164
|
+
// with another path keep the source (retryReaches). A staged error
|
|
4165
|
+
// answered what the pass carried: the same.
|
|
4166
|
+
if ((notReady || stagedErr) && outgoingPendingSources)
|
|
3823
4167
|
for (const source of outgoingPendingSources)
|
|
3824
4168
|
if (source !== el && !el._x?._pendingSources?.has(source))
|
|
3825
4169
|
settlePendingSource(el, source);
|
|
3826
4170
|
}
|
|
3827
4171
|
} finally {
|
|
3828
4172
|
tracking = prevTracking;
|
|
4173
|
+
if (laneStageHook) laneRoot = prevMount;
|
|
3829
4174
|
strictRead = prevStrictRead;
|
|
3830
4175
|
// Consume the missed-wake latch (#3037, set by insertSubs): a dep write
|
|
3831
4176
|
// landed beneath this pass on a link it had already validated. The wipe
|
|
@@ -3833,10 +4178,11 @@ function recompute(el, create = false) {
|
|
|
3833
4178
|
// (markNode(c) in read()) marks the running node as part of ordinary
|
|
3834
4179
|
// bookkeeping, and those marks are correctly discarded here.
|
|
3835
4180
|
missedWake = (el._flags & REACTIVE_MISSED_WAKE) !== 0;
|
|
3836
|
-
joined = (el._flags & REACTIVE_JOINED
|
|
3837
|
-
// A verdict reader is one for as long as it probes
|
|
3838
|
-
//
|
|
3839
|
-
|
|
4181
|
+
joined = !!(el._flags & (mount ? REACTIVE_JOINED | REACTIVE_STAGED_READ : REACTIVE_JOINED));
|
|
4182
|
+
// A verdict reader is one for as long as it probes, or reads a memo
|
|
4183
|
+
// that does (`read`): a pass that did neither (REACTIVE_PROBED, the same
|
|
4184
|
+
// bit) is an ordinary derivation again.
|
|
4185
|
+
el._config = (el._config & ~CONFIG_VERDICT) | (el._flags & REACTIVE_PROBED);
|
|
3840
4186
|
// REACTIVE_DISPOSED survives too (#3621): the pass may have disposed its
|
|
3841
4187
|
// own owner (a memo calling its root's `dispose()`, a cleanup doing so
|
|
3842
4188
|
// #3601/#3606), and `disposeChildren` set the flag on this node
|
|
@@ -3855,6 +4201,7 @@ function recompute(el, create = false) {
|
|
|
3855
4201
|
REACTIVE_PROBE_UNANSWERED)) |
|
|
3856
4202
|
(create ? el._flags & REACTIVE_SNAPSHOT_STALE : 0);
|
|
3857
4203
|
context = oldcontext;
|
|
4204
|
+
if (released) for (const node of released) releaseIfSettledUnobserved(node);
|
|
3858
4205
|
// A19 exc. 2: a pass that went pending on a `refresh()` re-asks the
|
|
3859
4206
|
// question already answered — quiet for the verdict; any other pending
|
|
3860
4207
|
// pass is a new question. Settled, there is nothing to classify.
|
|
@@ -3883,15 +4230,15 @@ function recompute(el, create = false) {
|
|
|
3883
4230
|
// seat that read none of the lane's world has left it: its result is the
|
|
3884
4231
|
// frame's (a derivation whose branch no longer reaches the guess). A
|
|
3885
4232
|
// guess is written, not derived — it never leaves this way.
|
|
3886
|
-
let lane = passLane;
|
|
4233
|
+
let lane = !laneStageHook ? null : passLane;
|
|
3887
4234
|
// Listed before its staging, a pending pass included (the lane's own
|
|
3888
4235
|
// flight is the lane's); false: the pass left the lane (lanes.ts).
|
|
3889
|
-
const errored = !!el._x?._error;
|
|
3890
|
-
if (lane !== null) {
|
|
3891
|
-
if (
|
|
4236
|
+
const errored = !!el._x?._error || stagedErr;
|
|
4237
|
+
if (laneStageHook && lane !== null) {
|
|
4238
|
+
if (laneStageHook(el, lane, create, errored)) setPassLane(lane);
|
|
3892
4239
|
else lane = null;
|
|
3893
4240
|
}
|
|
3894
|
-
if (!
|
|
4241
|
+
if (!errored) {
|
|
3895
4242
|
// Observe-tier fan-in (HUGE_FAN_IN): the validated prefix [_deps.._depsTail]
|
|
3896
4243
|
// IS this pass's distinct sources — count it here rather than per link. A
|
|
3897
4244
|
// begin/end bracket around the pass plus a per-link increment measured
|
|
@@ -3918,17 +4265,9 @@ function recompute(el, create = false) {
|
|
|
3918
4265
|
: el._config & CONFIG_OVERRIDE && el._x._lane !== NOT_PENDING
|
|
3919
4266
|
? el._x._lane
|
|
3920
4267
|
: el._value;
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
(!isEffect && wasUninitialized) || !el._equals || !el._equals(compareValue, value);
|
|
3925
|
-
} catch (e) {
|
|
3926
|
-
// A throwing user comparator is an error of this node's computation.
|
|
3927
|
-
// Route it through the same status path as a compute-phase throw so
|
|
3928
|
-
// error boundaries contain it; otherwise it unwinds the scheduler
|
|
3929
|
-
// flush, bypassing every boundary and wedging the queue (#2837).
|
|
3930
|
-
notifyStatus(el, STATUS_ERROR, e);
|
|
3931
|
-
}
|
|
4268
|
+
// A throwing comparator is contained inside `isSame` (A3, #2837; #4111).
|
|
4269
|
+
const valueChanged =
|
|
4270
|
+
(!isEffect && wasUninitialized) || !!el._x?._shownError || !isSame(el, compareValue, value);
|
|
3932
4271
|
// A committed derived change becomes a cause for this node's subscribers,
|
|
3933
4272
|
// chaining their attribution through this node to the root write.
|
|
3934
4273
|
if (attrHooks !== null) {
|
|
@@ -3972,7 +4311,7 @@ function recompute(el, create = false) {
|
|
|
3972
4311
|
!create &&
|
|
3973
4312
|
lane === null &&
|
|
3974
4313
|
el._config & CONFIG_GUESS &&
|
|
3975
|
-
|
|
4314
|
+
laneOutcomeHook(el, value, errored)
|
|
3976
4315
|
);
|
|
3977
4316
|
else if (errored);
|
|
3978
4317
|
else if (valueChanged) {
|
|
@@ -4006,9 +4345,7 @@ function recompute(el, create = false) {
|
|
|
4006
4345
|
create
|
|
4007
4346
|
? !(
|
|
4008
4347
|
(flushTransaction !== null || passTx !== null) &&
|
|
4009
|
-
(joined ||
|
|
4010
|
-
(prevLane && el._flags & REACTIVE_STAGED_READ) ||
|
|
4011
|
-
(creatorPass(oldcontext)?._flags ?? 0) & REACTIVE_JOINED)
|
|
4348
|
+
(joined || creatorPass(oldcontext)?._flags & REACTIVE_JOINED)
|
|
4012
4349
|
)
|
|
4013
4350
|
: isEffect && el._pendingValue === NOT_PENDING
|
|
4014
4351
|
) {
|
|
@@ -4035,6 +4372,14 @@ function recompute(el, create = false) {
|
|
|
4035
4372
|
// derives from the future (`read`), an untracked one throws
|
|
4036
4373
|
// (A19 exc. 1).
|
|
4037
4374
|
el._statusFlags |= STATUS_UNINITIALIZED;
|
|
4375
|
+
// A boundary that has not shown content catches it: its fallback
|
|
4376
|
+
// waits for the commit (#3540). (The pass's own status clear above
|
|
4377
|
+
// dropped what the read recorded.) Not when the flush has joined
|
|
4378
|
+
// the hold and the pass is its own (not a verdict lane's mount,
|
|
4379
|
+
// #3851): the mount is part of the hold, and the boundary appears
|
|
4380
|
+
// at its commit, its fallback never seen (D-13, U-3) — a `<Show>`
|
|
4381
|
+
// mounting it over a landing included.
|
|
4382
|
+
freshHook?.(el, !mount && flushTransaction);
|
|
4038
4383
|
}
|
|
4039
4384
|
}
|
|
4040
4385
|
// insertSubs only walks _subs (no scheduling of its own), so a
|
|
@@ -4068,16 +4413,16 @@ function recompute(el, create = false) {
|
|
|
4068
4413
|
}
|
|
4069
4414
|
// A first pass that went pending where the born-held arm would have held
|
|
4070
4415
|
// its value (it read a held node, or a verdict lane's mount read a staging,
|
|
4071
|
-
// #3851) is
|
|
4072
|
-
//
|
|
4073
|
-
//
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4416
|
+
// #3851) is held like it: its answer is the hold's, and the hold waits
|
|
4417
|
+
// for it (A29, #3800). A boundary that has not shown content catches it
|
|
4418
|
+
// and it is held all the same — its answer reveals no earlier than the
|
|
4419
|
+
// hold's commit (D-12) — but the hold does not wait for it: its only
|
|
4420
|
+
// readers are behind the fallback (`_hidden`; F-4, #3540). A boundary
|
|
4421
|
+
// re-armed by `on` owns what it collects outright (D-14): not held.
|
|
4422
|
+
if (create && lane === null && joined && el._statusFlags & STATUS_PENDING) {
|
|
4423
|
+
const t = flushTransaction ?? passTx;
|
|
4424
|
+
if (t && !t._lane && freshHook?.(el) !== CAUGHT_REARMED) holdNode(el, t);
|
|
4425
|
+
}
|
|
4081
4426
|
// Dependencies are the committed frame's until it is replaced (A30, #3410):
|
|
4082
4427
|
// a pass that staged its value leaves the previous pass's tail linked for
|
|
4083
4428
|
// `commitPendingNode` to trim, so a write to a dependency the committed
|
|
@@ -4188,12 +4533,12 @@ function computed(fn, options) {
|
|
|
4188
4533
|
const loading = options !== null && typeof options === "object" && "loadingValue" in options;
|
|
4189
4534
|
// Two literals, one per tier, selected at build time (the observe flag is
|
|
4190
4535
|
// a literal after replacement; the untaken branch is dead code). The observe
|
|
4191
|
-
// literal is the prod literal plus its `_name`
|
|
4192
|
-
// boilerplate, because a post-construction `self._name = …` forces a
|
|
4536
|
+
// literal is the prod literal plus its `_name` and `_loc` slots — slots in
|
|
4537
|
+
// the boilerplate, because a post-construction `self._name = …` forces a
|
|
4193
4538
|
// hidden-class transition and an out-of-object property store on EVERY node
|
|
4194
4539
|
// (measured: the whole of the observe tier's creation overhead). Keep the
|
|
4195
4540
|
// two in sync — the dist artifact test pins observe's key set to prod's
|
|
4196
|
-
// plus
|
|
4541
|
+
// plus the slots.
|
|
4197
4542
|
const self = {
|
|
4198
4543
|
id: inheritId(options, transparent, context),
|
|
4199
4544
|
_config:
|
|
@@ -4235,6 +4580,7 @@ function computed(fn, options) {
|
|
|
4235
4580
|
// The slot is always present (hidden class); plumbing leaves it
|
|
4236
4581
|
// unset, which `ownerPath` skips.
|
|
4237
4582
|
_name: options?._plumbing ? undefined : (options?.name ?? "computed"),
|
|
4583
|
+
_loc: options?.loc,
|
|
4238
4584
|
// Observe: the verdict windows this pass entered (verdict.ts) — a
|
|
4239
4585
|
// slot, so the stamp is never a write after construction.
|
|
4240
4586
|
_devWindows: 0
|
|
@@ -4261,13 +4607,13 @@ function ext(el) {
|
|
|
4261
4607
|
_pendingFirstChild: null,
|
|
4262
4608
|
_pendingDisposal: null,
|
|
4263
4609
|
_transaction: null,
|
|
4264
|
-
_bornIn: null,
|
|
4265
4610
|
_reask: false,
|
|
4266
4611
|
_flushed: NOT_PENDING,
|
|
4267
4612
|
_flushedAt: -1,
|
|
4268
4613
|
_q: 0,
|
|
4269
4614
|
_lane: NOT_PENDING,
|
|
4270
|
-
_marks: 0
|
|
4615
|
+
_marks: 0,
|
|
4616
|
+
_shownError: undefined
|
|
4271
4617
|
});
|
|
4272
4618
|
}
|
|
4273
4619
|
/** A tracked read of a held node (the reader derives from its transaction's
|
|
@@ -4283,8 +4629,13 @@ function ext(el) {
|
|
|
4283
4629
|
* boundary's, not the tick's (A29's boundary exemption, #3540). */
|
|
4284
4630
|
function joinPass(c, el) {
|
|
4285
4631
|
c._flags |= REACTIVE_JOINED;
|
|
4632
|
+
// A new memo's first pass is the hold's work (born held) but not the
|
|
4633
|
+
// tick's: a render effect that reads it decides — under a boundary that
|
|
4634
|
+
// catches it, the boundary's; otherwise the tick joins the hold (#3540:
|
|
4635
|
+
// the reader's position, not where the memo was created). Its catch, if
|
|
4636
|
+
// any, is at its staging.
|
|
4286
4637
|
if (el._statusFlags & STATUS_UNINITIALIZED || c._type !== EFFECT_RENDER)
|
|
4287
|
-
joinPassTx(txOf(el),
|
|
4638
|
+
joinPassTx(txOf(el), (!c._type && c._statusFlags & STATUS_UNINITIALIZED) || freshHook?.(c));
|
|
4288
4639
|
}
|
|
4289
4640
|
/** A15's stale reader (shared-hole and reveal corollaries): a render effect
|
|
4290
4641
|
* reading a node held by a transaction that is not the flush's. It is served
|
|
@@ -4292,11 +4643,11 @@ function joinPass(c, el) {
|
|
|
4292
4643
|
* re-derived after that transaction's landing (`_reruns`,
|
|
4293
4644
|
* REACTIVE_FRAME_READ) so the frame catches up with it once it is the
|
|
4294
4645
|
* committed world. Not a frame born into that transaction (held and never
|
|
4295
|
-
* committed — created by one of its held passes, A29): that
|
|
4296
|
-
* transaction's values and keeps staging. Not of a flight
|
|
4297
|
-
* already visible (CONFIG_INPUTS_PUBLISHED, #3305): its
|
|
4298
|
-
* would tear against them, so the reveal observes the
|
|
4299
|
-
* per pass.
|
|
4646
|
+
* committed — created by one of its held passes, A29; `bornInto`): that
|
|
4647
|
+
* one reads its transaction's values and keeps staging. Not of a flight
|
|
4648
|
+
* whose inputs are already visible (CONFIG_INPUTS_PUBLISHED, #3305): its
|
|
4649
|
+
* committed value would tear against them, so the reveal observes the
|
|
4650
|
+
* flight instead. Once per pass.
|
|
4300
4651
|
*
|
|
4301
4652
|
* Lane work is a stale reader too (maintainer, 2026-10-01: a lane sees the
|
|
4302
4653
|
* screen plus its own guesses). A held write is held because showing it
|
|
@@ -4321,10 +4672,11 @@ function frameRead(c, el) {
|
|
|
4321
4672
|
// read the screen. Lane work with no committed value yet reads a flight
|
|
4322
4673
|
// as a mount's memo does: it enters, and the boundary it mounts catches
|
|
4323
4674
|
// the pending (#3540).
|
|
4324
|
-
|
|
4675
|
+
// (Verdict readers and lanes exist only with their engines installed.)
|
|
4676
|
+
const verdict = observeFlightHook && c._config & CONFIG_VERDICT;
|
|
4677
|
+
const lane = laneStageHook && passLane !== null;
|
|
4325
4678
|
if (
|
|
4326
|
-
(
|
|
4327
|
-
(c._statusFlags & STATUS_UNINITIALIZED && el._statusFlags & STATUS_PENDING)) &&
|
|
4679
|
+
(!lane || (c._statusFlags & STATUS_UNINITIALIZED && el._statusFlags & STATUS_PENDING)) &&
|
|
4328
4680
|
!verdict &&
|
|
4329
4681
|
(c._type !== EFFECT_RENDER || el._config & CONFIG_INPUTS_PUBLISHED)
|
|
4330
4682
|
)
|
|
@@ -4333,14 +4685,22 @@ function frameRead(c, el) {
|
|
|
4333
4685
|
// Lane work and verdict readers are never the transaction's pass, whichever
|
|
4334
4686
|
// flush runs it: a node T holds re-enters T when it re-runs (`recompute`'s
|
|
4335
4687
|
// head), and the pass may then read a guess and become the lane's.
|
|
4336
|
-
if (
|
|
4337
|
-
(t === flushTransaction && passLane === null && !verdict) ||
|
|
4338
|
-
(c._statusFlags & STATUS_UNINITIALIZED && c._config & CONFIG_HELD && txOf(c) === t)
|
|
4339
|
-
)
|
|
4340
|
-
return false;
|
|
4688
|
+
if ((t === flushTransaction && !lane && !verdict) || bornInto(c, t)) return false;
|
|
4341
4689
|
staleReader(c, t);
|
|
4342
4690
|
return true;
|
|
4343
4691
|
}
|
|
4692
|
+
/** A node born into transaction `t`: held by it and never committed (A29,
|
|
4693
|
+
* ruling A) — uninitialized (born held), or a member of the frame one of its
|
|
4694
|
+
* held passes built (CONFIG_HELD_FRAME). Its branch exists only in `t`: it
|
|
4695
|
+
* has no screen to read, so it reads `t`'s values, whichever flush runs it,
|
|
4696
|
+
* and keeps staging (#4010). */
|
|
4697
|
+
function bornInto(c, t) {
|
|
4698
|
+
return (
|
|
4699
|
+
(c._statusFlags & STATUS_UNINITIALIZED || c._config & CONFIG_HELD_FRAME) &&
|
|
4700
|
+
c._config & CONFIG_HELD &&
|
|
4701
|
+
txOf(c) === t
|
|
4702
|
+
);
|
|
4703
|
+
}
|
|
4344
4704
|
/** The computed whose pass a creation under `owner` belongs to (a root's is
|
|
4345
4705
|
* its nearest computed ancestor's), or null outside every pass. */
|
|
4346
4706
|
function creatorPass(owner) {
|
|
@@ -4412,6 +4772,7 @@ function createEffectNode(fn, effectFn, errorFn, type, options) {
|
|
|
4412
4772
|
_type: type,
|
|
4413
4773
|
_x: null,
|
|
4414
4774
|
_name: options?.name ?? "effect",
|
|
4775
|
+
_loc: options?.loc,
|
|
4415
4776
|
_devWindows: 0
|
|
4416
4777
|
};
|
|
4417
4778
|
// Effects dispatch status through the SHARED notifier (statusNotifierOf,
|
|
@@ -4463,7 +4824,7 @@ function setupComputedNode(self, options) {
|
|
|
4463
4824
|
if (context) linkChild(context, self);
|
|
4464
4825
|
DEV.hooks.onOwner?.(self);
|
|
4465
4826
|
if (parent) self._height = parent._height + 1;
|
|
4466
|
-
|
|
4827
|
+
wireExternalSourceHook?.(self);
|
|
4467
4828
|
!options?.lazy && recompute(self, true);
|
|
4468
4829
|
if (snapshotCaptureActive && !options?.lazy) {
|
|
4469
4830
|
if (!(self._statusFlags & STATUS_PENDING) && !(self._config & CONFIG_NO_SNAPSHOT)) {
|
|
@@ -4475,7 +4836,7 @@ function setupComputedNode(self, options) {
|
|
|
4475
4836
|
}
|
|
4476
4837
|
function signal(v, options) {
|
|
4477
4838
|
// Prod and observe boilerplates — see computed(). The observe literal adds
|
|
4478
|
-
// `_name` and `_owner` (the creating owner, stamped by registerGraph for
|
|
4839
|
+
// `_name`, `_loc` and `_owner` (the creating owner, stamped by registerGraph for
|
|
4479
4840
|
// createSignal nodes so ownerPath can locate signal subjects; null here,
|
|
4480
4841
|
// and staying null on internal signals — one shape either way).
|
|
4481
4842
|
const s = {
|
|
@@ -4491,6 +4852,7 @@ function signal(v, options) {
|
|
|
4491
4852
|
_notifiedAt: -1,
|
|
4492
4853
|
_x: null,
|
|
4493
4854
|
_name: options?.name ?? "signal",
|
|
4855
|
+
_loc: options?.loc,
|
|
4494
4856
|
_owner: null
|
|
4495
4857
|
};
|
|
4496
4858
|
s._internal = false;
|
|
@@ -4514,7 +4876,8 @@ function isEqual(a, b) {
|
|
|
4514
4876
|
* object, no NodeExtension, no post-construction expandos. `ownedWrite` is
|
|
4515
4877
|
* baked in: the setter carries the owned-scope guard; node-level writes are
|
|
4516
4878
|
* the store's notification machinery. The observe literal adds the label
|
|
4517
|
-
*
|
|
4879
|
+
* and location slots (the store relabels `store.<key>` and stamps the
|
|
4880
|
+
* store's `loc` when the engine is installed). */
|
|
4518
4881
|
function slotSignal(v, equals, host, key, acc) {
|
|
4519
4882
|
const s = {
|
|
4520
4883
|
_equals: equals,
|
|
@@ -4532,6 +4895,7 @@ function slotSignal(v, equals, host, key, acc) {
|
|
|
4532
4895
|
px: undefined,
|
|
4533
4896
|
pxv: undefined,
|
|
4534
4897
|
_name: "signal",
|
|
4898
|
+
_loc: undefined,
|
|
4535
4899
|
_owner: null
|
|
4536
4900
|
};
|
|
4537
4901
|
s._internal = false;
|
|
@@ -4568,7 +4932,11 @@ function exitCallback() {
|
|
|
4568
4932
|
}
|
|
4569
4933
|
/**
|
|
4570
4934
|
* Dev-only: > 0 while owner teardown runs cleanups (`_disposal` entries and
|
|
4571
|
-
* effect-returned cleanups — owner.ts), for the same reason.
|
|
4935
|
+
* effect-returned cleanups — owner.ts), for the same reason. The owned-scope
|
|
4936
|
+
* write guard (setSignal, markRefresh, the store setter) stands down here
|
|
4937
|
+
* too: a cleanup is teardown, not part of whichever computation is disposing
|
|
4938
|
+
* its owner (a list pass removing a row, a branch switching away), so its
|
|
4939
|
+
* writes are imperative like an effect callback's (#4165). Kept apart from
|
|
4572
4940
|
* `callbackDepth` because its sites cannot use try/finally (the frame would
|
|
4573
4941
|
* survive into prod): a throwing cleanup leaves it raised, and dev.ts resets
|
|
4574
4942
|
* it on the next microtask, which teardown — synchronous — never spans.
|
|
@@ -4595,6 +4963,16 @@ function setStrictRead(v) {
|
|
|
4595
4963
|
* Use `untrack` inside a memo or effect when you need to read a signal once
|
|
4596
4964
|
* without making the surrounding computation depend on its future changes.
|
|
4597
4965
|
*
|
|
4966
|
+
* `untrack` stops dependency tracking, not async settlement. Reading an
|
|
4967
|
+
* uninitialized async source still suspends the owning computation; it runs
|
|
4968
|
+
* again when that source first resolves. Afterward, the untracked read is a
|
|
4969
|
+
* snapshot and does not subscribe the computation to later changes.
|
|
4970
|
+
*
|
|
4971
|
+
* If the first read must return immediately, give the async source a
|
|
4972
|
+
* `loadingValue` before reading it with `untrack`. `isPending` and `latest`
|
|
4973
|
+
* inspect updates to an existing value; neither supplies a missing initial
|
|
4974
|
+
* value.
|
|
4975
|
+
*
|
|
4598
4976
|
* Pass a `strictReadLabel` string to enable a dev-mode warning: any reactive
|
|
4599
4977
|
* read inside `fn` that isn't inside a nested tracking scope will log a
|
|
4600
4978
|
* warning naming the label.
|
|
@@ -4608,17 +4986,12 @@ function setStrictRead(v) {
|
|
|
4608
4986
|
* log(snapshot);
|
|
4609
4987
|
* }
|
|
4610
4988
|
* );
|
|
4989
|
+
*
|
|
4990
|
+
* const color = createMemo<string>(() => fetchColor(), { loadingValue: "gray" });
|
|
4991
|
+
* const initialColor = untrack(color); // "gray" while the fetch is unresolved
|
|
4611
4992
|
* ```
|
|
4612
4993
|
*/
|
|
4613
4994
|
function untrack(fn, strictReadLabel) {
|
|
4614
|
-
if (
|
|
4615
|
-
GlobalQueue._externalUntrack === null &&
|
|
4616
|
-
!tracking &&
|
|
4617
|
-
!strictRead &&
|
|
4618
|
-
!strictReadLabel &&
|
|
4619
|
-
asyncTailFlights === 0
|
|
4620
|
-
)
|
|
4621
|
-
return fn();
|
|
4622
4995
|
const prevTracking = tracking;
|
|
4623
4996
|
const prevStrictRead = strictRead;
|
|
4624
4997
|
tracking = false;
|
|
@@ -4627,7 +5000,7 @@ function untrack(fn, strictReadLabel) {
|
|
|
4627
5000
|
untrackDepth++;
|
|
4628
5001
|
}
|
|
4629
5002
|
try {
|
|
4630
|
-
if (
|
|
5003
|
+
if (externalUntrackHook) return externalUntrackHook(fn);
|
|
4631
5004
|
return fn();
|
|
4632
5005
|
} finally {
|
|
4633
5006
|
tracking = prevTracking;
|
|
@@ -4699,7 +5072,7 @@ function prepareComputed(comp, refresh) {
|
|
|
4699
5072
|
* False means the reader derives from the staged value.
|
|
4700
5073
|
*/
|
|
4701
5074
|
function readerSeesCommitted(el, c) {
|
|
4702
|
-
return
|
|
5075
|
+
return !c || el._pendingValue === NOT_PENDING || c._config & CONFIG_CHILDREN_FORBIDDEN;
|
|
4703
5076
|
}
|
|
4704
5077
|
/** A28 — set when a node is staged (queuePendingNode) OUTSIDE a flush;
|
|
4705
5078
|
* cleared when the next flush begins. The read sites test this one module
|
|
@@ -4778,6 +5151,7 @@ function pullComputed(owner, c) {
|
|
|
4778
5151
|
if (height >= c._height && owner._parent !== c) c._height = height + 1;
|
|
4779
5152
|
}
|
|
4780
5153
|
function read(el) {
|
|
5154
|
+
DEV.hooks.onRead?.(el);
|
|
4781
5155
|
if (asyncTailFlights !== 0 && !tracking && untrackDepth === 0)
|
|
4782
5156
|
checkPostAwaitRead(
|
|
4783
5157
|
el,
|
|
@@ -4828,7 +5202,7 @@ function read(el) {
|
|
|
4828
5202
|
if (!c || el._pendingValue === NOT_PENDING || c._config & CONFIG_CHILDREN_FORBIDDEN)
|
|
4829
5203
|
return el._value;
|
|
4830
5204
|
// (A plain signal is never uninitialized — the carve-out is `serve`'s.)
|
|
4831
|
-
if (c._config & CONFIG_VERDICT && stagedScreen(c)) return el._value;
|
|
5205
|
+
if (observeFlightHook && c._config & CONFIG_VERDICT && stagedScreen(c)) return el._value;
|
|
4832
5206
|
stagedRead(c);
|
|
4833
5207
|
return el._pendingValue;
|
|
4834
5208
|
}
|
|
@@ -4844,7 +5218,15 @@ function read(el) {
|
|
|
4844
5218
|
if (c) {
|
|
4845
5219
|
if (tracking) {
|
|
4846
5220
|
link(el, c);
|
|
4847
|
-
if (owner._fn)
|
|
5221
|
+
if (owner._fn) {
|
|
5222
|
+
pullComputed(owner, c);
|
|
5223
|
+
// F-6 (#4050): a memo over a verdict reader's value derives from a
|
|
5224
|
+
// display-ahead read — a verdict reader from this pass's end, as if
|
|
5225
|
+
// it probed inline: it reads held values as the screen and shows the
|
|
5226
|
+
// verdict now. Not an effect: a render effect is a frame reader
|
|
5227
|
+
// already, and a user effect's posture is its own reads'.
|
|
5228
|
+
if (!c._type) c._flags |= owner._config & CONFIG_VERDICT;
|
|
5229
|
+
}
|
|
4848
5230
|
}
|
|
4849
5231
|
// L2, as in the fast block above (tracked or not). A node born into the
|
|
4850
5232
|
// future (uninitialized) has no committed value: every reader derives
|
|
@@ -4861,8 +5243,9 @@ function read(el) {
|
|
|
4861
5243
|
}
|
|
4862
5244
|
// Lanes: a lane's node (after the pull — the node is current). NOT_PENDING
|
|
4863
5245
|
// falls through: lane work's read of a pending member throws like any.
|
|
4864
|
-
|
|
4865
|
-
|
|
5246
|
+
// (CONFIG_OVERRIDE is the lane engine's: no engine, no lane's node.)
|
|
5247
|
+
if (laneReadHook && el._config & CONFIG_OVERRIDE) {
|
|
5248
|
+
const v = laneReadHook(c, el);
|
|
4866
5249
|
if (v !== NOT_PENDING) return v;
|
|
4867
5250
|
}
|
|
4868
5251
|
// A stale reader of a held flight (rule 3, A15's reveal corollary) is served
|
|
@@ -4877,7 +5260,8 @@ function read(el) {
|
|
|
4877
5260
|
// effect keeps its DOM by throwing, a memo has no DOM and is handed the
|
|
4878
5261
|
// value. `[isPending(x), x()]` reads `[true, stale]` in either order (A10)
|
|
4879
5262
|
// — unless the flight committed beneath inputs already on screen
|
|
4880
|
-
// (`observeFlight`)
|
|
5263
|
+
// (`observeFlight`), or the reader was born into the flight's transaction
|
|
5264
|
+
// (#4010): it has no screen, and waits on the flight with its frame.
|
|
4881
5265
|
if (
|
|
4882
5266
|
owner._statusFlags & STATUS_PENDING &&
|
|
4883
5267
|
!committed &&
|
|
@@ -4886,9 +5270,11 @@ function read(el) {
|
|
|
4886
5270
|
) {
|
|
4887
5271
|
// A reader with no committed value yet sees the flight pending: a
|
|
4888
5272
|
// boundary a lane mounts shows its fallback (A29's boundary exemption).
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
committed =
|
|
5273
|
+
// (A lane, and a verdict reader, exist only with their engines installed.)
|
|
5274
|
+
if (laneStageHook && passLane !== null && !(c?._statusFlags & STATUS_UNINITIALIZED))
|
|
5275
|
+
committed = true;
|
|
5276
|
+
else if (observeFlightHook && c !== null && c._config & CONFIG_VERDICT)
|
|
5277
|
+
committed = observeFlightHook(c, owner);
|
|
4892
5278
|
}
|
|
4893
5279
|
if (owner._statusFlags & STATUS_PENDING && !committed) {
|
|
4894
5280
|
// A reader landing on a pending node throws; an untracked read of an
|
|
@@ -4957,6 +5343,9 @@ function read(el) {
|
|
|
4957
5343
|
}
|
|
4958
5344
|
}
|
|
4959
5345
|
if (committed) return el._value;
|
|
5346
|
+
// D-41: an untracked read reads the screen, and a shown error is on it
|
|
5347
|
+
// until the answer that replaces it commits.
|
|
5348
|
+
if (!c && el._x?._shownError) throw el._x._shownError;
|
|
4960
5349
|
const value = serve(el, c);
|
|
4961
5350
|
if (
|
|
4962
5351
|
!c &&
|
|
@@ -5003,9 +5392,16 @@ function serve(el, c) {
|
|
|
5003
5392
|
return u;
|
|
5004
5393
|
}
|
|
5005
5394
|
if (readerSeesCommitted(el, c)) return el._value;
|
|
5006
|
-
if (
|
|
5395
|
+
if (
|
|
5396
|
+
observeFlightHook &&
|
|
5397
|
+
c._config & CONFIG_VERDICT &&
|
|
5398
|
+
!(el._statusFlags & STATUS_UNINITIALIZED) &&
|
|
5399
|
+
stagedScreen(c)
|
|
5400
|
+
)
|
|
5007
5401
|
return el._value;
|
|
5008
5402
|
stagedRead(c, el);
|
|
5403
|
+
// D-41: the staged answer is an error (`stageError`).
|
|
5404
|
+
if (el._config & CONFIG_STAGED_ERROR) throw el._pendingValue;
|
|
5009
5405
|
return el._pendingValue;
|
|
5010
5406
|
}
|
|
5011
5407
|
/** A pass read a staging of this flush: it derives from what the flush may
|
|
@@ -5017,11 +5413,14 @@ function serve(el, c) {
|
|
|
5017
5413
|
* write never shows through the lane. A verdict lane's work likewise: the
|
|
5018
5414
|
* lane holds verdicts, not the frame's other stagings (#3851) — except a
|
|
5019
5415
|
* verdict reader, which answered for itself (the lane seam, lanes.ts). Not
|
|
5020
|
-
* a read of a node born staged: it has no committed value to
|
|
5021
|
-
* and a re-run would read the same staging and re-queue every
|
|
5416
|
+
* a read of a node born staged at all: it has no committed value to
|
|
5417
|
+
* re-derive on, and a re-run would read the same staging and re-queue every
|
|
5418
|
+
* round (#3969: nor does it make a later lane read a re-derive, `enterLane`). */
|
|
5022
5419
|
function stagedRead(c, el) {
|
|
5023
|
-
|
|
5024
|
-
|
|
5420
|
+
if (!(el?._statusFlags & STATUS_UNINITIALIZED)) {
|
|
5421
|
+
c._flags |= REACTIVE_STAGED_READ;
|
|
5422
|
+
if (laneSeamsHook && passLane !== null) stagedReaders.push(c);
|
|
5423
|
+
}
|
|
5025
5424
|
}
|
|
5026
5425
|
/** A10 for a staged node: a verdict reader (the pass entered a window) that
|
|
5027
5426
|
* also reads a frame's proposal plainly sees the screen — `[latest(q), q()]`
|
|
@@ -5030,9 +5429,10 @@ function stagedRead(c, el) {
|
|
|
5030
5429
|
* #3322 — one run, on the committed world), or with the hold. Before any
|
|
5031
5430
|
* transaction the answer is the seam's and the reader watches it
|
|
5032
5431
|
* (verdict.ts). A node born into the future has no screen: its staging is
|
|
5033
|
-
* its only value (A29).
|
|
5432
|
+
* its only value (A29). Nor has a reader born into the flush's transaction
|
|
5433
|
+
* (`bornInto`, #4010): its branch exists only there. */
|
|
5034
5434
|
function stagedScreen(c) {
|
|
5035
|
-
if (flushTransaction === null) return false;
|
|
5435
|
+
if (flushTransaction === null || bornInto(c, flushTransaction)) return false;
|
|
5036
5436
|
staleReader(c, flushTransaction);
|
|
5037
5437
|
return true;
|
|
5038
5438
|
}
|
|
@@ -5050,9 +5450,10 @@ function notePromotedWrite(el) {
|
|
|
5050
5450
|
el._config |= CONFIG_PROMOTED;
|
|
5051
5451
|
promotedWrites.push(el);
|
|
5052
5452
|
}
|
|
5053
|
-
/** Cold half of setSignal's snapshot arm (see there)
|
|
5054
|
-
*
|
|
5055
|
-
*
|
|
5453
|
+
/** Cold half of setSignal's snapshot arm (see there), shared with a store
|
|
5454
|
+
* container's structural notify: first write during capture to a plain
|
|
5455
|
+
* signal without a live snapshot records the pre-write value. Only plain
|
|
5456
|
+
* user signals qualify. Computeds reach setSignal from an
|
|
5056
5457
|
* async landing (asyncWrite), and a value arriving from async during the pass
|
|
5057
5458
|
* REVEALS — the creation-time arm skips pending computeds for the same
|
|
5058
5459
|
* reason. Firewall leaves belong to a projection whose compute is the tree's
|
|
@@ -5068,11 +5469,46 @@ function captureWriteSnapshot(el, current) {
|
|
|
5068
5469
|
el._config |= CONFIG_HAS_SNAPSHOT;
|
|
5069
5470
|
snapshotSources.add(el);
|
|
5070
5471
|
}
|
|
5472
|
+
/**
|
|
5473
|
+
* A callback run before every signal or store write lands, while set, with
|
|
5474
|
+
* the written signal or the store proxy whose setter runs. The
|
|
5475
|
+
* server-components client sets it while slot occurrences are left
|
|
5476
|
+
* unmounted after hydration (deferred hydration): a write may be one an
|
|
5477
|
+
* unmounted component would have observed, so the client mounts what is
|
|
5478
|
+
* left before the write applies, and clears the hook. `null` otherwise.
|
|
5479
|
+
*/
|
|
5480
|
+
let writeHook = null;
|
|
5481
|
+
/** @internal */
|
|
5482
|
+
function setWriteHook(hook) {
|
|
5483
|
+
writeHook = hook;
|
|
5484
|
+
}
|
|
5485
|
+
/**
|
|
5486
|
+
* `el`'s comparator verdict (`_equals`), the one guarded call for recompute,
|
|
5487
|
+
* setSignal (user setters and async landings) and the seam's
|
|
5488
|
+
* staged-equals-committed check.
|
|
5489
|
+
* - A comparator that read an unresolved async source has not shown the
|
|
5490
|
+
* values equal (#4111): falsy, a change, whose readers suspend in their own
|
|
5491
|
+
* tracked context.
|
|
5492
|
+
* - Any other throw is an error of `el`'s computation (A3, #2837): routed
|
|
5493
|
+
* through the status path so error boundaries contain it, instead of
|
|
5494
|
+
* unwinding a flush or a setter; answered "same" so nothing commits.
|
|
5495
|
+
* @internal
|
|
5496
|
+
*/
|
|
5497
|
+
function isSame(el, a, b) {
|
|
5498
|
+
if (el._equals === isEqual) return a === b;
|
|
5499
|
+
try {
|
|
5500
|
+
return el._equals && el._equals(a, b);
|
|
5501
|
+
} catch (e) {
|
|
5502
|
+
if (!(e instanceof NotReadyError)) return (notifyStatus(el, STATUS_ERROR, e), true);
|
|
5503
|
+
}
|
|
5504
|
+
}
|
|
5071
5505
|
function setSignal(el, v) {
|
|
5506
|
+
if (writeHook !== null) writeHook(el);
|
|
5072
5507
|
if (
|
|
5073
5508
|
!(el._config & CONFIG_OWNED_WRITE) &&
|
|
5074
5509
|
!(context && context._config & CONFIG_CHILDREN_FORBIDDEN) &&
|
|
5075
|
-
context
|
|
5510
|
+
context &&
|
|
5511
|
+
!disposalDepth
|
|
5076
5512
|
) {
|
|
5077
5513
|
emitDiagnostic({
|
|
5078
5514
|
code: "REACTIVE_WRITE_IN_OWNED_SCOPE",
|
|
@@ -5091,7 +5527,7 @@ function setSignal(el, v) {
|
|
|
5091
5527
|
// Lanes: a write landing on a guess is its truth (A18 — an async guess's
|
|
5092
5528
|
// own landing), judged against the guess; one landing on a lane's
|
|
5093
5529
|
// derivation (its flight) is the lane's staging (lanes.ts).
|
|
5094
|
-
if (el._config & CONFIG_OVERRIDE) return
|
|
5530
|
+
if (laneWriteHook && el._config & CONFIG_OVERRIDE) return laneWriteHook(el, v);
|
|
5095
5531
|
// L2 (T4) / A34 (1): a write to a node whose staged state is held is a
|
|
5096
5532
|
// second proposal for the slot — the same value again or another — and
|
|
5097
5533
|
// the two cannot finish at different times: the writer's tick joins the
|
|
@@ -5099,8 +5535,12 @@ function setSignal(el, v) {
|
|
|
5099
5535
|
if (el._config & CONFIG_HELD) joinFuture(txOf(el));
|
|
5100
5536
|
// Uninitialized check first: the first commit has no previous value, so the
|
|
5101
5537
|
// user comparator must not run against `undefined` (matches recompute).
|
|
5538
|
+
// D-41: a write over a shown error is a change whatever the last good
|
|
5539
|
+
// value was — the error leaves at its commit (`commitPendingNode`).
|
|
5102
5540
|
const valueChanged =
|
|
5103
|
-
!!(el._statusFlags & STATUS_UNINITIALIZED) ||
|
|
5541
|
+
!!(el._statusFlags & STATUS_UNINITIALIZED) ||
|
|
5542
|
+
!!el._x?._shownError ||
|
|
5543
|
+
!isSame(el, currentValue, v);
|
|
5104
5544
|
if (!valueChanged) return v;
|
|
5105
5545
|
// Attribution hook: this committed write is where a re-run chain begins.
|
|
5106
5546
|
if (attrHooks !== null) attrHooks.write(el, currentValue, v);
|
|
@@ -5158,8 +5598,21 @@ function setMemo(el, v) {
|
|
|
5158
5598
|
!(el._flags & REACTIVE_MANUAL_WRITE) &&
|
|
5159
5599
|
txOf(el) !== flushTransaction;
|
|
5160
5600
|
// The writer read the committed frame; its updater composes on it.
|
|
5161
|
-
if (held && typeof v === "function")
|
|
5162
|
-
|
|
5601
|
+
if (held && typeof v === "function") {
|
|
5602
|
+
const value = v(el._value);
|
|
5603
|
+
v = () => value;
|
|
5604
|
+
}
|
|
5605
|
+
// #4110: the write replaces a failed derivation's outcome. It lands without
|
|
5606
|
+
// re-running `_fn`, and nothing else clears STATUS_ERROR, so without this
|
|
5607
|
+
// every later read keeps throwing the error the derivation left behind.
|
|
5608
|
+
// The error stays the display until the write commits (D-41).
|
|
5609
|
+
if (el._statusFlags & STATUS_ERROR) {
|
|
5610
|
+
el._x._shownError = el._x._error;
|
|
5611
|
+
notifyStatus(el, STATUS_NONE, undefined);
|
|
5612
|
+
}
|
|
5613
|
+
v = setSignal(el, v);
|
|
5614
|
+
// A write replaces an error a pass staged (D-41): it is a value again.
|
|
5615
|
+
el._config &= ~CONFIG_STAGED_ERROR;
|
|
5163
5616
|
if (held) {
|
|
5164
5617
|
// The one write that re-runs: the staging it replaced was derived from
|
|
5165
5618
|
// inputs the writer never saw, so the hold re-derives over the write.
|
|
@@ -5167,7 +5620,7 @@ function setMemo(el, v) {
|
|
|
5167
5620
|
insertIntoHeap(el, dirtyQueue);
|
|
5168
5621
|
schedule();
|
|
5169
5622
|
} else if (el._pendingValue !== NOT_PENDING) el._flags |= REACTIVE_MANUAL_WRITE;
|
|
5170
|
-
return
|
|
5623
|
+
return v;
|
|
5171
5624
|
}
|
|
5172
5625
|
/**
|
|
5173
5626
|
* Executes `fn` with the given `owner` set as the current owner. Any reactive
|
|
@@ -5234,7 +5687,8 @@ function markRefresh(node) {
|
|
|
5234
5687
|
if (
|
|
5235
5688
|
context &&
|
|
5236
5689
|
!((node._config ?? 0) & CONFIG_OWNED_WRITE) &&
|
|
5237
|
-
!(context._config & CONFIG_CHILDREN_FORBIDDEN)
|
|
5690
|
+
!(context._config & CONFIG_CHILDREN_FORBIDDEN) &&
|
|
5691
|
+
!disposalDepth
|
|
5238
5692
|
) {
|
|
5239
5693
|
emitDiagnostic({
|
|
5240
5694
|
code: "REACTIVE_WRITE_IN_OWNED_SCOPE",
|
|
@@ -5268,171 +5722,194 @@ function markRefresh(node) {
|
|
|
5268
5722
|
}
|
|
5269
5723
|
|
|
5270
5724
|
export {
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5725
|
+
setPassLane as $,
|
|
5726
|
+
tracking as A,
|
|
5727
|
+
CONFIG_CHILDREN_FORBIDDEN as B,
|
|
5274
5728
|
CONFIG_GUESS as C,
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5729
|
+
passLane as D,
|
|
5730
|
+
STATUS_PENDING as E,
|
|
5731
|
+
EFFECT_RENDER as F,
|
|
5278
5732
|
GlobalQueue as G,
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5733
|
+
sameLane as H,
|
|
5734
|
+
flushTransaction as I,
|
|
5735
|
+
REACTIVE_SCREEN_READ as J,
|
|
5736
|
+
REACTIVE_FRAME_READ as K,
|
|
5737
|
+
attrHooks as L,
|
|
5738
|
+
commitPendingNode as M,
|
|
5285
5739
|
NOT_PENDING as N,
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5740
|
+
REACTIVE_IN_HEAP as O,
|
|
5741
|
+
REACTIVE_DIRTY as P,
|
|
5742
|
+
REACTIVE_CHECK as Q,
|
|
5289
5743
|
REACTIVE_DISPOSED as R,
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5744
|
+
STATUS_UNINITIALIZED as S,
|
|
5745
|
+
CONFIG_INPUTS_PUBLISHED as T,
|
|
5746
|
+
releaseQueues as U,
|
|
5747
|
+
CONFIG_SLOT_NODE as V,
|
|
5748
|
+
holdNode as W,
|
|
5749
|
+
joinFuture as X,
|
|
5750
|
+
reruns as Y,
|
|
5751
|
+
CONFIG_STAGED as Z,
|
|
5752
|
+
merge as _,
|
|
5753
|
+
NotReadyError as a,
|
|
5754
|
+
setMemo as a$,
|
|
5755
|
+
REACTIVE_JOINED as a0,
|
|
5756
|
+
REACTIVE_STAGED_READ as a1,
|
|
5757
|
+
stagedRead as a2,
|
|
5758
|
+
setVerdict as a3,
|
|
5759
|
+
context as a4,
|
|
5760
|
+
installVerdictHooks as a5,
|
|
5761
|
+
REACTIVE_PROBED as a6,
|
|
5762
|
+
verdict as a7,
|
|
5763
|
+
link as a8,
|
|
5764
|
+
pullComputed as a9,
|
|
5765
|
+
cleanup as aA,
|
|
5766
|
+
read as aB,
|
|
5767
|
+
DEV as aC,
|
|
5768
|
+
CONFIG_TRANSPARENT as aD,
|
|
5769
|
+
NoOwnerError as aE,
|
|
5770
|
+
ContextNotFoundError as aF,
|
|
5771
|
+
createEffectNode as aG,
|
|
5772
|
+
recompute as aH,
|
|
5773
|
+
EFFECT_USER as aI,
|
|
5774
|
+
reportDiagnostic as aJ,
|
|
5775
|
+
computed as aK,
|
|
5776
|
+
staleValues as aL,
|
|
5777
|
+
CONFIG_AUTO_DISPOSE as aM,
|
|
5778
|
+
EFFECT_TRACKED as aN,
|
|
5779
|
+
heldRunHook as aO,
|
|
5780
|
+
unwrapStatusError as aP,
|
|
5781
|
+
haltReactivity as aQ,
|
|
5782
|
+
setStrictRead as aR,
|
|
5783
|
+
StatusError as aS,
|
|
5784
|
+
trimStaleDeps as aT,
|
|
5785
|
+
setTrackedQueueCallback as aU,
|
|
5786
|
+
setEffectStatusNotify as aV,
|
|
5787
|
+
_hitUnhandledAsync as aW,
|
|
5788
|
+
resetUnhandledAsync as aX,
|
|
5789
|
+
ROOT_ERROR_HOOK as aY,
|
|
5790
|
+
registerGraph as aZ,
|
|
5791
|
+
runWithOwner as a_,
|
|
5792
|
+
strictRead as aa,
|
|
5793
|
+
warnStrictReadUntracked as ab,
|
|
5794
|
+
staleReader as ac,
|
|
5795
|
+
globalQueue as ad,
|
|
5796
|
+
joinPass as ae,
|
|
5797
|
+
markLateLinker as af,
|
|
5798
|
+
spectating as ag,
|
|
5799
|
+
unflushedValue as ah,
|
|
5800
|
+
CONFIG_STAGED_ERROR as ai,
|
|
5801
|
+
stagedReaders as aj,
|
|
5802
|
+
bornInto as ak,
|
|
5803
|
+
enterAction as al,
|
|
5804
|
+
setEffectCallback as am,
|
|
5805
|
+
exitAction as an,
|
|
5806
|
+
isThenable as ao,
|
|
5807
|
+
actionDepth as ap,
|
|
5808
|
+
flush as aq,
|
|
5809
|
+
setQuestion as ar,
|
|
5810
|
+
emitDiagnostic as as,
|
|
5811
|
+
enterCallback as at,
|
|
5812
|
+
exitCallback as au,
|
|
5813
|
+
getOwner as av,
|
|
5814
|
+
inEffectCallback as aw,
|
|
5815
|
+
setExternalHooks as ax,
|
|
5816
|
+
signal as ay,
|
|
5817
|
+
setSignal as az,
|
|
5818
|
+
CONFIG_AUTHORITATIVE as b,
|
|
5819
|
+
isSuppressed as b$,
|
|
5820
|
+
$REFRESH as b0,
|
|
5821
|
+
markRefresh as b1,
|
|
5822
|
+
createRoot as b2,
|
|
5823
|
+
getObserver as b3,
|
|
5824
|
+
dispose as b4,
|
|
5825
|
+
TimeoutError as b5,
|
|
5826
|
+
untrack as b6,
|
|
5827
|
+
dirtyQueue as b7,
|
|
5828
|
+
CONFIG_REDERIVE as b8,
|
|
5829
|
+
passTx as b9,
|
|
5830
|
+
settlePendingSource as bA,
|
|
5831
|
+
notifyEpoch as bB,
|
|
5832
|
+
SUPPORTS_PROXY as bC,
|
|
5833
|
+
releaseMarksHook as bD,
|
|
5834
|
+
installMarkHooks as bE,
|
|
5835
|
+
CONFIG_LAGS as bF,
|
|
5836
|
+
REACTIVE_REASK as bG,
|
|
5837
|
+
heldUnder as bH,
|
|
5838
|
+
unhold as bI,
|
|
5839
|
+
laneStageHook as bJ,
|
|
5840
|
+
REACTIVE_ZOMBIE as bK,
|
|
5841
|
+
installBoundaryHooks as bL,
|
|
5842
|
+
CAUGHT_REARMED as bM,
|
|
5843
|
+
OBSERVE as bN,
|
|
5844
|
+
clearSnapshots as bO,
|
|
5845
|
+
enforceLoadingBoundary as bP,
|
|
5846
|
+
getNextChildId as bQ,
|
|
5847
|
+
markSnapshotScope as bR,
|
|
5848
|
+
peekNextChildId as bS,
|
|
5849
|
+
releaseSnapshotScope as bT,
|
|
5850
|
+
resetErrorHalt as bU,
|
|
5851
|
+
setConsoleFooter as bV,
|
|
5852
|
+
setSnapshotCapture as bW,
|
|
5853
|
+
setWriteHook as bX,
|
|
5854
|
+
setAttributionHooks as bY,
|
|
5855
|
+
liveRootOwners as bZ,
|
|
5856
|
+
ownerPath as b_,
|
|
5857
|
+
CONFIG_FRESH_READ as ba,
|
|
5858
|
+
createOwner as bb,
|
|
5859
|
+
writeHook as bc,
|
|
5860
|
+
REACTIVE_WRITE_IN_OWNED_SCOPE_SIGNAL_MESSAGE as bd,
|
|
5861
|
+
ownedScopeWriteMessage as be,
|
|
5862
|
+
isEqual as bf,
|
|
5863
|
+
CONFIG_MANUAL_WRITE as bg,
|
|
5864
|
+
snapshotCaptureActive as bh,
|
|
5865
|
+
captureWriteSnapshot as bi,
|
|
5866
|
+
notePromotedWrite as bj,
|
|
5867
|
+
slotSignal as bk,
|
|
5868
|
+
stagedScreen as bl,
|
|
5869
|
+
CONFIG_PROMOTED as bm,
|
|
5870
|
+
setSlotUnobserved as bn,
|
|
5871
|
+
installStoreHooks as bo,
|
|
5872
|
+
joinPassTx as bp,
|
|
5873
|
+
asyncTailFlights as bq,
|
|
5874
|
+
untrackDepth as br,
|
|
5875
|
+
checkPostAwaitRead as bs,
|
|
5876
|
+
disposalDepth as bt,
|
|
5877
|
+
insertIntoHeap as bu,
|
|
5878
|
+
isDisposed as bv,
|
|
5879
|
+
handleAsync as bw,
|
|
5880
|
+
addPendingSource as bx,
|
|
5881
|
+
propagateStatus as by,
|
|
5882
|
+
forEachDependent as bz,
|
|
5883
|
+
CONFIG_OVERRIDE as c,
|
|
5884
|
+
blockersOf as c0,
|
|
5885
|
+
CONFIG_PLUMBING as c1,
|
|
5886
|
+
anyExcluded as c2,
|
|
5887
|
+
isExcluded as c3,
|
|
5888
|
+
CONFIG_WIDE as c4,
|
|
5889
|
+
GRAPH_SIZE_WARN_AT as c5,
|
|
5890
|
+
noteFanOut as c6,
|
|
5891
|
+
CONFIG_SUPERSEDES as c7,
|
|
5892
|
+
newTransaction as d,
|
|
5416
5893
|
enqueueSub as e,
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
5894
|
+
CONFIG_VERDICT as f,
|
|
5895
|
+
REACTIVE_PROBE_UNANSWERED as g,
|
|
5896
|
+
blocked as h,
|
|
5897
|
+
installLaneHooks as i,
|
|
5898
|
+
REACTIVE_LANE_DIRTY as j,
|
|
5899
|
+
CONFIG_HELD as k,
|
|
5900
|
+
clock as l,
|
|
5901
|
+
insertSubs as m,
|
|
5425
5902
|
nextQuestion as n,
|
|
5426
|
-
|
|
5427
|
-
|
|
5903
|
+
ownFlights as o,
|
|
5904
|
+
laneDirty as p,
|
|
5428
5905
|
question as q,
|
|
5429
5906
|
resolveTx as r,
|
|
5430
5907
|
schedule as s,
|
|
5431
5908
|
txOf as t,
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
|
|
5909
|
+
REACTIVE_LANE_READ as u,
|
|
5910
|
+
ext as v,
|
|
5911
|
+
REACTIVE_RECOMPUTING_DEPS as w,
|
|
5912
|
+
queuePendingNode as x,
|
|
5913
|
+
list as y,
|
|
5914
|
+
STATUS_ERROR as z
|
|
5438
5915
|
};
|