@solidjs/signals 2.0.0-rc.15 → 2.0.0-rc.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/dist/dev-shared.js +2268 -1734
  2. package/dist/dev.attribution.js +75 -52
  3. package/dist/dev.js +1348 -658
  4. package/dist/observe/affects-marks.js +86 -0
  5. package/dist/observe/affects.js +22 -73
  6. package/dist/observe/boundaries.js +192 -190
  7. package/dist/observe/core/async.js +177 -131
  8. package/dist/observe/core/attribution-queries.js +1 -1
  9. package/dist/observe/core/attribution.js +176 -159
  10. package/dist/observe/core/constants.js +17 -3
  11. package/dist/observe/core/context.js +5 -3
  12. package/dist/observe/core/core.js +520 -364
  13. package/dist/observe/core/dev.js +66 -38
  14. package/dist/observe/core/effect.js +50 -48
  15. package/dist/observe/core/error.js +5 -3
  16. package/dist/observe/core/external.js +5 -6
  17. package/dist/observe/core/graph.js +60 -44
  18. package/dist/observe/core/heap.js +33 -33
  19. package/dist/observe/core/identities.js +62 -0
  20. package/dist/observe/core/lanes.js +238 -260
  21. package/dist/observe/core/owner.js +117 -79
  22. package/dist/observe/core/scheduler.js +381 -217
  23. package/dist/observe/core/truth.js +45 -0
  24. package/dist/observe/core/verdict.js +91 -50
  25. package/dist/observe/deferred.js +184 -0
  26. package/dist/observe/index.js +7 -1
  27. package/dist/observe/map.js +345 -260
  28. package/dist/observe/reveal.js +76 -76
  29. package/dist/observe/signals.js +47 -31
  30. package/dist/observe/store/affects.js +18 -18
  31. package/dist/observe/store/edges.js +39 -0
  32. package/dist/observe/store/optimistic.js +146 -100
  33. package/dist/observe/store/projection.js +68 -55
  34. package/dist/observe/store/store.js +180 -118
  35. package/dist/prod/affects-marks.js +86 -0
  36. package/dist/prod/affects.js +23 -72
  37. package/dist/prod/boundaries.js +156 -154
  38. package/dist/prod/core/async.js +173 -127
  39. package/dist/prod/core/constants.js +17 -3
  40. package/dist/prod/core/context.js +5 -3
  41. package/dist/prod/core/core.js +495 -343
  42. package/dist/prod/core/effect.js +70 -68
  43. package/dist/prod/core/error.js +5 -3
  44. package/dist/prod/core/external.js +5 -6
  45. package/dist/prod/core/graph.js +60 -44
  46. package/dist/prod/core/heap.js +38 -38
  47. package/dist/prod/core/identities.js +62 -0
  48. package/dist/prod/core/lanes.js +230 -252
  49. package/dist/prod/core/owner.js +114 -77
  50. package/dist/prod/core/scheduler.js +327 -163
  51. package/dist/prod/core/truth.js +45 -0
  52. package/dist/prod/core/verdict.js +90 -49
  53. package/dist/prod/deferred.js +182 -0
  54. package/dist/prod/index.js +7 -1
  55. package/dist/prod/map.js +360 -275
  56. package/dist/prod/reveal.js +76 -76
  57. package/dist/prod/signals.js +42 -26
  58. package/dist/prod/store/affects.js +6 -6
  59. package/dist/prod/store/edges.js +39 -0
  60. package/dist/prod/store/optimistic.js +143 -98
  61. package/dist/prod/store/projection.js +25 -19
  62. package/dist/prod/store/store.js +157 -100
  63. package/dist/types/affects-marks.d.ts +14 -0
  64. package/dist/types/boundaries.d.ts +1 -1
  65. package/dist/types/core/async.d.ts +10 -0
  66. package/dist/types/core/constants.d.ts +13 -1
  67. package/dist/types/core/core.d.ts +54 -5
  68. package/dist/types/core/dev.d.ts +51 -13
  69. package/dist/types/core/error.d.ts +9 -1
  70. package/dist/types/core/graph.d.ts +11 -0
  71. package/dist/types/core/identities.d.ts +17 -0
  72. package/dist/types/core/index.d.ts +2 -1
  73. package/dist/types/core/lanes.d.ts +2 -14
  74. package/dist/types/core/owner.d.ts +9 -3
  75. package/dist/types/core/scheduler.d.ts +70 -32
  76. package/dist/types/core/truth.d.ts +16 -0
  77. package/dist/types/core/types.d.ts +21 -0
  78. package/dist/types/core/verdict.d.ts +32 -0
  79. package/dist/types/deferred.d.ts +28 -0
  80. package/dist/types/index.d.ts +6 -0
  81. package/dist/types/map.d.ts +2 -2
  82. package/dist/types/signals.d.ts +28 -7
  83. package/dist/types/store/edges.d.ts +9 -0
  84. package/dist/types/store/index.d.ts +10 -3
  85. package/dist/types/store/optimistic.d.ts +3 -2
  86. package/dist/types/store/projection.d.ts +4 -3
  87. package/dist/types/store/store.d.ts +1 -1
  88. package/dist/types/store/target.d.ts +16 -8
  89. package/dist/types/store/types.d.ts +14 -0
  90. 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, CONFIG_INPUTS_PUBLISHED, CONFIG_HELD, REACTIVE_JOINED, REACTIVE_STAGED_READ, REACTIVE_LANE_READ, CONFIG_CHILDREN_FORBIDDEN, CONFIG_OVERRIDE, STATUS_PENDING, CONFIG_GUESS, NOT_PENDING, STATUS_UNINITIALIZED, EFFECT_RENDER, STATUS_ERROR, REACTIVE_PROBE_UNANSWERED } from "./constants.js";
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, tracking, setVerdict, bornInto, verdict, pullComputed, joinPass, markLateLinker, spectating, unflushedValue } from "./core.js";
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 { verdictLane, laneValueOf, display } from "./lanes.js";
9
+ import { display, verdictLane } from "./lanes.js";
10
10
 
