@solidjs/signals 2.0.0-rc.14 → 2.0.0-rc.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dev-shared.js +882 -405
- package/dist/dev.attribution.js +172 -66
- package/dist/dev.js +1529 -745
- package/dist/observe/affects-marks.js +86 -0
- package/dist/observe/affects.js +32 -80
- package/dist/observe/boundaries.js +202 -166
- package/dist/observe/core/async.js +192 -141
- package/dist/observe/core/attribution-queries.js +1 -1
- package/dist/observe/core/attribution.js +315 -214
- package/dist/observe/core/constants.js +23 -4
- package/dist/observe/core/context.js +3 -3
- package/dist/observe/core/core.js +532 -360
- package/dist/observe/core/dev.js +63 -35
- package/dist/observe/core/effect.js +47 -47
- package/dist/observe/core/error.js +5 -3
- package/dist/observe/core/external.js +5 -6
- package/dist/observe/core/graph.js +56 -42
- package/dist/observe/core/heap.js +33 -33
- package/dist/observe/core/identities.js +62 -0
- package/dist/observe/core/lanes.js +242 -229
- package/dist/observe/core/owner.js +62 -50
- package/dist/observe/core/scheduler.js +322 -168
- package/dist/observe/core/truth.js +45 -0
- package/dist/observe/core/verdict.js +114 -64
- package/dist/observe/deferred.js +184 -0
- package/dist/observe/index.js +7 -1
- package/dist/observe/map.js +339 -254
- package/dist/observe/reveal.js +77 -77
- package/dist/observe/signals.js +54 -33
- package/dist/observe/store/affects.js +76 -69
- package/dist/observe/store/edges.js +39 -0
- package/dist/observe/store/hooks.js +13 -0
- package/dist/observe/store/index.js +0 -2
- package/dist/observe/store/optimistic.js +82 -44
- package/dist/observe/store/projection.js +83 -64
- package/dist/observe/store/reconcile.js +32 -30
- package/dist/observe/store/store.js +219 -123
- package/dist/prod/affects-marks.js +86 -0
- package/dist/prod/affects.js +33 -79
- package/dist/prod/boundaries.js +185 -149
- package/dist/prod/core/async.js +176 -135
- package/dist/prod/core/constants.js +23 -4
- package/dist/prod/core/context.js +3 -3
- package/dist/prod/core/core.js +521 -353
- package/dist/prod/core/effect.js +66 -66
- package/dist/prod/core/error.js +5 -3
- package/dist/prod/core/external.js +5 -6
- package/dist/prod/core/graph.js +56 -42
- package/dist/prod/core/heap.js +38 -38
- package/dist/prod/core/identities.js +62 -0
- package/dist/prod/core/lanes.js +237 -224
- package/dist/prod/core/owner.js +62 -51
- package/dist/prod/core/scheduler.js +312 -158
- package/dist/prod/core/truth.js +45 -0
- package/dist/prod/core/verdict.js +113 -63
- package/dist/prod/deferred.js +182 -0
- package/dist/prod/index.js +7 -1
- package/dist/prod/map.js +366 -281
- package/dist/prod/reveal.js +77 -77
- package/dist/prod/signals.js +59 -38
- package/dist/prod/store/affects.js +76 -69
- package/dist/prod/store/edges.js +39 -0
- package/dist/prod/store/hooks.js +13 -0
- package/dist/prod/store/index.js +0 -2
- package/dist/prod/store/optimistic.js +121 -84
- package/dist/prod/store/projection.js +46 -34
- package/dist/prod/store/reconcile.js +32 -30
- package/dist/prod/store/store.js +205 -116
- package/dist/types/affects-marks.d.ts +14 -0
- package/dist/types/boundaries.d.ts +5 -1
- package/dist/types/core/async.d.ts +10 -0
- package/dist/types/core/attribution.d.ts +6 -2
- package/dist/types/core/constants.d.ts +18 -1
- package/dist/types/core/core.d.ts +77 -5
- package/dist/types/core/dev.d.ts +43 -13
- package/dist/types/core/graph.d.ts +11 -0
- package/dist/types/core/identities.d.ts +17 -0
- package/dist/types/core/index.d.ts +2 -1
- package/dist/types/core/lanes.d.ts +2 -4
- package/dist/types/core/owner.d.ts +5 -3
- package/dist/types/core/scheduler.d.ts +59 -34
- package/dist/types/core/truth.d.ts +16 -0
- package/dist/types/core/types.d.ts +21 -6
- package/dist/types/core/verdict.d.ts +32 -0
- package/dist/types/deferred.d.ts +28 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/map.d.ts +2 -2
- package/dist/types/signals.d.ts +28 -7
- package/dist/types/store/affects.d.ts +11 -0
- package/dist/types/store/edges.d.ts +9 -0
- package/dist/types/store/hooks.d.ts +48 -0
- package/dist/types/store/index.d.ts +10 -4
- package/dist/types/store/optimistic.d.ts +3 -2
- package/dist/types/store/projection.d.ts +4 -3
- package/dist/types/store/store.d.ts +4 -39
- package/dist/types/store/target.d.ts +16 -8
- package/dist/types/store/types.d.ts +14 -0
- package/package.json +1 -1
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { CONFIG_GUESS, NOT_PENDING, CONFIG_AUTHORITATIVE, CONFIG_OVERRIDE } from "./constants.js";
|
|
2
|
+
|
|
3
|
+
import { NotReadyError } from "./error.js";
|
|
4
|
+
|
|
5
|
+
import { enqueueSub } from "./heap.js";
|
|
6
|
+
|
|
7
|
+
// Truth reads (`until`, `refresh`; CONFIG_AUTHORITATIVE) over lane nodes.
|
|
8
|
+
// Side-effect free and apart from lanes.ts so a truth reader alone does not
|
|
9
|
+
// retain the lane engine: its reads reach these only through `laneRead`,
|
|
10
|
+
// which exists once something installs lanes (`createOptimistic`, verdicts).
|
|
11
|
+
/** A lane node's current value: its guess, or the latest it derived — the
|
|
12
|
+
* staging a shown lane holds for its next reveal, else its slot; the
|
|
13
|
+
* committed value while it has none (its first lane pass in flight). */
|
|
14
|
+
function laneValueOf(r) {
|
|
15
|
+
if (!(r.l & CONFIG_GUESS) && r.se !== NOT_PENDING) return r.se;
|
|
16
|
+
const t = r.T.v;
|
|
17
|
+
return t !== NOT_PENDING ? t : r.Ie;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Truth readers (`until`, `refresh`; CONFIG_AUTHORITATIVE) — pay-for-use:
|
|
21
|
+
* the first one installs these on GlobalQueue (signals.ts `watch`).
|
|
22
|
+
*
|
|
23
|
+
* A truth reader's read of a lane node: the lane's value when it is the
|
|
24
|
+
* truth (#3943), else the truth staged beneath a guess, else the committed
|
|
25
|
+
* value. */ function truthRead(r) {
|
|
26
|
+
if (truth(r)) return laneValueOf(r);
|
|
27
|
+
if (!(r.l & CONFIG_GUESS)) throw new NotReadyError(null);
|
|
28
|
+
return r.se !== NOT_PENDING ? r.se : r.Ie;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** A guess confirmed: its lane's truth readers re-read — of the lane's
|
|
32
|
+
* derivations too, which may be the truth now (#3943). */ function truthConfirmed(r) {
|
|
33
|
+
for (let t = 0; t < r.tn.length; t++) for (let e = r.tn[t].o; e !== null; e = e.t) if (e.u.l & CONFIG_AUTHORITATIVE) enqueueSub(e.u);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** A lane node's value is the truth when every guess beneath it is
|
|
37
|
+
* confirmed — its source staged a value equal to the guess — and nothing
|
|
38
|
+
* beneath it is held in the parent: same inputs, same value. */ function truth(r) {
|
|
39
|
+
if (r.l & CONFIG_GUESS) return r.se !== NOT_PENDING && r.xe && r.xe(r.T.v, r.se);
|
|
40
|
+
if (r.se !== NOT_PENDING) return;
|
|
41
|
+
if (r.l & CONFIG_OVERRIDE) for (let t = r.C; t; t = t.G) if (!truth(t.S)) return;
|
|
42
|
+
return 1;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export { laneValueOf, truthConfirmed, truthRead };
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import { CONFIG_VERDICT, REACTIVE_PROBED,
|
|
1
|
+
import { CONFIG_VERDICT, REACTIVE_PROBED, CONFIG_CHILDREN_FORBIDDEN, CONFIG_OVERRIDE, STATUS_ERROR, CONFIG_GUESS, STATUS_PENDING, NOT_PENDING, STATUS_UNINITIALIZED, CONFIG_HELD, EFFECT_RENDER, CONFIG_STAGED_ERROR, REACTIVE_STAGED_READ, REACTIVE_PROBE_UNANSWERED, CONFIG_INPUTS_PUBLISHED, REACTIVE_JOINED, REACTIVE_LANE_READ } from "./constants.js";
|
|
2
2
|
|
|
3
|
-
import { context, setVerdict, verdict,
|
|
3
|
+
import { context, tracking, setVerdict, verdict, pullComputed, joinPass, markLateLinker, spectating, unflushedValue, bornInto } from "./core.js";
|
|
4
4
|
|
|
5
5
|
import { NotReadyError } from "./error.js";
|
|
6
6
|
|
|
7
7
|
import { link } from "./graph.js";
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { display, verdictLane } from "./lanes.js";
|
|
10
10
|
|
|
11
|
-
import { GlobalQueue,
|
|
11
|
+
import { installVerdictHooks, GlobalQueue, flushTransaction, txOf, passLane, staleReader, globalQueue, joinFuture, stagedReaders, queuePendingNode, setPassLane } from "./scheduler.js";
|
|
12
|
+
|
|
13
|
+
import { laneValueOf } from "./truth.js";
|
|
12
14
|
|
|
13
15
|
// Verdicts (plan sec. 28 replay, 2026-10-02; maintainer, 2026-10-01: verdicts are
|
|
14
16
|
// optimistic state the system supplies). `isPending(fn)` asks whether what
|
|
@@ -59,11 +61,13 @@ function watchVerdict(e, t = false) {
|
|
|
59
61
|
* every flush (fuzzer F12). True when the reader is served the committed
|
|
60
62
|
* value — not over a flight committed beneath inputs already on screen (A15
|
|
61
63
|
* reveal corollary, #3305): that value would tear against them, and the
|
|
62
|
-
* reveal observes the flight.
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
* reveal observes the flight. Nor for a reader born into the flight's
|
|
65
|
+
* transaction (`bornInto`, #4010): it has no screen — it waits on the flight
|
|
66
|
+
* with the rest of its frame. */ function observeFlight(e, t) {
|
|
67
|
+
if (t.l & CONFIG_INPUTS_PUBLISHED || t.l & CONFIG_HELD && bornInto(e, txOf(t))) return false;
|
|
68
|
+
if (globalQueue.q) {
|
|
65
69
|
joinFuture(null);
|
|
66
|
-
if (!(t.
|
|
70
|
+
if (!(t.l & CONFIG_HELD)) queuePendingNode(t);
|
|
67
71
|
verdictRead(e, flushTransaction, true);
|
|
68
72
|
}
|
|
69
73
|
return true;
|
|
@@ -86,9 +90,9 @@ let probeFound = false;
|
|
|
86
90
|
* value notifying anyone (a held write's commit; a held landing's): its
|
|
87
91
|
* reader re-derives there. `latest` shows the proposal that lands — a
|
|
88
92
|
* rewrite or a landing reaches its reader through the link it holds — so a
|
|
89
|
-
* re-run would only repeat the frame. */ function verdictRead(e, t, n
|
|
93
|
+
* re-run would only repeat the frame. */ function verdictRead(e, t, n) {
|
|
90
94
|
route(e, t);
|
|
91
|
-
if (n) staleReader(e, t);
|
|
95
|
+
if (n) staleReader(e, t); else if (probing && t.En[t.En.length - 1] !== e) t.En.push(e);
|
|
92
96
|
}
|
|
93
97
|
|
|
94
98
|
/** A verdict read of a node staged or in flight but not held yet: whether it
|
|
@@ -131,13 +135,13 @@ let probeFound = false;
|
|
|
131
135
|
/** A19 exc. 2 / A24: a flight that re-asks the question already answered
|
|
132
136
|
* (a `refresh()`, a poll) is quiet; a node pending through others is quiet
|
|
133
137
|
* when every flight it waits on is. */ function quietPending(e) {
|
|
134
|
-
const t = e.
|
|
138
|
+
const t = e.T;
|
|
135
139
|
if (t === null) return false;
|
|
136
|
-
if (t.
|
|
137
|
-
for (const e of t.
|
|
140
|
+
if (t.fe !== undefined) {
|
|
141
|
+
for (const e of t.fe) if (!e.T?.Ve) return false;
|
|
138
142
|
return true;
|
|
139
143
|
}
|
|
140
|
-
return t.
|
|
144
|
+
return t.Ve;
|
|
141
145
|
}
|
|
142
146
|
|
|
143
147
|
/** `isPending`'s answer for a held node (A19), relative to what the reader
|
|
@@ -152,25 +156,28 @@ let probeFound = false;
|
|
|
152
156
|
* frame with neither is final. A24: pending iff a value change is in
|
|
153
157
|
* flight — a staging equal to what is displayed changes nothing when it
|
|
154
158
|
* lands: final now (maintainer, 2026-10-02). */ function heldNotFinal(e) {
|
|
155
|
-
if ((e.
|
|
156
|
-
return e.
|
|
159
|
+
if ((e.m & STATUS_PENDING) !== 0 && !quietPending(e)) return true;
|
|
160
|
+
return e.se !== NOT_PENDING && !latestActive && !e.T.Ve && !(e.m & STATUS_UNINITIALIZED) && (!!e.T.de || !e.xe || !e.xe(e.Ie, e.se));
|
|
157
161
|
}
|
|
158
162
|
|
|
159
163
|
/** `isPending`'s answer for an unheld node whose flight is up (cause ii). */ function pendingVerdict(e, t) {
|
|
160
|
-
if (t.
|
|
164
|
+
if (t.m & STATUS_UNINITIALIZED) return;
|
|
161
165
|
if (probing && !quietPending(t)) probeFound = true;
|
|
162
166
|
if (e !== null && tracking) {
|
|
163
|
-
if (t.
|
|
167
|
+
if (t.l & CONFIG_HELD) verdictRead(e, txOf(t)); else if (globalQueue.q) provisionalVerdict(e, false);
|
|
164
168
|
// decided above
|
|
165
169
|
}
|
|
166
170
|
}
|
|
167
171
|
|
|
168
172
|
/** The pass entering a verdict window is a verdict reader (CONFIG_VERDICT):
|
|
169
|
-
* a dependency going pending re-derives it, the verdict having changed.
|
|
173
|
+
* a dependency going pending re-derives it, the verdict having changed. Not
|
|
174
|
+
* an untracked window: that is a snapshot, like any untracked read (#3946 —
|
|
175
|
+
* a router's link claim probing inside the computation that creates the
|
|
176
|
+
* anchor). A throw still reaches the pass through `untrack`. */ function markVerdictReader(e) {
|
|
170
177
|
let t = context;
|
|
171
|
-
if (t?.
|
|
172
|
-
if (t !== null) {
|
|
173
|
-
t.
|
|
178
|
+
if (t?.je) t = t.Ye;
|
|
179
|
+
if (t !== null && tracking) {
|
|
180
|
+
t.l |= CONFIG_VERDICT;
|
|
174
181
|
t.R |= REACTIVE_PROBED;
|
|
175
182
|
}
|
|
176
183
|
}
|
|
@@ -190,7 +197,38 @@ let probeFound = false;
|
|
|
190
197
|
}
|
|
191
198
|
}
|
|
192
199
|
|
|
193
|
-
|
|
200
|
+
/**
|
|
201
|
+
* Reports whether what `fn` reads is still waiting on an update. Read the
|
|
202
|
+
* values in question inside `fn`: the probe answers `true` while any of
|
|
203
|
+
* them depends on a change that is in flight — a write or an async source
|
|
204
|
+
* that has started producing a new value whose result has not landed yet —
|
|
205
|
+
* and `false` once the latest value is the one on screen. The answer is
|
|
206
|
+
* tracked like any read, so a memo or effect that calls `isPending` re-runs
|
|
207
|
+
* when it flips.
|
|
208
|
+
*
|
|
209
|
+
* A read that has no value yet is loading, not pending: inside `fn` it
|
|
210
|
+
* follows the surrounding `Loading` path like any other read, and the probe
|
|
211
|
+
* reports only updates to a value that already exists. A bare `refresh()`
|
|
212
|
+
* does not flip `isPending`; pair it with `affects()` to make the re-ask
|
|
213
|
+
* visible.
|
|
214
|
+
*
|
|
215
|
+
* @param fn expression to probe; read the values in question inside it
|
|
216
|
+
*
|
|
217
|
+
* @returns `true` while a read inside `fn` depends on a change that has not committed yet
|
|
218
|
+
*
|
|
219
|
+
* @example
|
|
220
|
+
* ```ts
|
|
221
|
+
* const [query, setQuery] = createSignal("");
|
|
222
|
+
* const results = createMemo(() => searchProducts(query()));
|
|
223
|
+
* const stale = createMemo(() => isPending(results));
|
|
224
|
+
*
|
|
225
|
+
* setQuery("shoes");
|
|
226
|
+
* // stale() is true until the new results land, while results() keeps
|
|
227
|
+
* // serving the previous list — dim it instead of showing a spinner.
|
|
228
|
+
* ```
|
|
229
|
+
*
|
|
230
|
+
* @description https://v2.solidjs.com/reference/solid-js/reactivity/is-pending
|
|
231
|
+
*/ function isPending(e) {
|
|
194
232
|
markVerdictReader();
|
|
195
233
|
const t = probing;
|
|
196
234
|
const n = probeFound;
|
|
@@ -217,11 +255,16 @@ function isPending(e) {
|
|
|
217
255
|
}
|
|
218
256
|
}
|
|
219
257
|
|
|
258
|
+
/** The staging, or the staged error thrown (D-41). */ function staged(e) {
|
|
259
|
+
if (e.l & CONFIG_STAGED_ERROR) throw e.se;
|
|
260
|
+
return e.se;
|
|
261
|
+
}
|
|
262
|
+
|
|
220
263
|
/** `latest()` of a held node: the flushed staging — the proposal — and, for
|
|
221
264
|
* a tracked reader of a rewrite not yet flushed, the late-linker re-run in
|
|
222
265
|
* the flush that carries it. */ function heldLatest(e, t) {
|
|
223
266
|
const n = unflushedValue(e);
|
|
224
|
-
if (n === NOT_PENDING) return e
|
|
267
|
+
if (n === NOT_PENDING) return staged(e);
|
|
225
268
|
if (t !== null) markLateLinker(t);
|
|
226
269
|
return n;
|
|
227
270
|
}
|
|
@@ -248,10 +291,11 @@ function isPending(e) {
|
|
|
248
291
|
* uninitialized one throws NotReady: loading, not pending (A19 exc. 1);
|
|
249
292
|
* - an errored derivation throws for every reader (#2897). */ function verdictValue(e, t) {
|
|
250
293
|
const n = e;
|
|
251
|
-
const r = t !== null &&
|
|
294
|
+
const r = t !== null && !(t.l & CONFIG_CHILDREN_FORBIDDEN);
|
|
295
|
+
const i = r && tracking;
|
|
252
296
|
if (t !== null && tracking) {
|
|
253
297
|
link(e, t);
|
|
254
|
-
if (n.
|
|
298
|
+
if (n.X !== undefined) {
|
|
255
299
|
// A31: the pulled memo's pass is its own, not the probe's — it reads
|
|
256
300
|
// outside the window, and the probe's read below answers for what
|
|
257
301
|
// the pass produced.
|
|
@@ -264,86 +308,92 @@ function isPending(e) {
|
|
|
264
308
|
// final by declaration — the value itself is read as below. The verdict is
|
|
265
309
|
// display-ahead like any: in a transaction's flush (the declaring action's
|
|
266
310
|
// body) the reader is its verdict lane's, not a staging the frame parks.
|
|
267
|
-
if (probing && GlobalQueue.
|
|
311
|
+
if (probing && GlobalQueue.p !== undefined && GlobalQueue.p(n)) {
|
|
268
312
|
probeFound = true;
|
|
269
|
-
if (
|
|
313
|
+
if (i && flushTransaction !== null) verdictRead(t, flushTransaction);
|
|
270
314
|
}
|
|
271
|
-
if (e.
|
|
272
|
-
const
|
|
273
|
-
if (
|
|
274
|
-
const u = (n.
|
|
275
|
-
if (e.
|
|
276
|
-
if (probing && (u || e.
|
|
277
|
-
return e.
|
|
315
|
+
if (e.l & CONFIG_OVERRIDE && !(n.m & STATUS_ERROR && !(e.l & CONFIG_GUESS))) {
|
|
316
|
+
const r = txOf(e);
|
|
317
|
+
if (i) verdictRead(t, r._parent ?? r);
|
|
318
|
+
const u = (n.m & STATUS_PENDING) !== 0 && !quietPending(n);
|
|
319
|
+
if (e.l & CONFIG_GUESS) {
|
|
320
|
+
if (probing && (u || e.se !== NOT_PENDING && (!e.xe || !e.xe(e.T.v, e.se)))) probeFound = true;
|
|
321
|
+
return e.T.v;
|
|
278
322
|
}
|
|
279
323
|
if (probing && u) probeFound = true;
|
|
280
324
|
return latestActive && !u ? laneValueOf(e) : display(e);
|
|
281
325
|
}
|
|
282
|
-
const
|
|
283
|
-
if (e.
|
|
284
|
-
if (!
|
|
285
|
-
const
|
|
326
|
+
const u = (n.m & STATUS_UNINITIALIZED) !== 0;
|
|
327
|
+
if (e.l & CONFIG_HELD) {
|
|
328
|
+
if (!u) {
|
|
329
|
+
const r = txOf(e);
|
|
286
330
|
// A render effect re-run (or mounted) outside the verdict lane's flush
|
|
287
331
|
// while the lane is blocked — its display held on its own derivation
|
|
288
332
|
// in flight — is a stale reader of the lane (#3460, as `laneRead`): the
|
|
289
333
|
// committed value now, re-derived at the reveal. Not inside a probe:
|
|
290
334
|
// `isPending` is answered below either way.
|
|
291
|
-
if (
|
|
292
|
-
staleReader(t,
|
|
293
|
-
return e.
|
|
335
|
+
if (i && !probing && t.i === EFFECT_RENDER && passLane === null && r.he !== null && r.he.ln && flushTransaction !== r) {
|
|
336
|
+
staleReader(t, r.he);
|
|
337
|
+
return e.Ie;
|
|
294
338
|
}
|
|
295
|
-
if (
|
|
339
|
+
if (i) verdictRead(t, r);
|
|
296
340
|
if (probing && heldNotFinal(n)) probeFound = true;
|
|
297
|
-
if (latestActive && e.
|
|
341
|
+
if (latestActive && e.se !== NOT_PENDING) {
|
|
298
342
|
// An action body reading another transaction's proposal derives
|
|
299
343
|
// from it: the two settle as one (A15; posture C, 2026-09-15). An
|
|
300
344
|
// untracked read elsewhere has no transaction to entangle.
|
|
301
|
-
if (!
|
|
302
|
-
return heldLatest(e,
|
|
345
|
+
if (!i && flushTransaction !== null && !globalQueue.q) joinFuture(txOf(e));
|
|
346
|
+
return heldLatest(e, i ? t : null);
|
|
303
347
|
}
|
|
304
|
-
return e.
|
|
348
|
+
return e.Ie;
|
|
349
|
+
}
|
|
350
|
+
// Born into the future: its staging is its only value. It has no
|
|
351
|
+
// committed value, so no verdict: `isPending` reads it as its read does —
|
|
352
|
+
// the owner joins the hold (held with it, or caught by a boundary that
|
|
353
|
+
// has not shown content) and answers for the commit, where it is final.
|
|
354
|
+
if (e.se !== NOT_PENDING && !(n.m & STATUS_PENDING)) {
|
|
355
|
+
if (probing && r) joinPass(t, e);
|
|
356
|
+
return staged(e);
|
|
305
357
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
} else if (e.Ae !== NOT_PENDING) {
|
|
309
|
-
if (n.G & STATUS_PENDING && !i) pendingVerdict(r ? t : null, n); else if (r && globalQueue.ee) provisionalVerdict(t, !latestActive && !i);
|
|
358
|
+
} else if (e.se !== NOT_PENDING) {
|
|
359
|
+
if (n.m & STATUS_PENDING && !u) pendingVerdict(i ? t : null, n); else if (i && globalQueue.q) provisionalVerdict(t, !latestActive && !u);
|
|
310
360
|
// Outside a flush, the staging is an unflushed write (A28): the probe
|
|
311
361
|
// answers for the flushed world, and watches the seam of the flush that
|
|
312
362
|
// carries the write — a memo of `isPending(x)` created after the write
|
|
313
363
|
// flips if that flush parks (#3078). (The write's own fan-out ran before
|
|
314
364
|
// the reader existed; nothing else would re-derive it.)
|
|
315
|
-
else if (
|
|
365
|
+
else if (i && probing) watchVerdict(t);
|
|
316
366
|
if (latestActive) {
|
|
317
367
|
// A28: an unflushed write is not what `latest` serves; a tracked
|
|
318
368
|
// reader derived from the flushed world runs again in the carrying
|
|
319
369
|
// flush.
|
|
320
|
-
if (globalQueue.
|
|
370
|
+
if (globalQueue.q) return staged(e);
|
|
321
371
|
if (t !== null) markLateLinker(t);
|
|
322
372
|
}
|
|
323
|
-
return e.
|
|
373
|
+
return e.Ie;
|
|
324
374
|
}
|
|
325
|
-
if (n.
|
|
326
|
-
if (!
|
|
327
|
-
pendingVerdict(
|
|
328
|
-
return e.
|
|
375
|
+
if (n.m & STATUS_PENDING) {
|
|
376
|
+
if (!u) {
|
|
377
|
+
pendingVerdict(i ? t : null, n);
|
|
378
|
+
return e.Ie;
|
|
329
379
|
}
|
|
330
380
|
// As `read()`: an untracked read of a pending node still re-runs its
|
|
331
381
|
// reader when the node settles (a projection's pull reads its firewall
|
|
332
382
|
// untracked, store/store.ts).
|
|
333
383
|
if (t !== null && !tracking && !spectating && e !== t) link(e, t);
|
|
334
|
-
throw n.
|
|
384
|
+
throw n.T?.F;
|
|
335
385
|
}
|
|
336
|
-
if (n.
|
|
337
|
-
return e.
|
|
386
|
+
if (n.X !== undefined && n.m & STATUS_ERROR) throw n.T.F;
|
|
387
|
+
return e.Ie;
|
|
338
388
|
}
|
|
339
389
|
|
|
340
390
|
// Installed at module evaluation — present exactly when something imports
|
|
341
391
|
// `latest` or `isPending`. (`read` dispatches to `verdictValue` through
|
|
342
392
|
// `verdict`, set while a window is open.)
|
|
343
|
-
|
|
393
|
+
installVerdictHooks(observeFlight);
|
|
344
394
|
|
|
345
395
|
/** A store read inside an untracked `isPending` probe found a live mark on
|
|
346
|
-
* a record with no node to carry it (store/affects.ts): not final. */ GlobalQueue.
|
|
396
|
+
* a record with no node to carry it (store/affects.ts): not final. */ GlobalQueue.rt = () => {
|
|
347
397
|
if (probing) probeFound = true;
|
|
348
398
|
};
|
|
349
399
|
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { CONFIG_REDERIVE, STATUS_UNINITIALIZED, CONFIG_LAGS, CONFIG_SLOT_NODE, REACTIVE_RECOMPUTING_DEPS, REACTIVE_DISPOSED, REACTIVE_REASK, CONFIG_HELD, NOT_PENDING } from "./core/constants.js";
|
|
2
|
+
|
|
3
|
+
import { isThenable, handleAsync } from "./core/async.js";
|
|
4
|
+
|
|
5
|
+
import { NotReadyError } from "./core/error.js";
|
|
6
|
+
|
|
7
|
+
import "./core/verdict.js";
|
|
8
|
+
|
|
9
|
+
import { computed } from "./core/core.js";
|
|
10
|
+
|
|
11
|
+
import { cleanup, getOwner } from "./core/owner.js";
|
|
12
|
+
|
|
13
|
+
import { GlobalQueue, holdNode, txOf, schedule } from "./core/scheduler.js";
|
|
14
|
+
|
|
15
|
+
import "./core/effect.js";
|
|
16
|
+
|
|
17
|
+
import { mark, repoll, unmark } from "./affects-marks.js";
|
|
18
|
+
|
|
19
|
+
import { accessor } from "./signals.js";
|
|
20
|
+
|
|
21
|
+
// `createDeferred` — an async memo that may lag, but never leads (D-40,
|
|
22
|
+
// docs/create-deferred.md).
|
|
23
|
+
// Once the node has a committed value, its own non-final work is invisible
|
|
24
|
+
// to the graph: its readers keep the committed value and hold nothing, its
|
|
25
|
+
// landings are changes of their own, and only the verdict (`isPending`) and
|
|
26
|
+
// the truth readers (`until`, `refresh`'s waiter) see the flight.
|
|
27
|
+
// Built from parts the engine already has, so core pays nothing for it:
|
|
28
|
+
// - **Read side: the loading window** (`_loading`, async.ts). Every pass of
|
|
29
|
+
// a node with a committed value opens the window before it asks, so a
|
|
30
|
+
// flight serves the committed value instead of throwing, and an unready
|
|
31
|
+
// source parks the node instead of pending it. `CONFIG_REDERIVE` makes a
|
|
32
|
+
// source going pending a question for the node's pass (which parks) rather
|
|
33
|
+
// than a status pushed through it to its readers.
|
|
34
|
+
// - **Verdict: a mark** (affects-marks.ts) whose lifetime is the flight —
|
|
35
|
+
// started when a pass asks a new question (a `refresh()` re-ask is quiet, F-9),
|
|
36
|
+
// dropped at the landing, the rejection, a sync answer or the node's death.
|
|
37
|
+
// - **Never leads:** a flight that lands while an input it read is held
|
|
38
|
+
// derived from that hold's world, so it lands into the hold (`holdNode`;
|
|
39
|
+
// `setSignal` stages it there). An input that changed after the pass
|
|
40
|
+
// would have superseded the flight, so a held input at the landing is one
|
|
41
|
+
// the pass read held.
|
|
42
|
+
// - **Truth readers** wait the flight out (`GlobalQueue._lagging`, signals.ts
|
|
43
|
+
// `watch`): `await refresh(d)` delivers the landing, `until` sees only
|
|
44
|
+
// settled values.
|
|
45
|
+
/** Deferred nodes with a flight up. */
|
|
46
|
+
const lagging = new Set;
|
|
47
|
+
|
|
48
|
+
/** The deferred node with a flight up that `c`'s value derives from, if
|
|
49
|
+
* any: `c` itself, or one reached through its current dependencies. */ function lagOf(e, r) {
|
|
50
|
+
if (lagging.has(e)) return e;
|
|
51
|
+
if (r.has(e)) return;
|
|
52
|
+
r.add(e);
|
|
53
|
+
if (e.l & CONFIG_SLOT_NODE) return lagOf(e.O?.fam?.node ?? e, r);
|
|
54
|
+
const t = e.R & REACTIVE_RECOMPUTING_DEPS ? e._ : undefined;
|
|
55
|
+
if (t !== null) for (let o = e.C ?? null; o !== null; o = o.G) {
|
|
56
|
+
const e = lagOf(o.S, r);
|
|
57
|
+
if (e) return e;
|
|
58
|
+
if (o === t) break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
GlobalQueue.er = e => lagging.size !== 0 ? lagOf(e, new Set) : undefined;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Creates an async memo that may lag, but never leads. Before its first
|
|
66
|
+
* answer it is an ordinary async memo: readers suspend to the nearest
|
|
67
|
+
* `<Loading>`. After it, a refetch holds nothing: readers keep the last
|
|
68
|
+
* committed value until the new one lands, and the rest of the screen moves
|
|
69
|
+
* on without it. `isPending` reports the refetch, on the node and on what
|
|
70
|
+
* derives from it. Use it for independent widgets (dashboards, feeds,
|
|
71
|
+
* search results) where the previous answer with a "refreshing" indicator is
|
|
72
|
+
* better than waiting with everything else.
|
|
73
|
+
*
|
|
74
|
+
* - It never shows a value ahead of a held change: a refetch that read a
|
|
75
|
+
* held value lands with that hold.
|
|
76
|
+
* - A rejected refetch errors like any memo; the stale value never masks it.
|
|
77
|
+
* - `refresh(d)` is a quiet re-ask, and `await refresh(d)` and `until`
|
|
78
|
+
* wait for the landing rather than the stale value.
|
|
79
|
+
* - Combine with `loadingValue` for a node that never suspends at all.
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```tsx
|
|
83
|
+
* const rows = createDeferred(() => fetchRows(store.period));
|
|
84
|
+
*
|
|
85
|
+
* <section class={{ stale: isPending(() => rows()) }}>
|
|
86
|
+
* <For each={rows()}>{row => <Row row={row} />}</For>
|
|
87
|
+
* </section>
|
|
88
|
+
* ```
|
|
89
|
+
*/ function createDeferred(e, r) {
|
|
90
|
+
let t;
|
|
91
|
+
/** The flight in force: a promise, a stream, or the error an unready
|
|
92
|
+
* source parked the node with. */ let o = null;
|
|
93
|
+
let n = false;
|
|
94
|
+
let l = false;
|
|
95
|
+
/** The flight `r` is over. An async answer (`held`) arriving while an
|
|
96
|
+
* input is held lands into that hold first. */ const land = (e, r) => {
|
|
97
|
+
if (e === null || o !== e) return;
|
|
98
|
+
o = null;
|
|
99
|
+
lagging.delete(t);
|
|
100
|
+
if (r) for (let e = t.C; e !== null; e = e.G) if (e.S.l & CONFIG_HELD) {
|
|
101
|
+
holdNode(t, txOf(e.S));
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
if (n) {
|
|
105
|
+
n = false;
|
|
106
|
+
unmark(t);
|
|
107
|
+
schedule();
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
const ask = e => {
|
|
111
|
+
o = e;
|
|
112
|
+
lagging.add(t);
|
|
113
|
+
if (!n && !(t.R & REACTIVE_REASK)) {
|
|
114
|
+
n = true;
|
|
115
|
+
mark(t);
|
|
116
|
+
repoll(t);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
/** Registers the flight and serves the pass's value: the committed one,
|
|
120
|
+
* or over a held landing the landing itself, which a lagging question
|
|
121
|
+
* does not replace (D-40) — restaged as the committed value, the hold's
|
|
122
|
+
* readers would re-derive against the old input. */ const serve = e => {
|
|
123
|
+
const r = handleAsync(t, e);
|
|
124
|
+
return t.l & CONFIG_HELD && t.se !== NOT_PENDING ? t.se : r;
|
|
125
|
+
};
|
|
126
|
+
const end = () => t.R & REACTIVE_DISPOSED && land(o);
|
|
127
|
+
return accessor(computed(r => {
|
|
128
|
+
t = getOwner();
|
|
129
|
+
t.l |= CONFIG_REDERIVE;
|
|
130
|
+
cleanup(end);
|
|
131
|
+
// Answered: the node has a real committed answer — not a first load,
|
|
132
|
+
// not a `loadingValue` window still open (both are loading, F-9).
|
|
133
|
+
// Sticky, and read before this wrapper ever opens the window itself.
|
|
134
|
+
const n = l ||= !(t.m & STATUS_UNINITIALIZED) && !t.ce;
|
|
135
|
+
if (n) t.ce = true, t.l |= CONFIG_LAGS;
|
|
136
|
+
let c;
|
|
137
|
+
try {
|
|
138
|
+
c = e(r);
|
|
139
|
+
} catch (e) {
|
|
140
|
+
// The wrapping form: an unready source parks the node in the window,
|
|
141
|
+
// and the wait is its flight until the source's settle re-runs it.
|
|
142
|
+
if (n && e instanceof NotReadyError) ask(e);
|
|
143
|
+
throw e;
|
|
144
|
+
}
|
|
145
|
+
if (n && typeof c === "object" && c !== null) {
|
|
146
|
+
if (isThenable(c)) {
|
|
147
|
+
ask(c);
|
|
148
|
+
const done = () => land(c, true);
|
|
149
|
+
c.then(done, done);
|
|
150
|
+
return serve(c);
|
|
151
|
+
}
|
|
152
|
+
const e = c[Symbol.asyncIterator];
|
|
153
|
+
if (e) {
|
|
154
|
+
// A stream's flight ends at its first yield (or its end).
|
|
155
|
+
const r = e.call(c);
|
|
156
|
+
let t = true;
|
|
157
|
+
const done = () => land(o, true);
|
|
158
|
+
const o = {
|
|
159
|
+
// Registered ahead of the engine's own reaction to the same
|
|
160
|
+
// promise, as for a plain flight: verdict and value in one flush.
|
|
161
|
+
next: e => {
|
|
162
|
+
const o = r.next(e);
|
|
163
|
+
if (t) t = false, o.then(done, done);
|
|
164
|
+
return o;
|
|
165
|
+
},
|
|
166
|
+
return: e => r.return?.(e) ?? Promise.resolve({
|
|
167
|
+
done: true,
|
|
168
|
+
value: e
|
|
169
|
+
})
|
|
170
|
+
};
|
|
171
|
+
ask(o);
|
|
172
|
+
return serve({
|
|
173
|
+
[Symbol.asyncIterator]: () => o
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
land(o);
|
|
178
|
+
return c;
|
|
179
|
+
}, r));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export { createDeferred };
|
package/dist/prod/index.js
CHANGED
|
@@ -4,12 +4,14 @@ export { isPending, latest } from "./core/verdict.js";
|
|
|
4
4
|
|
|
5
5
|
export { action } from "./core/action.js";
|
|
6
6
|
|
|
7
|
-
export { clearSnapshots, isEqual, markSnapshotScope, releaseSnapshotScope, runWithOwner, setSnapshotCapture, untrack } from "./core/core.js";
|
|
7
|
+
export { clearSnapshots, isEqual, markSnapshotScope, releaseSnapshotScope, runWithOwner, setSnapshotCapture, setWriteHook, untrack } from "./core/core.js";
|
|
8
8
|
|
|
9
9
|
export { enableExternalSource } from "./core/external.js";
|
|
10
10
|
|
|
11
11
|
export { createOwner, createRoot, getNextChildId, getObserver, getOwner, isDisposed, peekNextChildId } from "./core/owner.js";
|
|
12
12
|
|
|
13
|
+
export { setReadWatch, setSignalIdentities, signalName } from "./core/identities.js";
|
|
14
|
+
|
|
13
15
|
export { createContext, getContext, setContext } from "./core/context.js";
|
|
14
16
|
|
|
15
17
|
export { $REFRESH, SUPPORTS_PROXY } from "./core/constants.js";
|
|
@@ -22,6 +24,8 @@ import "./core/effect.js";
|
|
|
22
24
|
|
|
23
25
|
export { configureClientErrors } from "./core/error-hooks.js";
|
|
24
26
|
|
|
27
|
+
export { storeName } from "./store/edges.js";
|
|
28
|
+
|
|
25
29
|
export { createEffect, createMemo, createOptimistic, createReaction, createRenderEffect, createSignal, createTrackedEffect, onCleanup, onSettled, refresh, resolve, until } from "./signals.js";
|
|
26
30
|
|
|
27
31
|
export { mapArray, repeat } from "./map.js";
|
|
@@ -30,6 +34,8 @@ export { createStore, deep, reconcile, snapshot } from "./store/index.js";
|
|
|
30
34
|
|
|
31
35
|
export { affects } from "./affects.js";
|
|
32
36
|
|
|
37
|
+
export { createDeferred } from "./deferred.js";
|
|
38
|
+
|
|
33
39
|
export { flatten } from "./flatten.js";
|
|
34
40
|
|
|
35
41
|
export { createErrorBoundary, createLoadingBoundary } from "./boundaries.js";
|