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

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 (40) hide show
  1. package/dist/dev-shared.js +296 -191
  2. package/dist/dev.attribution.js +16 -16
  3. package/dist/dev.js +527 -311
  4. package/dist/observe/core/async.js +148 -115
  5. package/dist/observe/core/constants.js +3 -1
  6. package/dist/observe/core/core.js +27 -27
  7. package/dist/observe/core/effect.js +29 -32
  8. package/dist/observe/core/graph.js +5 -5
  9. package/dist/observe/core/owner.js +7 -7
  10. package/dist/observe/core/scheduler.js +23 -12
  11. package/dist/observe/core/verdict.js +2 -2
  12. package/dist/observe/deferred.js +16 -13
  13. package/dist/observe/from.js +248 -0
  14. package/dist/observe/index.js +2 -0
  15. package/dist/observe/store/reconcile.js +137 -121
  16. package/dist/observe/store/store.js +595 -618
  17. package/dist/observe/store/target.js +1 -5
  18. package/dist/prod/core/async.js +209 -176
  19. package/dist/prod/core/constants.js +3 -1
  20. package/dist/prod/core/core.js +33 -33
  21. package/dist/prod/core/effect.js +24 -27
  22. package/dist/prod/core/graph.js +2 -2
  23. package/dist/prod/core/lanes.js +14 -14
  24. package/dist/prod/core/scheduler.js +35 -24
  25. package/dist/prod/core/truth.js +4 -4
  26. package/dist/prod/core/verdict.js +7 -7
  27. package/dist/prod/deferred.js +17 -14
  28. package/dist/prod/from.js +248 -0
  29. package/dist/prod/index.js +2 -0
  30. package/dist/prod/store/optimistic.js +1 -1
  31. package/dist/prod/store/reconcile.js +137 -121
  32. package/dist/prod/store/store.js +94 -117
  33. package/dist/prod/store/target.js +13 -17
  34. package/dist/types/core/async.d.ts +12 -2
  35. package/dist/types/core/constants.d.ts +5 -0
  36. package/dist/types/core/core.d.ts +19 -3
  37. package/dist/types/core/scheduler.d.ts +15 -3
  38. package/dist/types/from.d.ts +47 -0
  39. package/dist/types/index.d.ts +1 -0
  40. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  import { notifyStatus, releaseFlightTeardown, handleAsync, clearStatus, parkLoadingWindow, stageError, settlePendingSource, releaseIfSettledUnobserved, settleErroredDependents } from "./async.js";
2
2
 
3
- import { STATUS_UNINITIALIZED, REACTIVE_LAZY, REACTIVE_NONE, defaultContext, CONFIG_TRANSPARENT, CONFIG_OWNED_WRITE, CONFIG_AUTO_DISPOSE, CONFIG_SYNC, CONFIG_NO_SNAPSHOT, CONFIG_PLUMBING, CONFIG_IN_SNAPSHOT_SCOPE, NOT_PENDING, NO_SNAPSHOT, CONFIG_HAS_SNAPSHOT, CONFIG_HELD, REACTIVE_MANUAL_WRITE, STATUS_ERROR, REACTIVE_DIRTY, CONFIG_OVERRIDE, REACTIVE_DISPOSED, REACTIVE_CHECK, REACTIVE_IN_HEAP, REACTIVE_REASK, REACTIVE_SNAPSHOT_STALE, CONFIG_CHILDREN_FORBIDDEN, CONFIG_VERDICT, STATUS_PENDING, REACTIVE_LANE_DIRTY, CONFIG_GUESS, REACTIVE_RECOMPUTING_DEPS, CONFIG_LAGS, EFFECT_RENDER, CONFIG_HELD_FRAME, EFFECT_TRACKED, CONFIG_STAGED, CONFIG_STAGED_ERROR, REACTIVE_ZOMBIE, REACTIVE_MISSED_WAKE, REACTIVE_JOINED, REACTIVE_STAGED_READ, REACTIVE_PROBED, REACTIVE_FRAME_READ, REACTIVE_LANE_READ, REACTIVE_SCREEN_READ, REACTIVE_PROBE_UNANSWERED, CAUGHT_REARMED, CONFIG_PROMOTED, CONFIG_INPUTS_PUBLISHED, CONFIG_FRESH_READ, REACTIVE_IN_HEAP_HEIGHT, CONFIG_SLOT_NODE, EFFECT_USER, STATUS_NONE } from "./constants.js";
3
+ import { STATUS_UNINITIALIZED, REACTIVE_LAZY, REACTIVE_NONE, defaultContext, CONFIG_TRANSPARENT, CONFIG_OWNED_WRITE, CONFIG_AUTO_DISPOSE, CONFIG_SYNC, CONFIG_NO_SNAPSHOT, CONFIG_PLUMBING, CONFIG_IN_SNAPSHOT_SCOPE, NOT_PENDING, NO_SNAPSHOT, CONFIG_HAS_SNAPSHOT, CONFIG_HELD, REACTIVE_MANUAL_WRITE, STATUS_ERROR, REACTIVE_DIRTY, CONFIG_OVERRIDE, REACTIVE_DISPOSED, REACTIVE_CHECK, REACTIVE_IN_HEAP, REACTIVE_REASK, REACTIVE_SNAPSHOT_STALE, CONFIG_CHILDREN_FORBIDDEN, CONFIG_VERDICT, STATUS_PENDING, REACTIVE_LANE_DIRTY, CONFIG_GUESS, REACTIVE_RECOMPUTING_DEPS, CONFIG_LAGS, EFFECT_RENDER, CONFIG_HELD_FRAME, EFFECT_TRACKED, CONFIG_STAGED, CONFIG_STAGED_ERROR, REACTIVE_ZOMBIE, REACTIVE_MISSED_WAKE, REACTIVE_JOINED, REACTIVE_STAGED_READ, REACTIVE_PROBED, REACTIVE_FRAME_READ, REACTIVE_LANE_READ, REACTIVE_SCREEN_READ, REACTIVE_PROBE_UNANSWERED, CAUGHT_REARMED, CONFIG_PROMOTED, CONFIG_INPUTS_PUBLISHED, CONFIG_FRESH_READ, REACTIVE_IN_HEAP_HEIGHT, CONFIG_SLOT_NODE, EFFECT_USER, NewSet, STATUS_NONE } from "./constants.js";
4
4
 
