@solidjs/signals 2.0.0-rc.7 → 2.0.0-rc.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/dist/dev-shared.js +5243 -0
  2. package/dist/dev.attribution.js +2333 -0
  3. package/dist/dev.js +1229 -6834
  4. package/dist/observe/affects.js +127 -0
  5. package/dist/observe/attribution.js +1 -0
  6. package/dist/observe/boundaries.js +586 -0
  7. package/dist/observe/core/action.js +167 -0
  8. package/dist/observe/core/async.js +736 -0
  9. package/dist/observe/core/attribution-hooks.js +60 -0
  10. package/dist/observe/core/attribution.js +2318 -0
  11. package/dist/{types-cjs/core/constants.d.cts → observe/core/constants.js} +129 -73
  12. package/dist/{types-cjs/core/context.d.cts → observe/core/context.js} +37 -12
  13. package/dist/observe/core/core.js +1289 -0
  14. package/dist/observe/core/dev.js +232 -0
  15. package/dist/observe/core/effect.js +179 -0
  16. package/dist/observe/core/error.js +80 -0
  17. package/dist/observe/core/external.js +98 -0
  18. package/dist/observe/core/graph.js +155 -0
  19. package/dist/observe/core/heap.js +147 -0
  20. package/dist/observe/core/invariants.js +43 -0
  21. package/dist/observe/core/lanes.js +168 -0
  22. package/dist/observe/core/optimistic.js +438 -0
  23. package/dist/observe/core/owner.js +308 -0
  24. package/dist/observe/core/scheduler.js +1190 -0
  25. package/dist/observe/core/verdict.js +551 -0
  26. package/dist/observe/index.js +55 -0
  27. package/dist/observe/map.js +482 -0
  28. package/dist/{types-cjs/signals.d.cts → observe/signals.js} +295 -317
  29. package/dist/observe/store/index.js +38 -0
  30. package/dist/observe/store/next/optimistic.js +651 -0
  31. package/dist/observe/store/next/projection.js +242 -0
  32. package/dist/observe/store/next/reconcile.js +373 -0
  33. package/dist/observe/store/next/store.js +2213 -0
  34. package/dist/observe/store/next/target.js +51 -0
  35. package/dist/observe/store/store.js +323 -0
  36. package/dist/observe/store/storePath.js +103 -0
  37. package/dist/observe/store/utils.js +210 -0
  38. package/dist/prod/attribution.js +32 -0
  39. package/dist/prod/core/action.js +52 -32
  40. package/dist/prod/core/async.js +171 -127
  41. package/dist/prod/core/constants.js +24 -1
  42. package/dist/prod/core/context.js +3 -3
  43. package/dist/prod/core/core.js +434 -291
  44. package/dist/prod/core/effect.js +54 -34
  45. package/dist/prod/core/external.js +2 -2
  46. package/dist/prod/core/graph.js +32 -32
  47. package/dist/prod/core/heap.js +49 -44
  48. package/dist/prod/core/lanes.js +45 -38
  49. package/dist/prod/core/optimistic.js +207 -73
  50. package/dist/prod/core/owner.js +43 -39
  51. package/dist/prod/core/scheduler.js +319 -190
  52. package/dist/prod/core/verdict.js +67 -67
  53. package/dist/prod/index.js +9 -3
  54. package/dist/prod/map.js +292 -141
  55. package/dist/prod/signals.js +7 -10
  56. package/dist/prod/store/next/optimistic.js +140 -125
  57. package/dist/prod/store/next/projection.js +3 -3
  58. package/dist/prod/store/next/reconcile.js +87 -84
  59. package/dist/prod/store/next/store.js +524 -280
  60. package/dist/prod/store/next/target.js +32 -10
  61. package/dist/prod/store/store.js +12 -14
  62. package/dist/prod/store/utils.js +36 -27
  63. package/dist/types/attribution.d.ts +15 -0
  64. package/dist/types/attribution.prod.d.ts +13 -0
  65. package/dist/types/core/async.d.ts +1 -1
  66. package/dist/types/core/attribution-hooks.d.ts +119 -6
  67. package/dist/types/core/attribution.d.ts +208 -53
  68. package/dist/types/core/constants.d.ts +23 -0
  69. package/dist/types/core/core.d.ts +7 -0
  70. package/dist/types/core/dev.d.ts +124 -26
  71. package/dist/types/core/effect.d.ts +8 -1
  72. package/dist/types/core/index.d.ts +2 -1
  73. package/dist/types/core/lanes.d.ts +8 -1
  74. package/dist/types/core/scheduler.d.ts +60 -0
  75. package/dist/types/core/types.d.ts +29 -9
  76. package/dist/types/index.d.ts +9 -2
  77. package/dist/types/map.d.ts +2 -0
  78. package/dist/types/store/index.d.ts +1 -0
  79. package/dist/types/store/next/store.d.ts +5 -0
  80. package/dist/types/store/next/target.d.ts +36 -9
  81. package/dist/types/store/utils.d.ts +7 -0
  82. package/package.json +21 -17
  83. package/dist/node.cjs +0 -10541
  84. package/dist/node.dev.cjs +0 -13724
  85. package/dist/types-cjs/affects.d.cts +0 -47
  86. package/dist/types-cjs/boundaries.d.cts +0 -175
  87. package/dist/types-cjs/core/action.d.cts +0 -64
  88. package/dist/types-cjs/core/async.d.cts +0 -23
  89. package/dist/types-cjs/core/attribution-hooks.d.cts +0 -127
  90. package/dist/types-cjs/core/attribution.d.cts +0 -495
  91. package/dist/types-cjs/core/core.d.cts +0 -185
  92. package/dist/types-cjs/core/dev.d.cts +0 -136
  93. package/dist/types-cjs/core/effect.d.cts +0 -30
  94. package/dist/types-cjs/core/error.d.cts +0 -56
  95. package/dist/types-cjs/core/external.d.cts +0 -15
  96. package/dist/types-cjs/core/graph.d.cts +0 -28
  97. package/dist/types-cjs/core/heap.d.cts +0 -24
  98. package/dist/types-cjs/core/index.d.cts +0 -13
  99. package/dist/types-cjs/core/invariants.d.cts +0 -59
  100. package/dist/types-cjs/core/lanes.d.cts +0 -54
  101. package/dist/types-cjs/core/optimistic.d.cts +0 -6
  102. package/dist/types-cjs/core/owner.d.cts +0 -124
  103. package/dist/types-cjs/core/scheduler.d.cts +0 -236
  104. package/dist/types-cjs/core/types.d.cts +0 -204
  105. package/dist/types-cjs/core/verdict.d.cts +0 -2
  106. package/dist/types-cjs/index.d.cts +0 -10
  107. package/dist/types-cjs/map.d.cts +0 -69
  108. package/dist/types-cjs/package.json +0 -3
  109. package/dist/types-cjs/store/index.d.cts +0 -18
  110. package/dist/types-cjs/store/next/optimistic.d.cts +0 -25
  111. package/dist/types-cjs/store/next/projection.d.cts +0 -8
  112. package/dist/types-cjs/store/next/reconcile.d.cts +0 -10
  113. package/dist/types-cjs/store/next/store.d.cts +0 -121
  114. package/dist/types-cjs/store/next/target.d.cts +0 -166
  115. package/dist/types-cjs/store/store.d.cts +0 -143
  116. package/dist/types-cjs/store/storePath.d.cts +0 -58
  117. package/dist/types-cjs/store/utils.d.cts +0 -74