11
- import { GlobalQueue, txOf, globalQueue, joinFuture, queuePendingNode, staleReader, passLane, setPassLane, flushTransaction, stagedReaders } from "./scheduler.js";
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
@@ -62,10 +64,10 @@ function watchVerdict(e, t = false) {
62
64
  * reveal observes the flight. Nor for a reader born into the flight's
63
65
  * transaction (`bornInto`, #4010): it has no screen — it waits on the flight
64
66
  * with the rest of its frame. */ function observeFlight(e, t) {
65
- if (t.i & CONFIG_INPUTS_PUBLISHED || t.i & CONFIG_HELD && bornInto(e, txOf(t))) return false;
66
- if (globalQueue.X) {
67
+ if (t.l & CONFIG_INPUTS_PUBLISHED || t.l & CONFIG_HELD && bornInto(e, txOf(t))) return false;
68
+ if (globalQueue.q) {
67
69
  joinFuture(null);
68
- if (!(t.i & CONFIG_HELD)) queuePendingNode(t);
70
+ if (!(t.l & CONFIG_HELD)) queuePendingNode(t);
69
71
  verdictRead(e, flushTransaction, true);
70
72
  }
71
73
  return true;
@@ -90,7 +92,7 @@ let probeFound = false;
90
92
  * rewrite or a landing reaches its reader through the link it holds — so a
91
93
  * re-run would only repeat the frame. */ function verdictRead(e, t, n) {
92
94
  route(e, t);
93
- if (n) staleReader(e, t); else if (probing && t.fn[t.fn.length - 1] !== e) t.fn.push(e);
95
+ if (n) staleReader(e, t); else if (probing && t.En[t.En.length - 1] !== e) t.En.push(e);
94
96
  }
95
97
 
96
98
  /** A verdict read of a node staged or in flight but not held yet: whether it
@@ -133,13 +135,13 @@ let probeFound = false;
133
135
  /** A19 exc. 2 / A24: a flight that re-asks the question already answered
134
136
  * (a `refresh()`, a poll) is quiet; a node pending through others is quiet
135
137
  * when every flight it waits on is. */ function quietPending(e) {
136
- const t = e.m;
138
+ const t = e.T;
137
139
  if (t === null) return false;
138
- if (t.Se !== undefined) {
139
- for (const e of t.Se) if (!e.m?.ke) return false;
140
+ if (t.fe !== undefined) {
141
+ for (const e of t.fe) if (!e.T?.Ve) return false;
140
142
  return true;
141
143
  }
142
- return t.ke;
144
+ return t.Ve;
143
145
  }
144
146
 
145
147
  /** `isPending`'s answer for a held node (A19), relative to what the reader
@@ -154,15 +156,15 @@ let probeFound = false;
154
156
  * frame with neither is final. A24: pending iff a value change is in
155
157
  * flight — a staging equal to what is displayed changes nothing when it
156
158
  * lands: final now (maintainer, 2026-10-02). */ function heldNotFinal(e) {
157
- if ((e.S & STATUS_PENDING) !== 0 && !quietPending(e)) return true;
158
- return e.Oe !== NOT_PENDING && !latestActive && !e.m.ke && !(e.S & STATUS_UNINITIALIZED) && (!e.ye || !e.ye(e.Ae, e.Oe));
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));
159
161
  }
160
162
 
161
163
  /** `isPending`'s answer for an unheld node whose flight is up (cause ii). */ function pendingVerdict(e, t) {
162
- if (t.S & STATUS_UNINITIALIZED) return;
164
+ if (t.m & STATUS_UNINITIALIZED) return;
163
165
  if (probing && !quietPending(t)) probeFound = true;
164
166
  if (e !== null && tracking) {
165
- if (t.i & CONFIG_HELD) verdictRead(e, txOf(t)); else if (globalQueue.X) provisionalVerdict(e, false);
167
+ if (t.l & CONFIG_HELD) verdictRead(e, txOf(t)); else if (globalQueue.q) provisionalVerdict(e, false);
166
168
  // decided above
167
169
  }
168
170
  }
@@ -173,9 +175,9 @@ let probeFound = false;
173
175
  * a router's link claim probing inside the computation that creates the
174
176
  * anchor). A throw still reaches the pass through `untrack`. */ function markVerdictReader(e) {
175
177
  let t = context;
176
- if (t?.$e) t = t.et;
178
+ if (t?.Qe) t = t.Ye;
177
179
  if (t !== null && tracking) {
178
- t.i |= CONFIG_VERDICT;
180
+ t.l |= CONFIG_VERDICT;
179
181
  t.R |= REACTIVE_PROBED;
180
182
  }
181
183
  }
@@ -195,7 +197,38 @@ let probeFound = false;
195
197
  }
196
198
  }
