@solidjs/signals 2.0.0-rc.3 → 2.0.0-rc.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dev.js +2494 -271
- package/dist/node.cjs +3691 -1595
- package/dist/prod/affects.js +13 -12
- package/dist/prod/boundaries.js +43 -35
- package/dist/prod/core/action.js +3 -3
- package/dist/prod/core/async.js +137 -106
- package/dist/prod/core/constants.js +55 -1
- package/dist/prod/core/core.js +354 -247
- package/dist/prod/core/effect.js +47 -50
- package/dist/prod/core/error.js +13 -1
- package/dist/prod/core/external.js +4 -4
- package/dist/prod/core/graph.js +88 -52
- package/dist/prod/core/heap.js +38 -38
- package/dist/prod/core/lanes.js +34 -34
- package/dist/prod/core/optimistic.js +61 -58
- package/dist/prod/core/owner.js +38 -38
- package/dist/prod/core/scheduler.js +401 -174
- package/dist/prod/core/verdict.js +132 -65
- package/dist/prod/index.js +7 -3
- package/dist/prod/map.js +106 -106
- package/dist/prod/signals.js +253 -25
- package/dist/prod/store/index.js +2 -0
- package/dist/prod/store/next/optimistic.js +314 -121
- package/dist/prod/store/next/patch-hooks.js +13 -0
- package/dist/prod/store/next/patch.js +614 -0
- package/dist/prod/store/next/projection.js +23 -19
- package/dist/prod/store/next/reconcile.js +307 -120
- package/dist/prod/store/next/store.js +440 -117
- package/dist/prod/store/next/target.js +13 -4
- package/dist/prod/store/store.js +5 -5
- package/dist/types/core/async.d.ts +2 -0
- package/dist/types/core/attribution.d.ts +9 -4
- package/dist/types/core/constants.d.ts +54 -0
- package/dist/types/core/core.d.ts +34 -21
- package/dist/types/core/dev.d.ts +8 -0
- package/dist/types/core/error.d.ts +9 -0
- package/dist/types/core/graph.d.ts +22 -0
- package/dist/types/core/index.d.ts +2 -2
- package/dist/types/core/scheduler.d.ts +46 -0
- package/dist/types/core/types.d.ts +12 -0
- package/dist/types/index.d.ts +3 -3
- package/dist/types/signals.d.ts +108 -0
- package/dist/types/store/index.d.ts +2 -0
- package/dist/types/store/next/optimistic.d.ts +13 -10
- package/dist/types/store/next/patch-hooks.d.ts +41 -0
- package/dist/types/store/next/patch.d.ts +91 -0
- package/dist/types/store/next/projection.d.ts +1 -1
- package/dist/types/store/next/reconcile.d.ts +14 -0
- package/dist/types/store/next/store.d.ts +52 -2
- package/dist/types/store/next/target.d.ts +73 -8
- package/dist/types-cjs/core/async.d.cts +2 -0
- package/dist/types-cjs/core/attribution.d.cts +9 -4
- package/dist/types-cjs/core/constants.d.cts +54 -0
- package/dist/types-cjs/core/core.d.cts +34 -21
- package/dist/types-cjs/core/dev.d.cts +8 -0
- package/dist/types-cjs/core/error.d.cts +9 -0
- package/dist/types-cjs/core/graph.d.cts +22 -0
- package/dist/types-cjs/core/index.d.cts +2 -2
- package/dist/types-cjs/core/scheduler.d.cts +46 -0
- package/dist/types-cjs/core/types.d.cts +12 -0
- package/dist/types-cjs/index.d.cts +3 -3
- package/dist/types-cjs/signals.d.cts +108 -0
- package/dist/types-cjs/store/index.d.cts +2 -0
- package/dist/types-cjs/store/next/optimistic.d.cts +13 -10
- package/dist/types-cjs/store/next/patch-hooks.d.cts +41 -0
- package/dist/types-cjs/store/next/patch.d.cts +91 -0
- package/dist/types-cjs/store/next/projection.d.cts +1 -1
- package/dist/types-cjs/store/next/reconcile.d.cts +14 -0
- package/dist/types-cjs/store/next/store.d.cts +52 -2
- package/dist/types-cjs/store/next/target.d.cts +73 -8
- package/package.json +2 -2
package/dist/prod/core/core.js
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import { handleAsync, clearStatus, parkLoadingWindow, notifyStatus, settleErroredDependents } from "./async.js";
|
|
1
|
+
import { releaseFlightTeardown, handleAsync, clearStatus, parkLoadingWindow, notifyStatus, settleErroredDependents, settlePendingSource } 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, 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_AUTHORITATIVE_OBSERVED, CONFIG_HAS_COMPANIONS, REACTIVE_LAZY, REACTIVE_DISPOSED, CONFIG_AUTO_DISPOSE, CONFIG_CHILDREN_FORBIDDEN, CONFIG_FRESH_READ, CONFIG_AUTHORITATIVE_READ, 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, CONFIG_FW_CHILDREN, NO_SNAPSHOT, CONFIG_HAS_SNAPSHOT, REACTIVE_MANUAL_WRITE, STORE_SNAPSHOT_PROPS, EFFECT_USER } from "./constants.js";
|
|
4
4
|
|
|
5
5
|
import { NotReadyError } from "./error.js";
|
|
6
6
|
|
|
7
|
-
import { trimStaleDeps, link,
|
|
7
|
+
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, activeTransition, globalQueue, clock, insertSubs, queuePendingNode, runInTransition, schedule, notifyEpoch, dirtyQueue,
|
|
11
|
+
import { GlobalQueue, bumpNotifyEpoch, activeTransition, globalQueue, clock, 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
|
-
GlobalQueue.
|
|
17
|
+
GlobalQueue.Fe = recompute;
|
|
18
18
|
|
|
19
|
-
GlobalQueue.
|
|
19
|
+
GlobalQueue.He = disposeChildren;
|
|
20
20
|
|
|
21
21
|
let tracking = false;
|
|
22
22
|
|
|
@@ -48,8 +48,8 @@ let snapshotSources = null;
|
|
|
48
48
|
|
|
49
49
|
function ownerInSnapshotScope(e) {
|
|
50
50
|
while (e) {
|
|
51
|
-
if (e.
|
|
52
|
-
e = e.
|
|
51
|
+
if (e.Ve) return true;
|
|
52
|
+
e = e.ke;
|
|
53
53
|
}
|
|
54
54
|
return false;
|
|
55
55
|
}
|
|
@@ -60,41 +60,41 @@ function setSnapshotCapture(e) {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
function markSnapshotScope(e) {
|
|
63
|
-
e.
|
|
63
|
+
e.Ve = true;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
function releaseSnapshotScope(e) {
|
|
67
|
-
e.
|
|
67
|
+
e.Ve = false;
|
|
68
68
|
releaseSubtree(e);
|
|
69
69
|
schedule();
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
function releaseSubtree(e) {
|
|
73
|
-
let t = e.
|
|
73
|
+
let t = e.xe;
|
|
74
74
|
while (t) {
|
|
75
|
-
if (t.
|
|
76
|
-
t = t.
|
|
75
|
+
if (t.Ve) {
|
|
76
|
+
t = t.Le;
|
|
77
77
|
continue;
|
|
78
78
|
}
|
|
79
|
-
if (t.
|
|
79
|
+
if (t.oe) {
|
|
80
80
|
const e = t;
|
|
81
81
|
e.T &= ~CONFIG_IN_SNAPSHOT_SCOPE;
|
|
82
82
|
if (e.ie & REACTIVE_SNAPSHOT_STALE) {
|
|
83
83
|
e.ie &= ~REACTIVE_SNAPSHOT_STALE;
|
|
84
84
|
e.ie |= REACTIVE_DIRTY;
|
|
85
|
-
if (dirtyQueue.
|
|
85
|
+
if (dirtyQueue.Qe > e.Me) dirtyQueue.Qe = e.Me;
|
|
86
86
|
insertIntoHeap(e, dirtyQueue);
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
releaseSubtree(t);
|
|
90
|
-
t = t.
|
|
90
|
+
t = t.Le;
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
function clearSnapshots() {
|
|
95
95
|
if (snapshotSources) {
|
|
96
96
|
for (const e of snapshotSources) {
|
|
97
|
-
delete e.o?.
|
|
97
|
+
delete e.o?.We;
|
|
98
98
|
// StoreNode targets share one pre-initialized hidden class (see
|
|
99
99
|
// createStoreProxy) — assign undefined instead of deleting, and only
|
|
100
100
|
// when present so signal-node sources don't grow the field.
|
|
@@ -108,48 +108,63 @@ function clearSnapshots() {
|
|
|
108
108
|
function recompute(e, t = false) {
|
|
109
109
|
// §12d: any recompute can clean a marked subscriber — invalidate skips.
|
|
110
110
|
bumpNotifyEpoch();
|
|
111
|
-
const n = e.
|
|
111
|
+
const n = e.he;
|
|
112
112
|
if (!t) {
|
|
113
|
-
if (e.
|
|
113
|
+
if (e.Ae && (!n || activeTransition) && activeTransition !== e.Ae) globalQueue.initTransition(e.Ae);
|
|
114
114
|
deleteFromHeap(e, queueFor(e));
|
|
115
|
-
if (e.o !== null)
|
|
115
|
+
if (e.o !== null) {
|
|
116
|
+
e.o.Ie = null;
|
|
117
|
+
// Supersede is where an iterator flight dies (#3122): close it now.
|
|
118
|
+
// Its cleanup(close) registration may sit in a zombie-deferred
|
|
119
|
+
// disposal list that a held transition only drains when the
|
|
120
|
+
// SUPERSEDING flight settles — cancellation must not wait for the
|
|
121
|
+
// work that replaced it. Idempotent with the cleanup-channel close.
|
|
122
|
+
releaseFlightTeardown(e);
|
|
123
|
+
}
|
|
116
124
|
// Tracked effects run after finalizePureQueue, so dispose immediately instead of deferring
|
|
117
|
-
if (e.
|
|
125
|
+
if (e.Ae || n === EFFECT_TRACKED) disposeChildren(e); else if (e.xe !== null || e.pe !== null) {
|
|
118
126
|
markDisposal(e);
|
|
119
127
|
const t = ext(e);
|
|
120
|
-
t.
|
|
121
|
-
t.
|
|
122
|
-
e.
|
|
123
|
-
e.
|
|
124
|
-
e.
|
|
128
|
+
t.qe = e.pe;
|
|
129
|
+
t.Ye = e.xe;
|
|
130
|
+
e.pe = null;
|
|
131
|
+
e.xe = null;
|
|
132
|
+
e.Ze = 0;
|
|
125
133
|
} else ;
|
|
126
134
|
}
|
|
127
135
|
let i = !!(e.ie & REACTIVE_OPTIMISTIC_DIRTY);
|
|
128
|
-
const u = (e.T & CONFIG_OPTIMISTIC) !== 0 && e.o?.
|
|
136
|
+
const u = (e.T & CONFIG_OPTIMISTIC) !== 0 && e.o?.Oe !== NOT_PENDING && e.o?.Oe !== undefined;
|
|
129
137
|
const l = !!(e.S & STATUS_UNINITIALIZED);
|
|
130
138
|
// Outgoing error, captured before the compute clears status: if this run
|
|
131
139
|
// recovers to an unchanged value, dependents still holding this object must
|
|
132
140
|
// be swept (settleErroredDependents, #2949).
|
|
133
141
|
const o = e.S & STATUS_ERROR ? e.o?._ : undefined;
|
|
142
|
+
// Pending SOURCE-hood, captured before the compute clears status: a node
|
|
143
|
+
// whose own flight parked dependents self-registers in _pendingSources
|
|
144
|
+
// (notifyStatus, isSource). If this recompute supersedes that flight and
|
|
145
|
+
// settles synchronously, those dependents settle HERE — asyncWrite's
|
|
146
|
+
// settlePendingSource walk never runs for a landing that was preempted
|
|
147
|
+
// (#3181).
|
|
148
|
+
const s = (e.S & STATUS_PENDING) !== 0 && e.o?.le?.has(e) === true;
|
|
134
149
|
// Re-ask classification lives in the verdict module; capture the flag before
|
|
135
150
|
// the recompute wipes _flags below.
|
|
136
|
-
const
|
|
151
|
+
const a = (e.ie & REACTIVE_REASK) !== 0;
|
|
137
152
|
// Captured before the compute clears it on a sync landing: if that landing
|
|
138
153
|
// is transition-held below, the window must stay open until the hold
|
|
139
154
|
// commits (commitPendingNode) — a closed window plus a held value reads as
|
|
140
155
|
// a pending frame to live observers of the verdict (#2990).
|
|
141
|
-
const
|
|
142
|
-
const
|
|
156
|
+
const r = e.Ne;
|
|
157
|
+
const c = context;
|
|
143
158
|
context = e;
|
|
144
|
-
e.
|
|
145
|
-
e.
|
|
159
|
+
e.je = null;
|
|
160
|
+
e.Ke++;
|
|
146
161
|
e.ie = REACTIVE_RECOMPUTING_DEPS;
|
|
147
162
|
e.Te = clock;
|
|
148
|
-
let
|
|
149
|
-
let
|
|
150
|
-
let
|
|
163
|
+
let _ = e.Pe === NOT_PENDING ? e.be : e.Pe;
|
|
164
|
+
let f = e.Me;
|
|
165
|
+
let I = false;
|
|
151
166
|
let E = tracking;
|
|
152
|
-
let
|
|
167
|
+
let N = currentOptimisticLane;
|
|
153
168
|
tracking = true;
|
|
154
169
|
// A computed's fn establishes its OWN dependencies, so it must never run
|
|
155
170
|
// inside a latest() read window: read() short-circuits through the
|
|
@@ -157,54 +172,54 @@ function recompute(e, t = false) {
|
|
|
157
172
|
// computed) inside latest(fn) came out permanently dependency-less (#2926).
|
|
158
173
|
// latestRead() already suspends the flag for its pull-recomputes; this
|
|
159
174
|
// covers creation-time computes and flushes that run inside the window.
|
|
160
|
-
const
|
|
175
|
+
const T = latestReadActive;
|
|
161
176
|
latestReadActive = false;
|
|
162
177
|
// Lane posture lives with the engine: OPTIMISTIC_DIRTY is only ever set by
|
|
163
178
|
// engine-driven paths, and _optimisticNodes is only pushed by
|
|
164
179
|
// _optimisticWrite, so the hook is installed whenever either gate holds.
|
|
165
180
|
if (i) {
|
|
166
|
-
const t = GlobalQueue.
|
|
181
|
+
const t = GlobalQueue.Be(e, true);
|
|
167
182
|
if (t) currentOptimisticLane = t;
|
|
168
183
|
// `false` = wake-only lane demotion: recompute plain so a mid-tick
|
|
169
184
|
// latest()/isPending() pull stages instead of direct-committing (#3009).
|
|
170
185
|
// The predicate lives with the engine (recomputeLane).
|
|
171
186
|
else if (t === false) i = false;
|
|
172
|
-
} else if (activeTransition && !t && activeTransition.
|
|
187
|
+
} else if (activeTransition && !t && activeTransition.ze.length) {
|
|
173
188
|
// Lane adoption: parent-deeper-than-owned-child can run before its OPT-dirty
|
|
174
189
|
// child propagates. Walk deps once and inherit the OPT lane so this node
|
|
175
190
|
// recomputes under the right posture and propagates correctly.
|
|
176
|
-
const t = GlobalQueue.
|
|
191
|
+
const t = GlobalQueue.Be(e, false);
|
|
177
192
|
if (t) {
|
|
178
193
|
i = true;
|
|
179
194
|
currentOptimisticLane = t;
|
|
180
195
|
}
|
|
181
196
|
}
|
|
182
|
-
const
|
|
197
|
+
const S = n && n !== EFFECT_USER;
|
|
183
198
|
const d = stale;
|
|
184
|
-
if (
|
|
199
|
+
if (S) stale = true;
|
|
185
200
|
try {
|
|
186
201
|
if (!false && e.T & CONFIG_SYNC) {
|
|
187
|
-
|
|
188
|
-
if (e.o !== null) e.o.
|
|
189
|
-
e.
|
|
202
|
+
_ = e.oe(_);
|
|
203
|
+
if (e.o !== null) e.o.Ie = null;
|
|
204
|
+
e.Ne = false;
|
|
190
205
|
} else {
|
|
191
206
|
// Snapshot `_inFlight` so we can detect whether `_fn` self-registered an async
|
|
192
207
|
// subscription (e.g. `createProjection` calls `handleAsync` from inside its body
|
|
193
208
|
// with a setter callback). In that case, the outer `handleAsync` call below would
|
|
194
209
|
// clobber the fresh subscription, so we skip it and let the internally-registered
|
|
195
210
|
// iteration drive updates.
|
|
196
|
-
const t = e.o?.
|
|
197
|
-
const n = e.
|
|
211
|
+
const t = e.o?.Ie;
|
|
212
|
+
const n = e.oe(_);
|
|
198
213
|
const i = typeof n === "object" && n !== null;
|
|
199
|
-
const u = e.o?.
|
|
200
|
-
|
|
214
|
+
const u = e.o?.Ie !== t;
|
|
215
|
+
_ = u || !i ? n : handleAsync(e, n);
|
|
201
216
|
if (!u && !i) {
|
|
202
|
-
if (e.o !== null) e.o.
|
|
217
|
+
if (e.o !== null) e.o.Ie = null;
|
|
203
218
|
// A sync (non-object) return is the first real answer; async-shaped
|
|
204
219
|
// results clear inside handleAsync at their own landing points, and a
|
|
205
220
|
// self-registered flight (inFlightChanged — projections) clears when
|
|
206
221
|
// its internal handleAsync lands.
|
|
207
|
-
e.
|
|
222
|
+
e.Ne = false;
|
|
208
223
|
}
|
|
209
224
|
}
|
|
210
225
|
// On a status-free node clearStatus is a guaranteed no-op: every field
|
|
@@ -214,10 +229,10 @@ function recompute(e, t = false) {
|
|
|
214
229
|
if (e.S !== 0 || e.o !== null) clearStatus(e, t);
|
|
215
230
|
// _optimisticLane is only ever assigned by engine paths (CONFIG_HAS_LANE
|
|
216
231
|
// is their sticky presence mark).
|
|
217
|
-
if (e.T & CONFIG_HAS_LANE && e.o?.
|
|
232
|
+
if (e.T & CONFIG_HAS_LANE && e.o?.Je) GlobalQueue.Xe(e);
|
|
218
233
|
} catch (t) {
|
|
219
234
|
const n = t instanceof NotReadyError;
|
|
220
|
-
if (n && e.
|
|
235
|
+
if (n && e.Ne) {
|
|
221
236
|
// Loading window with an unready sync dependency: register for the
|
|
222
237
|
// source's settle (the settlePendingSource walk runs off
|
|
223
238
|
// _pendingSources + _blocked alone) but take NO read-visible pending
|
|
@@ -232,31 +247,31 @@ function recompute(e, t = false) {
|
|
|
232
247
|
if (n && currentOptimisticLane) GlobalQueue.$e(e);
|
|
233
248
|
let i = false;
|
|
234
249
|
if (n) {
|
|
235
|
-
ext(e).
|
|
236
|
-
if (GlobalQueue.
|
|
250
|
+
ext(e).fe = true;
|
|
251
|
+
if (GlobalQueue.et !== null) i = GlobalQueue.et(e, a);
|
|
237
252
|
}
|
|
238
|
-
notifyStatus(e, n ? STATUS_PENDING : STATUS_ERROR, t, undefined, n ? e.o?.
|
|
239
|
-
if (i) GlobalQueue.
|
|
253
|
+
notifyStatus(e, n ? STATUS_PENDING : STATUS_ERROR, t, undefined, n ? e.o?.Je : undefined);
|
|
254
|
+
if (i) GlobalQueue.k(e);
|
|
240
255
|
}
|
|
241
256
|
} finally {
|
|
242
257
|
tracking = E;
|
|
243
|
-
latestReadActive =
|
|
244
|
-
if (
|
|
258
|
+
latestReadActive = T;
|
|
259
|
+
if (S) stale = d;
|
|
245
260
|
// Consume the missed-wake latch (#3037, set by insertSubs): a dep write
|
|
246
261
|
// landed beneath this pass on a link it had already validated. The wipe
|
|
247
262
|
// below must not key off DIRTY/CHECK — the read-time pull protocol
|
|
248
263
|
// (markNode(c) in read()) marks the running node as part of ordinary
|
|
249
264
|
// bookkeeping, and those marks are correctly discarded here.
|
|
250
|
-
|
|
265
|
+
I = (e.ie & REACTIVE_MISSED_WAKE) !== 0;
|
|
251
266
|
e.ie = REACTIVE_NONE | (t ? e.ie & REACTIVE_SNAPSHOT_STALE : 0);
|
|
252
|
-
context =
|
|
267
|
+
context = c;
|
|
253
268
|
}
|
|
254
269
|
if (!e.o?._) {
|
|
255
270
|
trimStaleDeps(e);
|
|
256
|
-
const
|
|
257
|
-
let
|
|
271
|
+
const a = u ? unwrapOverride(e.o?.Oe) : e.Pe === NOT_PENDING ? e.be : e.Pe;
|
|
272
|
+
let c = false;
|
|
258
273
|
try {
|
|
259
|
-
|
|
274
|
+
c = !n && l || !e.ge || !e.ge(a, _);
|
|
260
275
|
} catch (t) {
|
|
261
276
|
// A throwing user comparator is an error of this node's computation.
|
|
262
277
|
// Route it through the same status path as a compute-phase throw so
|
|
@@ -269,55 +284,65 @@ function recompute(e, t = false) {
|
|
|
269
284
|
// its runner — happen here instead. `!create` matches the previous `initialized`
|
|
270
285
|
// gate: the explicit recompute(node, true) inside effect() does not enqueue, so
|
|
271
286
|
// effect() can call its runner synchronously for the first run.
|
|
272
|
-
if (n &&
|
|
273
|
-
e.
|
|
287
|
+
if (n && c) {
|
|
288
|
+
e.tt = !e.o?._;
|
|
274
289
|
// Reuse one bound runner per effect — runEffect no-ops on a stale
|
|
275
290
|
// `_modified`, so re-enqueueing the same function is harmless.
|
|
276
|
-
if (!t) e.C.enqueue(n, e.
|
|
291
|
+
if (!t) e.C.enqueue(n, e.nt ??= GlobalQueue.it.bind(null, e));
|
|
277
292
|
}
|
|
278
|
-
if (e.o?._) ; else if (
|
|
279
|
-
const l = u ? e.o?.
|
|
293
|
+
if (e.o?._) ; else if (c) {
|
|
294
|
+
const l = u ? e.o?.Oe : undefined;
|
|
280
295
|
if (t ||
|
|
281
296
|
// Plain sync flush (no transition on either side) commits effect
|
|
282
297
|
// values directly — the pending round-trip (queuePendingNode +
|
|
283
298
|
// commitPendingNodes) exists to sequence transition reveals, and
|
|
284
299
|
// paying it per effect on the plain path is pure overhead.
|
|
285
|
-
|
|
286
|
-
|
|
300
|
+
// DIRECT_COMMIT effects (resolve/until) commit directly even under
|
|
301
|
+
// their own held transition: their applies deliver on a microtask,
|
|
302
|
+
// not the stashed queues, so a staged value would hand the immediate
|
|
303
|
+
// apply stale state — see CONFIG_DIRECT_COMMIT.
|
|
304
|
+
n && (activeTransition !== e.Ae || activeTransition === null || e.T & CONFIG_DIRECT_COMMIT) || i) {
|
|
305
|
+
e.be = _;
|
|
287
306
|
// Lane-propagated correction: upstream data is fresh, correct the
|
|
288
307
|
// override unconditionally. The direct _value commit is the lane's
|
|
289
308
|
// own reveal schedule; drop any superseded older hold so its queued
|
|
290
309
|
// commit can't clobber the fresh value.
|
|
291
310
|
if (u && i) {
|
|
292
|
-
ext(e).
|
|
311
|
+
ext(e).Oe = _ === undefined ? OVERRIDE_UNDEFINED : _;
|
|
293
312
|
e.Pe = NOT_PENDING;
|
|
294
313
|
}
|
|
295
314
|
} else {
|
|
296
|
-
e.Pe =
|
|
315
|
+
e.Pe = _;
|
|
297
316
|
// A window landing that gets held re-opens the window until the hold
|
|
298
317
|
// commits — the verdict's held-value branch is window-gated (#2990).
|
|
299
|
-
if (
|
|
318
|
+
if (r) e.Ne = true;
|
|
300
319
|
// Transition-held sync recompute is a write path like setSignal/asyncWrite,
|
|
301
320
|
// so sync derivations of held sources stay visible to isPending()/latest()
|
|
302
321
|
// (#2831). Both companion writes are transition-scoped (optimistic) and
|
|
303
322
|
// auto-revert/re-derive at commit. Skipped for plain flushes where the
|
|
304
323
|
// pending value commits before effects run.
|
|
305
|
-
if ((activeTransition || e.
|
|
324
|
+
if ((activeTransition || e.Ae) && GlobalQueue.Re !== null) GlobalQueue.Re(e, _);
|
|
306
325
|
}
|
|
307
326
|
// insertSubs only walks _subs (no scheduling of its own), so a
|
|
308
327
|
// subscriber-less node has nothing to notify.
|
|
309
|
-
if (e.u !== null && (!u || i || e.o?.
|
|
328
|
+
if (e.u !== null && (!u || i || e.o?.Oe !== l)) insertSubs(e, i || u);
|
|
310
329
|
} else if (u) {
|
|
311
330
|
// Unchanged value (equals the override) recomputed while the override
|
|
312
331
|
// is active: _value may still be stale, so hold the authoritative value
|
|
313
332
|
// for commit on its own transition's schedule — invisibly (A17/A18).
|
|
314
333
|
if (e.Pe === NOT_PENDING) queuePendingNode(e);
|
|
315
|
-
e.Pe =
|
|
316
|
-
if (
|
|
334
|
+
e.Pe = _;
|
|
335
|
+
if (r) e.Ne = true;
|
|
317
336
|
// see the held branch above (#2990)
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
337
|
+
// A authoritative-view reader (until()) observed this node past its
|
|
338
|
+
// override — and "authoritative arrival equal to the override" is
|
|
339
|
+
// exactly the acknowledgment it waits for. Wake those readers only;
|
|
340
|
+
// A17 silence holds for every ordinary subscriber. (Hook installed by
|
|
341
|
+
// until(), the only setter of the gating bit.)
|
|
342
|
+
if (e.T & CONFIG_AUTHORITATIVE_OBSERVED) GlobalQueue.Ue(e);
|
|
343
|
+
} else if (e.Me != f) {
|
|
344
|
+
for (let t = e.u; t !== null; t = t.ae) {
|
|
345
|
+
insertIntoHeapHeight(t.ce, queueFor(t.ce));
|
|
321
346
|
}
|
|
322
347
|
}
|
|
323
348
|
// Silent recovery: errored → unchanged value fires no notification, but
|
|
@@ -325,23 +350,34 @@ function recompute(e, t = false) {
|
|
|
325
350
|
// in an errored run and may sit on stale commits (#2949). Changed-value
|
|
326
351
|
// recoveries ride insertSubs above; a comparator throw re-errored the node
|
|
327
352
|
// (el._x?._error re-set), so this only runs on a genuinely clean recovery.
|
|
328
|
-
if (o !== undefined && !
|
|
353
|
+
if (o !== undefined && !c && !e.o?._) settleErroredDependents(e, o);
|
|
354
|
+
// Pending twin of the sweep above (#3181): a flight superseded by this
|
|
355
|
+
// synchronous settle leaves every registered dependent still flagged
|
|
356
|
+
// STATUS_PENDING with a pending source that will never land — asyncWrite
|
|
357
|
+
// owns the walk only for flights that actually land. An unchanged value
|
|
358
|
+
// is the dangerous shape (a projection reconciling in place: a memo over
|
|
359
|
+
// it re-throws its cached NotReadyError forever, and every reader that
|
|
360
|
+
// suspended through that memo re-parks on the dead source), but the walk
|
|
361
|
+
// runs on the changed shape too, exactly as the landing path does —
|
|
362
|
+
// insertSubs notifies value SUBSCRIBERS, not pending REGISTRANTS, and
|
|
363
|
+
// the two sets only partially overlap.
|
|
364
|
+
if (s && !(e.S & STATUS_PENDING)) settlePendingSource(e);
|
|
329
365
|
}
|
|
330
|
-
currentOptimisticLane =
|
|
331
|
-
const
|
|
366
|
+
currentOptimisticLane = N;
|
|
367
|
+
const A = e.Pe !== NOT_PENDING || e.o !== null && (e.o.Ye !== null || e.o.qe !== null) || (e.S & (STATUS_PENDING | STATUS_UNINITIALIZED)) !== 0;
|
|
332
368
|
// Override-covered holds (hasOverride) always queue: their commit belongs
|
|
333
369
|
// to their own transition's schedule (A18 re-rule) and is unobservable
|
|
334
370
|
// under the override (A17). Revert no longer commits anything, so an
|
|
335
371
|
// unqueued covered hold would leak (INV-7) once the revert clears
|
|
336
372
|
// _transition.
|
|
337
|
-
|
|
338
|
-
e.
|
|
373
|
+
A && (!t || e.S & STATUS_PENDING) && (!e.Ae || u) && queuePendingNode(e);
|
|
374
|
+
e.Ae && n && activeTransition !== e.Ae && runInTransition(e.Ae, () => recompute(e));
|
|
339
375
|
// Missed-wake reschedule (see the finally above): values this pass read
|
|
340
376
|
// before the nested commit are stale, so run again now that the heap will
|
|
341
377
|
// accept the node. Equality gates stop same-value landings from cascading,
|
|
342
378
|
// and a re-run only latches again if another nested commit changes a dep
|
|
343
379
|
// beneath it — convergent unless deps genuinely keep changing.
|
|
344
|
-
if (
|
|
380
|
+
if (I) {
|
|
345
381
|
enqueueSub(e);
|
|
346
382
|
schedule();
|
|
347
383
|
}
|
|
@@ -352,12 +388,14 @@ function updateIfNecessary(e) {
|
|
|
352
388
|
// (_depsTail/_depGen) is live, and a nested recompute would corrupt it.
|
|
353
389
|
// A mid-pass mark stays latched for recompute's own tail to reschedule
|
|
354
390
|
// (#3037); readers meanwhile serve the values the pass has so far.
|
|
355
|
-
|
|
391
|
+
// Never recompute a DISPOSED node either: recompute rewrites _flags and
|
|
392
|
+
// would resurrect it (#2983) — readers serve its last value.
|
|
393
|
+
if (e.ie & (REACTIVE_RECOMPUTING_DEPS | REACTIVE_DISPOSED)) return;
|
|
356
394
|
if (e.ie & REACTIVE_CHECK) {
|
|
357
|
-
for (let t = e.
|
|
358
|
-
const n = t.
|
|
359
|
-
const i = n.
|
|
360
|
-
if (i.
|
|
395
|
+
for (let t = e.ut; t; t = t.lt) {
|
|
396
|
+
const n = t.ot;
|
|
397
|
+
const i = n.st || n;
|
|
398
|
+
if (i.oe) {
|
|
361
399
|
updateIfNecessary(i);
|
|
362
400
|
}
|
|
363
401
|
if (e.ie & REACTIVE_DIRTY) {
|
|
@@ -365,7 +403,7 @@ function updateIfNecessary(e) {
|
|
|
365
403
|
}
|
|
366
404
|
}
|
|
367
405
|
}
|
|
368
|
-
if (e.ie & (REACTIVE_DIRTY | REACTIVE_OPTIMISTIC_DIRTY) || e.o?._ && e.Te < clock && !e.o?.
|
|
406
|
+
if (e.ie & (REACTIVE_DIRTY | REACTIVE_OPTIMISTIC_DIRTY) || e.o?._ && e.Te < clock && !e.o?.Ie) {
|
|
369
407
|
recompute(e);
|
|
370
408
|
}
|
|
371
409
|
e.ie = e.ie & (REACTIVE_SNAPSHOT_STALE | REACTIVE_IN_HEAP | REACTIVE_IN_HEAP_HEIGHT);
|
|
@@ -380,40 +418,40 @@ function computed(e, t) {
|
|
|
380
418
|
const u = {
|
|
381
419
|
id: inheritId(t, n, context),
|
|
382
420
|
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),
|
|
383
|
-
|
|
384
|
-
|
|
421
|
+
ge: t?.equals ?? isEqual,
|
|
422
|
+
pe: null,
|
|
385
423
|
C: context?.C ?? globalQueue,
|
|
386
424
|
we: context?.we ?? defaultContext,
|
|
387
|
-
Me: 0,
|
|
388
|
-
Se: e,
|
|
389
|
-
be: i ? t.loadingValue : undefined,
|
|
390
|
-
Le: 0,
|
|
391
|
-
ot: undefined,
|
|
392
|
-
st: null,
|
|
393
|
-
nt: null,
|
|
394
|
-
Ye: null,
|
|
395
425
|
Ze: 0,
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
426
|
+
oe: e,
|
|
427
|
+
be: i ? t.loadingValue : undefined,
|
|
428
|
+
Me: 0,
|
|
429
|
+
rt: undefined,
|
|
400
430
|
ct: null,
|
|
401
|
-
|
|
431
|
+
ut: null,
|
|
432
|
+
je: null,
|
|
433
|
+
Ke: 0,
|
|
434
|
+
u: null,
|
|
435
|
+
_t: null,
|
|
436
|
+
ke: context,
|
|
437
|
+
Le: null,
|
|
438
|
+
ft: null,
|
|
439
|
+
xe: null,
|
|
402
440
|
ie: t?.lazy ? REACTIVE_LAZY : REACTIVE_NONE,
|
|
403
441
|
// A loadingValue node is born committed: commit #0 is already in _value.
|
|
404
442
|
S: i ? 0 : STATUS_UNINITIALIZED,
|
|
405
443
|
Te: clock,
|
|
406
444
|
Pe: NOT_PENDING,
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
445
|
+
Ae: null,
|
|
446
|
+
It: -1,
|
|
447
|
+
Ne: i,
|
|
410
448
|
// Cold machinery (async/transition/optimistic/verdict slots) lives one
|
|
411
449
|
// hop away in the lazily-allocated extension — the core literal MUST
|
|
412
450
|
// stay under V8's in-object boundary (§12: past ~39 fields every
|
|
413
451
|
// allocation spills to a backing store and creation cost ~4x's).
|
|
414
452
|
o: null
|
|
415
453
|
};
|
|
416
|
-
if (t?.unobserved) ext(u).
|
|
454
|
+
if (t?.unobserved) ext(u).Et = t.unobserved;
|
|
417
455
|
setupComputedNode(u, t);
|
|
418
456
|
return u;
|
|
419
457
|
}
|
|
@@ -423,25 +461,26 @@ function computed(e, t) {
|
|
|
423
461
|
* this; hot paths read `el._x?._field` gated by the _config presence bits.
|
|
424
462
|
* Never call ext() just to store a field's default. */ function ext(e) {
|
|
425
463
|
return e.o ??= {
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
464
|
+
Oe: undefined,
|
|
465
|
+
Nt: undefined,
|
|
466
|
+
Je: undefined,
|
|
467
|
+
ye: undefined,
|
|
468
|
+
Ce: undefined,
|
|
469
|
+
Tt: undefined,
|
|
432
470
|
t: 0,
|
|
471
|
+
Ie: null,
|
|
433
472
|
Ee: null,
|
|
434
473
|
_: undefined,
|
|
435
|
-
|
|
474
|
+
fe: undefined,
|
|
436
475
|
le: undefined,
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
We: null,
|
|
476
|
+
h: undefined,
|
|
477
|
+
Ge: false,
|
|
478
|
+
i: null,
|
|
479
|
+
Et: undefined,
|
|
480
|
+
We: undefined,
|
|
443
481
|
qe: null,
|
|
444
|
-
|
|
482
|
+
Ye: null,
|
|
483
|
+
St: undefined
|
|
445
484
|
};
|
|
446
485
|
}
|
|
447
486
|
|
|
@@ -450,50 +489,74 @@ function computed(e, t) {
|
|
|
450
489
|
* so V8 sees the full hidden class shape at construction time. Effects always run in lazy
|
|
451
490
|
* mode (recompute is called explicitly by `effect()`), so we hardcode the lazy bits and skip
|
|
452
491
|
* the auto-dispose CONFIG bit (effect() previously cleared it post-construction).
|
|
453
|
-
*/ function createEffectNode(e, t, n, i, u
|
|
454
|
-
const
|
|
455
|
-
const
|
|
456
|
-
id: inheritId(
|
|
457
|
-
T: (
|
|
458
|
-
|
|
459
|
-
|
|
492
|
+
*/ function createEffectNode(e, t, n, i, u) {
|
|
493
|
+
const l = u?.transparent ?? false;
|
|
494
|
+
const o = {
|
|
495
|
+
id: inheritId(u, l, context),
|
|
496
|
+
T: (l ? CONFIG_TRANSPARENT : 0) | (u?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (u?.sync ? CONFIG_SYNC : 0) | (u?.dt ?? 0) | (snapshotCaptureActive && ownerInSnapshotScope(context) ? CONFIG_IN_SNAPSHOT_SCOPE : 0),
|
|
497
|
+
ge: false,
|
|
498
|
+
pe: null,
|
|
460
499
|
C: context?.C ?? globalQueue,
|
|
461
500
|
we: context?.we ?? defaultContext,
|
|
462
|
-
Me: 0,
|
|
463
|
-
Se: e,
|
|
464
|
-
be: undefined,
|
|
465
|
-
Le: 0,
|
|
466
|
-
ot: undefined,
|
|
467
|
-
st: null,
|
|
468
|
-
nt: null,
|
|
469
|
-
Ye: null,
|
|
470
501
|
Ze: 0,
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
502
|
+
oe: e,
|
|
503
|
+
be: undefined,
|
|
504
|
+
Me: 0,
|
|
505
|
+
rt: undefined,
|
|
475
506
|
ct: null,
|
|
476
|
-
|
|
507
|
+
ut: null,
|
|
508
|
+
je: null,
|
|
509
|
+
Ke: 0,
|
|
510
|
+
u: null,
|
|
511
|
+
_t: null,
|
|
512
|
+
ke: context,
|
|
513
|
+
Le: null,
|
|
514
|
+
ft: null,
|
|
515
|
+
xe: null,
|
|
477
516
|
ie: REACTIVE_LAZY,
|
|
478
517
|
S: STATUS_UNINITIALIZED,
|
|
479
518
|
Te: clock,
|
|
480
519
|
Pe: NOT_PENDING,
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
Tt: undefined,
|
|
486
|
-
dt: t,
|
|
487
|
-
St: n,
|
|
520
|
+
Ae: null,
|
|
521
|
+
It: -1,
|
|
522
|
+
Ne: false,
|
|
523
|
+
tt: false,
|
|
488
524
|
At: undefined,
|
|
489
|
-
|
|
525
|
+
Ot: t,
|
|
526
|
+
Ct: n,
|
|
527
|
+
Rt: undefined,
|
|
528
|
+
he: i,
|
|
490
529
|
o: null
|
|
491
530
|
};
|
|
492
|
-
//
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
531
|
+
// Effects dispatch status through the SHARED notifier (statusNotifierOf,
|
|
532
|
+
// keyed off _type) — storing it per node forced a full NodeExtension
|
|
533
|
+
// allocation on EVERY effect at creation (an alloc + 19 field stores,
|
|
534
|
+
// +23% effect creation, caught by the creation benches). Only genuinely
|
|
535
|
+
// per-node channels (boundaries) live on _x.
|
|
536
|
+
if (u?.unobserved) ext(o).Et = u.unobserved;
|
|
537
|
+
setupComputedNode(o, lazyOptions);
|
|
538
|
+
return o;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* The shared status notifier for effect nodes, installed once by effect.ts
|
|
543
|
+
* at module evaluation (`this`-dispatched — one function serves every
|
|
544
|
+
* effect, so nodes never store it). Boundary computeds keep their own
|
|
545
|
+
* per-node channel on `_x._notifyStatus`, which takes precedence.
|
|
546
|
+
*/ let effectStatusNotify = null;
|
|
547
|
+
|
|
548
|
+
function setEffectStatusNotify(e) {
|
|
549
|
+
effectStatusNotify = e;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/** Resolve a node's status notifier: an own `_x` channel (boundaries) wins;
|
|
553
|
+
* effect nodes (`_type` — EFFECT_PURE is 0, and only effect literals carry
|
|
554
|
+
* the field) fall back to the shared notifier. Presence doubles as the
|
|
555
|
+
* "display consumer" membership test in the status walks, exactly as the
|
|
556
|
+
* per-node field did when every effect carried one. */ function statusNotifierOf(e) {
|
|
557
|
+
const t = e.o?.h;
|
|
558
|
+
if (t !== undefined) return t;
|
|
559
|
+
return e.he ? effectStatusNotify ?? undefined : undefined;
|
|
497
560
|
}
|
|
498
561
|
|
|
499
562
|
const lazyOptions = {
|
|
@@ -501,24 +564,24 @@ const lazyOptions = {
|
|
|
501
564
|
};
|
|
502
565
|
|
|
503
566
|
function setupComputedNode(e, t) {
|
|
504
|
-
e.
|
|
505
|
-
const n = context?.
|
|
567
|
+
e.ct = e;
|
|
568
|
+
const n = context?.Gt ? context.Dt : context;
|
|
506
569
|
if (context) {
|
|
507
|
-
const t = context.
|
|
570
|
+
const t = context.xe;
|
|
508
571
|
if (t === null) {
|
|
509
|
-
context.
|
|
572
|
+
context.xe = e;
|
|
510
573
|
} else {
|
|
511
|
-
e.
|
|
512
|
-
t.
|
|
513
|
-
context.
|
|
574
|
+
e.Le = t;
|
|
575
|
+
t.ft = e;
|
|
576
|
+
context.xe = e;
|
|
514
577
|
}
|
|
515
578
|
}
|
|
516
|
-
if (n) e.
|
|
517
|
-
if (GlobalQueue.
|
|
579
|
+
if (n) e.Me = n.Me + 1;
|
|
580
|
+
if (GlobalQueue.Pt !== null) GlobalQueue.Pt(e);
|
|
518
581
|
!t?.lazy && recompute(e, true);
|
|
519
582
|
if (snapshotCaptureActive && !t?.lazy) {
|
|
520
583
|
if (!(e.S & STATUS_PENDING) && !(e.T & CONFIG_NO_SNAPSHOT)) {
|
|
521
|
-
ext(e).
|
|
584
|
+
ext(e).We = e.be === undefined ? NO_SNAPSHOT : e.be;
|
|
522
585
|
e.T |= CONFIG_HAS_SNAPSHOT;
|
|
523
586
|
snapshotSources.add(e);
|
|
524
587
|
}
|
|
@@ -527,31 +590,31 @@ function setupComputedNode(e, t) {
|
|
|
527
590
|
|
|
528
591
|
function signal(e, t, n = null) {
|
|
529
592
|
const i = {
|
|
530
|
-
|
|
593
|
+
ge: t?.equals ?? isEqual,
|
|
531
594
|
T: (t?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (t?.H ? CONFIG_NO_SNAPSHOT : 0),
|
|
532
595
|
be: e,
|
|
533
596
|
u: null,
|
|
534
|
-
|
|
597
|
+
_t: null,
|
|
535
598
|
Te: clock,
|
|
536
|
-
|
|
537
|
-
|
|
599
|
+
st: n,
|
|
600
|
+
Se: n?.o?.i || null,
|
|
538
601
|
Pe: NOT_PENDING,
|
|
539
602
|
// Signal-literal diet (§12e): NO _time/_fn/_statusFlags slots. Stores
|
|
540
603
|
// materialize one signal per touched leaf, so signal bytes are store
|
|
541
604
|
// bytes. _time is write-only on signals (every read site is computed-
|
|
542
605
|
// typed error-retry gating); _fn/_statusFlags read falsy-identically as
|
|
543
606
|
// missing properties on the shared paths (undefined masks to 0).
|
|
544
|
-
|
|
545
|
-
|
|
607
|
+
Ae: null,
|
|
608
|
+
It: -1,
|
|
546
609
|
o: null
|
|
547
610
|
};
|
|
548
|
-
if (t?.unobserved) ext(i).
|
|
611
|
+
if (t?.unobserved) ext(i).Et = t.unobserved;
|
|
549
612
|
if (n) {
|
|
550
|
-
ext(n).
|
|
613
|
+
ext(n).i = i;
|
|
551
614
|
n.T |= CONFIG_FW_CHILDREN;
|
|
552
615
|
}
|
|
553
616
|
if (snapshotCaptureActive && !(i.T & CONFIG_NO_SNAPSHOT) && !((n?.S ?? 0) & STATUS_PENDING)) {
|
|
554
|
-
ext(i).
|
|
617
|
+
ext(i).We = e === undefined ? NO_SNAPSHOT : e;
|
|
555
618
|
i.T |= CONFIG_HAS_SNAPSHOT;
|
|
556
619
|
snapshotSources.add(i);
|
|
557
620
|
}
|
|
@@ -560,14 +623,14 @@ function signal(e, t, n = null) {
|
|
|
560
623
|
|
|
561
624
|
function optimisticSignal(e, t) {
|
|
562
625
|
const n = signal(e, t);
|
|
563
|
-
ext(n).
|
|
626
|
+
ext(n).Oe = NOT_PENDING;
|
|
564
627
|
n.T |= CONFIG_OPTIMISTIC;
|
|
565
628
|
return n;
|
|
566
629
|
}
|
|
567
630
|
|
|
568
631
|
function optimisticComputed(e, t) {
|
|
569
632
|
const n = computed(e, t);
|
|
570
|
-
ext(n).
|
|
633
|
+
ext(n).Oe = NOT_PENDING;
|
|
571
634
|
n.T |= CONFIG_OPTIMISTIC;
|
|
572
635
|
return n;
|
|
573
636
|
}
|
|
@@ -598,11 +661,11 @@ function isEqual(e, t) {
|
|
|
598
661
|
* );
|
|
599
662
|
* ```
|
|
600
663
|
*/ function untrack(e, t) {
|
|
601
|
-
if (GlobalQueue.
|
|
664
|
+
if (GlobalQueue.ht === null && !tracking && true) return e();
|
|
602
665
|
const n = tracking;
|
|
603
666
|
tracking = false;
|
|
604
667
|
try {
|
|
605
|
-
if (GlobalQueue.
|
|
668
|
+
if (GlobalQueue.ht !== null) return GlobalQueue.ht(e);
|
|
606
669
|
return e();
|
|
607
670
|
} finally {
|
|
608
671
|
tracking = n;
|
|
@@ -643,10 +706,38 @@ function isEqual(e, t) {
|
|
|
643
706
|
* fast path, `_fn` nodes need prepareComputed, and firewall / override /
|
|
644
707
|
* snapshot / transition / lane / dev-strictRead state all take the full
|
|
645
708
|
* resolution. Anything slow returns READ_SLOW; the caller then calls read().
|
|
646
|
-
*/
|
|
647
|
-
|
|
709
|
+
*/
|
|
710
|
+
/**
|
|
711
|
+
* Wake only authoritative-view readers (until() predicates) subscribed to `el`.
|
|
712
|
+
* The A17-silent ack paths — an authoritative arrival equal to the active
|
|
713
|
+
* override — use this so the predicate re-evaluates without re-firing
|
|
714
|
+
* ordinary subscribers whose visible (override) value did not change.
|
|
715
|
+
* Pay-for-use: reached through GlobalQueue._notifyAuthoritativeObservers,
|
|
716
|
+
* installed at first until() call — apps that never use until() shake it.
|
|
717
|
+
*/ function notifyAuthoritativeObservers(e) {
|
|
718
|
+
for (let t = e.u; t !== null; t = t.ae) {
|
|
719
|
+
const e = t.ce;
|
|
720
|
+
if (!(e.T & CONFIG_AUTHORITATIVE_READ)) continue;
|
|
721
|
+
// Missed-wake latch (#3037), same contract as insertSubs: the reader may
|
|
722
|
+
// itself have pulled this recompute (updateIfNecessary from its own
|
|
723
|
+
// read), and the heap refuses RECOMPUTING nodes — latch so recompute's
|
|
724
|
+
// tail reschedules it with the staged value visible.
|
|
725
|
+
if (e.ie & REACTIVE_RECOMPUTING_DEPS && t.Ft === e.Ke && t !== e.je) e.ie |= REACTIVE_MISSED_WAKE;
|
|
726
|
+
enqueueSub(e);
|
|
727
|
+
}
|
|
728
|
+
schedule();
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/** Installs the until() machinery hook. Idempotent; called by until() before
|
|
732
|
+
* any authoritative-view read happens (same late-binding contract as the
|
|
733
|
+
* optimistic engine). */ function installAuthoritativeRead() {
|
|
734
|
+
if (GlobalQueue.Ue === null) GlobalQueue.Ue = notifyAuthoritativeObservers;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function readNodeFast(e) {
|
|
738
|
+
if (latestReadActive || pendingCheckActive || e.oe || e.st || e.o?.Oe !== undefined || e.o?.We !== undefined || activeTransition !== null || currentOptimisticLane !== null || snapshotCaptureActive || false) return READ_SLOW;
|
|
648
739
|
let t = context;
|
|
649
|
-
if (t?.
|
|
740
|
+
if (t?.Gt) t = t.Dt;
|
|
650
741
|
if (t && tracking) link(e, t);
|
|
651
742
|
// Children-forbidden readers (createTrackedEffect / onSettled callbacks) get
|
|
652
743
|
// committed visibility: like the effect half of createEffect and event
|
|
@@ -660,47 +751,54 @@ function read(e) {
|
|
|
660
751
|
// Checked before isPending so that isPending(() => latest(x)) checks
|
|
661
752
|
// the _pendingSignal of _latestValueComputed (async in flight) rather
|
|
662
753
|
// than the original node (which stays "pending" while held in a transition).
|
|
663
|
-
if (latestReadActive) return GlobalQueue.
|
|
754
|
+
if (latestReadActive) return GlobalQueue.gt(e);
|
|
664
755
|
let t = context;
|
|
665
|
-
if (t?.
|
|
756
|
+
if (t?.Gt) t = t.Dt;
|
|
666
757
|
const n = e;
|
|
667
|
-
const i = e.
|
|
758
|
+
const i = e.st;
|
|
668
759
|
const u = i || e;
|
|
669
760
|
// Handle isPending() mode: collect pending state while preserving normal read semantics.
|
|
670
761
|
// Probe mode is suspended while preparing the node so nested reads during a
|
|
671
762
|
// recompute don't collect into the probe.
|
|
672
763
|
if (pendingCheckActive) {
|
|
673
|
-
GlobalQueue.
|
|
674
|
-
} else if (typeof n.
|
|
764
|
+
GlobalQueue.Ht(e, t, u, i);
|
|
765
|
+
} else if (typeof n.oe === "function") {
|
|
675
766
|
prepareComputed(e, false);
|
|
676
767
|
}
|
|
677
|
-
if (!n.
|
|
768
|
+
if (!n.oe && u === e && e.o?.Oe === undefined && e.o?.We === undefined && activeTransition === null && currentOptimisticLane === null && !snapshotCaptureActive && true) {
|
|
678
769
|
if (t && tracking) link(e, t);
|
|
679
770
|
// Committed visibility for children-forbidden readers — see readNodeFast.
|
|
680
771
|
return !t || e.Pe === NOT_PENDING || t.T & CONFIG_CHILDREN_FORBIDDEN ? e.be : e.Pe;
|
|
681
772
|
}
|
|
682
773
|
if (t && tracking) {
|
|
683
774
|
link(e, t, pendingCheckActive);
|
|
684
|
-
if (u.
|
|
775
|
+
if (u.oe) {
|
|
685
776
|
const n = queueFor(e);
|
|
686
|
-
if (u.
|
|
777
|
+
if (u.Me >= n.Qe) {
|
|
687
778
|
markNode(t);
|
|
688
779
|
markHeap(n);
|
|
689
780
|
updateIfNecessary(u);
|
|
690
781
|
}
|
|
691
|
-
|
|
782
|
+
// Fresh-pull readers (awaitable refresh's waiter) recompute a dirty
|
|
783
|
+
// source inline even when the height gate defers to the flush: the
|
|
784
|
+
// waiter must park on the re-ask's window (or serve its sync answer),
|
|
785
|
+
// never read the PRE-re-ask value as settled. Self-guarded: a clean
|
|
786
|
+
// node no-ops and updateIfNecessary refuses disposed nodes (#2983) —
|
|
787
|
+
// a dead target serves its last value, which is already quiescent.
|
|
788
|
+
else if (t.T & CONFIG_FRESH_READ) updateIfNecessary(u);
|
|
789
|
+
const i = u.Me;
|
|
692
790
|
// parent check is shallow, might need to be recursive
|
|
693
|
-
if (i >= t.
|
|
694
|
-
t.
|
|
791
|
+
if (i >= t.Me && e.ke !== t) {
|
|
792
|
+
t.Me = i + 1;
|
|
695
793
|
}
|
|
696
794
|
}
|
|
697
795
|
}
|
|
698
796
|
if (u.S & STATUS_PENDING) {
|
|
699
|
-
if (t && !(stale && u.
|
|
797
|
+
if (t && !(stale && u.Ae && activeTransition !== u.Ae)) {
|
|
700
798
|
// Per-lane suspension lives with the engine (a non-null lane implies it
|
|
701
799
|
// is installed): under a lane, only same-lane pending async without an
|
|
702
800
|
// active override throws.
|
|
703
|
-
if (currentOptimisticLane === null || GlobalQueue.
|
|
801
|
+
if (currentOptimisticLane === null || GlobalQueue.Vt(u)) {
|
|
704
802
|
if (!tracking && e !== t) link(e, t);
|
|
705
803
|
throw u.o?._;
|
|
706
804
|
}
|
|
@@ -725,7 +823,7 @@ function read(e) {
|
|
|
725
823
|
// firewall-backed reads follow the same rules (memo parity, #2897 ruling):
|
|
726
824
|
// an errored derive throws for every late reader instead of silently
|
|
727
825
|
// serving node values (the seed, or last-good data after a failed refetch).
|
|
728
|
-
if (u.
|
|
826
|
+
if (u.oe && u.S & STATUS_ERROR) {
|
|
729
827
|
// Only a genuine reactive re-read may retry an errored async source:
|
|
730
828
|
// - tracking: owned/tracked scope only (never events / `untrack` / effect side-effect phase)
|
|
731
829
|
// - !pendingCheckActive: an `isPending` probe observes the error, never refetches
|
|
@@ -736,7 +834,7 @@ function read(e) {
|
|
|
736
834
|
} else throw u.o?._;
|
|
737
835
|
}
|
|
738
836
|
if (snapshotCaptureActive && t && t.T & CONFIG_IN_SNAPSHOT_SCOPE) {
|
|
739
|
-
const n = e.o?.
|
|
837
|
+
const n = e.o?.We;
|
|
740
838
|
if (n !== undefined) {
|
|
741
839
|
const i = n === NO_SNAPSHOT ? undefined : n;
|
|
742
840
|
const u = e.Pe !== NOT_PENDING ? e.Pe : e.be;
|
|
@@ -744,9 +842,19 @@ function read(e) {
|
|
|
744
842
|
return i;
|
|
745
843
|
}
|
|
746
844
|
}
|
|
747
|
-
if (e.o?.
|
|
748
|
-
// A17: the override IS the value for every reader
|
|
749
|
-
|
|
845
|
+
if (e.o?.Oe !== undefined && e.o?.Oe !== NOT_PENDING) {
|
|
846
|
+
// A17: the override IS the value for every reader — except an authoritative
|
|
847
|
+
// reader (until()'s predicate carries CONFIG_AUTHORITATIVE_READ): it must
|
|
848
|
+
// observe independently-arriving truth, and serving it the caller's own
|
|
849
|
+
// tentative write would trivially satisfy the predicate. The bit is checked
|
|
850
|
+
// on the reading computation itself, so a shared computed the predicate
|
|
851
|
+
// pulls recomputes as ITSELF (context = the memo, no bit) under the normal
|
|
852
|
+
// view. Fall through to normal value selection (staged `_pendingValue` is
|
|
853
|
+
// authoritative — optimism never lives there); the sticky mark makes the
|
|
854
|
+
// A17-silent "landing equals override" paths notify this node's subs so
|
|
855
|
+
// the reader re-runs when truth arrives.
|
|
856
|
+
if (!(t && t.T & CONFIG_AUTHORITATIVE_READ)) return unwrapOverride(e.o?.Oe);
|
|
857
|
+
e.T |= CONFIG_AUTHORITATIVE_OBSERVED;
|
|
750
858
|
}
|
|
751
859
|
// Entanglement gate: a reader recomputing under an optimistic lane that reads
|
|
752
860
|
// a pending mid-transition write sees the committed value. Projection-store
|
|
@@ -755,7 +863,7 @@ function read(e) {
|
|
|
755
863
|
// _pendingValue for correct fetching. The sub is recorded for replay at commit
|
|
756
864
|
// so it re-runs with the new committed view. (Gate details live with the
|
|
757
865
|
// engine — a non-null lane implies it is installed.)
|
|
758
|
-
if (currentOptimisticLane !== null && activeTransition !== null && t !== null && GlobalQueue.
|
|
866
|
+
if (currentOptimisticLane !== null && activeTransition !== null && t !== null && GlobalQueue.vt(e, u, t)) {
|
|
759
867
|
return e.be;
|
|
760
868
|
}
|
|
761
869
|
// In optimistic lane context, return _value for optimistic/lane-assigned signals
|
|
@@ -764,29 +872,45 @@ function read(e) {
|
|
|
764
872
|
// (The lane-context clause lives with the engine.) Children-forbidden readers
|
|
765
873
|
// (createTrackedEffect / onSettled callbacks) get committed visibility — see
|
|
766
874
|
// readNodeFast (#3006).
|
|
767
|
-
const l = !t || currentOptimisticLane !== null && GlobalQueue.
|
|
875
|
+
const l = !t || currentOptimisticLane !== null && GlobalQueue.bt(e, u, t) || e.Pe === NOT_PENDING || t.T & CONFIG_CHILDREN_FORBIDDEN || stale && e.Ae && activeTransition !== e.Ae ||
|
|
876
|
+
// A17 for HELD truth (#3164, see CONFIG_HELD_TRUTH): staged confirming
|
|
877
|
+
// truth — fold-staged onto an armed family, or entangle-stolen by an
|
|
878
|
+
// awaited until() — is masked from ordinary readers until its
|
|
879
|
+
// transaction's reveal; the retaining transaction's own speculative
|
|
880
|
+
// recomputes included (partial override coverage would otherwise
|
|
881
|
+
// compose override + staged truth into a state no timeline contains).
|
|
882
|
+
// Authoritative readers (until()'s predicate) and latest() see the
|
|
883
|
+
// staged truth — the tunnel that keeps the hold deadlock-free.
|
|
884
|
+
e.T & CONFIG_HELD_TRUTH && !latestReadActive && !(t.T & CONFIG_AUTHORITATIVE_READ) ? e.be : e.Pe;
|
|
768
885
|
// Record that this isPending() probe observed the fresh pending value, so
|
|
769
886
|
// the probe doesn't pair "pending" with the new value (#2831).
|
|
770
|
-
if (pendingCheckActive) GlobalQueue.
|
|
771
|
-
if (!t && u === e && typeof n.
|
|
772
|
-
unobserved(
|
|
887
|
+
if (pendingCheckActive) GlobalQueue.kt(e, l);
|
|
888
|
+
if (!t && u === e && typeof n.oe === "function" && e.T & CONFIG_AUTO_DISPOSE && !(u.S & STATUS_PENDING) && !e.u) {
|
|
889
|
+
// Deferred, not inline (#3078): an inline unobserved() here made untracked
|
|
890
|
+
// reads destructive — dispose on this read, full revival recompute on the
|
|
891
|
+
// next — so consecutive reads could answer differently with no write in
|
|
892
|
+
// between (the revival samples the ambient transition/lane context).
|
|
893
|
+
// The sweep at flush finalization re-validates and reclaims; schedule()
|
|
894
|
+
// guarantees that flush happens even if nothing else is queued.
|
|
895
|
+
dormantNodes.add(e);
|
|
896
|
+
schedule();
|
|
773
897
|
}
|
|
774
898
|
return l;
|
|
775
899
|
}
|
|
776
900
|
|
|
777
901
|
function setSignal(e, t) {
|
|
778
|
-
if (e.
|
|
902
|
+
if (e.Ae && activeTransition !== e.Ae) globalQueue.initTransition(e.Ae);
|
|
779
903
|
// The optimistic write path lives with the engine: only optimisticSignal /
|
|
780
904
|
// optimisticComputed callers and optimistic store nodes carry an
|
|
781
905
|
// _overrideValue slot (flagged by CONFIG_OPTIMISTIC — a masked read of the
|
|
782
906
|
// always-present config instead of a missing-property probe), and every
|
|
783
907
|
// module that installs one installs the engine first.
|
|
784
|
-
if (e.T & CONFIG_OPTIMISTIC && !projectionWriteActive) return GlobalQueue.
|
|
908
|
+
if (e.T & CONFIG_OPTIMISTIC && !projectionWriteActive) return GlobalQueue.xt(e, t);
|
|
785
909
|
const n = e.Pe === NOT_PENDING ? e.be : e.Pe;
|
|
786
910
|
if (typeof t === "function") t = t(n);
|
|
787
911
|
// Uninitialized check first: the first commit has no previous value, so the
|
|
788
912
|
// user comparator must not run against `undefined` (matches recompute).
|
|
789
|
-
const i = !!(e.S & STATUS_UNINITIALIZED) || !e.
|
|
913
|
+
const i = !!(e.S & STATUS_UNINITIALIZED) || !e.ge || !e.ge(n, t);
|
|
790
914
|
if (!i) return t;
|
|
791
915
|
const u = e.Pe !== NOT_PENDING;
|
|
792
916
|
if (!u) queuePendingNode(e);
|
|
@@ -796,16 +920,16 @@ function setSignal(e, t) {
|
|
|
796
920
|
// only ever created, never removed, and creating one installs the hook and
|
|
797
921
|
// sets CONFIG_HAS_COMPANIONS — one masked read replaces two optional-field
|
|
798
922
|
// probes on every write).
|
|
799
|
-
e.T & CONFIG_HAS_COMPANIONS && GlobalQueue.
|
|
923
|
+
e.T & CONFIG_HAS_COMPANIONS && GlobalQueue.Re !== null && GlobalQueue.Re(e, t);
|
|
800
924
|
// _time is a computed-only slot (§12e): writing it on a signal would fork
|
|
801
925
|
// the lean shape. Every read site is computed-typed.
|
|
802
|
-
if (e.
|
|
926
|
+
if (e.oe !== undefined) e.Te = clock;
|
|
803
927
|
// Staged-rewrite fast path (§12d): a re-write to a node whose subscribers
|
|
804
928
|
// were already walked — and where nothing has recomputed or linked since
|
|
805
929
|
// (epoch) — re-stages the value and stops. The walk is idempotent (subs
|
|
806
930
|
// marked, heap entries flag-guarded, effects queued once); lane and reask
|
|
807
931
|
// contexts change what a walk MEANS, so they always walk.
|
|
808
|
-
if (u && e.
|
|
932
|
+
if (u && e.It === notifyEpoch && currentOptimisticLane === null && !reaskArmed) return t;
|
|
809
933
|
insertSubs(e);
|
|
810
934
|
schedule();
|
|
811
935
|
return t;
|
|
@@ -824,7 +948,7 @@ function setSignal(e, t) {
|
|
|
824
948
|
schedule();
|
|
825
949
|
}
|
|
826
950
|
e.ie = e.ie & -4 | REACTIVE_MANUAL_WRITE;
|
|
827
|
-
e.
|
|
951
|
+
e.Ut = clock;
|
|
828
952
|
}
|
|
829
953
|
|
|
830
954
|
/**
|
|
@@ -880,39 +1004,22 @@ function staleValues(e, t = true) {
|
|
|
880
1004
|
}
|
|
881
1005
|
|
|
882
1006
|
/**
|
|
883
|
-
*
|
|
884
|
-
*
|
|
885
|
-
*
|
|
886
|
-
*
|
|
887
|
-
*
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
* Use it to invalidate cached async values (e.g. force a re-fetch) without
|
|
892
|
-
* tearing the consumer down.
|
|
893
|
-
*
|
|
894
|
-
* @example
|
|
895
|
-
* ```ts
|
|
896
|
-
* const user = createMemo(async () => fetch(`/users/${id()}`).then(r => r.json()));
|
|
897
|
-
*
|
|
898
|
-
* // Re-fetch on demand
|
|
899
|
-
* <button onClick={() => refresh(user)}>Reload</button>
|
|
900
|
-
* ```
|
|
901
|
-
*/ function refresh(e) {
|
|
902
|
-
const t = e?.[$REFRESH];
|
|
903
|
-
if (!t) {
|
|
904
|
-
return;
|
|
905
|
-
}
|
|
906
|
-
if (typeof t.Se === "function" && !(t.ie & REACTIVE_DISPOSED)) {
|
|
907
|
-
if (t.ie & REACTIVE_MANUAL_WRITE) {
|
|
1007
|
+
* Core marking half of `refresh()` (the public wrapper lives in signals.ts —
|
|
1008
|
+
* it validates the target, marks through here, then builds the quiescence
|
|
1009
|
+
* promise on the resolve()/until() effect machinery). Flags the node's next
|
|
1010
|
+
* recompute as a quiet re-ask and schedules it; no-ops for non-derived or
|
|
1011
|
+
* disposed targets and for same-tick manual writes.
|
|
1012
|
+
*/ function markRefresh(e) {
|
|
1013
|
+
if (typeof e.oe === "function" && !(e.ie & REACTIVE_DISPOSED)) {
|
|
1014
|
+
if (e.ie & REACTIVE_MANUAL_WRITE) {
|
|
908
1015
|
// A manual write in the CURRENT tick wins over the refresh (#2692).
|
|
909
1016
|
// A mask stamped in an earlier tick only survives because a
|
|
910
1017
|
// transaction (action) is holding the pending drain open; there the
|
|
911
1018
|
// refresh is a later, explicit re-ask and lifts the mask — otherwise
|
|
912
1019
|
// any setStore early in an action silently swallows every refresh()
|
|
913
1020
|
// for the rest of the transaction (#3026).
|
|
914
|
-
if (
|
|
915
|
-
|
|
1021
|
+
if (e.Ut === clock) return;
|
|
1022
|
+
e.ie &= ~REACTIVE_MANUAL_WRITE;
|
|
916
1023
|
// No REASK below: the batch carries a manual value change, so the
|
|
917
1024
|
// recompute is not a quiet re-ask of an unchanged question.
|
|
918
1025
|
}
|
|
@@ -923,14 +1030,14 @@ function staleValues(e, t = true) {
|
|
|
923
1030
|
// REACTIVE_IN_HEAP counts as dirt: insertSubs schedules subscribers by
|
|
924
1031
|
// heap insertion alone (no DIRTY/CHECK flag), so a same-batch value
|
|
925
1032
|
// change followed by refresh() must not be laundered into a quiet re-ask.
|
|
926
|
-
else if (!(
|
|
927
|
-
|
|
1033
|
+
else if (!(e.ie & (REACTIVE_DIRTY | REACTIVE_CHECK | REACTIVE_IN_HEAP))) {
|
|
1034
|
+
e.ie |= REACTIVE_REASK;
|
|
928
1035
|
armReaskClear();
|
|
929
1036
|
}
|
|
930
|
-
|
|
931
|
-
insertIntoHeap(
|
|
1037
|
+
e.ie = e.ie & ~REACTIVE_CHECK | REACTIVE_DIRTY;
|
|
1038
|
+
insertIntoHeap(e, queueFor(e));
|
|
932
1039
|
schedule();
|
|
933
1040
|
}
|
|
934
1041
|
}
|
|
935
1042
|
|
|
936
|
-
export { READ_SLOW, clearSnapshots, computed, context, createEffectNode, currentOptimisticLane, ext, isEqual, latestReadActive, markSnapshotScope, optimisticComputed, optimisticSignal, pendingCheckActive, prepareComputed, read, readNodeFast, recompute,
|
|
1043
|
+
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 };
|