@solidjs/signals 2.0.0-rc.15 → 2.0.0-rc.17
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 +2268 -1734
- package/dist/dev.attribution.js +75 -52
- package/dist/dev.js +1348 -658
- package/dist/observe/affects-marks.js +86 -0
- package/dist/observe/affects.js +22 -73
- package/dist/observe/boundaries.js +192 -190
- package/dist/observe/core/async.js +177 -131
- package/dist/observe/core/attribution-queries.js +1 -1
- package/dist/observe/core/attribution.js +176 -159
- package/dist/observe/core/constants.js +17 -3
- package/dist/observe/core/context.js +5 -3
- package/dist/observe/core/core.js +520 -364
- package/dist/observe/core/dev.js +66 -38
- package/dist/observe/core/effect.js +50 -48
- package/dist/observe/core/error.js +5 -3
- package/dist/observe/core/external.js +5 -6
- package/dist/observe/core/graph.js +60 -44
- package/dist/observe/core/heap.js +33 -33
- package/dist/observe/core/identities.js +62 -0
- package/dist/observe/core/lanes.js +238 -260
- package/dist/observe/core/owner.js +117 -79
- package/dist/observe/core/scheduler.js +381 -217
- package/dist/observe/core/truth.js +45 -0
- package/dist/observe/core/verdict.js +91 -50
- package/dist/observe/deferred.js +184 -0
- package/dist/observe/index.js +7 -1
- package/dist/observe/map.js +345 -260
- package/dist/observe/reveal.js +76 -76
- package/dist/observe/signals.js +47 -31
- package/dist/observe/store/affects.js +18 -18
- package/dist/observe/store/edges.js +39 -0
- package/dist/observe/store/optimistic.js +146 -100
- package/dist/observe/store/projection.js +68 -55
- package/dist/observe/store/store.js +180 -118
- package/dist/prod/affects-marks.js +86 -0
- package/dist/prod/affects.js +23 -72
- package/dist/prod/boundaries.js +156 -154
- package/dist/prod/core/async.js +173 -127
- package/dist/prod/core/constants.js +17 -3
- package/dist/prod/core/context.js +5 -3
- package/dist/prod/core/core.js +495 -343
- package/dist/prod/core/effect.js +70 -68
- package/dist/prod/core/error.js +5 -3
- package/dist/prod/core/external.js +5 -6
- package/dist/prod/core/graph.js +60 -44
- package/dist/prod/core/heap.js +38 -38
- package/dist/prod/core/identities.js +62 -0
- package/dist/prod/core/lanes.js +230 -252
- package/dist/prod/core/owner.js +114 -77
- package/dist/prod/core/scheduler.js +327 -163
- package/dist/prod/core/truth.js +45 -0
- package/dist/prod/core/verdict.js +90 -49
- package/dist/prod/deferred.js +182 -0
- package/dist/prod/index.js +7 -1
- package/dist/prod/map.js +360 -275
- package/dist/prod/reveal.js +76 -76
- package/dist/prod/signals.js +42 -26
- package/dist/prod/store/affects.js +6 -6
- package/dist/prod/store/edges.js +39 -0
- package/dist/prod/store/optimistic.js +143 -98
- package/dist/prod/store/projection.js +25 -19
- package/dist/prod/store/store.js +157 -100
- package/dist/types/affects-marks.d.ts +14 -0
- package/dist/types/boundaries.d.ts +1 -1
- package/dist/types/core/async.d.ts +10 -0
- package/dist/types/core/constants.d.ts +13 -1
- package/dist/types/core/core.d.ts +54 -5
- package/dist/types/core/dev.d.ts +51 -13
- package/dist/types/core/error.d.ts +9 -1
- 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 -14
- package/dist/types/core/owner.d.ts +9 -3
- package/dist/types/core/scheduler.d.ts +70 -32
- package/dist/types/core/truth.d.ts +16 -0
- package/dist/types/core/types.d.ts +21 -0
- 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/edges.d.ts +9 -0
- package/dist/types/store/index.d.ts +10 -3
- 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 +1 -1
- package/dist/types/store/target.d.ts +16 -8
- package/dist/types/store/types.d.ts +14 -0
- package/package.json +1 -1
|
@@ -17,7 +17,7 @@ import { blockersOf, GlobalQueue } from "./scheduler.js";
|
|
|
17
17
|
* observer exclusion is cached per node once any exists, before that a flag
|
|
18
18
|
* read.
|
|
19
19
|
*/ function excludedNode(e) {
|
|
20
|
-
if ((e.
|
|
20
|
+
if ((e.l & CONFIG_PLUMBING) !== 0) return true;
|
|
21
21
|
if (!anyExcluded()) return false;
|
|
22
22
|
const n = e;
|
|
23
23
|
if (n.he === undefined) n.he = isExcluded(e);
|
|
@@ -121,7 +121,7 @@ function nodeName(e) {
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
/** The record vocabulary for a computation's kind: effects carry a `_type`, memos do not. */ function nodeKind(e) {
|
|
124
|
-
return e.
|
|
124
|
+
return e.i ? "effect" : "memo";
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
/**
|
|
@@ -327,7 +327,7 @@ const effectFrames = new WeakMap;
|
|
|
327
327
|
const n = effectStack[effectStack.length - 1];
|
|
328
328
|
if (n !== undefined) effectFrames.set(e, {
|
|
329
329
|
node: n,
|
|
330
|
-
causes: n.
|
|
330
|
+
causes: n.pe
|
|
331
331
|
});
|
|
332
332
|
}
|
|
333
333
|
|
|
@@ -345,7 +345,7 @@ function pushFrame(e, n, t, o) {
|
|
|
345
345
|
if (s !== undefined) i.interaction = s;
|
|
346
346
|
if (o !== undefined) {
|
|
347
347
|
const e = o;
|
|
348
|
-
if (e.
|
|
348
|
+
if (e.me !== undefined) i.run = e.me;
|
|
349
349
|
// The frame → node map is filled by the first write inside the callback
|
|
350
350
|
// (noteEffectOrigin), not here: most callbacks never write.
|
|
351
351
|
effectStack.push(o);
|
|
@@ -458,9 +458,9 @@ function originEnd() {
|
|
|
458
458
|
if (typeof t !== "number") return;
|
|
459
459
|
let o = 0;
|
|
460
460
|
let i = 0;
|
|
461
|
-
for (let n = e.
|
|
461
|
+
for (let n = e.o; n !== null; n = n.t) {
|
|
462
462
|
o++;
|
|
463
|
-
if (!(n.l
|
|
463
|
+
if (!(n.u.l & CONFIG_PLUMBING)) i++;
|
|
464
464
|
}
|
|
465
465
|
if (i < t || o >= GRAPH_SIZE_WARN_AT) return;
|
|
466
466
|
noteFanOut(e, i, n);
|
|
@@ -483,8 +483,8 @@ function stampWrite(e, n, t = NO_VALUES, o = NO_VALUES) {
|
|
|
483
483
|
i.origin = n === "async" ? asyncOrigin(e) : currentOrigin();
|
|
484
484
|
i.at = now();
|
|
485
485
|
i.stack = captureStack();
|
|
486
|
-
const s = e.
|
|
487
|
-
e.
|
|
486
|
+
const s = e.ge;
|
|
487
|
+
e.ge = i;
|
|
488
488
|
noteNavigationWrite(s, i);
|
|
489
489
|
noteInteractionWrite(i.origin, excludedNode(e));
|
|
490
490
|
if (n === "write") trackEffectWrite(e, i, o);
|
|
@@ -495,7 +495,7 @@ function stampWrite(e, n, t = NO_VALUES, o = NO_VALUES) {
|
|
|
495
495
|
}
|
|
496
496
|
|
|
497
497
|
/** Record a derived change (memo produced a new value) with its causes. */ function stampDerived(e, n) {
|
|
498
|
-
e.
|
|
498
|
+
e.ge = {
|
|
499
499
|
seq: ++changeSeq,
|
|
500
500
|
kind: "derived",
|
|
501
501
|
name: nodeName(e),
|
|
@@ -510,19 +510,19 @@ function stampWrite(e, n, t = NO_VALUES, o = NO_VALUES) {
|
|
|
510
510
|
* run's links. A refresh() stamp on the node itself also counts — that is a
|
|
511
511
|
* self-invalidation, not a dep change.
|
|
512
512
|
*/ function collectCauses(e) {
|
|
513
|
-
const n = e.
|
|
513
|
+
const n = e.we ?? 0;
|
|
514
514
|
const t = [];
|
|
515
|
-
const o = e.
|
|
515
|
+
const o = e.ge;
|
|
516
516
|
if (o !== undefined && o.seq > n && o.kind === "refresh") t.push(o);
|
|
517
|
-
for (let o = e.
|
|
518
|
-
const e = o.
|
|
517
|
+
for (let o = e.C; o !== null; o = o.G) {
|
|
518
|
+
const e = o.S.ge;
|
|
519
519
|
if (e !== undefined && e.seq > n) t.push(e);
|
|
520
520
|
}
|
|
521
521
|
return t;
|
|
522
522
|
}
|
|
523
523
|
|
|
524
524
|
/** Advance the node's seen-cursor to the present. Call after every run. */ function markSeen(e) {
|
|
525
|
-
e.
|
|
525
|
+
e.we = changeSeq;
|
|
526
526
|
}
|
|
527
527
|
|
|
528
528
|
/**
|
|
@@ -534,9 +534,9 @@ function stampWrite(e, n, t = NO_VALUES, o = NO_VALUES) {
|
|
|
534
534
|
* still owed) and not part of the subscription diff.
|
|
535
535
|
*/ function captureDeps(e) {
|
|
536
536
|
const n = [];
|
|
537
|
-
for (let t = e.
|
|
538
|
-
n.push(t.
|
|
539
|
-
if (t === e.
|
|
537
|
+
for (let t = e.C; t !== null; t = t.G) {
|
|
538
|
+
n.push(t.S);
|
|
539
|
+
if (t === e._) break;
|
|
540
540
|
}
|
|
541
541
|
return n;
|
|
542
542
|
}
|
|
@@ -555,18 +555,18 @@ function stampWrite(e, n, t = NO_VALUES, o = NO_VALUES) {
|
|
|
555
555
|
if (n === false || false) return;
|
|
556
556
|
let t = 0;
|
|
557
557
|
const o = [];
|
|
558
|
-
for (let n = e.
|
|
558
|
+
for (let n = e.C; n !== null; n = n.G) {
|
|
559
559
|
// HMR plumbing is a dev-only hop the scope cannot read past: not a source.
|
|
560
|
-
if (!(n.
|
|
560
|
+
if (!(n.S.l & CONFIG_PLUMBING)) {
|
|
561
561
|
t++;
|
|
562
|
-
if (o.length < 12) o.push(nodeName(n.
|
|
562
|
+
if (o.length < 12) o.push(nodeName(n.S));
|
|
563
563
|
}
|
|
564
|
-
if (n === e.
|
|
564
|
+
if (n === e._) break;
|
|
565
565
|
// the validated prefix, as captureDeps
|
|
566
566
|
}
|
|
567
567
|
const i = e;
|
|
568
|
-
if (t < n || t < (i.
|
|
569
|
-
i.
|
|
568
|
+
if (t < n || t < (i.ve ?? 0) * 1.5) return;
|
|
569
|
+
i.ve = t;
|
|
570
570
|
const s = `[WIDE_SCOPE_DEPS] ${nodeKind(e)} "${nodeName(e)}" is subscribed to ${t} sources — ` + `it re-runs when any of them change. Narrow its reads or split it into smaller memos. ` + `Sources: ${o.join(", ")}${t > o.length ? ", …" : ""}`;
|
|
571
571
|
reportDiagnostic(emitDiagnostic({
|
|
572
572
|
code: "WIDE_SCOPE_DEPS",
|
|
@@ -595,14 +595,14 @@ const HOT_FANOUT_FIRST_MILESTONE = 5;
|
|
|
595
595
|
if (t === false) return;
|
|
596
596
|
const o = e;
|
|
597
597
|
const i = Date.now();
|
|
598
|
-
if (o.
|
|
599
|
-
o.
|
|
600
|
-
o
|
|
601
|
-
o
|
|
602
|
-
}
|
|
603
|
-
o
|
|
604
|
-
if (o
|
|
605
|
-
o
|
|
598
|
+
if (o.ke === undefined || i - o.ke > t.windowMs) {
|
|
599
|
+
o.ke = i;
|
|
600
|
+
o.ye = 0;
|
|
601
|
+
o.$e = false;
|
|
602
|
+
}
|
|
603
|
+
o.ye = (o.ye ?? 0) + 1;
|
|
604
|
+
if (o.$e || o.ye < t.count) return;
|
|
605
|
+
o.$e = true;
|
|
606
606
|
// Root-cause key: the set of originating writes behind this scope's latest
|
|
607
607
|
// re-run. Scopes hot from the SAME roots share one aggregation window.
|
|
608
608
|
const s = new Set;
|
|
@@ -619,10 +619,10 @@ const HOT_FANOUT_FIRST_MILESTONE = 5;
|
|
|
619
619
|
hotCauses.set(r, a);
|
|
620
620
|
}
|
|
621
621
|
a.scopes++;
|
|
622
|
-
a.runs += o
|
|
622
|
+
a.runs += o.ye;
|
|
623
623
|
if (a.scopes === 1) {
|
|
624
624
|
const s = n.map(e => `"${e.name}" (${e.kind})`).join(", ");
|
|
625
|
-
const r = `[HOT_SCOPE_RERUNS] ${nodeKind(e)} "${nodeName(e)}" re-ran ${o
|
|
625
|
+
const r = `[HOT_SCOPE_RERUNS] ${nodeKind(e)} "${nodeName(e)}" re-ran ${o.ye} times ` + `in ${Math.max(1, i - o.ke)}ms — a hot signal is likely leaking into this ` + `scope. Latest cause: ${s || "(untracked pull)"}`;
|
|
626
626
|
reportDiagnostic(emitDiagnostic({
|
|
627
627
|
code: "HOT_SCOPE_RERUNS",
|
|
628
628
|
kind: "perf",
|
|
@@ -630,7 +630,7 @@ const HOT_FANOUT_FIRST_MILESTONE = 5;
|
|
|
630
630
|
message: r,
|
|
631
631
|
nodeName: nodeName(e),
|
|
632
632
|
data: {
|
|
633
|
-
runs: o
|
|
633
|
+
runs: o.ye,
|
|
634
634
|
windowMs: t.windowMs,
|
|
635
635
|
causes: n.map(e => e.name)
|
|
636
636
|
}
|
|
@@ -668,16 +668,16 @@ const HOT_FANOUT_FIRST_MILESTONE = 5;
|
|
|
668
668
|
if (o === false) return;
|
|
669
669
|
const i = e;
|
|
670
670
|
const s = now();
|
|
671
|
-
if (i.
|
|
672
|
-
i.
|
|
673
|
-
i.
|
|
674
|
-
i.
|
|
675
|
-
}
|
|
676
|
-
i.
|
|
677
|
-
if (i.
|
|
678
|
-
i.
|
|
671
|
+
if (i.be === undefined || s - i.be > o.windowMs) {
|
|
672
|
+
i.be = s;
|
|
673
|
+
i.Oe = 0;
|
|
674
|
+
i.Fe = false;
|
|
675
|
+
}
|
|
676
|
+
i.Oe = (i.Oe ?? 0) + n;
|
|
677
|
+
if (i.Fe || i.Oe < o.budgetMs) return;
|
|
678
|
+
i.Fe = true;
|
|
679
679
|
const r = t.map(e => `"${e.name}" (${e.kind})`).join(", ");
|
|
680
|
-
const a = `[HOT_SCOPE_TIME] ${nodeKind(e)} "${nodeName(e)}" spent ` + `${i.
|
|
680
|
+
const a = `[HOT_SCOPE_TIME] ${nodeKind(e)} "${nodeName(e)}" spent ` + `${i.Oe.toFixed(1)}ms of compute inside one ${o.windowMs}ms window ` + `(budget ${o.budgetMs}ms). Latest cause: ${r || "(untracked pull)"}`;
|
|
681
681
|
reportDiagnostic(emitDiagnostic({
|
|
682
682
|
code: "HOT_SCOPE_TIME",
|
|
683
683
|
kind: "perf",
|
|
@@ -685,7 +685,7 @@ const HOT_FANOUT_FIRST_MILESTONE = 5;
|
|
|
685
685
|
message: a,
|
|
686
686
|
nodeName: nodeName(e),
|
|
687
687
|
data: {
|
|
688
|
-
spentMs: i.
|
|
688
|
+
spentMs: i.Oe,
|
|
689
689
|
budgetMs: o.budgetMs,
|
|
690
690
|
windowMs: o.windowMs,
|
|
691
691
|
causes: t.map(e => e.name)
|
|
@@ -710,24 +710,24 @@ const HOT_FANOUT_FIRST_MILESTONE = 5;
|
|
|
710
710
|
// This runs on every re-run: fields on the node (one property read each,
|
|
711
711
|
// like hotRuns) and the run's own `at` — no clock read, no map lookup.
|
|
712
712
|
const a = e;
|
|
713
|
-
if (a.
|
|
714
|
-
a.
|
|
715
|
-
a.
|
|
716
|
-
a.
|
|
717
|
-
a.
|
|
718
|
-
a.
|
|
719
|
-
}
|
|
720
|
-
a.
|
|
713
|
+
if (a._e === undefined || n - a._e > r.windowMs) {
|
|
714
|
+
a._e = n;
|
|
715
|
+
a.Ae = 0;
|
|
716
|
+
a.Re = 0;
|
|
717
|
+
a.Le = 0;
|
|
718
|
+
a.Ce = false;
|
|
719
|
+
}
|
|
720
|
+
a.Ae = a.Ae + 1;
|
|
721
721
|
if (!o) {
|
|
722
|
-
a.
|
|
723
|
-
a.
|
|
722
|
+
a.Re = a.Re + 1;
|
|
723
|
+
a.Le = a.Le + i;
|
|
724
724
|
}
|
|
725
|
-
if (a.
|
|
726
|
-
a.
|
|
725
|
+
if (a.Ce || a.Ae < r.minRuns || a.Re / a.Ae < r.ratio || a.Le < r.budgetMs) return;
|
|
726
|
+
a.Ce = true;
|
|
727
727
|
const c = {
|
|
728
|
-
runs: a.
|
|
729
|
-
wasted: a.
|
|
730
|
-
wastedMs: a.
|
|
728
|
+
runs: a.Ae,
|
|
729
|
+
wasted: a.Re,
|
|
730
|
+
wastedMs: a.Le
|
|
731
731
|
};
|
|
732
732
|
const d = s.map(e => `"${e.name}" (${e.kind})`).join(", ");
|
|
733
733
|
const f = `[WASTED_RECOMPUTE] ${nodeKind(e)} "${nodeName(e)}" re-ran ${c.runs} times in ` + `${r.windowMs}ms and ${c.wasted} of those produced the same value — ` + `${c.wastedMs.toFixed(1)}ms of compute the equality gate then discarded. Its inputs ` + `change without changing its result: put an equality boundary upstream (a memo over the ` + `part of the input it reads, or an \`equals\` on the source), or read a narrower slice ` + `(the property, not the object). Latest cause: ${d || "(untracked pull)"}`;
|
|
@@ -750,14 +750,14 @@ const HOT_FANOUT_FIRST_MILESTONE = 5;
|
|
|
750
750
|
function recordRerun(e, n, t, o, i, s) {
|
|
751
751
|
const r = n.causes;
|
|
752
752
|
const a = e;
|
|
753
|
-
const c = a.
|
|
753
|
+
const c = a.pe;
|
|
754
754
|
const d = n.interaction;
|
|
755
755
|
if (excludedNode(e)) {
|
|
756
756
|
// The observer's own computation: keep the per-node bookkeeping its
|
|
757
757
|
// effect phase reads (see effectRunStart) and record nothing.
|
|
758
|
-
a.
|
|
759
|
-
a.
|
|
760
|
-
a.
|
|
758
|
+
a.Me = d;
|
|
759
|
+
a.me = undefined;
|
|
760
|
+
a.pe = r;
|
|
761
761
|
return;
|
|
762
762
|
}
|
|
763
763
|
// The facts every run leaves on the node, record or not: the run sequence
|
|
@@ -766,10 +766,10 @@ function recordRerun(e, n, t, o, i, s) {
|
|
|
766
766
|
// cause list of its own, so it takes this run's interaction and causes
|
|
767
767
|
// (see effectRunStart / pushFrame).
|
|
768
768
|
const f = ++runSeq;
|
|
769
|
-
const u = a.
|
|
770
|
-
a.
|
|
771
|
-
a.
|
|
772
|
-
a.
|
|
769
|
+
const u = a.De = (a.De ?? 0) + 1;
|
|
770
|
+
a.Me = d;
|
|
771
|
+
a.me = f;
|
|
772
|
+
a.pe = r;
|
|
773
773
|
noteInteractionRun(d, t.selfMs, false);
|
|
774
774
|
if (openFlush !== null) noteFlushRun(openFlush, false, d);
|
|
775
775
|
// The checks read the facts, not the record, so they run with or without
|
|
@@ -910,15 +910,15 @@ function shallowEquivalent(e, n) {
|
|
|
910
910
|
const i = e;
|
|
911
911
|
if (n === t || // paranoia: changed runs should never hit this
|
|
912
912
|
!isPlainShape(n) || !isPlainShape(t) || !shallowEquivalent(n, t)) {
|
|
913
|
-
i.
|
|
914
|
-
i.
|
|
913
|
+
i.He = 0;
|
|
914
|
+
i.We = false;
|
|
915
915
|
return;
|
|
916
916
|
}
|
|
917
|
-
i.
|
|
918
|
-
if (i.
|
|
919
|
-
i.
|
|
917
|
+
i.He = (i.He ?? 0) + 1;
|
|
918
|
+
if (i.We || i.He < o) return;
|
|
919
|
+
i.We = true;
|
|
920
920
|
const s = Array.isArray(t) ? "array" : "object";
|
|
921
|
-
const r = `[UNSTABLE_MEMO_OUTPUT] memo "${nodeName(e)}" produced a new-but-equivalent ${s} on ` + `${i.
|
|
921
|
+
const r = `[UNSTABLE_MEMO_OUTPUT] memo "${nodeName(e)}" produced a new-but-equivalent ${s} on ` + `${i.He} consecutive runs — its equality gate never closes, so every ` + `subscriber re-runs on every upstream change. Return stable references or pass an ` + `\`equals\` option.`;
|
|
922
922
|
reportDiagnostic(emitDiagnostic({
|
|
923
923
|
code: "UNSTABLE_MEMO_OUTPUT",
|
|
924
924
|
kind: "perf",
|
|
@@ -926,7 +926,7 @@ function shallowEquivalent(e, n) {
|
|
|
926
926
|
message: r,
|
|
927
927
|
nodeName: nodeName(e),
|
|
928
928
|
data: {
|
|
929
|
-
runs: i.
|
|
929
|
+
runs: i.He,
|
|
930
930
|
shape: s
|
|
931
931
|
}
|
|
932
932
|
}, e));
|
|
@@ -1065,17 +1065,20 @@ const copyReported = new WeakSet;
|
|
|
1065
1065
|
const s = i !== undefined && i.kind === "effect" ? effectFrames.get(i) : undefined;
|
|
1066
1066
|
const r = s === undefined ? 0 : devId(s.node);
|
|
1067
1067
|
recordNodes.set(n, e);
|
|
1068
|
-
o.
|
|
1069
|
-
|
|
1068
|
+
o.Pe = o.Pe === undefined || o.Pe === r ? r : null;
|
|
1069
|
+
// An apply half that takes no argument cannot be copying its compute
|
|
1070
|
+
// output: a match is coincidence (`createEffect(open, () =>
|
|
1071
|
+
// setFlipped(measure()))`).
|
|
1072
|
+
if (s !== undefined && t !== undefined && t === s.node.Ie && (s.node.Ge?.length ?? 1)) {
|
|
1070
1073
|
copyWrites.add(n);
|
|
1071
|
-
o.
|
|
1072
|
-
o.
|
|
1073
|
-
if (o.
|
|
1074
|
-
} else o.
|
|
1074
|
+
o.Ue = o.qe === r ? (o.Ue ?? 0) + 1 : 1;
|
|
1075
|
+
o.qe = r;
|
|
1076
|
+
if (o.Ue >= 2 && o.Pe === r) checkCopyEffect(s.node, e);
|
|
1077
|
+
} else o.Ue = 0;
|
|
1075
1078
|
}
|
|
1076
1079
|
|
|
1077
1080
|
/** The effect's source whose current value the compute output is, if any (the prop-to-state port). */ function passthroughSource(e) {
|
|
1078
|
-
for (let n = e.
|
|
1081
|
+
for (let n = e.C; n !== null; n = n.G) if (n.S.Ie === e.Ie) return nodeName(n.S);
|
|
1079
1082
|
return undefined;
|
|
1080
1083
|
}
|
|
1081
1084
|
|
|
@@ -1148,13 +1151,16 @@ function checkCopyEffect(e, n) {
|
|
|
1148
1151
|
c.count++;
|
|
1149
1152
|
const d = copyWrites.has(s);
|
|
1150
1153
|
const f = recordNodes.get(s);
|
|
1151
|
-
const u = f !== undefined && f.
|
|
1154
|
+
const u = f !== undefined && f.Pe === devId(i.node);
|
|
1152
1155
|
// Derivable outright: the value is the compute output and nothing else
|
|
1153
1156
|
// writes the signal. A copy INTO a signal that has other writers is the
|
|
1154
1157
|
// "reset editable state from a source" shape — the tear is real, but a memo
|
|
1155
1158
|
// is not the answer, so it stays advisory like any other non-derivable tear.
|
|
1156
1159
|
const l = d && u;
|
|
1157
|
-
|
|
1160
|
+
// An apply half that ignores its argument writes from outside the graph
|
|
1161
|
+
// (layout, time, a ref; a signal read there is STRICT_READ's): measuring,
|
|
1162
|
+
// so it stays advisory however often it repeats.
|
|
1163
|
+
const h = l || c.count >= RELAY_WARN_AT && (i.node.Ge?.length ?? 1) ? "warn" : "info";
|
|
1158
1164
|
// First sighting always reports (advisory); afterwards only the escalation.
|
|
1159
1165
|
if (c.count > 1 && (h !== "warn" || c.warned)) return;
|
|
1160
1166
|
// One verdict per derivable signal: the copy report (checkCopyEffect) and
|
|
@@ -1164,9 +1170,9 @@ function checkCopyEffect(e, n) {
|
|
|
1164
1170
|
copyReported.add(f);
|
|
1165
1171
|
}
|
|
1166
1172
|
if (h === "warn") c.warned = true;
|
|
1167
|
-
const p = e.
|
|
1173
|
+
const p = e.i ? "effect" : "memo";
|
|
1168
1174
|
const m = nodeName(i.node);
|
|
1169
|
-
const g = l ? copyRepair(i.node, s.name) : d ? `The written value is the effect's compute output, but "${s.name}" has other ` + `writers — editable state reset from a source. If the reset is the intent, the tear ` + `is its cost; if "${s.name}" only ever mirrors the source, drop the local copy ` + `and read the source.` : u ? `Nothing else writes "${s.name}"
|
|
1175
|
+
const g = l ? copyRepair(i.node, s.name) : d ? `The written value is the effect's compute output, but "${s.name}" has other ` + `writers — editable state reset from a source. If the reset is the intent, the tear ` + `is its cost; if "${s.name}" only ever mirrors the source, drop the local copy ` + `and read the source.` : (u ? `Nothing else writes "${s.name}". ` : "") + `If "${s.name}" is computed from what the effect reads, make it a memo so readers ` + `get it in the same flush; if the write reads something outside the graph (layout, ` + `time), the tear is the cost of measuring.`;
|
|
1170
1176
|
const w = `[EFFECT_RELAY_TEAR] ${p} "${nodeName(e)}" ran twice for one write of ` + `"${r.name}": once in the flush where "${r.name}" changed, and again after ` + `effect "${m}" relayed it by writing "${s.name}" — the first frame showed the ` + `new "${r.name}" with the stale "${s.name}"` + (c.count > 1 ? ` (${c.count} times so far)` : "") + `. ${g}`;
|
|
1171
1177
|
emitDiagnostic({
|
|
1172
1178
|
code: "EFFECT_RELAY_TEAR",
|
|
@@ -1255,7 +1261,7 @@ const listIdentityWarned = new WeakSet;
|
|
|
1255
1261
|
function recordId(e) {
|
|
1256
1262
|
if (e === null || typeof e !== "object") return undefined;
|
|
1257
1263
|
const n = e;
|
|
1258
|
-
return n.id ?? n.key ?? n.
|
|
1264
|
+
return n.id ?? n.key ?? n.Be ?? undefined;
|
|
1259
1265
|
}
|
|
1260
1266
|
|
|
1261
1267
|
function checkListIdentity(e, n, t, o, i) {
|
|
@@ -1317,13 +1323,11 @@ const liveFlights = new WeakMap;
|
|
|
1317
1323
|
|
|
1318
1324
|
const landedFlights = new WeakMap;
|
|
1319
1325
|
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
*/ const flightOrigins = new WeakMap;
|
|
1326
|
-
|
|
1326
|
+
// Flight-object identity → earliest known start lives on the core's slot
|
|
1327
|
+
// (`OBSERVE.attribution.markFlight`/`flightOrigin`), so marks made with no
|
|
1328
|
+
// engine loaded — a router's preloads, `lazy()`'s import — and the server's
|
|
1329
|
+
// boundary check share it. Fed by markFlight() and by first sightings at
|
|
1330
|
+
// registration.
|
|
1327
1331
|
let waterfallLog = [];
|
|
1328
1332
|
|
|
1329
1333
|
/** Deepest landed-flight cause reachable through a cause list (derived links included). */ function flightCauseIn(e) {
|
|
@@ -1366,8 +1370,8 @@ let waterfallLog = [];
|
|
|
1366
1370
|
|
|
1367
1371
|
function trackFlightStart(e, n) {
|
|
1368
1372
|
const t = now();
|
|
1369
|
-
const o =
|
|
1370
|
-
if (o === t)
|
|
1373
|
+
const o = OBSERVE.attribution.flightOrigin(n) ?? t;
|
|
1374
|
+
if (o === t) OBSERVE.attribution.markFlight(n, t);
|
|
1371
1375
|
// Census: a flight still in the air when the node starts another was
|
|
1372
1376
|
// superseded — its answer will be discarded.
|
|
1373
1377
|
const i = liveFlights.get(e);
|
|
@@ -1412,7 +1416,7 @@ function trackFlightStart(e, n) {
|
|
|
1412
1416
|
const o = t - n.origin;
|
|
1413
1417
|
for (const n of folds) n.flightLanded?.(e, o);
|
|
1414
1418
|
if (records.observed("flight")) emitFlight(e, n, t, "landed");
|
|
1415
|
-
const i = e.
|
|
1419
|
+
const i = e.ge;
|
|
1416
1420
|
// Only a stamp this landing produced may carry the measurement — a stale
|
|
1417
1421
|
// async record from a previous landing must not be re-labeled.
|
|
1418
1422
|
if (i !== undefined && i.kind === "async" && i.seq > n.startSeq) landedFlights.set(i, {
|
|
@@ -1448,8 +1452,8 @@ function checkWaterfall(e, n, t) {
|
|
|
1448
1452
|
}
|
|
1449
1453
|
if (i < 2) return;
|
|
1450
1454
|
const r = e;
|
|
1451
|
-
if ((r.
|
|
1452
|
-
r.
|
|
1455
|
+
if ((r.xe ?? 0) >= i) return;
|
|
1456
|
+
r.xe = i;
|
|
1453
1457
|
const a = [ ...n.slice(n.length - (i - 1)), {
|
|
1454
1458
|
name: nodeName(e),
|
|
1455
1459
|
ms: t
|
|
@@ -1486,7 +1490,7 @@ let holdLog = [];
|
|
|
1486
1490
|
/** A lane's derivation (L2: a node carrying the lane's value that is not a
|
|
1487
1491
|
* written guess — lanes.ts) is a lane pass's result, not a write anyone
|
|
1488
1492
|
* made: not an acknowledgement. */ function isCompanion(e) {
|
|
1489
|
-
return (e.
|
|
1493
|
+
return (e.l & (CONFIG_OVERRIDE | CONFIG_GUESS)) === CONFIG_OVERRIDE;
|
|
1490
1494
|
}
|
|
1491
1495
|
|
|
1492
1496
|
const HOLD_CENSUS_CAP = 1e4;
|
|
@@ -1512,9 +1516,9 @@ function acknowledge(e, n, t, o) {
|
|
|
1512
1516
|
const t = {
|
|
1513
1517
|
left: HOLD_CENSUS_CAP
|
|
1514
1518
|
};
|
|
1515
|
-
const o = GlobalQueue.
|
|
1519
|
+
const o = GlobalQueue.Ke?.(e);
|
|
1516
1520
|
if (o !== undefined) for (const e of o) acknowledge(n, "optimistic", nodeName(e), reachesEffect(e, t));
|
|
1517
|
-
if (e.
|
|
1521
|
+
if (e.I !== null) for (const o of e.I) acknowledge(n, "affects", nodeName(o), reachesEffect(o, t));
|
|
1518
1522
|
}
|
|
1519
1523
|
|
|
1520
1524
|
/**
|
|
@@ -1528,9 +1532,9 @@ function acknowledge(e, n, t, o) {
|
|
|
1528
1532
|
const o = [ e ];
|
|
1529
1533
|
while (o.length > 0 && n.left-- > 0) {
|
|
1530
1534
|
const e = o.pop();
|
|
1531
|
-
for (let n = e.
|
|
1532
|
-
const e = n.
|
|
1533
|
-
if (e.
|
|
1535
|
+
for (let n = e.o; n !== null; n = n.t) {
|
|
1536
|
+
const e = n.u;
|
|
1537
|
+
if (e.i) return e;
|
|
1534
1538
|
if (!t.has(e)) {
|
|
1535
1539
|
t.add(e);
|
|
1536
1540
|
o.push(e);
|
|
@@ -1553,11 +1557,11 @@ function acknowledge(e, n, t, o) {
|
|
|
1553
1557
|
const e = o.pop();
|
|
1554
1558
|
if (t.has(e)) continue;
|
|
1555
1559
|
t.add(e);
|
|
1556
|
-
for (let t = e.
|
|
1557
|
-
const s = t.
|
|
1558
|
-
if (s.
|
|
1559
|
-
const t = s.
|
|
1560
|
-
const o = s.
|
|
1560
|
+
for (let t = e.o; t !== null; t = t.t) {
|
|
1561
|
+
const s = t.u;
|
|
1562
|
+
if (s.l & CONFIG_VERDICT) {
|
|
1563
|
+
const t = s.Ve ?? 0;
|
|
1564
|
+
const o = s.i ? s : reachesEffect(s, i);
|
|
1561
1565
|
if (t & 1 && o !== null) acknowledge(n, "isPending", nodeName(e), o);
|
|
1562
1566
|
if (t & 2 && o !== null) acknowledge(n, "latest", nodeName(e), o);
|
|
1563
1567
|
}
|
|
@@ -1628,9 +1632,9 @@ function trackHoldSettled(e) {
|
|
|
1628
1632
|
let i;
|
|
1629
1633
|
let s;
|
|
1630
1634
|
let r = -Infinity;
|
|
1631
|
-
for (const n of e.
|
|
1632
|
-
if (typeof n.
|
|
1633
|
-
const e = n.
|
|
1635
|
+
for (const n of e.je) {
|
|
1636
|
+
if (typeof n.X === "function" || isCompanion(n)) continue;
|
|
1637
|
+
const e = n.ge;
|
|
1634
1638
|
if (e === undefined || e.kind !== "write") continue;
|
|
1635
1639
|
if (o === null) o = n;
|
|
1636
1640
|
const a = {
|
|
@@ -1657,7 +1661,7 @@ function trackHoldSettled(e) {
|
|
|
1657
1661
|
return;
|
|
1658
1662
|
}
|
|
1659
1663
|
censusRegistrations(e, n);
|
|
1660
|
-
censusCompanions([ ...e.
|
|
1664
|
+
censusCompanions([ ...e.je, ...n.blockers ], n);
|
|
1661
1665
|
const a = now();
|
|
1662
1666
|
// The hold began no later than its first parked flush; an interaction stamp
|
|
1663
1667
|
// reaches further back (dispatch). A node rewritten mid-hold keeps only its
|
|
@@ -1818,7 +1822,7 @@ function checkLongHold(e, n) {
|
|
|
1818
1822
|
if (!options.optimisticReverts || isCompanion(e)) return;
|
|
1819
1823
|
// Method call, as every commit path does: store slot nodes share one
|
|
1820
1824
|
// comparator that reads `this._host` (#3687).
|
|
1821
|
-
const i = e.
|
|
1825
|
+
const i = e.Ye;
|
|
1822
1826
|
if (i && i.call(e, n, t)) return;
|
|
1823
1827
|
const s = nodeName(e);
|
|
1824
1828
|
// The two values are user data: quoted only under `values: "full"`; the
|
|
@@ -1847,8 +1851,7 @@ function checkLongHold(e, n) {
|
|
|
1847
1851
|
// --- Graph growth -----------------------------------------------------------------
|
|
1848
1852
|
/**
|
|
1849
1853
|
* Per route: at its last `visits` compared settles, oldest first, the
|
|
1850
|
-
* graph's size and the part of it
|
|
1851
|
-
* navigation opened.
|
|
1854
|
+
* graph's size and the part of it already alive as a navigation opened.
|
|
1852
1855
|
*/ const routeCounts = new Map;
|
|
1853
1856
|
|
|
1854
1857
|
/**
|
|
@@ -1856,6 +1859,12 @@ function checkLongHold(e, n) {
|
|
|
1856
1859
|
* its params or query change) continues the visit and is not compared.
|
|
1857
1860
|
*/ let visitRoute;
|
|
1858
1861
|
|
|
1862
|
+
/**
|
|
1863
|
+
* Every node met as a navigation opened, weakly, for the session: one set,
|
|
1864
|
+
* not one per navigation (see `trackGraph`). Never reset — a node in it was
|
|
1865
|
+
* alive as a navigation opened whichever window met it.
|
|
1866
|
+
*/ const opened = new WeakSet;
|
|
1867
|
+
|
|
1859
1868
|
/**
|
|
1860
1869
|
* The live graph's size — a walk, not a counter: nothing is charged at node
|
|
1861
1870
|
* creation, disposal, write or re-run; the engine asks at a navigation's
|
|
@@ -1876,7 +1885,7 @@ function checkLongHold(e, n) {
|
|
|
1876
1885
|
* The walk behind `graphSize()`. Given `before` alone, it adds every node it
|
|
1877
1886
|
* meets to that set: what is alive as a navigation opens. Given `before` and
|
|
1878
1887
|
* `survived`, it also counts into `survived` the nodes it meets that are in
|
|
1879
|
-
* the set, and an edge when both its ends are: what outlived
|
|
1888
|
+
* the set, and an edge when both its ends are: what outlived a navigation.
|
|
1880
1889
|
* Nothing is retained — the set is weak.
|
|
1881
1890
|
*/ function walkGraph(e, n) {
|
|
1882
1891
|
const t = liveRootOwners();
|
|
@@ -1900,35 +1909,35 @@ function checkLongHold(e, n) {
|
|
|
1900
1909
|
const meet = e => {
|
|
1901
1910
|
if (i.has(e)) return;
|
|
1902
1911
|
i.add(e);
|
|
1903
|
-
count(e, typeof e.
|
|
1912
|
+
count(e, typeof e.X === "function" ? "computations" : "signals");
|
|
1904
1913
|
s.push(e);
|
|
1905
1914
|
};
|
|
1906
1915
|
for (const e of t) {
|
|
1907
1916
|
count(e, "roots");
|
|
1908
1917
|
count(e, "owners");
|
|
1909
1918
|
// Iterative: a deep tree must not grow the call stack.
|
|
1910
|
-
let n = e.
|
|
1919
|
+
let n = e.ze;
|
|
1911
1920
|
for (;;) {
|
|
1912
1921
|
while (n !== null) {
|
|
1913
1922
|
count(n, "owners");
|
|
1914
|
-
if (typeof n.
|
|
1915
|
-
if (n.
|
|
1916
|
-
n = n.
|
|
1923
|
+
if (typeof n.X === "function") meet(n);
|
|
1924
|
+
if (n.ze !== null) r.push(n);
|
|
1925
|
+
n = n.Xe;
|
|
1917
1926
|
}
|
|
1918
1927
|
const e = r.pop();
|
|
1919
1928
|
if (e === undefined) break;
|
|
1920
|
-
n = e.
|
|
1929
|
+
n = e.ze;
|
|
1921
1930
|
}
|
|
1922
1931
|
}
|
|
1923
1932
|
for (let t = 0; t < s.length; t++) {
|
|
1924
1933
|
const i = s[t];
|
|
1925
1934
|
// Whoever reads this node: owned computations are already known; an
|
|
1926
1935
|
// ownerless one is met only here.
|
|
1927
|
-
for (let e = i.
|
|
1928
|
-
if (typeof i.
|
|
1936
|
+
for (let e = i.o; e !== null; e = e.t) meet(e.u);
|
|
1937
|
+
if (typeof i.X === "function") for (let t = i.C; t !== null; t = t.G) {
|
|
1929
1938
|
o.edges++;
|
|
1930
|
-
meet(t.
|
|
1931
|
-
if (n !== undefined && e.has(i) && e.has(t.
|
|
1939
|
+
meet(t.S);
|
|
1940
|
+
if (n !== undefined && e.has(i) && e.has(t.S)) n.edges++;
|
|
1932
1941
|
}
|
|
1933
1942
|
}
|
|
1934
1943
|
return o;
|
|
@@ -1936,11 +1945,11 @@ function checkLongHold(e, n) {
|
|
|
1936
1945
|
|
|
1937
1946
|
/**
|
|
1938
1947
|
* openNavigation, while the growth check is on: what is alive as a
|
|
1939
|
-
* navigation opens, for its settle to tell
|
|
1948
|
+
* navigation opens, added to what earlier ones met, for its settle to tell
|
|
1949
|
+
* apart from what it created.
|
|
1940
1950
|
*/ function snapshotGraph() {
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
return e;
|
|
1951
|
+
walkGraph(opened);
|
|
1952
|
+
return opened;
|
|
1944
1953
|
}
|
|
1945
1954
|
|
|
1946
1955
|
/**
|
|
@@ -1954,10 +1963,19 @@ function checkLongHold(e, n) {
|
|
|
1954
1963
|
*
|
|
1955
1964
|
* A count taken at an arrival also holds everything the visit just created,
|
|
1956
1965
|
* so a page whose correctly owned content is bigger each visit climbs too.
|
|
1957
|
-
* What tells the two apart is what outlived
|
|
1958
|
-
*
|
|
1959
|
-
* visit's own content is not among them; what an earlier
|
|
1960
|
-
* is, whatever the content does. A series reports only
|
|
1966
|
+
* What tells the two apart is what outlived a navigation: the nodes alive
|
|
1967
|
+
* as this or any earlier navigation opened (`opened`) that are still alive
|
|
1968
|
+
* at this settle. A visit's own content is not among them; what an earlier
|
|
1969
|
+
* visit left behind is, whatever the content does. A series reports only
|
|
1970
|
+
* when both climb.
|
|
1971
|
+
*
|
|
1972
|
+
* The set is the session's, not this navigation's own snapshot: the walk
|
|
1973
|
+
* meets a long-lived source only through a page that reads it, so a
|
|
1974
|
+
* per-navigation snapshot holds a module-level signal (an HMR registry, a
|
|
1975
|
+
* global store) only when the page just left read it too, and arriving from
|
|
1976
|
+
* pages that share ever more with this one read as growth (#4101). Kept
|
|
1977
|
+
* across navigations, such a source counts at every settle once met, the
|
|
1978
|
+
* same each visit; only what keeps accumulating climbs.
|
|
1961
1979
|
*/ const GRAPH_SERIES = [ "owners", "computations", "signals", "edges" ];
|
|
1962
1980
|
|
|
1963
1981
|
function trackGraph(e) {
|
|
@@ -2022,7 +2040,7 @@ function trackGraph(e) {
|
|
|
2022
2040
|
const join = (e, n) => e.map(e => e[n]).join(" → ");
|
|
2023
2041
|
const p = l.map(e => `${e} ${join(u, e)} (of ${join(f, e)})`).join("; ");
|
|
2024
2042
|
const m = l.includes("owners") ? "a createRoot() in an effect or handler with no dispose, or a Portal or panel mounted per visit" : l.includes("computations") ? "an effect or memo created with no owner (a module-level or callback createEffect) that only its sources keep alive" : "a subscription per visit to something long-lived — a global store or signal read by a computation that outlives the visit";
|
|
2025
|
-
const g = `[GRAPH_GROWTH] the live graph grew on ${o.visits} consecutive visits to ${r}, and so did ` + `the part that outlived each navigation there: ${p} ` + `(${s.roots} roots)${h.length > 1 ? `, across visits to ${h.join(", ")}` : ""}. Each ` + `visit left something behind that the next did not reclaim — the shape says ${m}. Dispose ` + `what a visit creates (onCleanup, or return the disposer from onSettled) and own it under the ` + `route's component so leaving the route tears it down.`;
|
|
2043
|
+
const g = `[GRAPH_GROWTH] the live graph grew on ${o.visits} consecutive visits to ${r}, and so did ` + `the part that outlived each navigation there: ${p} ` + `(${s.roots} roots)${h.length > 1 ? `, across visits to ${h.join(", ")}` : ""}. Each ` + `visit left something behind that the next did not reclaim — the shape says ${m}. Dispose ` + `what a visit creates (onCleanup, or return the disposer from onSettled) and own it under the ` + `route's component so leaving the route tears it down. A cache that never evicts grows alike.`;
|
|
2026
2044
|
const w = {
|
|
2027
2045
|
route: r,
|
|
2028
2046
|
grew: l,
|
|
@@ -2265,8 +2283,8 @@ function closeNavigation(e) {
|
|
|
2265
2283
|
|
|
2266
2284
|
/** holdStart: `t`'s staged writes are parked — their navigations settle with `t`. */ function markNavigationsHeld(e) {
|
|
2267
2285
|
if (openNavs.size === 0) return;
|
|
2268
|
-
for (const n of e.
|
|
2269
|
-
const e = n.
|
|
2286
|
+
for (const n of e.je) {
|
|
2287
|
+
const e = n.ge?.origin;
|
|
2270
2288
|
if (e?.kind !== "navigation") continue;
|
|
2271
2289
|
const t = navStates.get(e);
|
|
2272
2290
|
if (t !== undefined) t.held = true;
|
|
@@ -2275,8 +2293,8 @@ function closeNavigation(e) {
|
|
|
2275
2293
|
|
|
2276
2294
|
/** transitionSettled: `t` commits — the navigations whose writes it staged are done. */ function settleNavigations(e, n) {
|
|
2277
2295
|
if (openNavs.size === 0) return;
|
|
2278
|
-
for (const t of e.
|
|
2279
|
-
const e = t.
|
|
2296
|
+
for (const t of e.je) {
|
|
2297
|
+
const e = t.ge?.origin;
|
|
2280
2298
|
if (e?.kind !== "navigation") continue;
|
|
2281
2299
|
const o = navStates.get(e);
|
|
2282
2300
|
if (o === undefined || !openNavs.has(o)) continue;
|
|
@@ -2597,8 +2615,8 @@ function openInteraction(e, n, t) {
|
|
|
2597
2615
|
|
|
2598
2616
|
/** holdStart: `t` parked writes — the interactions that performed them wait for `t`. */ function markInteractionsHeld(e) {
|
|
2599
2617
|
if (openInteractions.size === 0) return;
|
|
2600
|
-
for (const n of e.
|
|
2601
|
-
const t = openInteractionOf(n.
|
|
2618
|
+
for (const n of e.je) {
|
|
2619
|
+
const t = openInteractionOf(n.ge?.origin);
|
|
2602
2620
|
if (t !== undefined) {
|
|
2603
2621
|
t.heldIn.add(e);
|
|
2604
2622
|
t.held = true;
|
|
@@ -2685,7 +2703,7 @@ const engineHooks = {
|
|
|
2685
2703
|
prevDeps: n || !wantsRerun() ? null : captureDeps(e),
|
|
2686
2704
|
// Mirror recompute's own prev-value resolution: an earlier run in the
|
|
2687
2705
|
// same flush may still be holding in _pendingValue.
|
|
2688
|
-
prevValue: e.
|
|
2706
|
+
prevValue: e.ue !== NOT_PENDING ? e.ue : e.Ie,
|
|
2689
2707
|
// A create run inherits the interaction of whatever is building it: the
|
|
2690
2708
|
// enclosing recompute (a parent's fn creating children) or, at the top
|
|
2691
2709
|
// of the recompute stack, the effect callback / handler frame.
|
|
@@ -2711,8 +2729,8 @@ const engineHooks = {
|
|
|
2711
2729
|
// committed compute output is an unchanged run. `undefined` outputs are
|
|
2712
2730
|
// exempt — a side-effect-only compute's work IS its effect phase, and
|
|
2713
2731
|
// identity of `undefined` proves nothing.
|
|
2714
|
-
if (t && r.causes !== null && e.
|
|
2715
|
-
const n = e.
|
|
2732
|
+
if (t && r.causes !== null && e.i) {
|
|
2733
|
+
const n = e.ue !== NOT_PENDING ? e.ue : e.Ie;
|
|
2716
2734
|
if (n !== undefined && n === r.prevValue) t = false;
|
|
2717
2735
|
}
|
|
2718
2736
|
// Unstable-output check: memos only, non-create, plain runs with a
|
|
@@ -2720,12 +2738,12 @@ const engineHooks = {
|
|
|
2720
2738
|
// plain-flush memo commits and in `_value` for direct ones. Overlay runs
|
|
2721
2739
|
// are excluded — an optimistic re-derive legitimately produces fresh
|
|
2722
2740
|
// equivalents while the lane settles.
|
|
2723
|
-
if (r.causes !== null && t && !o && !i && !e.
|
|
2741
|
+
if (r.causes !== null && t && !o && !i && !e.i) checkUnstableOutput(e, r.prevValue, e.ue !== NOT_PENDING ? e.ue : e.Ie);
|
|
2724
2742
|
// The same exemption for memos: a run that committed `undefined` did its
|
|
2725
2743
|
// work as a side effect (a projection's draft writes or reconcile, a
|
|
2726
2744
|
// write from inside a memo), so core's `undefined === undefined` gate
|
|
2727
2745
|
// proves nothing. Reported as changed, so no consumer counts it as waste.
|
|
2728
|
-
if (!t && (e.
|
|
2746
|
+
if (!t && (e.ue !== NOT_PENDING ? e.ue : e.Ie) === undefined) t = true;
|
|
2729
2747
|
if (r.causes !== null) recordRerun(e, r, {
|
|
2730
2748
|
selfMs: c,
|
|
2731
2749
|
totalMs: a
|
|
@@ -2737,7 +2755,7 @@ const engineHooks = {
|
|
|
2737
2755
|
noteInteractionRun(r.interaction, c, true);
|
|
2738
2756
|
// The first effect callback runs with no re-run record to inherit from;
|
|
2739
2757
|
// hand it the interaction that built the node (see effectRunStart).
|
|
2740
|
-
e.
|
|
2758
|
+
e.Me = r.interaction;
|
|
2741
2759
|
if (openFlush !== null) noteFlushRun(openFlush, true, r.interaction);
|
|
2742
2760
|
if (records.observed("create")) {
|
|
2743
2761
|
const n = {
|
|
@@ -2767,9 +2785,9 @@ const engineHooks = {
|
|
|
2767
2785
|
trackFlightStart(e, n);
|
|
2768
2786
|
},
|
|
2769
2787
|
asyncStart(e) {
|
|
2770
|
-
asyncStartSeq = e.
|
|
2771
|
-
asyncStartTime = e.
|
|
2772
|
-
asyncStartValue = e.
|
|
2788
|
+
asyncStartSeq = e.ge?.seq ?? 0;
|
|
2789
|
+
asyncStartTime = e.ie;
|
|
2790
|
+
asyncStartValue = e.Ie;
|
|
2773
2791
|
},
|
|
2774
2792
|
asyncEnd(e, n, t, o) {
|
|
2775
2793
|
if (o) {
|
|
@@ -2779,7 +2797,7 @@ const engineHooks = {
|
|
|
2779
2797
|
// (or `_time`, for a same-reference commit under `equals: false`), and
|
|
2780
2798
|
// a transition hold parks the value in `_pendingValue`. A landing the
|
|
2781
2799
|
// equality gate swallowed moves none of them and must leave no stamp.
|
|
2782
|
-
const o = e.
|
|
2800
|
+
const o = e.Ie !== asyncStartValue || e.ie !== asyncStartTime || e.ue === t;
|
|
2783
2801
|
if (o) stampWrite(e, "async", n === undefined ? NO_VALUES : n, t);
|
|
2784
2802
|
// Flight over either way — an equality-swallowed landing still spent
|
|
2785
2803
|
// the wall time (finalizeFlight only chains through a fresh stamp).
|
|
@@ -2789,12 +2807,12 @@ const engineHooks = {
|
|
|
2789
2807
|
// Landed through setSignal: reclassify its "write" stamp as an async
|
|
2790
2808
|
// landing — but only if it actually stamped (the value changed) since
|
|
2791
2809
|
// asyncStart; a no-change landing must leave no fresh stamp behind.
|
|
2792
|
-
const i = e.
|
|
2810
|
+
const i = e.ge;
|
|
2793
2811
|
if (i !== undefined && i.seq > asyncStartSeq && i.kind === "write") stampWrite(e, "async", NO_VALUES, t);
|
|
2794
2812
|
finalizeFlight(e);
|
|
2795
2813
|
},
|
|
2796
2814
|
effectRunStart(e) {
|
|
2797
|
-
pushFrame("effect", nodeName(e), e.
|
|
2815
|
+
pushFrame("effect", nodeName(e), e.Me, e);
|
|
2798
2816
|
// The frame's own `at` doubles as the record's start: set only while
|
|
2799
2817
|
// listened, so a listener arriving mid-callback finds no start and the
|
|
2800
2818
|
// end emits nothing for it — and an unlistened callback pays no clock read.
|
|
@@ -3049,11 +3067,10 @@ const attribution = {
|
|
|
3049
3067
|
// The buffers are typed by their variable; the switch is the proof.
|
|
3050
3068
|
return n;
|
|
3051
3069
|
},
|
|
3052
|
-
markFlight(e, n
|
|
3053
|
-
// Earliest wins: re-marking (a cache re-serving the
|
|
3054
|
-
// not move the origin later.
|
|
3055
|
-
|
|
3056
|
-
if (t === undefined || n < t) flightOrigins.set(e, n);
|
|
3070
|
+
markFlight(e, n) {
|
|
3071
|
+
// Earliest wins (the slot's rule): re-marking (a cache re-serving the
|
|
3072
|
+
// same promise) must not move the origin later.
|
|
3073
|
+
OBSERVE.attribution.markFlight(e, n);
|
|
3057
3074
|
}
|
|
3058
3075
|
};
|
|
3059
3076
|
|