197
199
 
198
- function isPending(e) {
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) {
199
232
  markVerdictReader();
200
233
  const t = probing;
201
234
  const n = probeFound;
@@ -222,11 +255,16 @@ function isPending(e) {
222
255
  }
223
256
  }
224
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
+
225
263
  /** `latest()` of a held node: the flushed staging — the proposal — and, for
226
264
  * a tracked reader of a rewrite not yet flushed, the late-linker re-run in
227
265
  * the flush that carries it. */ function heldLatest(e, t) {
228
266
  const n = unflushedValue(e);
229
- if (n === NOT_PENDING) return e.Oe;
267
+ if (n === NOT_PENDING) return staged(e);
230
268
  if (t !== null) markLateLinker(t);
231
269
  return n;
232
270
  }
@@ -253,11 +291,11 @@ function isPending(e) {
253
291
  * uninitialized one throws NotReady: loading, not pending (A19 exc. 1);
254
292
  * - an errored derivation throws for every reader (#2897). */ function verdictValue(e, t) {
255
293
  const n = e;
256
- const r = t !== null && !(t.i & CONFIG_CHILDREN_FORBIDDEN);
294
+ const r = t !== null && !(t.l & CONFIG_CHILDREN_FORBIDDEN);
257
295
  const i = r && tracking;
258
296
  if (t !== null && tracking) {
259
297
  link(e, t);
260
- if (n.ee !== undefined) {
298
+ if (n.X !== undefined) {
261
299
  // A31: the pulled memo's pass is its own, not the probe's — it reads
262
300
  // outside the window, and the probe's read below answers for what
263
301
  // the pass produced.
@@ -270,23 +308,26 @@ function isPending(e) {
270
308
  // final by declaration — the value itself is read as below. The verdict is
271
309
  // display-ahead like any: in a transaction's flush (the declaring action's
272
310
  // body) the reader is its verdict lane's, not a staging the frame parks.
273
- if (probing && GlobalQueue.N !== undefined && GlobalQueue.N(n)) {
311
+ if (probing && GlobalQueue.p !== undefined && GlobalQueue.p(n)) {
274
312
  probeFound = true;
275
313
  if (i && flushTransaction !== null) verdictRead(t, flushTransaction);
276
314
  }
277
- if (e.i & CONFIG_OVERRIDE) {
315
+ if (e.l & CONFIG_OVERRIDE && !(n.m & STATUS_ERROR && !(e.l & CONFIG_GUESS))) {
278
316
  const r = txOf(e);
279
317
  if (i) verdictRead(t, r._parent ?? r);
280
- const u = (n.S & STATUS_PENDING) !== 0 && !quietPending(n);
281
- if (e.i & CONFIG_GUESS) {
282
- if (probing && (u || e.Oe !== NOT_PENDING && (!e.ye || !e.ye(e.m.B, e.Oe)))) probeFound = true;
283
- return e.m.B;
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
+ // A lane a correction folded under (D-34): `latest()` shows the hold's
322
+ // world whole — the truth beneath each guess, else what it covered.
323
+ if (latestActive && r.folded) return r.folded(null, e);
324
+ return e.T.v;
284
325
  }
285
326
  if (probing && u) probeFound = true;
286
327
  return latestActive && !u ? laneValueOf(e) : display(e);
287
328
  }
288
- const u = (n.S & STATUS_UNINITIALIZED) !== 0;
289
- if (e.i & CONFIG_HELD) {
329
+ const u = (n.m & STATUS_UNINITIALIZED) !== 0;
330
+ if (e.l & CONFIG_HELD) {
290
331
  if (!u) {
291
332
  const r = txOf(e);
292
333
  // A render effect re-run (or mounted) outside the verdict lane's flush
@@ -294,31 +335,31 @@ function isPending(e) {
294
335
  // in flight — is a stale reader of the lane (#3460, as `laneRead`): the
295
336
  // committed value now, re-derived at the reveal. Not inside a probe:
296
337
  // `isPending` is answered below either way.
297
- if (i && !probing && t.u === EFFECT_RENDER && passLane === null && r.xe !== null && r.xe.sn && flushTransaction !== r) {
298
- staleReader(t, r.xe);
299
- return e.Ae;
338
+ if (i && !probing && t.i === EFFECT_RENDER && passLane === null && r.he !== null && r.he.ln && flushTransaction !== r) {
339
+ staleReader(t, r.he);
340
+ return e.Ie;
300
341
  }
301
342
  if (i) verdictRead(t, r);
302
343
  if (probing && heldNotFinal(n)) probeFound = true;
303
- if (latestActive && e.Oe !== NOT_PENDING) {
344
+ if (latestActive && e.se !== NOT_PENDING) {
304
345
  // An action body reading another transaction's proposal derives
305
346
  // from it: the two settle as one (A15; posture C, 2026-09-15). An
306
347
  // untracked read elsewhere has no transaction to entangle.
307
- if (!i && flushTransaction !== null && !globalQueue.X) joinFuture(txOf(e));
348
+ if (!i && flushTransaction !== null && !globalQueue.q) joinFuture(txOf(e));
308
349
  return heldLatest(e, i ? t : null);
309
350
  }
310
- return e.Ae;
351
+ return e.Ie;
311
352
  }
312
353
  // Born into the future: its staging is its only value. It has no
313
354
  // committed value, so no verdict: `isPending` reads it as its read does —
314
355
  // the owner joins the hold (held with it, or caught by a boundary that
315
356
  // has not shown content) and answers for the commit, where it is final.
316
- if (e.Oe !== NOT_PENDING && !(n.S & STATUS_PENDING)) {
357
+ if (e.se !== NOT_PENDING && !(n.m & STATUS_PENDING)) {
317
358
  if (probing && r) joinPass(t, e);
318
- return e.Oe;
359
+ return staged(e);
319
360
  }
320
- } else if (e.Oe !== NOT_PENDING) {
321
- if (n.S & STATUS_PENDING && !u) pendingVerdict(i ? t : null, n); else if (i && globalQueue.X) provisionalVerdict(t, !latestActive && !u);
361
+ } else if (e.se !== NOT_PENDING) {
362
+ if (n.m & STATUS_PENDING && !u) pendingVerdict(i ? t : null, n); else if (i && globalQueue.q) provisionalVerdict(t, !latestActive && !u);
322
363
  // Outside a flush, the staging is an unflushed write (A28): the probe
323
364
  // answers for the flushed world, and watches the seam of the flush that
324
365
  // carries the write — a memo of `isPending(x)` created after the write
@@ -329,33 +370,33 @@ function isPending(e) {
329
370
  // A28: an unflushed write is not what `latest` serves; a tracked
330
371
  // reader derived from the flushed world runs again in the carrying
331
372
  // flush.
332
- if (globalQueue.X) return e.Oe;
373
+ if (globalQueue.q) return staged(e);
333
374
  if (t !== null) markLateLinker(t);
334
375
  }
335
- return e.Ae;
376
+ return e.Ie;
336
377
  }
337
- if (n.S & STATUS_PENDING) {
378
+ if (n.m & STATUS_PENDING) {
338
379
  if (!u) {
339
380
  pendingVerdict(i ? t : null, n);
340
- return e.Ae;
381
+ return e.Ie;
341
382
  }
342
383
  // As `read()`: an untracked read of a pending node still re-runs its
343
384
  // reader when the node settles (a projection's pull reads its firewall
344
385
  // untracked, store/store.ts).
345
386
  if (t !== null && !tracking && !spectating && e !== t) link(e, t);
346
- throw n.m?.F;
387
+ throw n.T?.F;
347
388
  }
348
- if (n.ee !== undefined && n.S & STATUS_ERROR) throw n.m.F;
349
- return e.Ae;
389
+ if (n.X !== undefined && n.m & STATUS_ERROR) throw n.T.F;
390
+ return e.Ie;
350
391
  }
351
392
 
352
393
  // Installed at module evaluation — present exactly when something imports
353
394
  // `latest` or `isPending`. (`read` dispatches to `verdictValue` through
354
395
  // `verdict`, set while a window is open.)
355
- GlobalQueue.It = observeFlight;
396
+ installVerdictHooks(observeFlight);
356
397
 
357
398
  /** A store read inside an untracked `isPending` probe found a live mark on
358
- * a record with no node to carry it (store/affects.ts): not final. */ GlobalQueue.ct = () => {
399
+ * a record with no node to carry it (store/affects.ts): not final. */ GlobalQueue.rt = () => {
359
400
  if (probing) probeFound = true;
360
401
  };
361
402
 
@@ -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 };
@@ -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";