@@ -0,0 +1,155 @@
1
+ import { CONFIG_AUTO_DISPOSE, STATUS_PENDING, REACTIVE_DISPOSED, REACTIVE_ZOMBIE, CONFIG_SLOT_NODE, REACTIVE_RECOMPUTING_DEPS } from "./constants.js";
2
+
3
+ import { slotUnobservedHook } from "./core.js";
4
+
5
+ import { deleteFromHeap, queueFor } from "./heap.js";
6
+
7
+ import { disposeChildren } from "./owner.js";
8
+
9
+ import { bumpNotifyEpoch } from "./scheduler.js";
10
+
11
+ // https://github.com/stackblitz/alien-signals/blob/v2.0.3/src/system.ts#L100
12
+ function unlinkSubs(e) {
13
+ const n = e.oe;
14
+ const l = e.ne;
15
+ const o = e.se;
16
+ const s = e.mn;
17
+ if (o !== null) o.mn = s; else n.Wt = s;
18
+ if (s !== null) s.se = o; else {
19
+ n.u = o;
20
+ if (o === null) {
21
+ // Slot nodes (store leaves) dispatch to the ONE shared hook — no
22
+ // per-node unobserved closure, no NodeExtension to hold it.
23
+ if (n.T & CONFIG_SLOT_NODE) slotUnobservedHook(n); else n.o?.qt?.();
24
+ // No more subscribers; only tear down if CONFIG_AUTO_DISPOSE is set.
25
+ // A pending node is exempt: its in-flight async work (or the
26
+ // transition holding it) is an observer — tearing down would orphan
27
+ // the work and re-execute it on the next read. The settle path runs
28
+ // this same last-one-out check when that observer releases (the
29
+ // untracked-read dormancy sweep guards on pending identically).
30
+ const e = n;
31
+ e.ot && e.T & CONFIG_AUTO_DISPOSE && !(e.it & REACTIVE_ZOMBIE) && !(e.S & STATUS_PENDING) && unobserved(e);
32
+ }
33
+ }
34
+ return l;
35
+ }
36
+
37
+ function trimStaleDeps(e) {
38
+ const n = e.Dt;
39
+ let l = n !== null ? n.ne : e.te;
40
+ if (l !== null) {
41
+ do {
42
+ l = unlinkSubs(l);
43
+ } while (l !== null);
44
+ if (n !== null) n.ne = null; else e.te = null;
45
+ }
46
+ }
47
+
48
+ // Shared by unobserved() and the disposeChildren child loop. The truthy guard
49
+ // (not `!== null`) matters: plain Owners in a child chain have no _deps field,
50
+ // and skipping early also avoids adding one (hidden-class churn) via the
51
+ // null-out below.
52
+ function clearDeps(e) {
53
+ let n = e.te;
54
+ if (!n) return;
55
+ do {
56
+ n = unlinkSubs(n);
57
+ } while (n !== null);
58
+ e.te = null;
59
+ e.Dt = null;
60
+ }
61
+
62
+ function unobserved(e) {
63
+ deleteFromHeap(e, queueFor(e));
64
+ clearDeps(e);
65
+ disposeChildren(e, true);
66
+ }
67
+
68
+ /**
69
+ * Deferred dormancy for never-observed auto-dispose computeds (#3078).
70
+ *
71
+ * An untracked top-level read of a subscriber-less observation-lifecycle memo
72
+ * used to call unobserved() inline at the end of read(). That kept the leak
73
+ * closed (the compute links the memo into its deps' sub lists — without a
74
+ * teardown point a never-observed memo is retained by its sources forever;
75
+ * upstream alien-signals has exactly this retention), but it made reads
76
+ * destructive: each read disposed the node, the next read revived it with a
77
+ * full recompute in whatever ambient transition/lane context happened to be
78
+ * current, so consecutive reads could return different answers with no write
79
+ * in between.
80
+ *
81
+ * Instead, reads queue the node here and the scheduler sweeps at the top of
82
+ * the next flush (before runHeap, so a same-tick dirtying is reclaimed
83
+ * instead of recomputed). Reads become idempotent within a tick (the node
84
+ * stays alive and serves its cache, uniform with observed memos) while
85
+ * reclamation still happens within one microtask — the enqueue site arms
86
+ * schedule(), so a flush is guaranteed even when no other work is queued.
87
+ */ const dormantNodes = new Set;
88
+
89
+ function sweepDormant() {
90
+ if (dormantNodes.size === 0) return;
91
+ for (const e of dormantNodes) {
92
+ // Re-validate at sweep time: the node may have gained a subscriber (its
93
+ // lifecycle is the unlinkSubs cascade now), gone pending (in-flight async
94
+ // is an observer; the settle path re-runs last-one-out), lost its
95
+ // AUTO_DISPOSE bit (owner teardown strips it, #3024), or already been
96
+ // torn down.
97
+ if (!e.u && e.T & CONFIG_AUTO_DISPOSE && !(e.S & STATUS_PENDING) && !(e.it & (REACTIVE_DISPOSED | REACTIVE_ZOMBIE))) {
98
+ unobserved(e);
99
+ }
100
+ }
101
+ dormantNodes.clear();
102
+ }
103
+
104
+ // https://github.com/stackblitz/alien-signals/blob/v2.0.3/src/system.ts#L52
105
+ function link(e, n, l = false) {
106
+ // Repeat touches within one pass AND-combine `_pendingObserver`: a probe
107
+ // read (`isPending(() => x())`) beside a value read of the same dep must
108
+ // not relabel the value dependency as probe-only — the value read is what
109
+ // real-error propagation and affects() coverage key off, regardless of
110
+ // read order within the computation.
111
+ const o = n.Dt;
112
+ if (o !== null && o.oe === e) {
113
+ o.ye &&= l;
114
+ return;
115
+ }
116
+ let s = null;
117
+ const t = n.it & REACTIVE_RECOMPUTING_DEPS;
118
+ if (t) {
119
+ s = o !== null ? o.ne : n.te;
120
+ if (s !== null && s.oe === e) {
121
+ s.fn = n.Pt;
122
+ n.Dt = s;
123
+ // First touch of this pass: the previous pass's label is stale.
124
+ s.ye = l;
125
+ return;
126
+ }
127
+ }
128
+ // A link stamped with the current pass generation was created or reused
129
+ // in-order during this recompute, i.e. it already sits in the validated
130
+ // [deps.._depsTail] prefix — the O(1) equivalent of scanning the dep list
131
+ // (the old alien-signals `isValidLink` walk, O(n²) when a computation
132
+ // re-reads earlier deps non-consecutively, e.g. store leaf reads).
133
+ const r = e.Wt;
134
+ if (r !== null && r.le === n && (!t || r.fn === n.Pt)) {
135
+ // Gen-matched during a recompute = repeat touch this pass (AND); outside
136
+ // a recompute there is no pass boundary, so the latest read labels it.
137
+ if (t) r.ye &&= l; else r.ye = l;
138
+ return;
139
+ }
140
+ const u = n.Dt = e.Wt = {
141
+ oe: e,
142
+ le: n,
143
+ ne: s,
144
+ mn: r,
145
+ se: null,
146
+ fn: n.Pt,
147
+ ye: l
148
+ };
149
+ if (o !== null) o.ne = u; else n.te = u;
150
+ if (r !== null) r.se = u; else e.u = u;
151
+ // New subscriber edge: staged-rewrite skips (§12d) must not miss it.
152
+ bumpNotifyEpoch();
153
+ }
154
+
155
+ export { clearDeps, dormantNodes, link, sweepDormant, trimStaleDeps, unlinkSubs, unobserved };
@@ -0,0 +1,147 @@
1
+ import "./core.js";
2
+
3
+ import { REACTIVE_IN_HEAP, REACTIVE_IN_HEAP_HEIGHT, REACTIVE_ZOMBIE, REACTIVE_RECOMPUTING_DEPS, REACTIVE_MANUAL_WRITE, REACTIVE_CHECK, REACTIVE_DIRTY, CONFIG_FW_CHILDREN } from "./constants.js";
4
+
5
+ import { zombieQueue, dirtyQueue } from "./scheduler.js";
6
+
7
+ /** The queue a node belongs to, picked from its own zombie flag. */ function queueFor(e) {
8
+ return e.it & REACTIVE_ZOMBIE ? zombieQueue : dirtyQueue;
9
+ }
10
+
11
+ /**
12
+ * Schedule one subscriber to re-run on the next flush: inserted into its own
13
+ * (zombie-flag-routed) heap with the `_min` cursor pulled down. Tracked
14
+ * effects ride the heap too — the heap visit is their (empty) compute phase,
15
+ * which hands the callback to the user queue once the pass has committed
16
+ * (see GlobalQueue._update, #3291).
17
+ */ function enqueueSub(e) {
18
+ const E = queueFor(e);
19
+ if (E.St > e.At) E.St = e.At;
20
+ insertIntoHeap(e, E);
21
+ }
22
+
23
+ function actualInsertIntoHeap(e, E) {
24
+ const t = (e.Nt?.$t ? e.Nt.ln?.At : e.Nt?.At) ?? -1;
25
+ if (t >= e.At) e.At = t + 1;
26
+ const n = e.At;
27
+ const I = E.eE[n];
28
+ if (I === undefined) E.eE[n] = e; else {
29
+ const E = I.Qt;
30
+ E.yt = e;
31
+ e.Qt = E;
32
+ I.Qt = e;
33
+ }
34
+ if (n > E.EE) E.EE = n;
35
+ }
36
+
37
+ function insertIntoHeap(e, E) {
38
+ let t = e.it;
39
+ // RECOMPUTING refusals are not always losses: a genuinely missed wake (a
40
+ // write to a link this pass already validated) is latched link-side in
41
+ // insertSubs as REACTIVE_MISSED_WAKE for recompute's tail (#3037).
42
+ if (t & (REACTIVE_IN_HEAP | REACTIVE_RECOMPUTING_DEPS | REACTIVE_MANUAL_WRITE)) return;
43
+ if (t & REACTIVE_CHECK) {
44
+ e.it = t & -4 | REACTIVE_DIRTY | REACTIVE_IN_HEAP;
45
+ } else {
46
+ e.it = t | REACTIVE_IN_HEAP;
47
+ // An unmarked node entering an already-marked heap is marked on the
48
+ // spot, keeping the markHeap memo valid. `_marked` is only reset by
49
+ // runHeap, so a write between two mid-tick pulls (read-time markHeap +
50
+ // updateIfNecessary) would otherwise leave this node unmarked and every
51
+ // downstream pull stale until the next flush (#2922: the second
52
+ // `latest()` returned the first write's value). Invalidating the memo
53
+ // instead re-walked the WHOLE heap on the next pull — with N effects
54
+ // parked in the heap for a synchronous mount (each row writing a ref
55
+ // signal its effect subscribes to), mounting N rows was O(N²) (#3350).
56
+ // markNode's own guard skips an already-DIRTY node.
57
+ if (E.tE) markNode(e);
58
+ }
59
+ if (!(t & REACTIVE_IN_HEAP_HEIGHT)) actualInsertIntoHeap(e, E);
60
+ }
61
+
62
+ function insertIntoHeapHeight(e, E) {
63
+ let t = e.it;
64
+ if (t & (REACTIVE_IN_HEAP | REACTIVE_RECOMPUTING_DEPS | REACTIVE_IN_HEAP_HEIGHT | REACTIVE_MANUAL_WRITE)) return;
65
+ e.it = t | REACTIVE_IN_HEAP_HEIGHT;
66
+ actualInsertIntoHeap(e, E);
67
+ }
68
+
69
+ function deleteFromHeap(e, E) {
70
+ const t = e.it;
71
+ if (!(t & (REACTIVE_IN_HEAP | REACTIVE_IN_HEAP_HEIGHT))) return;
72
+ e.it = t & -25;
73
+ const n = e.At;
74
+ if (e.Qt === e) E.eE[n] = undefined; else {
75
+ const t = e.yt;
76
+ const I = E.eE[n];
77
+ const o = t ?? I;
78
+ if (e === I) E.eE[n] = t; else e.Qt.yt = t;
79
+ o.Qt = e.Qt;
80
+ }
81
+ e.Qt = e;
82
+ e.yt = undefined;
83
+ }
84
+
85
+ function markHeap(e) {
86
+ if (e.tE) return;
87
+ e.tE = true;
88
+ for (let E = 0; E <= e.EE; E++) {
89
+ for (let t = e.eE[E]; t !== undefined; t = t.yt) {
90
+ if (t.it & REACTIVE_IN_HEAP) markNode(t);
91
+ }
92
+ }
93
+ }
94
+
95
+ function markNode(e, E = REACTIVE_DIRTY) {
96
+ const t = e.it;
97
+ if ((t & (REACTIVE_CHECK | REACTIVE_DIRTY)) >= E) return;
98
+ e.it = t & -4 | E;
99
+ for (let E = e.u; E !== null; E = E.se) {
100
+ markNode(E.le, REACTIVE_CHECK);
101
+ }
102
+ // Firewall children (projection machinery only): gate the cold-extension
103
+ // deref on the config bit — markNode runs per sub edge per write, and an
104
+ // unconditional _x chase here taxed every propagation (diamond -22%).
105
+ if (e.T & CONFIG_FW_CHILDREN) {
106
+ for (let E = e.o.i; E !== null; E = E.ue) {
107
+ for (let e = E.u; e !== null; e = e.se) {
108
+ markNode(e.le, REACTIVE_CHECK);
109
+ }
110
+ }
111
+ }
112
+ }
113
+
114
+ function runHeap(e, E) {
115
+ e.tE = false;
116
+ for (e.St = 0; e.St <= e.EE; e.St++) {
117
+ let t = e.eE[e.St];
118
+ while (t !== undefined) {
119
+ if (t.it & REACTIVE_IN_HEAP) E(t); else adjustHeight(t, e);
120
+ t = e.eE[e.St];
121
+ }
122
+ }
123
+ e.EE = 0;
124
+ }
125
+
126
+ function adjustHeight(e, E) {
127
+ deleteFromHeap(e, E);
128
+ let t = e.At;
129
+ for (let E = e.te; E; E = E.ne) {
130
+ const e = E.oe;
131
+ const n = e.re || e;
132
+ if (n.ot && n.At >= t) t = n.At + 1;
133
+ }
134
+ if (e.At !== t) {
135
+ e.At = t;
136
+ for (let E = e.u; E !== null; E = E.se) {
137
+ // Route each subscriber by its own zombie flag, mirroring the
138
+ // post-recompute height-adjust path. Inserting into the running `heap`
139
+ // unconditionally can park a zombie in `dirtyQueue` (or a live node in
140
+ // `zombieQueue`), breaking the flag/queue invariant `deleteFromHeap`
141
+ // relies on — the same corruption class as #2759.
142
+ insertIntoHeapHeight(E.le, queueFor(E.le));
143
+ }
144
+ }
145
+ }
146
+
147
+ export { deleteFromHeap, enqueueSub, insertIntoHeap, insertIntoHeapHeight, markHeap, markNode, queueFor, runHeap };
@@ -0,0 +1,43 @@
1
+ import "./core.js";
2
+
3
+ /**
4
+ * INV-2: a node with an *active* override must be registered for reversion in
5
+ * the queue's or a transition's `_optimisticNodes`. An unregistered active
6
+ * override would survive transition completion forever. Runs at the end of
7
+ * every flush (not just quiescence — the invariant holds mid-transition).
8
+ * (There is no revert-target requirement: authoritative values commit
9
+ * silently into `_value` under the override mask — A17 — so reverting is
10
+ * just dropping the override.)
11
+ */ function devCheckActiveOverrides(e) {
12
+ return;
13
+ }
14
+
15
+ /** INV-1: an isPending() probe must never leak past its own call. */ function devCheckFlushStart() {
16
+ return;
17
+ }
18
+
19
+ /**
20
+ * Companion-vs-oracle census (#2838 pre-work). A NON-ASSERTING diff logger:
21
+ * at the end of every flush it compares each live companion's cached state
22
+ * against a fresh oracle and logs every distinct divergence fingerprint once
23
+ * (console, `[census]` prefix). Legit lane-scoped windows will show up too —
24
+ * the census exists to enumerate the full taxonomy of mid-flight divergence
25
+ * so the write-driven redesign knows every case it must produce, not to
26
+ * judge them. Enabled only when the COMPANION_CENSUS env var is set (in
27
+ * addition to `false`), so normal test runs pay one boolean check.
28
+ */ typeof globalThis !== "undefined" && !!globalThis.process?.env?.COMPANION_CENSUS;
29
+
30
+ function devCensusCompanions(e) {
31
+ return;
32
+ }
33
+
34
+ /**
35
+ * Quiescence checks. Run only when the system is fully drained: nothing
36
+ * scheduled, no active/stashed transitions, no live lanes. At that point no
37
+ * transition-scoped state may survive, and the lazily-created companions must
38
+ * agree with a fresh computation of their owner's state.
39
+ */ function devCheckQuiescent(e) {
40
+ return;
41
+ }
42
+
43
+ export { devCensusCompanions, devCheckActiveOverrides, devCheckFlushStart, devCheckQuiescent };
@@ -0,0 +1,168 @@
1
+ import { CONFIG_HAS_LANE, NOT_PENDING } from "./constants.js";
2
+
3
+ import { ext } from "./core.js";
4
+
5
+ import { currentTransition, waitingTransition, activeTransition } from "./scheduler.js";
6
+
7
+ // Map from optimistic signal to its lane (reused for multiple writes to same signal)
8
+ const signalLanes = new WeakMap;
9
+
10
+ // All active lanes (for cleanup on transition completion)
11
+ const activeLanes = new Set;
12
+
13
+ /**
14
+ * Get an existing lane for a signal or create a new one.
15
+ * Reuses lane for multiple writes to the same signal.
16
+ */ function getOrCreateLane(n) {
17
+ let e = signalLanes.get(n);
18
+ if (e) {
19
+ return findLane(e);
20
+ }
21
+ // Detect parent lane: _parentSource chains from pendingSignal → pendingValueComputed → original.
22
+ // The child lane should not merge with the parent lane.
23
+ const i = n.o?.Qe;
24
+ const t = i?.o?.Te;
25
+ const r = t ? findLane(t) : null;
26
+ e = {
27
+ Ln: n,
28
+ _e: new Set,
29
+ vn: [ [], [] ],
30
+ gn: null,
31
+ Ee: activeTransition,
32
+ hn: r
33
+ };
34
+ signalLanes.set(n, e);
35
+ activeLanes.add(e);
36
+ // A companion may have written before the owner's first optimistic write
37
+ // (affects() as an action's first statement pokes the verdict companion of a
38
+ // still lane-less node, #2887), leaving its lane parentless. Adopt it now:
39
+ // parent-child is a property of the nodes, not of write order — otherwise
40
+ // the owner's write merges the companion's subscribers into this lane and
41
+ // their effects wait on its async instead of flushing immediately.
42
+ adoptCompanionLane(n.o?.be, e);
43
+ adoptCompanionLane(n.o?.pe, e);
44
+ return e;
45
+ }
46
+
47
+ function adoptCompanionLane(n, e) {
48
+ if (!n) return;
49
+ const i = signalLanes.get(n);
50
+ if (!i) return;
51
+ const t = findLane(i);
52
+ // Only the companion's own unmerged root is safely re-parentable: a root
53
+ // that absorbed other lanes carries work that is not a child of this owner.
54
+ if (t !== e && t.Ln === n && !t.hn) t.hn = e;
55
+ }
56
+
57
+ /**
58
+ * Union-find: find the root lane.
59
+ */ function findLane(n) {
60
+ while (n.gn) n = n.gn;
61
+ return n;
62
+ }
63
+
64
+ /**
65
+ * Is the lane held? `_pendingAsync` records the async the lane OWNS (derived
66
+ * under it); a transaction's reporter map records the async a render effect
67
+ * OBSERVED pending with no boundary taking it (INV-3, the one registration
68
+ * site). A hold needs both — the same rule the transaction itself uses, so a
69
+ * memo nobody renders, or one a fallback-showing boundary caught, cannot tear
70
+ * a frame and holds nothing (#3289). An orphan lane has no observation record
71
+ * and never holds.
72
+ *
73
+ * The observation is looked up per NODE, in whichever live transaction
74
+ * recorded it — not in this lane's transaction. Lanes merge across
75
+ * transactions (#2912: ownership never travels through lanes), so after a
76
+ * merge the root's transaction holds the observations of only one member;
77
+ * the async the other member's transaction observed must hold the merged
78
+ * reveal just the same (A15 for lanes, #3335).
79
+ */ function laneHeld(n) {
80
+ if (!n.Ee) return false;
81
+ for (const e of n._e) if (waitingTransition(e) !== null) return true;
82
+ return false;
83
+ }
84
+
85
+ /**
86
+ * Merge two lanes when their dependency graphs overlap.
87
+ */ function mergeLanes(n, e) {
88
+ n = findLane(n);
89
+ e = findLane(e);
90
+ if (n === e) return n;
91
+ e.gn = n;
92
+ // Move (not copy) the merged lane's work: after the merge all routing goes
93
+ // through findLane() to the root, so anything left behind here is dead —
94
+ // and anything *added* here later is a routing bug (INV-5).
95
+ for (const i of e._e) n._e.add(i);
96
+ e._e.clear();
97
+ n.vn[0].push(...e.vn[0]);
98
+ n.vn[1].push(...e.vn[1]);
99
+ e.vn[0].length = 0;
100
+ e.vn[1].length = 0;
101
+ return n;
102
+ }
103
+
104
+ /**
105
+ * Resolve a node's lane: follow union-find chain, verify active, clear if stale.
106
+ */ function resolveLane(n) {
107
+ const e = n.o?.Te;
108
+ if (!e) return undefined;
109
+ const i = findLane(e);
110
+ if (activeLanes.has(i)) return i;
111
+ if (n.o !== null) n.o.Te = undefined;
112
+ return undefined;
113
+ }
114
+
115
+ function resolveTransition(n) {
116
+ // An active override answers with its owner, not its lane: lanes are
117
+ // scheduling affinity and a shared subscriber merges them across
118
+ // transactions (#2912) — the merged root's _transition would hand this
119
+ // node's override to whichever action wrote last through the shared
120
+ // reader. Chase merge chains; a dead owner settled through another path.
121
+ if (hasActiveOverride(n) && n.o?.Yt) {
122
+ const e = ext(n).Yt = currentTransition(n.o?.Yt);
123
+ if (e.Ut !== true) return e;
124
+ if (n.o !== null) n.o.Yt = null;
125
+ }
126
+ return resolveLane(n)?.Ee ?? n.Ee;
127
+ }
128
+
129
+ /**
130
+ * Check if a node has an active optimistic override.
131
+ */ function hasActiveOverride(n) {
132
+ const e = n.o;
133
+ return e !== null && e.De !== undefined && e.De !== NOT_PENDING;
134
+ }
135
+
136
+ /**
137
+ * Assign or merge a lane onto a node. At convergence points (node already has
138
+ * a different active lane), merge unless the node has an active override.
139
+ */ function assignOrMergeLane(n, e) {
140
+ const i = findLane(e);
141
+ const t = n.o?.Te;
142
+ if (t) {
143
+ // If the subscriber's lane was merged into another lane, it's stale —
144
+ // replace it with the new source lane instead of following the merge chain
145
+ // (which would incorrectly merge the new lane into the old group)
146
+ if (t.gn) {
147
+ ext(n).Te = e;
148
+ n.T |= CONFIG_HAS_LANE;
149
+ return;
150
+ }
151
+ const r = findLane(t);
152
+ if (activeLanes.has(r)) {
153
+ if (r !== i && !hasActiveOverride(n)) {
154
+ // Parent-child lanes stay independent so isPending resolves without
155
+ // waiting for the parent's async. The child keeps ownership.
156
+ if (i.hn && findLane(i.hn) === r) {
157
+ ext(n).Te = e;
158
+ n.T |= CONFIG_HAS_LANE;
159
+ } else if (r.hn && findLane(r.hn) === i) ; else mergeLanes(i, r);
160
+ }
161
+ return;
162
+ }
163
+ }
164
+ ext(n).Te = e;
165
+ n.T |= CONFIG_HAS_LANE;
166
+ }
167
+
168
+ export { activeLanes, assignOrMergeLane, findLane, getOrCreateLane, hasActiveOverride, laneHeld, mergeLanes, resolveLane, resolveTransition, signalLanes };