@solidjs/signals 2.0.0-rc.6 → 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.
- package/dist/dev-shared.js +5243 -0
- package/dist/dev.attribution.js +2333 -0
- package/dist/dev.js +1758 -6621
- package/dist/observe/affects.js +127 -0
- package/dist/observe/attribution.js +1 -0
- package/dist/observe/boundaries.js +586 -0
- package/dist/observe/core/action.js +167 -0
- package/dist/observe/core/async.js +736 -0
- package/dist/observe/core/attribution-hooks.js +60 -0
- package/dist/observe/core/attribution.js +2318 -0
- package/dist/{types-cjs/core/constants.d.cts → observe/core/constants.js} +135 -71
- package/dist/{types-cjs/core/context.d.cts → observe/core/context.js} +37 -12
- package/dist/observe/core/core.js +1289 -0
- package/dist/observe/core/dev.js +232 -0
- package/dist/observe/core/effect.js +179 -0
- package/dist/observe/core/error.js +80 -0
- package/dist/observe/core/external.js +98 -0
- package/dist/observe/core/graph.js +155 -0
- package/dist/observe/core/heap.js +147 -0
- package/dist/observe/core/invariants.js +43 -0
- package/dist/observe/core/lanes.js +168 -0
- package/dist/observe/core/optimistic.js +438 -0
- package/dist/observe/core/owner.js +308 -0
- package/dist/observe/core/scheduler.js +1190 -0
- package/dist/observe/core/verdict.js +551 -0
- package/dist/observe/index.js +55 -0
- package/dist/observe/map.js +482 -0
- package/dist/{types-cjs/signals.d.cts → observe/signals.js} +305 -317
- package/dist/observe/store/index.js +38 -0
- package/dist/observe/store/next/optimistic.js +651 -0
- package/dist/observe/store/next/projection.js +242 -0
- package/dist/observe/store/next/reconcile.js +373 -0
- package/dist/observe/store/next/store.js +2213 -0
- package/dist/observe/store/next/target.js +51 -0
- package/dist/observe/store/store.js +323 -0
- package/dist/observe/store/storePath.js +103 -0
- package/dist/observe/store/utils.js +210 -0
- package/dist/prod/attribution.js +32 -0
- package/dist/prod/boundaries.js +3 -1
- package/dist/prod/core/action.js +52 -32
- package/dist/prod/core/async.js +177 -128
- package/dist/prod/core/attribution-hooks.js +3 -0
- package/dist/prod/core/constants.js +32 -1
- package/dist/prod/core/context.js +10 -16
- package/dist/prod/core/core.js +505 -286
- package/dist/prod/core/dev.js +2 -0
- package/dist/prod/core/effect.js +72 -43
- package/dist/prod/core/external.js +2 -2
- package/dist/prod/core/graph.js +40 -36
- package/dist/prod/core/heap.js +50 -51
- package/dist/prod/core/lanes.js +43 -22
- package/dist/prod/core/optimistic.js +224 -80
- package/dist/prod/core/owner.js +43 -39
- package/dist/prod/core/scheduler.js +347 -232
- package/dist/prod/core/verdict.js +71 -74
- package/dist/prod/index.js +9 -5
- package/dist/prod/map.js +311 -150
- package/dist/prod/signals.js +52 -38
- package/dist/prod/store/next/optimistic.js +127 -131
- package/dist/prod/store/next/projection.js +3 -3
- package/dist/prod/store/next/reconcile.js +149 -294
- package/dist/prod/store/next/store.js +694 -380
- package/dist/prod/store/next/target.js +32 -10
- package/dist/prod/store/store.js +12 -14
- package/dist/prod/store/utils.js +36 -27
- package/dist/types/attribution.d.ts +15 -0
- package/dist/types/attribution.prod.d.ts +13 -0
- package/dist/types/core/async.d.ts +1 -1
- package/dist/types/core/attribution-hooks.d.ts +183 -6
- package/dist/types/core/attribution.d.ts +435 -24
- package/dist/types/core/constants.d.ts +31 -0
- package/dist/types/core/core.d.ts +19 -3
- package/dist/types/core/dev.d.ts +165 -26
- package/dist/types/core/effect.d.ts +8 -1
- package/dist/types/core/heap.d.ts +5 -3
- package/dist/types/core/index.d.ts +2 -1
- package/dist/types/core/invariants.d.ts +1 -1
- package/dist/types/core/lanes.d.ts +17 -0
- package/dist/types/core/scheduler.d.ts +69 -4
- package/dist/types/core/types.d.ts +29 -9
- package/dist/types/index.d.ts +9 -2
- package/dist/types/map.d.ts +2 -0
- package/dist/types/signals.d.ts +10 -0
- package/dist/types/store/index.d.ts +4 -6
- package/dist/types/store/next/optimistic.d.ts +4 -2
- package/dist/types/store/next/reconcile.d.ts +5 -12
- package/dist/types/store/next/store.d.ts +8 -9
- package/dist/types/store/next/target.d.ts +56 -55
- package/dist/types/store/store.d.ts +14 -9
- package/dist/types/store/utils.d.ts +7 -0
- package/package.json +21 -16
- package/dist/node.cjs +0 -11121
- package/dist/prod/store/next/patch-hooks.js +0 -13
- package/dist/prod/store/next/patch.js +0 -614
- package/dist/types/store/next/patch-hooks.d.ts +0 -41
- package/dist/types/store/next/patch.d.ts +0 -91
- package/dist/types-cjs/affects.d.cts +0 -47
- package/dist/types-cjs/boundaries.d.cts +0 -175
- package/dist/types-cjs/core/action.d.cts +0 -64
- package/dist/types-cjs/core/async.d.cts +0 -23
- package/dist/types-cjs/core/attribution-hooks.d.cts +0 -63
- package/dist/types-cjs/core/attribution.d.cts +0 -239
- package/dist/types-cjs/core/core.d.cts +0 -176
- package/dist/types-cjs/core/dev.d.cts +0 -95
- package/dist/types-cjs/core/effect.d.cts +0 -30
- package/dist/types-cjs/core/error.d.cts +0 -56
- package/dist/types-cjs/core/external.d.cts +0 -15
- package/dist/types-cjs/core/graph.d.cts +0 -28
- package/dist/types-cjs/core/heap.d.cts +0 -22
- package/dist/types-cjs/core/index.d.cts +0 -13
- package/dist/types-cjs/core/invariants.d.cts +0 -59
- package/dist/types-cjs/core/lanes.d.cts +0 -44
- package/dist/types-cjs/core/optimistic.d.cts +0 -6
- package/dist/types-cjs/core/owner.d.cts +0 -124
- package/dist/types-cjs/core/scheduler.d.cts +0 -231
- package/dist/types-cjs/core/types.d.cts +0 -204
- package/dist/types-cjs/core/verdict.d.cts +0 -2
- package/dist/types-cjs/index.d.cts +0 -10
- package/dist/types-cjs/map.d.cts +0 -69
- package/dist/types-cjs/package.json +0 -3
- package/dist/types-cjs/store/index.d.cts +0 -21
- package/dist/types-cjs/store/next/optimistic.d.cts +0 -23
- package/dist/types-cjs/store/next/patch-hooks.d.cts +0 -41
- package/dist/types-cjs/store/next/patch.d.cts +0 -91
- package/dist/types-cjs/store/next/projection.d.cts +0 -8
- package/dist/types-cjs/store/next/reconcile.d.cts +0 -17
- package/dist/types-cjs/store/next/store.d.cts +0 -127
- package/dist/types-cjs/store/next/target.d.cts +0 -192
- package/dist/types-cjs/store/store.d.cts +0 -138
- package/dist/types-cjs/store/storePath.d.cts +0 -58
- package/dist/types-cjs/store/utils.d.cts +0 -74
package/dist/prod/core/core.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { releaseFlightTeardown, handleAsync, clearStatus, parkLoadingWindow, notifyStatus, settlePendingSource, settleErroredDependents } from "./async.js";
|
|
2
2
|
|
|
3
|
-
import { EFFECT_TRACKED, REACTIVE_OPTIMISTIC_DIRTY, CONFIG_OPTIMISTIC, NOT_PENDING, STATUS_UNINITIALIZED, STATUS_ERROR, REACTIVE_REASK, REACTIVE_RECOMPUTING_DEPS, CONFIG_SYNC, CONFIG_HAS_LANE,
|
|
3
|
+
import { EFFECT_TRACKED, EFFECT_USER, REACTIVE_OPTIMISTIC_DIRTY, CONFIG_OPTIMISTIC, NOT_PENDING, STATUS_UNINITIALIZED, STATUS_ERROR, STATUS_PENDING, REACTIVE_REASK, REACTIVE_RECOMPUTING_DEPS, CONFIG_SYNC, CONFIG_HAS_LANE, REACTIVE_MISSED_WAKE, REACTIVE_NONE, REACTIVE_SNAPSHOT_STALE, unwrapOverride, CONFIG_DIRECT_COMMIT, OVERRIDE_UNDEFINED, CONFIG_HAS_COMPANIONS, REACTIVE_LAZY, REACTIVE_DISPOSED, CONFIG_AUTO_DISPOSE, CONFIG_CHILDREN_FORBIDDEN, CONFIG_FRESH_READ, CONFIG_INPUTS_PUBLISHED, CONFIG_AUTHORITATIVE_READ, CONFIG_OVERRIDE_SUPERSEDED, CONFIG_AUTHORITATIVE_OBSERVED, CONFIG_HELD_TRUTH, REACTIVE_CHECK, REACTIVE_DIRTY, REACTIVE_IN_HEAP, REACTIVE_IN_HEAP_HEIGHT, defaultContext, CONFIG_IN_SNAPSHOT_SCOPE, CONFIG_TRANSPARENT, CONFIG_OWNED_WRITE, CONFIG_NO_SNAPSHOT, NO_SNAPSHOT, CONFIG_HAS_SNAPSHOT, REACTIVE_MANUAL_WRITE, CONFIG_FW_CHILDREN, CONFIG_SLOT_NODE, STORE_SNAPSHOT_PROPS } from "./constants.js";
|
|
4
4
|
|
|
5
5
|
import { NotReadyError } from "./error.js";
|
|
6
6
|
|
|
@@ -8,15 +8,29 @@ import { trimStaleDeps, link, dormantNodes } from "./graph.js";
|
|
|
8
8
|
|
|
9
9
|
import { deleteFromHeap, queueFor, insertIntoHeapHeight, enqueueSub, markNode, markHeap, insertIntoHeap } from "./heap.js";
|
|
10
10
|
|
|
11
|
-
import { GlobalQueue, bumpNotifyEpoch, activeTransition, globalQueue, clock, insertSubs, queuePendingNode, runInTransition, schedule, notifyEpoch, dirtyQueue, projectionWriteActive, reaskArmed, armReaskClear } from "./scheduler.js";
|
|
11
|
+
import { GlobalQueue, bumpNotifyEpoch, activeTransition, globalQueue, clock, currentTransition, insertSubs, queuePendingNode, runInTransition, schedule, notifyEpoch, dirtyQueue, projectionWriteActive, reaskArmed, armReaskClear } from "./scheduler.js";
|
|
12
12
|
|
|
13
13
|
import "./invariants.js";
|
|
14
14
|
|
|
15
15
|
import { disposeChildren, markDisposal, inheritId } from "./owner.js";
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
// The heap's per-node step. A tracked effect's heap visit is its compute
|
|
18
|
+
// phase — empty, like a user effect whose compute reads nothing — and hands
|
|
19
|
+
// the callback to the user queue. Routing the wake through the heap, rather
|
|
20
|
+
// than straight into the queue at notify time, is what orders the run after
|
|
21
|
+
// the commit regardless of which phase the write came from: a write in a
|
|
22
|
+
// render-effect callback stages its value for the next pass, but a wake pushed
|
|
23
|
+
// directly into the user queue ran in the SAME pass, read the old value, and
|
|
24
|
+
// nothing re-notified it when the value landed (#3291).
|
|
25
|
+
GlobalQueue.Fe = e => {
|
|
26
|
+
if (e.Ce === EFFECT_TRACKED) {
|
|
27
|
+
deleteFromHeap(e, queueFor(e));
|
|
28
|
+
e.He = true;
|
|
29
|
+
e.C.enqueue(EFFECT_USER, e.Ve);
|
|
30
|
+
} else recompute(e);
|
|
31
|
+
};
|
|
18
32
|
|
|
19
|
-
GlobalQueue.
|
|
33
|
+
GlobalQueue.We = disposeChildren;
|
|
20
34
|
|
|
21
35
|
let tracking = false;
|
|
22
36
|
|
|
@@ -48,8 +62,8 @@ let snapshotSources = null;
|
|
|
48
62
|
|
|
49
63
|
function ownerInSnapshotScope(e) {
|
|
50
64
|
while (e) {
|
|
51
|
-
if (e.
|
|
52
|
-
e = e.
|
|
65
|
+
if (e.Me) return true;
|
|
66
|
+
e = e.qe;
|
|
53
67
|
}
|
|
54
68
|
return false;
|
|
55
69
|
}
|
|
@@ -60,20 +74,20 @@ function setSnapshotCapture(e) {
|
|
|
60
74
|
}
|
|
61
75
|
|
|
62
76
|
function markSnapshotScope(e) {
|
|
63
|
-
e.
|
|
77
|
+
e.Me = true;
|
|
64
78
|
}
|
|
65
79
|
|
|
66
80
|
function releaseSnapshotScope(e) {
|
|
67
|
-
e.
|
|
81
|
+
e.Me = false;
|
|
68
82
|
releaseSubtree(e);
|
|
69
83
|
schedule();
|
|
70
84
|
}
|
|
71
85
|
|
|
72
86
|
function releaseSubtree(e) {
|
|
73
|
-
let t = e.
|
|
87
|
+
let t = e.Ye;
|
|
74
88
|
while (t) {
|
|
75
|
-
if (t.
|
|
76
|
-
t = t.
|
|
89
|
+
if (t.Me) {
|
|
90
|
+
t = t.Ze;
|
|
77
91
|
continue;
|
|
78
92
|
}
|
|
79
93
|
if (t.oe) {
|
|
@@ -82,19 +96,19 @@ function releaseSubtree(e) {
|
|
|
82
96
|
if (e.ie & REACTIVE_SNAPSHOT_STALE) {
|
|
83
97
|
e.ie &= ~REACTIVE_SNAPSHOT_STALE;
|
|
84
98
|
e.ie |= REACTIVE_DIRTY;
|
|
85
|
-
if (dirtyQueue.
|
|
99
|
+
if (dirtyQueue.Ke > e.Be) dirtyQueue.Ke = e.Be;
|
|
86
100
|
insertIntoHeap(e, dirtyQueue);
|
|
87
101
|
}
|
|
88
102
|
}
|
|
89
103
|
releaseSubtree(t);
|
|
90
|
-
t = t.
|
|
104
|
+
t = t.Ze;
|
|
91
105
|
}
|
|
92
106
|
}
|
|
93
107
|
|
|
94
108
|
function clearSnapshots() {
|
|
95
109
|
if (snapshotSources) {
|
|
96
110
|
for (const e of snapshotSources) {
|
|
97
|
-
delete e.o?.
|
|
111
|
+
delete e.o?.ze;
|
|
98
112
|
// StoreNode targets share one pre-initialized hidden class (see
|
|
99
113
|
// createStoreProxy) — assign undefined instead of deleting, and only
|
|
100
114
|
// when present so signal-node sources don't grow the field.
|
|
@@ -108,12 +122,12 @@ function clearSnapshots() {
|
|
|
108
122
|
function recompute(e, t = false) {
|
|
109
123
|
// §12d: any recompute can clean a marked subscriber — invalidate skips.
|
|
110
124
|
bumpNotifyEpoch();
|
|
111
|
-
const n = e.
|
|
125
|
+
const n = e.Ce;
|
|
112
126
|
if (!t) {
|
|
113
|
-
if (e.
|
|
127
|
+
if (e.ge && (!n || activeTransition) && activeTransition !== e.ge) globalQueue.initTransition(e.ge);
|
|
114
128
|
deleteFromHeap(e, queueFor(e));
|
|
115
129
|
if (e.o !== null) {
|
|
116
|
-
e.o.
|
|
130
|
+
e.o.Pe = null;
|
|
117
131
|
// Supersede is where an iterator flight dies (#3122): close it now.
|
|
118
132
|
// Its cleanup(close) registration may sit in a zombie-deferred
|
|
119
133
|
// disposal list that a held transition only drains when the
|
|
@@ -122,49 +136,50 @@ function recompute(e, t = false) {
|
|
|
122
136
|
releaseFlightTeardown(e);
|
|
123
137
|
}
|
|
124
138
|
// Tracked effects run after finalizePureQueue, so dispose immediately instead of deferring
|
|
125
|
-
if (e.
|
|
139
|
+
if (e.ge || n === EFFECT_TRACKED) disposeChildren(e); else if (e.Ye !== null || e.we !== null) {
|
|
126
140
|
markDisposal(e);
|
|
127
141
|
const t = ext(e);
|
|
128
|
-
t.
|
|
129
|
-
t.
|
|
130
|
-
e.
|
|
131
|
-
e.
|
|
132
|
-
e
|
|
142
|
+
t.Je = e.we;
|
|
143
|
+
t.Xe = e.Ye;
|
|
144
|
+
e.we = null;
|
|
145
|
+
e.Ye = null;
|
|
146
|
+
e.$e = 0;
|
|
133
147
|
} else ;
|
|
134
148
|
}
|
|
135
149
|
let i = !!(e.ie & REACTIVE_OPTIMISTIC_DIRTY);
|
|
136
|
-
const
|
|
137
|
-
const
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
//
|
|
150
|
+
const l = (e.T & CONFIG_OPTIMISTIC) !== 0 && e.o?.be !== NOT_PENDING && e.o?.be !== undefined;
|
|
151
|
+
const u = !!(e.S & STATUS_UNINITIALIZED);
|
|
152
|
+
// Capture both error and pending status before the compute clears them.
|
|
153
|
+
// A conditional can drop its pending source and recover to an unchanged
|
|
154
|
+
// value, leaving blocked dependents outside that source’s settle walk.
|
|
141
155
|
const o = e.S & STATUS_ERROR ? e.o?._ : undefined;
|
|
156
|
+
const s = e.S & STATUS_PENDING ? e.o?.le : undefined;
|
|
142
157
|
// Pending SOURCE-hood, captured before the compute clears status: a node
|
|
143
158
|
// whose own flight parked dependents self-registers in _pendingSources
|
|
144
159
|
// (notifyStatus, isSource). If this recompute supersedes that flight and
|
|
145
160
|
// settles synchronously, those dependents settle HERE — asyncWrite's
|
|
146
161
|
// settlePendingSource walk never runs for a landing that was preempted
|
|
147
162
|
// (#3181).
|
|
148
|
-
const
|
|
163
|
+
const a = e.o?.le?.has(e);
|
|
149
164
|
// Re-ask classification lives in the verdict module; capture the flag before
|
|
150
165
|
// the recompute wipes _flags below.
|
|
151
|
-
const
|
|
166
|
+
const r = (e.ie & REACTIVE_REASK) !== 0;
|
|
152
167
|
// Captured before the compute clears it on a sync landing: if that landing
|
|
153
168
|
// is transition-held below, the window must stay open until the hold
|
|
154
169
|
// commits (commitPendingNode) — a closed window plus a held value reads as
|
|
155
170
|
// a pending frame to live observers of the verdict (#2990).
|
|
156
|
-
const
|
|
157
|
-
const
|
|
171
|
+
const c = e.Ae;
|
|
172
|
+
const _ = context;
|
|
158
173
|
context = e;
|
|
159
|
-
e.
|
|
160
|
-
e.
|
|
174
|
+
e.et = null;
|
|
175
|
+
e.tt++;
|
|
161
176
|
e.ie = REACTIVE_RECOMPUTING_DEPS;
|
|
162
|
-
e.
|
|
163
|
-
let
|
|
164
|
-
let
|
|
177
|
+
e._e = clock;
|
|
178
|
+
let f = e.Ge === NOT_PENDING ? e.me : e.Ge;
|
|
179
|
+
let E = e.Be;
|
|
165
180
|
let I = false;
|
|
166
|
-
let
|
|
167
|
-
let
|
|
181
|
+
let N = tracking;
|
|
182
|
+
let T = currentOptimisticLane;
|
|
168
183
|
tracking = true;
|
|
169
184
|
// A computed's fn establishes its OWN dependencies, so it must never run
|
|
170
185
|
// inside a latest() read window: read() short-circuits through the
|
|
@@ -172,54 +187,61 @@ function recompute(e, t = false) {
|
|
|
172
187
|
// computed) inside latest(fn) came out permanently dependency-less (#2926).
|
|
173
188
|
// latestRead() already suspends the flag for its pull-recomputes; this
|
|
174
189
|
// covers creation-time computes and flushes that run inside the window.
|
|
175
|
-
const
|
|
190
|
+
const d = latestReadActive;
|
|
176
191
|
latestReadActive = false;
|
|
177
192
|
// Lane posture lives with the engine: OPTIMISTIC_DIRTY is only ever set by
|
|
178
193
|
// engine-driven paths, and _optimisticNodes is only pushed by
|
|
179
194
|
// _optimisticWrite, so the hook is installed whenever either gate holds.
|
|
180
195
|
if (i) {
|
|
181
|
-
const t = GlobalQueue.
|
|
196
|
+
const t = GlobalQueue.nt(e, true);
|
|
182
197
|
if (t) currentOptimisticLane = t;
|
|
183
198
|
// `false` = wake-only lane demotion: recompute plain so a mid-tick
|
|
184
199
|
// latest()/isPending() pull stages instead of direct-committing (#3009).
|
|
185
200
|
// The predicate lives with the engine (recomputeLane).
|
|
186
201
|
else if (t === false) i = false;
|
|
187
|
-
} else if (activeTransition && !t && activeTransition.
|
|
202
|
+
} else if (activeTransition && !t && activeTransition.it.length) {
|
|
188
203
|
// Lane adoption: parent-deeper-than-owned-child can run before its OPT-dirty
|
|
189
204
|
// child propagates. Walk deps once and inherit the OPT lane so this node
|
|
190
205
|
// recomputes under the right posture and propagates correctly.
|
|
191
|
-
const t = GlobalQueue.
|
|
206
|
+
const t = GlobalQueue.nt(e, false);
|
|
192
207
|
if (t) {
|
|
193
208
|
i = true;
|
|
194
209
|
currentOptimisticLane = t;
|
|
195
210
|
}
|
|
196
211
|
}
|
|
197
212
|
const S = n && n !== EFFECT_USER;
|
|
198
|
-
const
|
|
213
|
+
const A = stale;
|
|
199
214
|
if (S) stale = true;
|
|
215
|
+
// An effect recorded for this transaction's commit replay (it once read a
|
|
216
|
+
// node the transaction held and showed the committed value) and now
|
|
217
|
+
// recomputing UNDER the transaction sees its staged view: the value this
|
|
218
|
+
// run produces is applied by the commit itself, and the stale recording
|
|
219
|
+
// would publish the frame a second time. Drop it; the reads below re-record
|
|
220
|
+
// if they are served the committed view again (a lane's committed read).
|
|
221
|
+
if (n && activeTransition !== null && activeTransition.lt.size) activeTransition.lt.delete(e);
|
|
200
222
|
try {
|
|
201
223
|
if (!false && e.T & CONFIG_SYNC) {
|
|
202
|
-
|
|
203
|
-
if (e.o !== null) e.o.
|
|
204
|
-
e.
|
|
224
|
+
f = e.oe(f);
|
|
225
|
+
if (e.o !== null) e.o.Pe = null;
|
|
226
|
+
e.Ae = false;
|
|
205
227
|
} else {
|
|
206
228
|
// Snapshot `_inFlight` so we can detect whether `_fn` self-registered an async
|
|
207
229
|
// subscription (e.g. `createProjection` calls `handleAsync` from inside its body
|
|
208
230
|
// with a setter callback). In that case, the outer `handleAsync` call below would
|
|
209
231
|
// clobber the fresh subscription, so we skip it and let the internally-registered
|
|
210
232
|
// iteration drive updates.
|
|
211
|
-
const t = e.o?.
|
|
212
|
-
const n = e.oe(
|
|
233
|
+
const t = e.o?.Pe;
|
|
234
|
+
const n = e.oe(f);
|
|
213
235
|
const i = typeof n === "object" && n !== null;
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
if (!
|
|
217
|
-
if (e.o !== null) e.o.
|
|
236
|
+
const l = e.o?.Pe !== t;
|
|
237
|
+
f = l || !i ? n : handleAsync(e, n);
|
|
238
|
+
if (!l && !i) {
|
|
239
|
+
if (e.o !== null) e.o.Pe = null;
|
|
218
240
|
// A sync (non-object) return is the first real answer; async-shaped
|
|
219
241
|
// results clear inside handleAsync at their own landing points, and a
|
|
220
242
|
// self-registered flight (inFlightChanged — projections) clears when
|
|
221
243
|
// its internal handleAsync lands.
|
|
222
|
-
e.
|
|
244
|
+
e.Ae = false;
|
|
223
245
|
}
|
|
224
246
|
}
|
|
225
247
|
// On a status-free node clearStatus is a guaranteed no-op: every field
|
|
@@ -229,10 +251,10 @@ function recompute(e, t = false) {
|
|
|
229
251
|
if (e.S !== 0 || e.o !== null) clearStatus(e, t);
|
|
230
252
|
// _optimisticLane is only ever assigned by engine paths (CONFIG_HAS_LANE
|
|
231
253
|
// is their sticky presence mark).
|
|
232
|
-
if (e.T & CONFIG_HAS_LANE && e.o?.
|
|
254
|
+
if (e.T & CONFIG_HAS_LANE && e.o?.Oe) GlobalQueue.ut(e);
|
|
233
255
|
} catch (t) {
|
|
234
256
|
const n = t instanceof NotReadyError;
|
|
235
|
-
if (n && e.
|
|
257
|
+
if (n && e.Ae) {
|
|
236
258
|
// Loading window with an unready sync dependency: register for the
|
|
237
259
|
// source's settle (the settlePendingSource walk runs off
|
|
238
260
|
// _pendingSources + _blocked alone) but take NO read-visible pending
|
|
@@ -244,22 +266,22 @@ function recompute(e, t = false) {
|
|
|
244
266
|
} else {
|
|
245
267
|
// Track pending async in the lane (not the lane's source — it creates the lane
|
|
246
268
|
// but doesn't belong to it). Set lane BEFORE notifyStatus for downstream propagation.
|
|
247
|
-
if (n && currentOptimisticLane) GlobalQueue
|
|
269
|
+
if (n && currentOptimisticLane) GlobalQueue.ot(e);
|
|
248
270
|
let i = false;
|
|
249
271
|
if (n) {
|
|
250
|
-
ext(e).
|
|
251
|
-
if (GlobalQueue.
|
|
272
|
+
ext(e).de = true;
|
|
273
|
+
if (GlobalQueue.st !== null) i = GlobalQueue.st(e, r);
|
|
252
274
|
}
|
|
253
|
-
notifyStatus(e, n ? STATUS_PENDING : STATUS_ERROR, t, undefined, n ? e.o?.
|
|
275
|
+
notifyStatus(e, n ? STATUS_PENDING : STATUS_ERROR, t, undefined, n ? e.o?.Oe : undefined);
|
|
254
276
|
// The replacement source is fully propagated now. If no new flight
|
|
255
277
|
// re-owned self, retire the superseded flight and its dependent copies.
|
|
256
|
-
if (n &&
|
|
278
|
+
if (n && a && !e.o?.Pe) settlePendingSource(e);
|
|
257
279
|
if (i) GlobalQueue.k(e);
|
|
258
280
|
}
|
|
259
281
|
} finally {
|
|
260
|
-
tracking =
|
|
261
|
-
latestReadActive =
|
|
262
|
-
if (S) stale =
|
|
282
|
+
tracking = N;
|
|
283
|
+
latestReadActive = d;
|
|
284
|
+
if (S) stale = A;
|
|
263
285
|
// Consume the missed-wake latch (#3037, set by insertSubs): a dep write
|
|
264
286
|
// landed beneath this pass on a link it had already validated. The wipe
|
|
265
287
|
// below must not key off DIRTY/CHECK — the read-time pull protocol
|
|
@@ -267,14 +289,20 @@ function recompute(e, t = false) {
|
|
|
267
289
|
// bookkeeping, and those marks are correctly discarded here.
|
|
268
290
|
I = (e.ie & REACTIVE_MISSED_WAKE) !== 0;
|
|
269
291
|
e.ie = REACTIVE_NONE | (t ? e.ie & REACTIVE_SNAPSHOT_STALE : 0);
|
|
270
|
-
context =
|
|
292
|
+
context = _;
|
|
271
293
|
}
|
|
272
294
|
if (!e.o?._) {
|
|
273
295
|
trimStaleDeps(e);
|
|
274
|
-
|
|
275
|
-
|
|
296
|
+
// INV-11 (#3330): the equality gate compares against the slot this run
|
|
297
|
+
// publishes to. An override-covered node publishes the override; a lane
|
|
298
|
+
// recompute (OPT-dirty) direct-commits `_value` — the lane's own reveal
|
|
299
|
+
// schedule — so a transaction-held `_pendingValue` that already equals
|
|
300
|
+
// the new result is not "unchanged": the screen still shows `_value`.
|
|
301
|
+
// Only a transaction-staged run compares against `_pendingValue`.
|
|
302
|
+
const r = l ? unwrapOverride(e.o?.be) : i || e.Ge === NOT_PENDING ? e.me : e.Ge;
|
|
303
|
+
let _ = false;
|
|
276
304
|
try {
|
|
277
|
-
|
|
305
|
+
_ = !n && u || !e.ve || !e.ve(r, f);
|
|
278
306
|
} catch (t) {
|
|
279
307
|
// A throwing user comparator is an error of this node's computation.
|
|
280
308
|
// Route it through the same status path as a compute-phase throw so
|
|
@@ -287,14 +315,37 @@ function recompute(e, t = false) {
|
|
|
287
315
|
// its runner — happen here instead. `!create` matches the previous `initialized`
|
|
288
316
|
// gate: the explicit recompute(node, true) inside effect() does not enqueue, so
|
|
289
317
|
// effect() can call its runner synchronously for the first run.
|
|
290
|
-
if (n &&
|
|
291
|
-
e.
|
|
318
|
+
if (n && _) {
|
|
319
|
+
e.He = !e.o?._;
|
|
292
320
|
// Reuse one bound runner per effect — runEffect no-ops on a stale
|
|
293
321
|
// `_modified`, so re-enqueueing the same function is harmless.
|
|
294
|
-
if (!t)
|
|
322
|
+
if (!t) {
|
|
323
|
+
e.C.enqueue(n, e.rt ??= GlobalQueue.ct.bind(null, e));
|
|
324
|
+
// Contested effect (#3322). Effects don't entangle transactions (a
|
|
325
|
+
// shared effect is not shared state), yet they have one value slot:
|
|
326
|
+
// when this write commits under a different transaction
|
|
327
|
+
// (`activeTransition`, null = mainline) than the one that produced the
|
|
328
|
+
// previous value (`_valueTransition`), that view is gone. Rather than
|
|
329
|
+
// merge the two, each commit re-derives the effect against its own
|
|
330
|
+
// committed world (Transition._contested, re-dirtied by
|
|
331
|
+
// finalizePureQueue ahead of the heap run). The previous owner always
|
|
332
|
+
// needs it if still live: its commit is silent (staging already
|
|
333
|
+
// notified) and the value it computed is gone. The new owner needs it
|
|
334
|
+
// too, for the same reason, unless it is mainline — mainline publishes
|
|
335
|
+
// what it computes. A previous owner that was mainline, or already
|
|
336
|
+
// committed, left nothing to protect.
|
|
337
|
+
let t = e._t;
|
|
338
|
+
if (t !== activeTransition) {
|
|
339
|
+
e._t = activeTransition;
|
|
340
|
+
if (t !== null && (t = currentTransition(t)) !== activeTransition && !t.ft) {
|
|
341
|
+
(t.Et ??= []).push(e);
|
|
342
|
+
if (activeTransition !== null) (activeTransition.Et ??= []).push(e);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
295
346
|
}
|
|
296
|
-
if (e.o?._) ; else if (
|
|
297
|
-
const
|
|
347
|
+
if (e.o?._) ; else if (_) {
|
|
348
|
+
const u = l ? e.o?.be : undefined;
|
|
298
349
|
if (t ||
|
|
299
350
|
// Plain sync flush (no transition on either side) commits effect
|
|
300
351
|
// values directly — the pending round-trip (queuePendingNode +
|
|
@@ -304,48 +355,62 @@ function recompute(e, t = false) {
|
|
|
304
355
|
// their own held transition: their applies deliver on a microtask,
|
|
305
356
|
// not the stashed queues, so a staged value would hand the immediate
|
|
306
357
|
// apply stale state — see CONFIG_DIRECT_COMMIT.
|
|
307
|
-
n && (activeTransition !== e.
|
|
308
|
-
e.
|
|
358
|
+
n && (activeTransition !== e.ge || activeTransition === null || e.T & CONFIG_DIRECT_COMMIT) || i) {
|
|
359
|
+
e.me = f;
|
|
309
360
|
// Lane-propagated correction: upstream data is fresh, correct the
|
|
310
361
|
// override unconditionally. The direct _value commit is the lane's
|
|
311
362
|
// own reveal schedule; drop any superseded older hold so its queued
|
|
312
363
|
// commit can't clobber the fresh value.
|
|
313
|
-
if (
|
|
314
|
-
ext(e).
|
|
315
|
-
e.
|
|
364
|
+
if (l && i) {
|
|
365
|
+
ext(e).be = f === undefined ? OVERRIDE_UNDEFINED : f;
|
|
366
|
+
e.Ge = NOT_PENDING;
|
|
316
367
|
}
|
|
317
368
|
} else {
|
|
318
|
-
e.
|
|
369
|
+
e.Ge = f;
|
|
319
370
|
// A window landing that gets held re-opens the window until the hold
|
|
320
371
|
// commits — the verdict's held-value branch is window-gated (#2990).
|
|
321
|
-
if (
|
|
372
|
+
if (c) e.Ae = true;
|
|
322
373
|
// Transition-held sync recompute is a write path like setSignal/asyncWrite,
|
|
323
374
|
// so sync derivations of held sources stay visible to isPending()/latest()
|
|
324
375
|
// (#2831). Both companion writes are transition-scoped (optimistic) and
|
|
325
376
|
// auto-revert/re-derive at commit. Skipped for plain flushes where the
|
|
326
377
|
// pending value commits before effects run.
|
|
327
|
-
if ((activeTransition || e.
|
|
378
|
+
if ((activeTransition || e.ge) && GlobalQueue.pe !== null) GlobalQueue.pe(e, f);
|
|
328
379
|
}
|
|
329
380
|
// insertSubs only walks _subs (no scheduling of its own), so a
|
|
330
381
|
// subscriber-less node has nothing to notify.
|
|
331
|
-
if (e.u !== null && (!
|
|
332
|
-
|
|
382
|
+
if (e.u !== null && (!l || i || e.o?.be !== u)) insertSubs(e, i || l);
|
|
383
|
+
// A18 supersession, sync twin of asyncWrite's override branch (#3331):
|
|
384
|
+
// this pass published truth that differs from the override (the gate
|
|
385
|
+
// compared against it) into the transaction-held slot. Whether the
|
|
386
|
+
// source is this node's own async or an upstream node it derives from
|
|
387
|
+
// synchronously makes no difference — "the source recomputed". The
|
|
388
|
+
// override stays displayed until the commit; the graph moves to the
|
|
389
|
+
// staged truth now (plain channel, lane demoted). Ordering: "a new
|
|
390
|
+
// value from the source" postdates the override — an override written
|
|
391
|
+
// in this same tick (optimisticWrite stamps `_overrideTime`) is the
|
|
392
|
+
// newer intent over whatever this pass derives from the batch's staged
|
|
393
|
+
// inputs, and is not superseded by it.
|
|
394
|
+
else if (l && !i && e.o.It !== clock) GlobalQueue.ye(e, f);
|
|
395
|
+
} else if (l) {
|
|
333
396
|
// Unchanged value (equals the override) recomputed while the override
|
|
334
397
|
// is active: _value may still be stale, so hold the authoritative value
|
|
335
398
|
// for commit on its own transition's schedule — invisibly (A17/A18).
|
|
336
|
-
if (e.
|
|
337
|
-
e.
|
|
338
|
-
if (
|
|
399
|
+
if (e.Ge === NOT_PENDING) queuePendingNode(e);
|
|
400
|
+
e.Ge = f;
|
|
401
|
+
if (c) e.Ae = true;
|
|
339
402
|
// see the held branch above (#2990)
|
|
340
|
-
// A
|
|
341
|
-
//
|
|
342
|
-
//
|
|
343
|
-
//
|
|
344
|
-
//
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
403
|
+
// A confirmation after a supersession restores the override as the
|
|
404
|
+
// graph's value and notifies; a plain confirmation wakes only an
|
|
405
|
+
// authoritative-view reader (until()'s predicate, refresh()'s waiter)
|
|
406
|
+
// that observed this node past its override — "authoritative arrival
|
|
407
|
+
// equal to the override" is exactly the acknowledgment it waits for;
|
|
408
|
+
// A17 silence holds for every ordinary subscriber. Both live in the
|
|
409
|
+
// engine's supersedeOverride.
|
|
410
|
+
GlobalQueue.ye(e, f);
|
|
411
|
+
} else if (e.Be != E) {
|
|
412
|
+
for (let t = e.u; t !== null; t = t.Te) {
|
|
413
|
+
insertIntoHeapHeight(t.Ie, queueFor(t.Ie));
|
|
349
414
|
}
|
|
350
415
|
}
|
|
351
416
|
// Silent recovery: errored → unchanged value fires no notification, but
|
|
@@ -353,21 +418,26 @@ function recompute(e, t = false) {
|
|
|
353
418
|
// in an errored run and may sit on stale commits (#2949). Changed-value
|
|
354
419
|
// recoveries ride insertSubs above; a comparator throw re-errored the node
|
|
355
420
|
// (el._x?._error re-set), so this only runs on a genuinely clean recovery.
|
|
356
|
-
if (
|
|
421
|
+
if (!_ && !e.o?._) {
|
|
422
|
+
if (o !== undefined) settleErroredDependents(e, o);
|
|
423
|
+
// Self-registration (this node's own superseded flight) is the #3181
|
|
424
|
+
// sweep's business below — retiring it here too would walk twice.
|
|
425
|
+
if (s) for (const t of s) if (t !== e) settlePendingSource(e, t);
|
|
426
|
+
}
|
|
357
427
|
// #3181: a synchronous settle supersedes the old landing callback, so
|
|
358
428
|
// recompute owns its pending-source sweep. An uninitialized node without
|
|
359
429
|
// a replacement source still has no truth to reveal and must stay parked.
|
|
360
|
-
if (
|
|
430
|
+
if (a && !(e.S & (STATUS_PENDING | STATUS_UNINITIALIZED))) settlePendingSource(e);
|
|
361
431
|
}
|
|
362
|
-
currentOptimisticLane =
|
|
363
|
-
const
|
|
432
|
+
currentOptimisticLane = T;
|
|
433
|
+
const C = e.Ge !== NOT_PENDING || e.o !== null && (e.o.Xe !== null || e.o.Je !== null) || (e.S & (STATUS_PENDING | STATUS_UNINITIALIZED)) !== 0;
|
|
364
434
|
// Override-covered holds (hasOverride) always queue: their commit belongs
|
|
365
435
|
// to their own transition's schedule (A18 re-rule) and is unobservable
|
|
366
436
|
// under the override (A17). Revert no longer commits anything, so an
|
|
367
437
|
// unqueued covered hold would leak (INV-7) once the revert clears
|
|
368
438
|
// _transition.
|
|
369
|
-
|
|
370
|
-
e.
|
|
439
|
+
C && (!t || e.S & STATUS_PENDING) && (!e.ge || l) && queuePendingNode(e);
|
|
440
|
+
e.ge && n && activeTransition !== e.ge && runInTransition(e.ge, () => recompute(e));
|
|
371
441
|
// Missed-wake reschedule (see the finally above): values this pass read
|
|
372
442
|
// before the nested commit are stale, so run again now that the heap will
|
|
373
443
|
// accept the node. Equality gates stop same-value landings from cascading,
|
|
@@ -388,9 +458,9 @@ function updateIfNecessary(e) {
|
|
|
388
458
|
// would resurrect it (#2983) — readers serve its last value.
|
|
389
459
|
if (e.ie & (REACTIVE_RECOMPUTING_DEPS | REACTIVE_DISPOSED)) return;
|
|
390
460
|
if (e.ie & REACTIVE_CHECK) {
|
|
391
|
-
for (let t = e.
|
|
392
|
-
const n = t.
|
|
393
|
-
const i = n.
|
|
461
|
+
for (let t = e.fe; t; t = t.ae) {
|
|
462
|
+
const n = t.Se;
|
|
463
|
+
const i = n.ce || n;
|
|
394
464
|
if (i.oe) {
|
|
395
465
|
updateIfNecessary(i);
|
|
396
466
|
}
|
|
@@ -399,7 +469,7 @@ function updateIfNecessary(e) {
|
|
|
399
469
|
}
|
|
400
470
|
}
|
|
401
471
|
}
|
|
402
|
-
if (e.ie & (REACTIVE_DIRTY | REACTIVE_OPTIMISTIC_DIRTY) || e.o?._ && e.
|
|
472
|
+
if (e.ie & (REACTIVE_DIRTY | REACTIVE_OPTIMISTIC_DIRTY) || e.o?._ && e._e < clock && !e.o?.Pe) {
|
|
403
473
|
recompute(e);
|
|
404
474
|
}
|
|
405
475
|
e.ie = e.ie & (REACTIVE_SNAPSHOT_STALE | REACTIVE_IN_HEAP | REACTIVE_IN_HEAP_HEIGHT);
|
|
@@ -411,45 +481,53 @@ function computed(e, t) {
|
|
|
411
481
|
// `T | undefined` node is a real commit #0. The typeof guard tolerates
|
|
412
482
|
// non-object option values that older call shapes force through `as any`.
|
|
413
483
|
const i = t !== null && typeof t === "object" && "loadingValue" in t;
|
|
414
|
-
|
|
484
|
+
// Two literals, one per tier, selected at build time (the observe flag is
|
|
485
|
+
// a literal after replacement; the untaken branch is dead code). The observe
|
|
486
|
+
// literal is the prod literal plus its `_name` slot — a slot in the
|
|
487
|
+
// boilerplate, because a post-construction `self._name = …` forces a
|
|
488
|
+
// hidden-class transition and an out-of-object property store on EVERY node
|
|
489
|
+
// (measured: the whole of the observe tier's creation overhead). Keep the
|
|
490
|
+
// two in sync — the dist artifact test pins observe's key set to prod's
|
|
491
|
+
// plus `_name`.
|
|
492
|
+
const l = {
|
|
415
493
|
id: inheritId(t, n, context),
|
|
416
494
|
T: (n ? CONFIG_TRANSPARENT : 0) | (t?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (!context || t?.lazy ? CONFIG_AUTO_DISPOSE : 0) | (t?.sync ? CONFIG_SYNC : 0) | (t?.H ? CONFIG_NO_SNAPSHOT : 0) | (snapshotCaptureActive && ownerInSnapshotScope(context) ? CONFIG_IN_SNAPSHOT_SCOPE : 0),
|
|
417
|
-
|
|
418
|
-
|
|
495
|
+
ve: t?.equals ?? isEqual,
|
|
496
|
+
we: null,
|
|
419
497
|
C: context?.C ?? globalQueue,
|
|
420
|
-
|
|
421
|
-
|
|
498
|
+
xe: context?.xe ?? defaultContext,
|
|
499
|
+
$e: 0,
|
|
422
500
|
oe: e,
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
501
|
+
me: i ? t.loadingValue : undefined,
|
|
502
|
+
Be: 0,
|
|
503
|
+
Nt: undefined,
|
|
504
|
+
Tt: null,
|
|
505
|
+
fe: null,
|
|
506
|
+
et: null,
|
|
507
|
+
tt: 0,
|
|
430
508
|
u: null,
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
509
|
+
dt: null,
|
|
510
|
+
qe: context,
|
|
511
|
+
Ze: null,
|
|
512
|
+
St: null,
|
|
513
|
+
Ye: null,
|
|
436
514
|
ie: t?.lazy ? REACTIVE_LAZY : REACTIVE_NONE,
|
|
437
515
|
// A loadingValue node is born committed: commit #0 is already in _value.
|
|
438
516
|
S: i ? 0 : STATUS_UNINITIALIZED,
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
517
|
+
_e: clock,
|
|
518
|
+
Ge: NOT_PENDING,
|
|
519
|
+
ge: null,
|
|
520
|
+
At: -1,
|
|
521
|
+
Ae: i,
|
|
444
522
|
// Cold machinery (async/transition/optimistic/verdict slots) lives one
|
|
445
523
|
// hop away in the lazily-allocated extension — the core literal MUST
|
|
446
524
|
// stay under V8's in-object boundary (§12: past ~39 fields every
|
|
447
525
|
// allocation spills to a backing store and creation cost ~4x's).
|
|
448
526
|
o: null
|
|
449
527
|
};
|
|
450
|
-
if (t?.unobserved) ext(
|
|
451
|
-
setupComputedNode(
|
|
452
|
-
return
|
|
528
|
+
if (t?.unobserved) ext(l).Ct = t.unobserved;
|
|
529
|
+
setupComputedNode(l, t);
|
|
530
|
+
return l;
|
|
453
531
|
}
|
|
454
532
|
|
|
455
533
|
/** Lazily allocate a node's cold extension (ONE shape for signals and
|
|
@@ -457,26 +535,28 @@ function computed(e, t) {
|
|
|
457
535
|
* this; hot paths read `el._x?._field` gated by the _config presence bits.
|
|
458
536
|
* Never call ext() just to store a field's default. */ function ext(e) {
|
|
459
537
|
return e.o ??= {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
538
|
+
be: undefined,
|
|
539
|
+
Ot: undefined,
|
|
540
|
+
It: 0,
|
|
541
|
+
Rt: 0,
|
|
542
|
+
Oe: undefined,
|
|
543
|
+
Le: undefined,
|
|
544
|
+
Qe: undefined,
|
|
545
|
+
Gt: undefined,
|
|
466
546
|
t: 0,
|
|
467
|
-
|
|
468
|
-
|
|
547
|
+
Pe: null,
|
|
548
|
+
De: null,
|
|
469
549
|
_: undefined,
|
|
470
|
-
|
|
550
|
+
de: undefined,
|
|
471
551
|
le: undefined,
|
|
472
552
|
h: undefined,
|
|
473
|
-
|
|
553
|
+
Ue: false,
|
|
474
554
|
i: null,
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
555
|
+
Ct: undefined,
|
|
556
|
+
ze: undefined,
|
|
557
|
+
Je: null,
|
|
558
|
+
Xe: null,
|
|
559
|
+
Dt: undefined
|
|
480
560
|
};
|
|
481
561
|
}
|
|
482
562
|
|
|
@@ -485,43 +565,49 @@ function computed(e, t) {
|
|
|
485
565
|
* so V8 sees the full hidden class shape at construction time. Effects always run in lazy
|
|
486
566
|
* mode (recompute is called explicitly by `effect()`), so we hardcode the lazy bits and skip
|
|
487
567
|
* the auto-dispose CONFIG bit (effect() previously cleared it post-construction).
|
|
488
|
-
*/ function createEffectNode(e, t, n, i,
|
|
489
|
-
const
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
568
|
+
*/ function createEffectNode(e, t, n, i, l) {
|
|
569
|
+
const u = l?.transparent ?? false;
|
|
570
|
+
// Prod and observe boilerplates — see computed() for why the observe tier
|
|
571
|
+
// gets its `_name` as a literal slot rather than a write after the fact.
|
|
572
|
+
// The default label is the node kind (tracked effects relabel their computed
|
|
573
|
+
// in trackedEffect); the wrappers in signals.ts no longer spread a name into
|
|
574
|
+
// the options to get it.
|
|
575
|
+
const o = {
|
|
576
|
+
id: inheritId(l, u, context),
|
|
577
|
+
T: (u ? CONFIG_TRANSPARENT : 0) | (l?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (l?.sync ? CONFIG_SYNC : 0) | (l?.Pt ?? 0) | (snapshotCaptureActive && ownerInSnapshotScope(context) ? CONFIG_IN_SNAPSHOT_SCOPE : 0),
|
|
578
|
+
ve: false,
|
|
579
|
+
we: null,
|
|
495
580
|
C: context?.C ?? globalQueue,
|
|
496
|
-
|
|
497
|
-
|
|
581
|
+
xe: context?.xe ?? defaultContext,
|
|
582
|
+
$e: 0,
|
|
498
583
|
oe: e,
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
584
|
+
me: undefined,
|
|
585
|
+
Be: 0,
|
|
586
|
+
Nt: undefined,
|
|
587
|
+
Tt: null,
|
|
588
|
+
fe: null,
|
|
589
|
+
et: null,
|
|
590
|
+
tt: 0,
|
|
506
591
|
u: null,
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
592
|
+
dt: null,
|
|
593
|
+
qe: context,
|
|
594
|
+
Ze: null,
|
|
595
|
+
St: null,
|
|
596
|
+
Ye: null,
|
|
512
597
|
ie: REACTIVE_LAZY,
|
|
513
598
|
S: STATUS_UNINITIALIZED,
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
599
|
+
_e: clock,
|
|
600
|
+
Ge: NOT_PENDING,
|
|
601
|
+
ge: null,
|
|
602
|
+
At: -1,
|
|
603
|
+
Ae: false,
|
|
604
|
+
He: false,
|
|
605
|
+
Ft: undefined,
|
|
606
|
+
ht: t,
|
|
607
|
+
vt: n,
|
|
608
|
+
Ht: undefined,
|
|
609
|
+
Ce: i,
|
|
610
|
+
_t: null,
|
|
525
611
|
o: null
|
|
526
612
|
};
|
|
527
613
|
// Effects dispatch status through the SHARED notifier (statusNotifierOf,
|
|
@@ -529,7 +615,7 @@ function computed(e, t) {
|
|
|
529
615
|
// allocation on EVERY effect at creation (an alloc + 19 field stores,
|
|
530
616
|
// +23% effect creation, caught by the creation benches). Only genuinely
|
|
531
617
|
// per-node channels (boundaries) live on _x.
|
|
532
|
-
if (
|
|
618
|
+
if (l?.unobserved) ext(o).Ct = l.unobserved;
|
|
533
619
|
setupComputedNode(o, lazyOptions);
|
|
534
620
|
return o;
|
|
535
621
|
}
|
|
@@ -552,7 +638,7 @@ function setEffectStatusNotify(e) {
|
|
|
552
638
|
* per-node field did when every effect carried one. */ function statusNotifierOf(e) {
|
|
553
639
|
const t = e.o?.h;
|
|
554
640
|
if (t !== undefined) return t;
|
|
555
|
-
return e.
|
|
641
|
+
return e.Ce ? effectStatusNotify ?? undefined : undefined;
|
|
556
642
|
}
|
|
557
643
|
|
|
558
644
|
const lazyOptions = {
|
|
@@ -560,24 +646,24 @@ const lazyOptions = {
|
|
|
560
646
|
};
|
|
561
647
|
|
|
562
648
|
function setupComputedNode(e, t) {
|
|
563
|
-
e.
|
|
564
|
-
const n = context?.
|
|
649
|
+
e.Tt = e;
|
|
650
|
+
const n = context?.gt ? context.bt : context;
|
|
565
651
|
if (context) {
|
|
566
|
-
const t = context.
|
|
652
|
+
const t = context.Ye;
|
|
567
653
|
if (t === null) {
|
|
568
|
-
context.
|
|
654
|
+
context.Ye = e;
|
|
569
655
|
} else {
|
|
570
|
-
e.
|
|
571
|
-
t.
|
|
572
|
-
context.
|
|
656
|
+
e.Ze = t;
|
|
657
|
+
t.St = e;
|
|
658
|
+
context.Ye = e;
|
|
573
659
|
}
|
|
574
660
|
}
|
|
575
|
-
if (n) e.
|
|
576
|
-
if (GlobalQueue.
|
|
661
|
+
if (n) e.Be = n.Be + 1;
|
|
662
|
+
if (GlobalQueue.kt !== null) GlobalQueue.kt(e);
|
|
577
663
|
!t?.lazy && recompute(e, true);
|
|
578
664
|
if (snapshotCaptureActive && !t?.lazy) {
|
|
579
665
|
if (!(e.S & STATUS_PENDING) && !(e.T & CONFIG_NO_SNAPSHOT)) {
|
|
580
|
-
ext(e).
|
|
666
|
+
ext(e).ze = e.me === undefined ? NO_SNAPSHOT : e.me;
|
|
581
667
|
e.T |= CONFIG_HAS_SNAPSHOT;
|
|
582
668
|
snapshotSources.add(e);
|
|
583
669
|
}
|
|
@@ -585,48 +671,128 @@ function setupComputedNode(e, t) {
|
|
|
585
671
|
}
|
|
586
672
|
|
|
587
673
|
function signal(e, t, n = null) {
|
|
674
|
+
// Prod and observe boilerplates — see computed(). The observe literal adds
|
|
675
|
+
// `_name` and `_owner` (the creating owner, stamped by registerGraph for
|
|
676
|
+
// createSignal nodes so ownerPath can locate signal subjects; null here,
|
|
677
|
+
// and staying null on internal signals — one shape either way).
|
|
588
678
|
const i = {
|
|
589
|
-
|
|
679
|
+
ve: t?.equals ?? isEqual,
|
|
590
680
|
T: (t?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (t?.H ? CONFIG_NO_SNAPSHOT : 0),
|
|
591
|
-
|
|
681
|
+
me: e,
|
|
592
682
|
u: null,
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
683
|
+
dt: null,
|
|
684
|
+
_e: clock,
|
|
685
|
+
ce: n,
|
|
686
|
+
Ee: n?.o?.i || null,
|
|
687
|
+
Vt: null,
|
|
688
|
+
Ge: NOT_PENDING,
|
|
598
689
|
// Signal-literal diet (§12e): NO _time/_fn/_statusFlags slots. Stores
|
|
599
690
|
// materialize one signal per touched leaf, so signal bytes are store
|
|
600
691
|
// bytes. _time is write-only on signals (every read site is computed-
|
|
601
692
|
// typed error-retry gating); _fn/_statusFlags read falsy-identically as
|
|
602
693
|
// missing properties on the shared paths (undefined masks to 0).
|
|
603
|
-
|
|
604
|
-
|
|
694
|
+
ge: null,
|
|
695
|
+
At: -1,
|
|
605
696
|
o: null
|
|
606
697
|
};
|
|
607
|
-
if (t?.unobserved) ext(i).
|
|
608
|
-
if (n)
|
|
609
|
-
ext(n).i = i;
|
|
610
|
-
n.T |= CONFIG_FW_CHILDREN;
|
|
611
|
-
}
|
|
698
|
+
if (t?.unobserved) ext(i).Ct = t.unobserved;
|
|
699
|
+
if (n) linkFirewallChild(n, i);
|
|
612
700
|
if (snapshotCaptureActive && !(i.T & CONFIG_NO_SNAPSHOT) && !((n?.S ?? 0) & STATUS_PENDING)) {
|
|
613
|
-
ext(i).
|
|
701
|
+
ext(i).ze = e === undefined ? NO_SNAPSHOT : e;
|
|
614
702
|
i.T |= CONFIG_HAS_SNAPSHOT;
|
|
615
703
|
snapshotSources.add(i);
|
|
616
704
|
}
|
|
617
705
|
return i;
|
|
618
706
|
}
|
|
619
707
|
|
|
708
|
+
// ---------------------------------------------------------------------------
|
|
709
|
+
// SLOT SIGNALS (store leaves) — the create-floor diet. Store mounts
|
|
710
|
+
// materialize one signal per touched leaf (~13 × rows on dbmon), so per-node
|
|
711
|
+
// allocations are mount bytes: the generic path costs an options object, an
|
|
712
|
+
// equals closure, an unobserved closure, a NodeExtension to hold it, and
|
|
713
|
+
// three post-construction expandos (acc/px/pxv → hidden-class transitions).
|
|
714
|
+
// slotSignal bakes everything into ONE literal: `_host`/`_key` backrefs
|
|
715
|
+
// replace the closures (equals is a method call — `this` is the node; the
|
|
716
|
+
// unobserved sweep dispatches CONFIG_SLOT_NODE to one shared hook), and the
|
|
717
|
+
// store's wrap-cache fields are pre-shaped.
|
|
718
|
+
/** The shared slot-node unobserved handler — a live binding read directly by
|
|
719
|
+
* the sweep sites (no wrapper frame, no null check: a CONFIG_SLOT_NODE node
|
|
720
|
+
* existing implies the store module loaded and registered the hook). */ let slotUnobservedHook;
|
|
721
|
+
|
|
722
|
+
/** Install the shared slot-node unobserved handler (store module, once). */ function setSlotUnobserved(e) {
|
|
723
|
+
slotUnobservedHook = e;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/** Push a new node onto its firewall's child chain (the literal already
|
|
727
|
+
* points `_nextChild` at the old head). Doubly linked so a released leaf
|
|
728
|
+
* unlinks in O(1) — the chain is walked per mark of the projection and
|
|
729
|
+
* would otherwise grow by one node per leaf ever read (#3351). */ function linkFirewallChild(e, t) {
|
|
730
|
+
const n = t.Ee;
|
|
731
|
+
if (n !== null) n.Vt = t;
|
|
732
|
+
ext(e).i = t;
|
|
733
|
+
e.T |= CONFIG_FW_CHILDREN;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/** Release a firewall child the store no longer addresses (unobserved sweep
|
|
737
|
+
* dropped it from its target's cache): unlink it from the chain so the
|
|
738
|
+
* projection stops retaining it and its last value. The node keeps its own
|
|
739
|
+
* `_nextChild` so a walk that is mid-chain on it still terminates. Nodes in
|
|
740
|
+
* `_companionChildren` stay there — companions are permanent by contract
|
|
741
|
+
* and snap through that set, not the chain. */ function unlinkFirewallChild(e) {
|
|
742
|
+
const t = e;
|
|
743
|
+
const n = t.ce;
|
|
744
|
+
if (!n) return;
|
|
745
|
+
const i = t.Vt;
|
|
746
|
+
const l = t.Ee;
|
|
747
|
+
if (i !== null) i.Ee = l; else if (n.o.i === t) n.o.i = l;
|
|
748
|
+
if (l !== null) l.Vt = i;
|
|
749
|
+
t.Vt = null;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
function slotSignal(e, t, n, i, l, u = null) {
|
|
753
|
+
// Prod and observe boilerplates — see computed(). The store relabels the
|
|
754
|
+
// observe slot (`store.<key>`) when the attribution engine is installed.
|
|
755
|
+
const o = {
|
|
756
|
+
ve: t,
|
|
757
|
+
T: CONFIG_OWNED_WRITE | CONFIG_SLOT_NODE,
|
|
758
|
+
me: e,
|
|
759
|
+
u: null,
|
|
760
|
+
dt: null,
|
|
761
|
+
_e: clock,
|
|
762
|
+
ce: u,
|
|
763
|
+
Ee: u?.o?.i || null,
|
|
764
|
+
Vt: null,
|
|
765
|
+
Ge: NOT_PENDING,
|
|
766
|
+
ge: null,
|
|
767
|
+
At: -1,
|
|
768
|
+
o: null,
|
|
769
|
+
// Slot backrefs: what the equals/unobserved closures used to capture.
|
|
770
|
+
Ut: n,
|
|
771
|
+
xt: i,
|
|
772
|
+
// Store read-path caches, pre-shaped (were post-construction expandos).
|
|
773
|
+
acc: l,
|
|
774
|
+
px: undefined,
|
|
775
|
+
pxv: undefined
|
|
776
|
+
};
|
|
777
|
+
if (u) linkFirewallChild(u, o);
|
|
778
|
+
if (snapshotCaptureActive && !((u?.S ?? 0) & STATUS_PENDING)) {
|
|
779
|
+
ext(o).ze = e === undefined ? NO_SNAPSHOT : e;
|
|
780
|
+
o.T |= CONFIG_HAS_SNAPSHOT;
|
|
781
|
+
snapshotSources.add(o);
|
|
782
|
+
}
|
|
783
|
+
return o;
|
|
784
|
+
}
|
|
785
|
+
|
|
620
786
|
function optimisticSignal(e, t) {
|
|
621
787
|
const n = signal(e, t);
|
|
622
|
-
ext(n).
|
|
788
|
+
ext(n).be = NOT_PENDING;
|
|
623
789
|
n.T |= CONFIG_OPTIMISTIC;
|
|
624
790
|
return n;
|
|
625
791
|
}
|
|
626
792
|
|
|
627
793
|
function optimisticComputed(e, t) {
|
|
628
794
|
const n = computed(e, t);
|
|
629
|
-
ext(n).
|
|
795
|
+
ext(n).be = NOT_PENDING;
|
|
630
796
|
n.T |= CONFIG_OPTIMISTIC;
|
|
631
797
|
return n;
|
|
632
798
|
}
|
|
@@ -657,11 +823,11 @@ function isEqual(e, t) {
|
|
|
657
823
|
* );
|
|
658
824
|
* ```
|
|
659
825
|
*/ function untrack(e, t) {
|
|
660
|
-
if (GlobalQueue.
|
|
826
|
+
if (GlobalQueue.Lt === null && !tracking && true) return e();
|
|
661
827
|
const n = tracking;
|
|
662
828
|
tracking = false;
|
|
663
829
|
try {
|
|
664
|
-
if (GlobalQueue.
|
|
830
|
+
if (GlobalQueue.Lt !== null) return GlobalQueue.Lt(e);
|
|
665
831
|
return e();
|
|
666
832
|
} finally {
|
|
667
833
|
tracking = n;
|
|
@@ -711,35 +877,62 @@ function isEqual(e, t) {
|
|
|
711
877
|
* Pay-for-use: reached through GlobalQueue._notifyAuthoritativeObservers,
|
|
712
878
|
* installed at first until() call — apps that never use until() shake it.
|
|
713
879
|
*/ function notifyAuthoritativeObservers(e) {
|
|
714
|
-
for (let t = e.u; t !== null; t = t.
|
|
715
|
-
const e = t.
|
|
880
|
+
for (let t = e.u; t !== null; t = t.Te) {
|
|
881
|
+
const e = t.Ie;
|
|
716
882
|
if (!(e.T & CONFIG_AUTHORITATIVE_READ)) continue;
|
|
717
883
|
// Missed-wake latch (#3037), same contract as insertSubs: the reader may
|
|
718
884
|
// itself have pulled this recompute (updateIfNecessary from its own
|
|
719
885
|
// read), and the heap refuses RECOMPUTING nodes — latch so recompute's
|
|
720
886
|
// tail reschedules it with the staged value visible.
|
|
721
|
-
if (e.ie & REACTIVE_RECOMPUTING_DEPS && t.
|
|
887
|
+
if (e.ie & REACTIVE_RECOMPUTING_DEPS && t.yt === e.tt && t !== e.et) e.ie |= REACTIVE_MISSED_WAKE;
|
|
722
888
|
enqueueSub(e);
|
|
723
889
|
}
|
|
724
890
|
schedule();
|
|
725
891
|
}
|
|
726
892
|
|
|
727
|
-
/** Installs the
|
|
728
|
-
*
|
|
729
|
-
*
|
|
730
|
-
|
|
893
|
+
/** Installs the authoritative-reader wakeup hook. Idempotent; called by every
|
|
894
|
+
* creator of a CONFIG_AUTHORITATIVE_READ computation — until() and refresh() —
|
|
895
|
+
* before its first read (same late-binding contract as the optimistic engine;
|
|
896
|
+
* the gating bit is only ever set by such a read, so the `!` call sites are
|
|
897
|
+
* safe once every setter installs, #3303). */ function installAuthoritativeRead() {
|
|
898
|
+
if (GlobalQueue.Qt === null) GlobalQueue.Qt = notifyAuthoritativeObservers;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
/**
|
|
902
|
+
* Stale-reader term of the value selections below: a render effect reading a
|
|
903
|
+
* node some OTHER live transaction has staged sees the committed value. The
|
|
904
|
+
* commit is silent — the staging walk was the notification — so a reader
|
|
905
|
+
* that linked AFTER that walk (an effect created during the hold, a store
|
|
906
|
+
* key first read under it) would show the old value past the reveal: record
|
|
907
|
+
* it for the transaction's commit replay (the `_gatedSubs` contract lanes
|
|
908
|
+
* already use). An effect the transaction itself computed re-derives at its
|
|
909
|
+
* commit on its own (parked run, or the contested re-derive, #3322) and is
|
|
910
|
+
* not recorded — replaying it too would publish the frame twice.
|
|
911
|
+
*/ function heldFromStale(e, t) {
|
|
912
|
+
const n = e.ge;
|
|
913
|
+
if (n === null || n === activeTransition) return false;
|
|
914
|
+
const i = currentTransition(n);
|
|
915
|
+
const l = t._t;
|
|
916
|
+
if (l == null || currentTransition(l) !== i) i.lt.add(t);
|
|
917
|
+
return true;
|
|
731
918
|
}
|
|
732
919
|
|
|
733
920
|
function readNodeFast(e) {
|
|
734
|
-
if (latestReadActive || pendingCheckActive || e.oe || e.
|
|
921
|
+
if (latestReadActive || pendingCheckActive || e.oe || e.ce || e.o?.be !== undefined || e.o?.ze !== undefined || activeTransition !== null || currentOptimisticLane !== null || snapshotCaptureActive || false) return READ_SLOW;
|
|
735
922
|
let t = context;
|
|
736
|
-
if (t?.
|
|
923
|
+
if (t?.gt) t = t.bt;
|
|
737
924
|
if (t && tracking) link(e, t);
|
|
738
925
|
// Children-forbidden readers (createTrackedEffect / onSettled callbacks) get
|
|
739
926
|
// committed visibility: like the effect half of createEffect and event
|
|
740
927
|
// handlers, effect-phase code never observes its own unsettled write — the
|
|
741
928
|
// write lands in the same flush's continuation (#3006).
|
|
742
|
-
|
|
929
|
+
// A stale reader (render effect) recomputing with no transaction active is
|
|
930
|
+
// mainline: a write staged by a live transaction (`_transition` stamped —
|
|
931
|
+
// ambient staging never is) stays masked until that transaction's reveal,
|
|
932
|
+
// the same rule the slow path applies (#3322). Without it a zombie
|
|
933
|
+
// recompute, or a commit-time re-derive of a contested effect, published
|
|
934
|
+
// another transaction's uncommitted value.
|
|
935
|
+
return !t || e.Ge === NOT_PENDING || t.T & CONFIG_CHILDREN_FORBIDDEN || stale && heldFromStale(e, t) ? e.me : e.Ge;
|
|
743
936
|
}
|
|
744
937
|
|
|
745
938
|
function read(e) {
|
|
@@ -747,33 +940,34 @@ function read(e) {
|
|
|
747
940
|
// Checked before isPending so that isPending(() => latest(x)) checks
|
|
748
941
|
// the _pendingSignal of _latestValueComputed (async in flight) rather
|
|
749
942
|
// than the original node (which stays "pending" while held in a transition).
|
|
750
|
-
if (latestReadActive) return GlobalQueue.
|
|
943
|
+
if (latestReadActive) return GlobalQueue.wt(e);
|
|
751
944
|
let t = context;
|
|
752
|
-
if (t?.
|
|
945
|
+
if (t?.gt) t = t.bt;
|
|
753
946
|
const n = e;
|
|
754
|
-
const i = e.
|
|
755
|
-
const
|
|
947
|
+
const i = e.ce;
|
|
948
|
+
const l = i || e;
|
|
756
949
|
// Handle isPending() mode: collect pending state while preserving normal read semantics.
|
|
757
950
|
// Probe mode is suspended while preparing the node so nested reads during a
|
|
758
951
|
// recompute don't collect into the probe.
|
|
759
952
|
if (pendingCheckActive) {
|
|
760
|
-
GlobalQueue.
|
|
953
|
+
GlobalQueue.Wt(e, t, l, i);
|
|
761
954
|
} else if (typeof n.oe === "function") {
|
|
762
955
|
prepareComputed(e, false);
|
|
763
956
|
}
|
|
764
|
-
if (!n.oe &&
|
|
957
|
+
if (!n.oe && l === e && e.o?.be === undefined && e.o?.ze === undefined && activeTransition === null && currentOptimisticLane === null && !snapshotCaptureActive && true) {
|
|
765
958
|
if (t && tracking) link(e, t);
|
|
766
|
-
// Committed visibility for children-forbidden readers
|
|
767
|
-
|
|
959
|
+
// Committed visibility for children-forbidden readers and for stale
|
|
960
|
+
// readers of a foreign transaction's staged write — see readNodeFast.
|
|
961
|
+
return !t || e.Ge === NOT_PENDING || t.T & CONFIG_CHILDREN_FORBIDDEN || stale && heldFromStale(e, t) ? e.me : e.Ge;
|
|
768
962
|
}
|
|
769
963
|
if (t && tracking) {
|
|
770
964
|
link(e, t, pendingCheckActive);
|
|
771
|
-
if (
|
|
965
|
+
if (l.oe) {
|
|
772
966
|
const n = queueFor(e);
|
|
773
|
-
if (
|
|
967
|
+
if (l.Be >= n.Ke) {
|
|
774
968
|
markNode(t);
|
|
775
969
|
markHeap(n);
|
|
776
|
-
updateIfNecessary(
|
|
970
|
+
updateIfNecessary(l);
|
|
777
971
|
}
|
|
778
972
|
// Fresh-pull readers (awaitable refresh's waiter) recompute a dirty
|
|
779
973
|
// source inline even when the height gate defers to the flush: the
|
|
@@ -781,64 +975,69 @@ function read(e) {
|
|
|
781
975
|
// never read the PRE-re-ask value as settled. Self-guarded: a clean
|
|
782
976
|
// node no-ops and updateIfNecessary refuses disposed nodes (#2983) —
|
|
783
977
|
// a dead target serves its last value, which is already quiescent.
|
|
784
|
-
else if (t.T & CONFIG_FRESH_READ) updateIfNecessary(
|
|
785
|
-
const i =
|
|
978
|
+
else if (t.T & CONFIG_FRESH_READ) updateIfNecessary(l);
|
|
979
|
+
const i = l.Be;
|
|
786
980
|
// parent check is shallow, might need to be recursive
|
|
787
|
-
if (i >= t.
|
|
788
|
-
t.
|
|
981
|
+
if (i >= t.Be && e.qe !== t) {
|
|
982
|
+
t.Be = i + 1;
|
|
789
983
|
}
|
|
790
984
|
}
|
|
791
985
|
}
|
|
792
|
-
if (
|
|
793
|
-
|
|
986
|
+
if (l.S & STATUS_PENDING) {
|
|
987
|
+
// A reader landing on a pending node throws — the reveal that discovered
|
|
988
|
+
// the flight holds on it (A15: observed async settles as one unit) — with
|
|
989
|
+
// one carve-out: a stale (render) reader of a node pending in some OTHER
|
|
990
|
+
// transaction keeps showing the node's committed value, no entanglement
|
|
991
|
+
// (parallel transactions; the reader is recorded for that transaction's
|
|
992
|
+
// commit replay, `heldFromStale`). The carve-out is sound only while the
|
|
993
|
+
// committed value is coherent with the visible frame, i.e. while the
|
|
994
|
+
// flight's inputs are themselves unpublished: the stamp alone does not
|
|
995
|
+
// say so (it is pending-node bookkeeping), so it is refused when the
|
|
996
|
+
// inputs are on screen — committed by a batch that left the flight in
|
|
997
|
+
// the air (CONFIG_INPUTS_PUBLISHED, #3305) or revealed through a lane
|
|
998
|
+
// (optimistic / latest, #3334) — and the reader holds instead. An
|
|
999
|
+
// UNINITIALIZED node has no committed value to show and holds too
|
|
1000
|
+
// (firewall-backed store reads always did; plain memos since the #3043
|
|
1001
|
+
// port): falling through served `undefined` as if settled and stranded
|
|
1002
|
+
// the reader outside both transactions, so it never re-ran.
|
|
1003
|
+
if (t && !(stale && !(l.S & STATUS_UNINITIALIZED) && !(l.T & CONFIG_INPUTS_PUBLISHED) && !(l.T & CONFIG_HAS_LANE && GlobalQueue.Mt(l)) && heldFromStale(l, t))) {
|
|
794
1004
|
// Per-lane suspension lives with the engine (a non-null lane implies it
|
|
795
1005
|
// is installed): under a lane, only same-lane pending async without an
|
|
796
|
-
// active override throws
|
|
797
|
-
|
|
1006
|
+
// active override throws — plus uninitialized sources regardless of
|
|
1007
|
+
// lane (#3276); that check rides laneSuspends so floor bundles don't
|
|
1008
|
+
// pay for it.
|
|
1009
|
+
if (currentOptimisticLane === null || GlobalQueue.qt(l)) {
|
|
798
1010
|
if (!tracking && e !== t) link(e, t);
|
|
799
|
-
throw
|
|
1011
|
+
throw l.o?._;
|
|
800
1012
|
}
|
|
801
|
-
} else if (t &&
|
|
802
|
-
|
|
803
|
-
// normally keeps showing the node's committed value instead of
|
|
804
|
-
// entangling the two transactions — but an uninitialized node has no
|
|
805
|
-
// committed value to show. Suspend on it (firewall-backed store reads
|
|
806
|
-
// always took this branch; plain memos now do too): the reader
|
|
807
|
-
// registers as a reporter of that source, and its pending-node stamp
|
|
808
|
-
// ties it to the active transaction, so the two transactions merge
|
|
809
|
-
// when the source settles. Falling through served `undefined` as if
|
|
810
|
-
// settled and stranded the reader outside both transactions, so it
|
|
811
|
-
// never re-ran when either landed (#3043 port).
|
|
812
|
-
if (!tracking && e !== t) link(e, t);
|
|
813
|
-
throw u.o?._;
|
|
814
|
-
} else if (!t && u.S & STATUS_UNINITIALIZED) {
|
|
815
|
-
throw u.o?._;
|
|
1013
|
+
} else if (!t && l.S & STATUS_UNINITIALIZED) {
|
|
1014
|
+
throw l.o?._;
|
|
816
1015
|
}
|
|
817
1016
|
}
|
|
818
1017
|
// `owner` is the computed itself, or the firewall behind a store node —
|
|
819
1018
|
// firewall-backed reads follow the same rules (memo parity, #2897 ruling):
|
|
820
1019
|
// an errored derive throws for every late reader instead of silently
|
|
821
1020
|
// serving node values (the seed, or last-good data after a failed refetch).
|
|
822
|
-
if (
|
|
1021
|
+
if (l.oe && l.S & STATUS_ERROR) {
|
|
823
1022
|
// Only a genuine reactive re-read may retry an errored async source:
|
|
824
1023
|
// - tracking: owned/tracked scope only (never events / `untrack` / effect side-effect phase)
|
|
825
1024
|
// - !pendingCheckActive: an `isPending` probe observes the error, never refetches
|
|
826
1025
|
// - owner._time < clock: only on a later cycle than the one the error was found
|
|
827
|
-
if (tracking && !pendingCheckActive &&
|
|
828
|
-
recompute(
|
|
1026
|
+
if (tracking && !pendingCheckActive && l._e < clock) {
|
|
1027
|
+
recompute(l);
|
|
829
1028
|
return read(e);
|
|
830
|
-
} else throw
|
|
1029
|
+
} else throw l.o?._;
|
|
831
1030
|
}
|
|
832
1031
|
if (snapshotCaptureActive && t && t.T & CONFIG_IN_SNAPSHOT_SCOPE) {
|
|
833
|
-
const n = e.o?.
|
|
1032
|
+
const n = e.o?.ze;
|
|
834
1033
|
if (n !== undefined) {
|
|
835
1034
|
const i = n === NO_SNAPSHOT ? undefined : n;
|
|
836
|
-
const
|
|
837
|
-
if (
|
|
1035
|
+
const l = e.Ge !== NOT_PENDING ? e.Ge : e.me;
|
|
1036
|
+
if (l !== i) t.ie |= REACTIVE_SNAPSHOT_STALE;
|
|
838
1037
|
return i;
|
|
839
1038
|
}
|
|
840
1039
|
}
|
|
841
|
-
if (e.o?.
|
|
1040
|
+
if (e.o?.be !== undefined && e.o?.be !== NOT_PENDING) {
|
|
842
1041
|
// A17: the override IS the value for every reader — except an authoritative
|
|
843
1042
|
// reader (until()'s predicate carries CONFIG_AUTHORITATIVE_READ): it must
|
|
844
1043
|
// observe independently-arriving truth, and serving it the caller's own
|
|
@@ -849,7 +1048,15 @@ function read(e) {
|
|
|
849
1048
|
// authoritative — optimism never lives there); the sticky mark makes the
|
|
850
1049
|
// A17-silent "landing equals override" paths notify this node's subs so
|
|
851
1050
|
// the reader re-runs when truth arrives.
|
|
852
|
-
if (!(t && t.T & CONFIG_AUTHORITATIVE_READ))
|
|
1051
|
+
if (!(t && t.T & CONFIG_AUTHORITATIVE_READ)) {
|
|
1052
|
+
// A18 supersession (#3331): the node's own source answered with a
|
|
1053
|
+
// DIFFERENT value. The optimism is over for the graph — a tracked
|
|
1054
|
+
// reader sees the staged truth — while the override remains the
|
|
1055
|
+
// DISPLAYED value for untracked reads (and for a stale reader of some
|
|
1056
|
+
// other transaction). The selection lives with the engine.
|
|
1057
|
+
if (t && e.T & CONFIG_OVERRIDE_SUPERSEDED) return GlobalQueue.Yt(e);
|
|
1058
|
+
return unwrapOverride(e.o?.be);
|
|
1059
|
+
}
|
|
853
1060
|
e.T |= CONFIG_AUTHORITATIVE_OBSERVED;
|
|
854
1061
|
}
|
|
855
1062
|
// Entanglement gate: a reader recomputing under an optimistic lane that reads
|
|
@@ -859,8 +1066,8 @@ function read(e) {
|
|
|
859
1066
|
// _pendingValue for correct fetching. The sub is recorded for replay at commit
|
|
860
1067
|
// so it re-runs with the new committed view. (Gate details live with the
|
|
861
1068
|
// engine — a non-null lane implies it is installed.)
|
|
862
|
-
if (currentOptimisticLane !== null && activeTransition !== null && t !== null && GlobalQueue.
|
|
863
|
-
return e.
|
|
1069
|
+
if (currentOptimisticLane !== null && activeTransition !== null && t !== null && GlobalQueue.Zt(e, l, t)) {
|
|
1070
|
+
return e.me;
|
|
864
1071
|
}
|
|
865
1072
|
// In optimistic lane context, return _value for optimistic/lane-assigned signals
|
|
866
1073
|
// and for regular signals in stale mode (render effects). Non-stale readers (user
|
|
@@ -868,7 +1075,7 @@ function read(e) {
|
|
|
868
1075
|
// (The lane-context clause lives with the engine.) Children-forbidden readers
|
|
869
1076
|
// (createTrackedEffect / onSettled callbacks) get committed visibility — see
|
|
870
1077
|
// readNodeFast (#3006).
|
|
871
|
-
const
|
|
1078
|
+
const u = !t || currentOptimisticLane !== null && GlobalQueue.Kt(e, l, t) || e.Ge === NOT_PENDING || t.T & CONFIG_CHILDREN_FORBIDDEN || stale && heldFromStale(e, t) ||
|
|
872
1079
|
// A17 for HELD truth (#3164, see CONFIG_HELD_TRUTH): staged confirming
|
|
873
1080
|
// truth — fold-staged onto an armed family, or entangle-stolen by an
|
|
874
1081
|
// awaited until() — is masked from ordinary readers until its
|
|
@@ -877,11 +1084,11 @@ function read(e) {
|
|
|
877
1084
|
// compose override + staged truth into a state no timeline contains).
|
|
878
1085
|
// Authoritative readers (until()'s predicate) and latest() see the
|
|
879
1086
|
// staged truth — the tunnel that keeps the hold deadlock-free.
|
|
880
|
-
e.T & CONFIG_HELD_TRUTH && !latestReadActive && !(t.T & CONFIG_AUTHORITATIVE_READ) ? e.
|
|
1087
|
+
e.T & CONFIG_HELD_TRUTH && !latestReadActive && !(t.T & CONFIG_AUTHORITATIVE_READ) ? e.me : e.Ge;
|
|
881
1088
|
// Record that this isPending() probe observed the fresh pending value, so
|
|
882
1089
|
// the probe doesn't pair "pending" with the new value (#2831).
|
|
883
|
-
if (pendingCheckActive) GlobalQueue.
|
|
884
|
-
if (!t &&
|
|
1090
|
+
if (pendingCheckActive) GlobalQueue.jt(e, u);
|
|
1091
|
+
if (!t && l === e && typeof n.oe === "function" && e.T & CONFIG_AUTO_DISPOSE && !(l.S & STATUS_PENDING) && !e.u) {
|
|
885
1092
|
// Deferred, not inline (#3078): an inline unobserved() here made untracked
|
|
886
1093
|
// reads destructive — dispose on this read, full revival recompute on the
|
|
887
1094
|
// next — so consecutive reads could answer differently with no write in
|
|
@@ -891,41 +1098,47 @@ function read(e) {
|
|
|
891
1098
|
dormantNodes.add(e);
|
|
892
1099
|
schedule();
|
|
893
1100
|
}
|
|
894
|
-
return
|
|
1101
|
+
return u;
|
|
895
1102
|
}
|
|
896
1103
|
|
|
897
1104
|
function setSignal(e, t) {
|
|
898
|
-
if (e.
|
|
1105
|
+
if (e.ge && activeTransition !== e.ge) globalQueue.initTransition(e.ge);
|
|
899
1106
|
// The optimistic write path lives with the engine: only optimisticSignal /
|
|
900
1107
|
// optimisticComputed callers and optimistic store nodes carry an
|
|
901
1108
|
// _overrideValue slot (flagged by CONFIG_OPTIMISTIC — a masked read of the
|
|
902
1109
|
// always-present config instead of a missing-property probe), and every
|
|
903
1110
|
// module that installs one installs the engine first.
|
|
904
|
-
if (e.T & CONFIG_OPTIMISTIC
|
|
905
|
-
|
|
1111
|
+
if (e.T & CONFIG_OPTIMISTIC) {
|
|
1112
|
+
if (!projectionWriteActive) return GlobalQueue.Bt(e, t);
|
|
1113
|
+
// An authoritative store landing on an override-covered node: the store
|
|
1114
|
+
// twin of asyncWrite's override branch, decided by the engine (#3331).
|
|
1115
|
+
const n = e.o?.be;
|
|
1116
|
+
if (n !== undefined && n !== NOT_PENDING) return GlobalQueue.zt(e, t);
|
|
1117
|
+
}
|
|
1118
|
+
const n = e.Ge === NOT_PENDING ? e.me : e.Ge;
|
|
906
1119
|
if (typeof t === "function") t = t(n);
|
|
907
1120
|
// Uninitialized check first: the first commit has no previous value, so the
|
|
908
1121
|
// user comparator must not run against `undefined` (matches recompute).
|
|
909
|
-
const i = !!(e.S & STATUS_UNINITIALIZED) || !e.
|
|
1122
|
+
const i = !!(e.S & STATUS_UNINITIALIZED) || !e.ve || !e.ve(n, t);
|
|
910
1123
|
if (!i) return t;
|
|
911
|
-
const
|
|
912
|
-
if (!
|
|
913
|
-
e.
|
|
1124
|
+
const l = e.Ge !== NOT_PENDING;
|
|
1125
|
+
if (!l) queuePendingNode(e);
|
|
1126
|
+
e.Ge = t;
|
|
914
1127
|
// syncCompanions only pokes _pendingSignal/_latestValueComputed — with
|
|
915
1128
|
// neither companion present the call is a guaranteed no-op (companions are
|
|
916
1129
|
// only ever created, never removed, and creating one installs the hook and
|
|
917
1130
|
// sets CONFIG_HAS_COMPANIONS — one masked read replaces two optional-field
|
|
918
1131
|
// probes on every write).
|
|
919
|
-
e.T & CONFIG_HAS_COMPANIONS && GlobalQueue.
|
|
1132
|
+
e.T & CONFIG_HAS_COMPANIONS && GlobalQueue.pe !== null && GlobalQueue.pe(e, t);
|
|
920
1133
|
// _time is a computed-only slot (§12e): writing it on a signal would fork
|
|
921
1134
|
// the lean shape. Every read site is computed-typed.
|
|
922
|
-
if (e.oe !== undefined) e.
|
|
1135
|
+
if (e.oe !== undefined) e._e = clock;
|
|
923
1136
|
// Staged-rewrite fast path (§12d): a re-write to a node whose subscribers
|
|
924
1137
|
// were already walked — and where nothing has recomputed or linked since
|
|
925
1138
|
// (epoch) — re-stages the value and stops. The walk is idempotent (subs
|
|
926
1139
|
// marked, heap entries flag-guarded, effects queued once); lane and reask
|
|
927
1140
|
// contexts change what a walk MEANS, so they always walk.
|
|
928
|
-
if (
|
|
1141
|
+
if (l && e.At === notifyEpoch && currentOptimisticLane === null && !reaskArmed) return t;
|
|
929
1142
|
insertSubs(e);
|
|
930
1143
|
schedule();
|
|
931
1144
|
return t;
|
|
@@ -939,12 +1152,12 @@ function setSignal(e, t) {
|
|
|
939
1152
|
* cleanup point.
|
|
940
1153
|
*/ function suppressComputedRecompute(e) {
|
|
941
1154
|
deleteFromHeap(e, queueFor(e));
|
|
942
|
-
if (!(e.ie & REACTIVE_MANUAL_WRITE) && e.
|
|
1155
|
+
if (!(e.ie & REACTIVE_MANUAL_WRITE) && e.Ge === NOT_PENDING) {
|
|
943
1156
|
queuePendingNode(e);
|
|
944
1157
|
schedule();
|
|
945
1158
|
}
|
|
946
1159
|
e.ie = e.ie & -4 | REACTIVE_MANUAL_WRITE;
|
|
947
|
-
e.
|
|
1160
|
+
e.Jt = clock;
|
|
948
1161
|
}
|
|
949
1162
|
|
|
950
1163
|
/**
|
|
@@ -1014,10 +1227,16 @@ function staleValues(e, t = true) {
|
|
|
1014
1227
|
// refresh is a later, explicit re-ask and lifts the mask — otherwise
|
|
1015
1228
|
// any setStore early in an action silently swallows every refresh()
|
|
1016
1229
|
// for the rest of the transaction (#3026).
|
|
1017
|
-
if (e.
|
|
1230
|
+
if (e.Jt === clock) return;
|
|
1018
1231
|
e.ie &= ~REACTIVE_MANUAL_WRITE;
|
|
1019
|
-
//
|
|
1020
|
-
//
|
|
1232
|
+
// The lift falls through to the re-ask classification below. The held
|
|
1233
|
+
// write's value change already rides the transaction; the refetch it
|
|
1234
|
+
// asks for is the same question with unchanged inputs. Skipping the
|
|
1235
|
+
// mark here classified that refetch as a NEW question, which pends
|
|
1236
|
+
// every leaf (3.1) — an action doing setStore + yield + refresh(store)
|
|
1237
|
+
// lit up every sibling row, and affects() could not narrow it (a mark
|
|
1238
|
+
// only turns pending on). Same-question motion stays silent (3.4);
|
|
1239
|
+
// the written slot and any declared mark carry the pending instead.
|
|
1021
1240
|
}
|
|
1022
1241
|
// A refresh with no value-change dirt already queued is a re-ask of the
|
|
1023
1242
|
// same question: mark it so the recompute classifies any resulting
|
|
@@ -1026,7 +1245,7 @@ function staleValues(e, t = true) {
|
|
|
1026
1245
|
// REACTIVE_IN_HEAP counts as dirt: insertSubs schedules subscribers by
|
|
1027
1246
|
// heap insertion alone (no DIRTY/CHECK flag), so a same-batch value
|
|
1028
1247
|
// change followed by refresh() must not be laundered into a quiet re-ask.
|
|
1029
|
-
|
|
1248
|
+
if (!(e.ie & (REACTIVE_DIRTY | REACTIVE_CHECK | REACTIVE_IN_HEAP))) {
|
|
1030
1249
|
e.ie |= REACTIVE_REASK;
|
|
1031
1250
|
armReaskClear();
|
|
1032
1251
|
}
|
|
@@ -1036,4 +1255,4 @@ function staleValues(e, t = true) {
|
|
|
1036
1255
|
}
|
|
1037
1256
|
}
|
|
1038
1257
|
|
|
1039
|
-
export { READ_SLOW, clearSnapshots, computed, context, createEffectNode, currentOptimisticLane, effectStatusNotify, ext, installAuthoritativeRead, isEqual, latestReadActive, markRefresh, markSnapshotScope, notifyAuthoritativeObservers, optimisticComputed, optimisticSignal, pendingCheckActive, prepareComputed, read, readNodeFast, recompute, releaseSnapshotScope, runWithOwner, setContextInternal, setEffectStatusNotify, setLatestReadActive, setMemo, setPendingCheckActive, setSignal, setSnapshotCapture, signal, snapshotCaptureActive, snapshotSources, stale, staleValues, statusNotifierOf, suppressComputedRecompute, tracking, untrack };
|
|
1258
|
+
export { READ_SLOW, clearSnapshots, computed, context, createEffectNode, currentOptimisticLane, effectStatusNotify, ext, installAuthoritativeRead, isEqual, latestReadActive, markRefresh, markSnapshotScope, notifyAuthoritativeObservers, optimisticComputed, optimisticSignal, pendingCheckActive, prepareComputed, read, readNodeFast, recompute, releaseSnapshotScope, runWithOwner, setContextInternal, setEffectStatusNotify, setLatestReadActive, setMemo, setPendingCheckActive, setSignal, setSlotUnobserved, setSnapshotCapture, signal, slotSignal, slotUnobservedHook, snapshotCaptureActive, snapshotSources, stale, staleValues, statusNotifierOf, suppressComputedRecompute, tracking, unlinkFirewallChild, untrack };
|