5
5
  import { NotReadyError } from "./error.js";
6
6
 
@@ -82,7 +82,7 @@ function ownerInSnapshotScope(e) {
82
82
 
83
83
  function setSnapshotCapture(e) {
84
84
  snapshotCaptureActive = e;
85
- if (e && !snapshotSources) snapshotSources = new Set;
85
+ if (e && !snapshotSources) snapshotSources = new NewSet;
86
86
  }
87
87
 
88
88
  function markSnapshotScope(e) {
@@ -243,14 +243,14 @@ function recompute(e, t = false) {
243
243
  // value, leaving blocked dependents outside that source’s settle walk.
244
244
  const u = e.m & STATUS_ERROR ? e.T?.F : undefined;
245
245
  const a = (e.m & STATUS_PENDING) !== 0;
246
- const _ = a ? e.T?.fe : undefined;
246
+ const _ = a ? e.T?.se : undefined;
247
247
  // Pending SOURCE-hood, captured before the compute clears status: a node
248
248
  // whose own flight parked dependents self-registers in _pendingSources
249
249
  // (notifyStatus, isSource). If this recompute supersedes that flight and
250
250
  // settles synchronously, those dependents settle HERE — asyncWrite's
251
251
  // settlePendingSource walk never runs for a landing that was preempted
252
252
  // (#3181).
253
- const I = e.T?.fe?.has(e);
253
+ const I = e.T?.se?.has(e);
254
254
  // D-41: this pass's error is staged, not published (`stageError`).
255
255
  let N = false;
256
256
  // A19 exc. 2: a `refresh()` re-asks the question already answered — quiet
@@ -275,7 +275,7 @@ function recompute(e, t = false) {
275
275
  // L2: did this pass read the future (REACTIVE_JOINED, set by read) — or,
276
276
  // a lane's mount, a staging of this flush (#3851, #4166)?
277
277
  let R = false;
278
- let d = tracking;
278
+ let C = tracking;
279
279
  tracking = true;
280
280
  try {
281
281
  if (!false && e.l & CONFIG_SYNC) {
@@ -319,7 +319,7 @@ function recompute(e, t = false) {
319
319
  // D-41: a pass of a held node, or one that read a staging of this
320
320
  // flush, stages its error (`stageError`).
321
321
  if (n || !(N = stageError(e, t, true))) {
322
- if (n) ext(e).se = true;
322
+ if (n) ext(e).fe = true;
323
323
  notifyStatus(e, n ? STATUS_PENDING : STATUS_ERROR, t);
324
324
  // The replacement source is fully propagated now. If no new flight
325
325
  // re-owned self, retire the superseded flight and its dependent copies.
@@ -334,10 +334,10 @@ function recompute(e, t = false) {
334
334
  // re-park twin of the unchanged-value recovery sweep below; dependents
335
335
  // with another path keep the source (retryReaches). A staged error
336
336
  // answered what the pass carried: the same.
337
- if ((n || N) && _) for (const t of _) if (t !== e && !e.T?.fe?.has(t)) settlePendingSource(e, t);
337
+ if ((n || N) && _) for (const t of _) if (t !== e && !e.T?.se?.has(t)) settlePendingSource(e, t);
338
338
  }
339
339
  } finally {
340
- tracking = d;
340
+ tracking = C;
341
341
  if (laneStageHook) laneRoot = E;
342
342
  // Consume the missed-wake latch (#3037, set by insertSubs): a dep write
343
343
  // landed beneath this pass on a link it had already validated. The wipe
@@ -391,12 +391,12 @@ function recompute(e, t = false) {
391
391
  // seat that read none of the lane's world has left it: its result is the
392
392
  // frame's (a derivation whose branch no longer reaches the guess). A
393
393
  // guess is written, not derived — it never leaves this way.
394
- let C = !laneStageHook ? null : passLane;
394
+ let d = !laneStageHook ? null : passLane;
395
395
  // Listed before its staging, a pending pass included (the lane's own
396
396
  // flight is the lane's); false: the pass left the lane (lanes.ts).
397
397
  const O = !!e.T?.F || N;
398
- if (laneStageHook && C !== null) {
399
- if (laneStageHook(e, C, t, O)) setPassLane(C); else C = null;
398
+ if (laneStageHook && d !== null) {
399
+ if (laneStageHook(e, d, t, O)) setPassLane(d); else d = null;
400
400
  }
401
401
  if (!O) {
402
402
  // Observe-tier fan-in (HUGE_FAN_IN): the validated prefix [_deps.._depsTail]
@@ -445,7 +445,7 @@ function recompute(e, t = false) {
445
445
  // Lane work's run is the lane's (released at its reveal) — a first
446
446
  // pass under a lane too: `effect()` skips the synchronous first run of
447
447
  // a lane's node.
448
- if (!t || C !== null) globalQueue.enqueue(n, e.W ??= GlobalQueue.K.bind(null, e), C);
448
+ if (!t || d !== null) globalQueue.enqueue(n, e.W ??= GlobalQueue.K.bind(null, e), d);
449
449
  }
450
450
  // Lanes. A written guess's own pass from the frame: its source
451
451
  // recomputed it — the truth (A18: "the source is whatever recomputes the
@@ -453,7 +453,7 @@ function recompute(e, t = false) {
453
453
  // confirm is a landing too — the lane's end commits it) and notifies
454
454
  // only a correction: an equal truth re-runs nothing (lanes.ts). (As the
455
455
  // lane's own work the lane judged it — `laneStage`.)
456
- if (!t && C === null && e.l & CONFIG_GUESS && laneOutcomeHook(e, S, O)) ; else if (O) ; else if (s) {
456
+ if (!t && d === null && e.l & CONFIG_GUESS && laneOutcomeHook(e, S, O)) ; else if (O) ; else if (s) {
457
457
  // L2: a first pass is born held — staged, committed with the
458
458
  // transaction, its effect's first run the landing's (A29; `effect()`
459
459
  // skips the synchronous first run on a staged value) — when the flush
@@ -470,14 +470,14 @@ function recompute(e, t = false) {
470
470
  // pass that runs before anything joins: the pass's input, not a verdict
471
471
  // after it. An effect still carrying an uncommitted staged value
472
472
  // re-stages: the commit applies the latest pass, not the born-held one.
473
- if (C !== null) {
473
+ if (d !== null) {
474
474
  // Lane work: the lane's value — into the slot while the lane has not
475
475
  // revealed (its reveal shows it), as its staging once it has (the
476
476
  // next reveal promotes it; the screen keeps the revealed value). An
477
477
  // effect's value slot is private: the run the lane holds is what
478
478
  // shows it — unless the effect was born held, whose first run is the
479
479
  // commit's (A29): it re-stages, as below.
480
- if (n && e.ue === NOT_PENDING) e.Ie = S; else if (n || C.It) e.ue = S; else e.T.V = S;
480
+ if (n && e.ue === NOT_PENDING) e.Ie = S; else if (n || d.It) e.ue = S; else e.T.V = S;
481
481
  } else if (t ? !((flushTransaction !== null || passTx !== null) && (R || creatorPass(T)?.R & REACTIVE_JOINED)) : n && e.ue === NOT_PENDING) {
482
482
  // A first pass publishes directly. So does an effect: its value slot
483
483
  // is private (only its own run reads it), and the staging round-trip
@@ -517,7 +517,7 @@ function recompute(e, t = false) {
517
517
  if (e.o !== null) {
518
518
  insertSubs(e);
519
519
  // Lane work re-staged: the lane's members re-derive as its work.
520
- if (C !== null) laneDirty(e, C);
520
+ if (d !== null) laneDirty(e, d);
521
521
  }
522
522
  } else if (e.Et != c) {
523
523
  for (let t = e.o; t !== null; t = t.t) insertIntoHeapHeight(t.u, dirtyQueue);
@@ -546,7 +546,7 @@ function recompute(e, t = false) {
546
546
  // hold's commit (D-12) — but the hold does not wait for it: its only
547
547
  // readers are behind the fallback (`_hidden`; F-4, #3540). A boundary
548
548
  // re-armed by `on` owns what it collects outright (D-14): not held.
549
- if (t && C === null && R && e.m & STATUS_PENDING) {
549
+ if (t && d === null && R && e.m & STATUS_PENDING) {
550
550
  const t = flushTransaction ?? passTx;
551
551
  if (t && !t.V && freshHook?.(e) !== CAUGHT_REARMED) holdNode(e, t);
552
552
  }
@@ -572,7 +572,7 @@ function recompute(e, t = false) {
572
572
  }
573
573
  // Observe: the posture the run executed under — lane work (optimistic:
574
574
  // overlay, never waste), a transaction's pass (held), or plain.
575
- if (attrHooks !== null) attrHooks.recomputeEnd(e, t, i, C !== null, flushTransaction !== null || passTx !== null || (e.l & CONFIG_HELD) !== 0, e.ue !== NOT_PENDING);
575
+ if (attrHooks !== null) attrHooks.recomputeEnd(e, t, i, d !== null, flushTransaction !== null || passTx !== null || (e.l & CONFIG_HELD) !== 0, e.ue !== NOT_PENDING);
576
576
  // A staged value, a parked frame (L2: the commit retires it), or status the
577
577
  // commit sweep must settle (a pending or uninitialized pass), queues the
578
578
  // node for this flush's commit. A first pass queues only when pending or
@@ -581,7 +581,7 @@ function recompute(e, t = false) {
581
581
  if (e.ue !== NOT_PENDING || e.T !== null && (e.T.Nt !== null || e.T.ft !== null) || (e.m & (STATUS_PENDING | STATUS_UNINITIALIZED)) !== 0 && (!t || (e.m & STATUS_PENDING) !== 0)) {
582
582
  e.l |= CONFIG_STAGED;
583
583
  // Lane work is the lane's to reveal (its seam), not this flush's commit.
584
- if (C === null) queuePendingNode(e);
584
+ if (d === null) queuePendingNode(e);
585
585
  }
586
586
  setPassLane(s);
587
587
  // Missed-wake reschedule (see the finally above): values this pass read
@@ -661,10 +661,10 @@ function computed(e, t) {
661
661
  _: null,
662
662
  Ne: 0,
663
663
  o: null,
664
- dt: null,
664
+ Ct: null,
665
665
  _parent: context,
666
666
  Xe: null,
667
- Ct: null,
667
+ dt: null,
668
668
  ze: null,
669
669
  R: t?.lazy ? REACTIVE_LAZY : REACTIVE_NONE,
670
670
  m: o ? 0 : STATUS_UNINITIALIZED,
@@ -694,8 +694,8 @@ function computed(e, t) {
694
694
  Se: null,
695
695
  ae: null,
696
696
  F: undefined,
697
- se: undefined,
698
697
  fe: undefined,
698
+ se: undefined,
699
699
  Dt: undefined,
700
700
  lt: undefined,
701
701
  Nt: null,
@@ -838,10 +838,10 @@ function computed(e, t) {
838
838
  _: null,
839
839
  Ne: 0,
840
840
  o: null,
841
- dt: null,
841
+ Ct: null,
842
842
  _parent: context,
843
843
  Xe: null,
844
- Ct: null,
844
+ dt: null,
845
845
  ze: null,
846
846
  R: REACTIVE_LAZY,
847
847
  m: STATUS_UNINITIALIZED,
@@ -886,7 +886,7 @@ function setEffectStatusNotify(e) {
886
886
  * 0, and only effect literals carry the field) get the shared notifier.
887
887
  * Presence doubles as the "display consumer" membership test in the status
888
888
  * walks, exactly as the per-node field did when every effect carried one. */ function statusNotifierOf(e) {
889
- return e.i ? effectStatusNotify ?? undefined : undefined;
889
+ return e.i ? effectStatusNotify : null;
890
890
  }
891
891
 
892
892
  const lazyOptions = {
@@ -919,7 +919,7 @@ function signal(e, t) {
919
919
  l: (t?.ownedWrite ? CONFIG_OWNED_WRITE : 0) | (t?.le ? CONFIG_NO_SNAPSHOT : 0),
920
920
  Ie: e,
921
921
  o: null,
922
- dt: null,
922
+ Ct: null,
923
923
  ie: clock,
924
924
  ue: NOT_PENDING,
925
925
  Ot: -1,
@@ -957,7 +957,7 @@ function isEqual(e, t) {
957
957
  l: CONFIG_OWNED_WRITE | CONFIG_SLOT_NODE,
958
958
  Ie: e,
959
959
  o: null,
960
- dt: null,
960
+ Ct: null,
961
961
  ie: clock,
962
962
  ue: NOT_PENDING,
963
963
  Ot: -1,
@@ -18,45 +18,48 @@ import { globalQueue, schedule, heldRunHook, haltReactivity, GlobalQueue } from
18
18
  * Effects are the leaf nodes of our reactive graph. When their sources change, they are
19
19
  * automatically added to the queue of effects to re-execute, which will cause them to fetch their
20
20
  * sources and recompute
21
- */ function effect(t, e, o, u) {
22
- const E = !!u?.user;
23
- const r = createEffectNode(t, e, o, E ? EFFECT_USER : EFFECT_RENDER, u);
24
- recompute(r, true);
21
+ */ function effect(t, e, o, r) {
22
+ const u = !!r?.user;
23
+ const E = createEffectNode(t, e, o, u ? EFFECT_USER : EFFECT_RENDER, r);
24
+ recompute(E, true);
25
25
  // A first pass that read a staged value was staged itself (recompute: born
26
26
  // staged); the flush's commit replays this effect. Its first run is not this
27
27
  // creation's (A29). A lane's node likewise: its run is the lane's, queued
28
28
  // by the pass and released at the reveal (lanes.ts).
29
- !u?.defer && r.ue === NOT_PENDING && !r.T?.Je?.V && (r.i === EFFECT_USER || u?.schedule ? globalQueue.enqueue(r.i, runEffect.bind(null, r)) : runEffect(r, r.i));
29
+ !r?.defer && E.ue === NOT_PENDING && !E.T?.Je?.V && (E.i === EFFECT_USER || r?.schedule ? globalQueue.enqueue(E.i, runEffect.bind(null, E)) : runEffect(E, E.i));
30
30
  }
31
31
 
32
- function notifyEffectStatus(t, e) {
33
- // Use passed values if provided, otherwise read from node
34
- const o = t !== undefined ? t : this.m;
35
- const u = e !== undefined ? e : this.T?.F;
36
- if (o & STATUS_ERROR) {
32
+ function notifyEffectStatus() {
33
+ const t = this.m;
34
+ const e = this.T?.F;
35
+ if (t & STATUS_ERROR) {
37
36
  globalQueue.notify(this, STATUS_PENDING, 0);
38
37
  if (this.i === EFFECT_USER) {
39
38
  // The error handler is the error arm of the effect phase (#2840 ruling):
40
39
  // queue it like the effect function. It runs in the same imperative,
41
40
  // writable scope and throws escalate the same way. No payload is queued — the node already carries `_statusFlags`/`_error`,
42
41
  // and the runner dispatches on them, so a recovery before the effect
43
- // phase takes the success arm instead. Blocked forwards (explicit
44
- // `status` arg without node-state writes) don't queue: the status
45
- // re-propagates unblocked at commit.
42
+ // phase takes the success arm instead. (Re-read: the boundary
43
+ // notify above can run a redraw.)
46
44
  if (this.m & STATUS_ERROR) {
47
45
  this.Qe = true;
48
46
  globalQueue.enqueue(this.i, this.W ??= runEffect.bind(null, this));
49
47
  }
50
48
  return;
51
49
  }
52
- if (!globalQueue.notify(this, STATUS_ERROR, STATUS_ERROR)) {
53
- // Dev locates the halt at the node that threw: the StatusError's
54
- // source, upstream of this effect when a computed failed.
55
- haltReactivity(unwrapStatusError(u));
56
- throw u;
57
- }
50
+ // Dev locates the halt at the node that threw: the StatusError's
51
+ // source, upstream of this effect when a computed failed.
52
+ escalate(this, unwrapStatusError(e), e, this);
58
53
  } else if (this.i === EFFECT_RENDER) {
59
- globalQueue.notify(this, STATUS_PENDING | STATUS_ERROR, o, u);
54
+ globalQueue.notify(this, STATUS_PENDING | STATUS_ERROR, t, e);
55
+ }
56
+ }
57
+
58
+ /** An effect's error no boundary takes halts the system (boundary-or-halt):
59
+ * halted with `cause`, `thrown` rethrown; dev locates it at `subject`. */ function escalate(t, e, o = e, r = t) {
60
+ if (!globalQueue.notify(t, STATUS_ERROR, STATUS_ERROR)) {
61
+ haltReactivity(e);
62
+ throw o;
60
63
  }
61
64
  }
62
65
 
@@ -92,10 +95,7 @@ function runEffect(t, e) {
92
95
  e?.();
93
96
  }) : console.error(e);
94
97
  } catch (e) {
95
- if (!globalQueue.notify(t, STATUS_ERROR, STATUS_ERROR)) {
96
- haltReactivity(e);
97
- throw e;
98
- }
98
+ escalate(t, e);
99
99
  }
100
100
  return;
101
101
  }
@@ -107,10 +107,10 @@ function runEffect(t, e) {
107
107
  // cascade an observer reports) and what times the callback (the `effect`
108
108
  // record) — facts a production observer needs, not only a dev console.
109
109
  if (attrHooks !== null) attrHooks.effectRunStart(t);
110
- const u = t.kt;
110
+ const r = t.kt;
111
111
  t.kt = undefined;
112
112
  try {
113
- u?.();
113
+ r?.();
114
114
  const e = t.Ge(t.Ie, t._t);
115
115
  if (false && e !== undefined && typeof e !== "function") ;
116
116
  // The final cleanup is invoked by disposeChildren at true disposal.
@@ -118,10 +118,7 @@ function runEffect(t, e) {
118
118
  } catch (e) {
119
119
  ext(t).F = new StatusError(t, e);
120
120
  t.m |= STATUS_ERROR;
121
- if (!globalQueue.notify(t, STATUS_ERROR, STATUS_ERROR)) {
122
- haltReactivity(e);
123
- throw e;
124
- }
121
+ escalate(t, e);
125
122
  } finally {
126
123
  t._t = t.Ie;
127
124
  t.Qe = false;
@@ -159,8 +156,8 @@ GlobalQueue.K = runEffect;
159
156
  const e = o.kt;
160
157
  o.kt = undefined;
161
158
  e?.();
162
- const u = staleValues(t);
163
- o.kt = u;
159
+ const r = staleValues(t);
160
+ o.kt = r;
164
161
  }, {
165
162
  ...e,
166
163
  lazy: true
@@ -1,4 +1,4 @@
1
- import { REACTIVE_RECOMPUTING_DEPS, CONFIG_AUTO_DISPOSE, STATUS_PENDING, REACTIVE_DISPOSED, REACTIVE_ZOMBIE, CONFIG_SLOT_NODE } from "./constants.js";
1
+ import { NewSet, REACTIVE_RECOMPUTING_DEPS, CONFIG_AUTO_DISPOSE, STATUS_PENDING, REACTIVE_DISPOSED, REACTIVE_ZOMBIE, CONFIG_SLOT_NODE } from "./constants.js";
2
2
 
3
3
  import { deleteFromHeap } from "./heap.js";
4
4
 
@@ -21,7 +21,7 @@ function unlinkSubs(e) {
21
21
  const l = e.G;
22
22
  const o = e.t;
23
23
  const s = e.en;
24
- if (o !== null) o.en = s; else n.dt = s;
24
+ if (o !== null) o.en = s; else n.Ct = s;
25
25
  if (s !== null) s.t = o; else {
26
26
  n.o = o;
27
27
  if (o === null) {
@@ -107,7 +107,7 @@ function unobserved(e) {
107
107
  * stays alive and serves its cache, uniform with observed memos) while
108
108
  * reclamation still happens within one microtask — the enqueue site arms
109
109
  * schedule(), so a flush is guaranteed even when no other work is queued.
110
- */ const dormantNodes = new Set;
110
+ */ const dormantNodes = new NewSet;
111
111
 
112
112
  function sweepDormant() {
113
113
  if (dormantNodes.size === 0) return;
@@ -143,9 +143,9 @@ function link(e, n) {
143
143
  // [deps.._depsTail] prefix — the O(1) equivalent of scanning the dep list
144
144
  // (the old alien-signals `isValidLink` walk, O(n²) when a computation
145
145
  // re-reads earlier deps non-consecutively, e.g. store leaf reads).
146
- const r = e.dt;
146
+ const r = e.Ct;
147
147
  if (r !== null && r.u === n && (!s || r.Te === n.Ne)) return;
148
- const t = n._ = e.dt = {
148
+ const t = n._ = e.Ct = {
149
149
  S: e,
150
150
  u: n,
151
151
  G: o,
@@ -107,7 +107,7 @@ function disposeChildren(e, n = false, r) {
107
107
  // on the fresh head and survives. Pointing each drained child's prev at
108
108
  // itself routes its later splice onto the detached chain, never the head,
109
109
  // and keeps the dev owner-chain-head invariant honest for those children.
110
- l.Ct = l;
110
+ l.dt = l;
111
111
  disposeChildren(l, true);
112
112
  // Read after, not before: a sibling this disposal made dormant spliced
113
113
  // itself out of the detached chain, and a cleanup may then have linked
@@ -122,11 +122,11 @@ function disposeChildren(e, n = false, r) {
122
122
  // node._nextSibling intact so outer walks that already advanced past us
123
123
  // still reach later siblings.
124
124
  if (n && !r && !(t & REACTIVE_ZOMBIE) && e._parent !== null && !(e._parent.R & REACTIVE_DISPOSED)) {
125
- const n = e.Ct;
125
+ const n = e.dt;
126
126
  const r = e.Xe;
127
127
  if (n !== null) n.Xe = r; else e._parent.ze = r;
128
- if (r !== null) r.Ct = n;
129
- e.Ct = null;
128
+ if (r !== null) r.dt = n;
129
+ e.dt = null;
130
130
  }
131
131
  runDisposal(e, r);
132
132
  // Final effect-returned cleanup fires at true disposal, after `_disposal`
@@ -140,9 +140,9 @@ function disposeChildren(e, n = false, r) {
140
140
 
141
141
  function linkChild(e, n) {
142
142
  const r = e.ze;
143
- n.Ct = null;
143
+ n.dt = null;
144
144
  n.Xe = r;
145
- if (r !== null) r.Ct = n;
145
+ if (r !== null) r.dt = n;
146
146
  e.ze = n;
147
147
  }
148
148
 
@@ -311,7 +311,7 @@ function disposeRootSelf(e = true) {
311
311
  Ht: n?.Pt ? n.Ht : n,
312
312
  ze: null,
313
313
  Xe: null,
314
- Ct: null,
314
+ dt: null,
315
315
  rt: null,
316
316
  A: n?.A || defaultContext,
317
317
  ct: 0,
@@ -291,7 +291,7 @@ let judge = null;
291
291
  /** A source the pending node waits on that is `t`'s (held by it, or by no
292
292
  * one) — not another transaction's or lane's work. Itself, when it is the
293
293
  * source. */ function ownSource(e, o) {
294
- const n = e.T?.fe;
294
+ const n = e.T?.se;
295
295
  if (n === undefined) return true;
296
296
  for (const e of n) {
297
297
  const n = e.T?.Je;
@@ -819,31 +819,34 @@ class GlobalQueue {
819
819
  * or without the held read) owes nothing. A run this transaction stashed
820
820
  * for it is void (#3322: an effect has one value slot, and the stale pass
821
821
  * overwrote the value that run was for) — the re-derivation's run replaces
822
- * it. */ function reruns(e) {
823
- for (let o = 0; o < e.En.length; o++) {
824
- const n = e.En[o];
822
+ * it. Truthy when an effect re-derives (`land`). */ function reruns(e) {
823
+ let o;
824
+ for (let n = 0; n < e.En.length; n++) {
825
+ const t = e.En[n];
825
826
  // An errored reader's display is its error (D-39): the landing gives it
826
827
  // nothing to re-derive, and a re-run would be a read that retries the
827
828
  // errored source (#3945).
828
- if (n.R & REACTIVE_DISPOSED || n.m & STATUS_ERROR) continue;
829
- if (n.R & REACTIVE_FRAME_READ || n.l & CONFIG_VERDICT) {
830
- n.R &= ~REACTIVE_FRAME_READ;
831
- n.Qe = false;
829
+ if (t.R & REACTIVE_DISPOSED || t.m & STATUS_ERROR) continue;
830
+ if (t.R & REACTIVE_FRAME_READ || t.l & CONFIG_VERDICT) {
831
+ o ||= t.i;
832
+ t.R &= ~REACTIVE_FRAME_READ;
833
+ t.Qe = false;
832
834
  // Its frame is replaced by the re-derivation: a run one of its
833
835
  // children queued this flush (re-run by the same landing) is for the
834
836
  // frame being replaced — void too (#3404).
835
- for (let e = n.ze; e !== null; e = e.Xe) if (e.i) e.Qe = false;
836
- enqueueSub(n);
837
+ for (let e = t.ze; e !== null; e = e.Xe) if (e.i) e.Qe = false;
838
+ enqueueSub(t);
837
839
  }
838
840
  }
839
841
  e.En.length = 0;
842
+ return o;
840
843
  }
841
844
 
842
845
  /** The landing: the held frame becomes the frame. */ function land(e) {
843
846
  // Attribution hook: judged complete — its held writes commit next
844
847
  // (`_nodes` still lists them).
845
848
  if (attrHooks !== null) attrHooks.transitionSettled(e);
846
- reruns(e);
849
+ const o = reruns(e);
847
850
  if (e.I !== null) releaseMarksHook(e.I);
848
851
  // Old children die in the commits (cleanups first), then the stashed
849
852
  // effects run ahead of this flush's own. A node a lane took over since
@@ -858,7 +861,15 @@ class GlobalQueue {
858
861
  // `u`, which has left `transactions`: the write is this landing's, and
859
862
  // the next flush must not park in it (#4089).
860
863
  if (flushTransaction === e) flushTransaction = null;
861
- releaseQueues(e);
864
+ // An effect re-derives next round (`reruns`): the held runs wait for it
865
+ // (D-9, the A15 reveal corollary, #4071). Its re-derivation can discover a
866
+ // flight — a stale reader whose mainline pass stopped reading it, now
867
+ // reading the landed world — and a landing that showed its runs first
868
+ // would reveal the held write beside a frame that can only show its old
869
+ // value. Queued last in this effect phase, the release hands them to the
870
+ // next round's own runs, ahead of the re-derivation's: they show with it,
871
+ // or park with it in the hold its pending opens. The commits stand.
872
+ if (o) globalQueue.un[1].push(() => releaseQueues(e)); else releaseQueues(e);
862
873
  }
863
874
 
864
875
  /** The held runs join this flush's effect phase after its own runs (the
@@ -139,8 +139,8 @@ let probeFound = false;
139
139
  * when every flight it waits on is. */ function quietPending(e) {
140
140
  const t = e.T;
141
141
  if (t === null) return false;
142
- if (t.fe !== undefined) {
143
- for (const e of t.fe) if (!e.T?.ut) return false;
142
+ if (t.se !== undefined) {
143
+ for (const e of t.se) if (!e.T?.ut) return false;
144
144
  return true;
145
145
  }
146
146
  return t.ut;
@@ -135,26 +135,26 @@ GlobalQueue.er = e => lagging.size !== 0 ? lagOf(e, new Set) : undefined;
135
135
  // Sticky, and read before this wrapper ever opens the window itself.
136
136
  const n = l ||= !(t.m & STATUS_UNINITIALIZED) && !t.ce;
137
137
  if (n) t.ce = true, t.l |= CONFIG_LAGS;
138
- let c;
138
+ let s;
139
139
  try {
140
- c = e(r);
140
+ s = e(r);
141
141
  } catch (e) {
142
142
  // The wrapping form: an unready source parks the node in the window,
143
143
  // and the wait is its flight until the source's settle re-runs it.
144
- if (n && e instanceof NotReadyError) ask(e);
144
+ if (n && e instanceof NotReadyError) ask(e); else land(o);
145
145
  throw e;
146
146
  }
147
- if (n && typeof c === "object" && c !== null) {
148
- if (isThenable(c)) {
149
- ask(c);
150
- const done = () => land(c, true);
151
- c.then(done, done);
152
- return serve(c);
147
+ if (n && typeof s === "object" && s !== null) {
148
+ if (isThenable(s)) {
149
+ ask(s);
150
+ const done = () => land(s, true);
151
+ s.then(done, done);
152
+ return serve(s);
153
153
  }
154
- const e = c[Symbol.asyncIterator];
154
+ const e = s[Symbol.asyncIterator];
155
155
  if (e) {
156
156
  // A stream's flight ends at its first yield (or its end).
157
- const r = e.call(c);
157
+ const r = e.call(s);
158
158
  let t = true;
159
159
  const done = () => land(o, true);
160
160
  const o = {
@@ -162,7 +162,10 @@ GlobalQueue.er = e => lagging.size !== 0 ? lagOf(e, new Set) : undefined;
162
162
  // promise, as for a plain flight: verdict and value in one flush.
163
163
  next: e => {
164
164
  const o = r.next(e);
165
- if (t) t = false, o.then(done, done);
165
+ if (t) {
166
+ t = false;
167
+ if (isThenable(o)) o.then(done, done); else done();
168
+ }
166
169
  return o;
167
170
  },
168
171
  return: e => r.return?.(e) ?? Promise.resolve({
@@ -177,7 +180,7 @@ GlobalQueue.er = e => lagging.size !== 0 ? lagOf(e, new Set) : undefined;
177
180
  }
178
181
  }
179
182
  land(o);
180
- return c;
183
+ return s;
181
184
  }, r));
182
185
  }
183
186