@solidjs/signals 2.0.0-rc.16 → 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 (39) hide show
  1. package/dist/dev-shared.js +1719 -1603
  2. package/dist/dev.attribution.js +13 -13
  3. package/dist/dev.js +182 -45
  4. package/dist/observe/boundaries.js +2 -2
  5. package/dist/observe/core/async.js +3 -1
  6. package/dist/observe/core/context.js +2 -0
  7. package/dist/observe/core/core.js +26 -10
  8. package/dist/observe/core/effect.js +2 -0
  9. package/dist/observe/core/graph.js +10 -8
  10. package/dist/observe/core/lanes.js +25 -14
  11. package/dist/observe/core/owner.js +73 -47
  12. package/dist/observe/core/scheduler.js +189 -173
  13. package/dist/observe/core/verdict.js +3 -0
  14. package/dist/observe/map.js +2 -2
  15. package/dist/observe/signals.js +6 -1
  16. package/dist/observe/store/affects.js +13 -13
  17. package/dist/observe/store/optimistic.js +112 -71
  18. package/dist/observe/store/store.js +25 -16
  19. package/dist/prod/boundaries.js +2 -2
  20. package/dist/prod/core/async.js +3 -1
  21. package/dist/prod/core/context.js +2 -0
  22. package/dist/prod/core/core.js +31 -15
  23. package/dist/prod/core/effect.js +2 -0
  24. package/dist/prod/core/graph.js +10 -8
  25. package/dist/prod/core/heap.js +1 -1
  26. package/dist/prod/core/lanes.js +25 -14
  27. package/dist/prod/core/owner.js +70 -44
  28. package/dist/prod/core/scheduler.js +43 -27
  29. package/dist/prod/core/verdict.js +4 -1
  30. package/dist/prod/map.js +2 -2
  31. package/dist/prod/signals.js +18 -13
  32. package/dist/prod/store/optimistic.js +112 -71
  33. package/dist/prod/store/store.js +18 -7
  34. package/dist/types/core/core.d.ts +10 -3
  35. package/dist/types/core/dev.d.ts +9 -1
  36. package/dist/types/core/error.d.ts +9 -1
  37. package/dist/types/core/owner.d.ts +4 -0
  38. package/dist/types/core/scheduler.d.ts +23 -4
  39. package/package.json +1 -1
@@ -10,7 +10,7 @@ import { insertIntoHeap, deleteFromHeap, insertIntoHeapHeight, enqueueSub, markN
10
10
 
11
11
  import { inheritId, linkChild, disposeChildren, markDisposal } from "./owner.js";
12
12
 
13
- import { schedule, externalUntrackHook, clock, txOf, flushTransaction, laneWriteHook, joinFuture, queuePendingNode, globalQueue, notifyEpoch, insertSubs, dirtyQueue, observeFlightHook, laneReadHook, laneStageHook, passLane, wireExternalSourceHook, bumpNotifyEpoch, setPassLane, joinPassTx, freshHook, GlobalQueue, laneOutcomeHook, passTx, holdNode, laneDirty, heldTrim, staleReader, laneSeamsHook, stagedReaders, deferZombie } from "./scheduler.js";
13
+ import { schedule, externalUntrackHook, clock, txOf, flushTransaction, laneWriteHook, joinFuture, queuePendingNode, globalQueue, notifyEpoch, insertSubs, dirtyQueue, observeFlightHook, laneStageHook, passLane, laneReadHook, wireExternalSourceHook, bumpNotifyEpoch, setPassLane, joinPassTx, freshHook, GlobalQueue, laneOutcomeHook, passTx, holdNode, laneDirty, heldTrim, staleReader, laneSeamsHook, stagedReaders, deferZombie } from "./scheduler.js";
14
14
 
15
15
  // The heap's per-node step. A tracked effect's heap visit is its compute
16
16
  // phase — empty, like a user effect whose compute reads nothing — and hands
@@ -210,7 +210,9 @@ function recompute(e, t = false) {
210
210
  // and own nothing to park.
211
211
  if (n === EFFECT_TRACKED || e.l & CONFIG_STAGED) {
212
212
  // A lazy node only those children read is released when this pass
213
- // ends, if the pass doesn't read it again (#4142).
213
+ // ends, if the pass doesn't read it again (#4142). A cleanup or
214
+ // `unobserved` hook that throws here neither stops the pass nor skips
215
+ // that drain: its error is rethrown when the flush ends (owner.ts).
214
216
  const t = deferredReleases;
215
217
  deferReleases(o = []);
216
218
  disposeChildren(e);
@@ -331,7 +333,10 @@ function recompute(e, t = false) {
331
333
  // (markNode(c) in read()) marks the running node as part of ordinary
332
334
  // bookkeeping, and those marks are correctly discarded here.
333
335
  c = (e.R & REACTIVE_MISSED_WAKE) !== 0;
334
- A = !!(e.R & (i ? REACTIVE_JOINED | REACTIVE_STAGED_READ : REACTIVE_JOINED));
336
+ // A mount's staged read joins it — except a nested derivation left
337
+ // pending: no render effect reaches it yet, so it holds nothing and
338
+ // reaches no boundary (Reader, #4203). A reader that does is caught.
339
+ A = !!(e.R & (i && (E || n || !(e.m & STATUS_PENDING)) ? REACTIVE_JOINED | REACTIVE_STAGED_READ : REACTIVE_JOINED));
335
340
  // A verdict reader is one for as long as it probes, or reads a memo
336
341
  // that does (`read`): a pass that did neither (REACTIVE_PROBED, the same
337
342
  // bit) is an ordinary derivation again.
@@ -653,8 +658,8 @@ function computed(e, t) {
653
658
  me: null,
654
659
  Re: null,
655
660
  Ve: false,
656
- Be: NOT_PENDING,
657
- Qe: -1,
661
+ je: NOT_PENDING,
662
+ Be: -1,
658
663
  Ee: 0,
659
664
  v: NOT_PENDING,
660
665
  I: 0,
@@ -682,6 +687,12 @@ function computed(e, t) {
682
687
  if (t.m & STATUS_UNINITIALIZED || e.i !== EFFECT_RENDER) joinPassTx(txOf(t), !e.i && e.m & STATUS_UNINITIALIZED || freshHook?.(e));
683
688
  }
684
689
 
690
+ /** `joinPass` into the hold `t` (a folded guess's: store/optimistic.ts
691
+ * `foldedRead`, D-34) — a reader is never a render effect there. */ function joinPassAt(e, t) {
692
+ e.R |= REACTIVE_JOINED;
693
+ joinPassTx(t, !e.i && e.m & STATUS_UNINITIALIZED || freshHook?.(e));
694
+ }
695
+
685
696
  /** A15's stale reader (shared-hole and reveal corollaries): a render effect
686
697
  * reading a node held by a transaction that is not the flush's. It is served
687
698
  * the committed value — a pending one's too — publishes mainline, and is
@@ -739,7 +750,7 @@ function computed(e, t) {
739
750
 
740
751
  /** The computed whose pass a creation under `owner` belongs to (a root's is
741
752
  * its nearest computed ancestor's), or null outside every pass. */ function creatorPass(e) {
742
- return e !== null && e.je ? e.Ye : e;
753
+ return e !== null && e.Qe ? e.Ye : e;
743
754
  }
744
755
 
745
756
  /** L2 — park the previous pass's frame for this node's commit: the children
@@ -837,7 +848,7 @@ const lazyOptions = {
837
848
 
838
849
  function setupComputedNode(e, t) {
839
850
  e.We = e;
840
- const n = context?.je ? context.Ye : context;
851
+ const n = context?.Qe ? context.Ye : context;
841
852
  if (context) linkChild(context, e);
842
853
  if (n) e.Fe = n.Fe + 1;
843
854
  wireExternalSourceHook?.(e);
@@ -1058,7 +1069,7 @@ function markUnflushedStaged() {
1058
1069
  // A held node (L2) carries a staged value between flushes too, but it was
1059
1070
  // flushed — and parked. Not an unflushed write — unless rewritten since:
1060
1071
  // then the flushed staging is what the world sees until the next flush.
1061
- if (e.l & CONFIG_HELD) return e.T.Qe === clock ? e.T.Be : NOT_PENDING;
1072
+ if (e.l & CONFIG_HELD) return e.T.Be === clock ? e.T.je : NOT_PENDING;
1062
1073
  return e.Ie;
1063
1074
  }
1064
1075
 
@@ -1066,9 +1077,9 @@ function markUnflushedStaged() {
1066
1077
  * flush left (`_flushed`) until the flush that carries the rewrite — the
1067
1078
  * stash is stamped with the clock, which that flush advances. */ function stashFlushed(e) {
1068
1079
  const t = e.T;
1069
- if (t.Qe !== clock) {
1070
- t.Be = e.se;
1071
- t.Qe = clock;
1080
+ if (t.Be !== clock) {
1081
+ t.je = e.se;
1082
+ t.Be = clock;
1072
1083
  unflushedStaged = true;
1073
1084
  }
1074
1085
  }
@@ -1115,7 +1126,7 @@ function resyncUnflushedCompanions() {
1115
1126
 
1116
1127
  function read(e) {
1117
1128
  let t = context;
1118
- if (t?.je) t = t.Ye;
1129
+ if (t?.Qe) t = t.Ye;
1119
1130
  const n = e;
1120
1131
  // CARVE 1: `owner` was `el._firewall || el` — a store leaf's status,
1121
1132
  // height and error were read off its projection computed. (Typed loosely
@@ -1149,7 +1160,9 @@ function read(e) {
1149
1160
  // Committed visibility for untracked and children-forbidden readers.
1150
1161
  if (!t || e.se === NOT_PENDING || t.l & CONFIG_CHILDREN_FORBIDDEN) return e.Ie;
1151
1162
  // (A plain signal is never uninitialized — the carve-out is `serve`'s.)
1152
- if (observeFlightHook && t.l & CONFIG_VERDICT && stagedScreen(t)) return e.Ie;
1163
+ // Lane work over a folded lane (D-34), once the frame has a
1164
+ // transaction, reads the screen too.
1165
+ if ((observeFlightHook && t.l & CONFIG_VERDICT || laneStageHook && passLane?.folded) && stagedScreen(t)) return e.Ie;
1153
1166
  stagedRead(t);
1154
1167
  return e.se;
1155
1168
  }
@@ -1290,7 +1303,10 @@ function read(e) {
1290
1303
 
1291
1304
  /** A pass read a staging of this flush: it derives from what the flush may
1292
1305
  * yet hold (REACTIVE_STAGED_READ — the seam decides). Lane work too (plan sec. 28, a
1293
- * lane sees the screen plus its own guesses): the staging is the screen if
1306
+ * lane sees the screen plus its own guesses) — a folded lane's only before
1307
+ * the frame has a transaction (after, it reads the screen like a verdict
1308
+ * reader, `stagedScreen` — a list pass never pairs a held length with the
1309
+ * guess's rows, D-34): the staging is the screen if
1294
1310
  * the frame commits — one pass, the common case — and a held write if it
1295
1311
  * parks, which the seam repairs (`stagedReaders`): the pass re-derives on
1296
1312
  * the committed world and its lane's runs wait that round, so the held
@@ -1527,4 +1543,4 @@ function staleValues(e, t = true) {
1527
1543
  }
1528
1544
  }
1529
1545
 
1530
- export { bornInto, captureWriteSnapshot, clearSnapshots, computed, context, createEffectNode, effectStatusNotify, ext, isEqual, isSame, joinPass, markLateLinker, markRefresh, markSnapshotScope, markUnflushedStaged, notePromotedWrite, prepareComputed, pullComputed, read, readerSeesCommitted, recompute, releaseSnapshotScope, resyncUnflushedCompanions, runWithOwner, serve, setEffectStatusNotify, setMemo, setSignal, setSnapshotCapture, setVerdict, setWriteHook, signal, slotSignal, snapshotCaptureActive, snapshotSources, spectating, stagedRead, stagedScreen, staleValues, stashFlushed, statusNotifierOf, tracking, unflushedStaged, unflushedValue, untrack, verdict, writeHook };
1546
+ export { bornInto, captureWriteSnapshot, clearSnapshots, computed, context, createEffectNode, effectStatusNotify, ext, isEqual, isSame, joinPass, joinPassAt, markLateLinker, markRefresh, markSnapshotScope, markUnflushedStaged, notePromotedWrite, prepareComputed, pullComputed, read, readerSeesCommitted, recompute, releaseSnapshotScope, resyncUnflushedCompanions, runWithOwner, serve, setEffectStatusNotify, setMemo, setSignal, setSnapshotCapture, setVerdict, setWriteHook, signal, slotSignal, snapshotCaptureActive, snapshotSources, spectating, stagedRead, stagedScreen, staleValues, stashFlushed, statusNotifierOf, tracking, unflushedStaged, unflushedValue, untrack, verdict, writeHook };
@@ -46,6 +46,8 @@ function notifyEffectStatus(t, e) {
46
46
  return;
47
47
  }
48
48
  if (!globalQueue.notify(this, STATUS_ERROR, STATUS_ERROR)) {
49
+ // Dev locates the halt at the node that threw: the StatusError's
50
+ // source, upstream of this effect when a computed failed.
49
51
  haltReactivity(unwrapStatusError(u));
50
52
  throw u;
51
53
  }
@@ -2,7 +2,7 @@ import { REACTIVE_RECOMPUTING_DEPS, CONFIG_AUTO_DISPOSE, STATUS_PENDING, REACTIV
2
2
 
3
3
  import { deleteFromHeap } from "./heap.js";
4
4
 
5
- import { disposeChildren } from "./owner.js";
5
+ import { disposeChildren, runCleanup } from "./owner.js";
6
6
 
7
7
  import { bumpNotifyEpoch, dirtyQueue } from "./scheduler.js";
8
8
 
@@ -27,7 +27,9 @@ function unlinkSubs(e) {
27
27
  if (o === null) {
28
28
  // Store slot nodes dispatch to the ONE shared hook — no per-node
29
29
  // unobserved closure, no NodeExtension to hold it.
30
- if (n.l & CONFIG_SLOT_NODE) slotUnobservedHook(n); else n.T?.we?.();
30
+ // A user hook that throws is collected like a cleanup (owner.ts): the
31
+ // unlink and the teardown around it carry on.
32
+ if (n.l & CONFIG_SLOT_NODE) slotUnobservedHook(n); else if (n.T?.we) runCleanup(n.T.we);
31
33
  // No more subscribers; only tear down if CONFIG_AUTO_DISPOSE is set.
32
34
  // A pending node is exempt: its in-flight async work is an observer —
33
35
  // tearing down would orphan the work and re-execute it on the next
@@ -141,18 +143,18 @@ function link(e, n) {
141
143
  // [deps.._depsTail] prefix — the O(1) equivalent of scanning the dep list
142
144
  // (the old alien-signals `isValidLink` walk, O(n²) when a computation
143
145
  // re-reads earlier deps non-consecutively, e.g. store leaf reads).
144
- const t = e.ve;
145
- if (t !== null && t.u === n && (!s || t.Te === n.Ne)) return;
146
- const r = n._ = e.ve = {
146
+ const r = e.ve;
147
+ if (r !== null && r.u === n && (!s || r.Te === n.Ne)) return;
148
+ const t = n._ = e.ve = {
147
149
  S: e,
148
150
  u: n,
149
151
  G: o,
150
- Xe: t,
152
+ Xe: r,
151
153
  t: null,
152
154
  Te: n.Ne
153
155
  };
154
- if (l !== null) l.G = r; else n.C = r;
155
- if (t !== null) t.t = r; else e.o = r;
156
+ if (l !== null) l.G = t; else n.C = t;
157
+ if (r !== null) r.t = t; else e.o = t;
156
158
  // New subscriber edge: staged-rewrite skips (§12d) must not miss it.
157
159
  bumpNotifyEpoch();
158
160
  }
@@ -16,7 +16,7 @@ import { dirtyQueue } from "./scheduler.js";
16
16
  }
17
17
 
18
18
  function actualInsertIntoHeap(e, t) {
19
- const E = (e._parent?.je ? e._parent.Ye?.Fe : e._parent?.Fe) ?? -1;
19
+ const E = (e._parent?.Qe ? e._parent.Ye?.Fe : e._parent?.Fe) ?? -1;
20
20
  if (E >= e.Fe) e.Fe = E + 1;
21
21
  const n = e.Fe;
22
22
  const I = t.$e[n];
@@ -435,12 +435,21 @@ function supersede(e, n, s) {
435
435
  * lane's latest. NOT_PENDING falls through: lane work's read of a pending
436
436
  * member throws like any. */ function laneRead(e, n) {
437
437
  const s = n.l & CONFIG_GUESS;
438
+ const t = txOf(n);
439
+ // A lane a correction folded under (D-34): store/optimistic.ts reads it.
440
+ // Before the authoritative arm: an `until` outside the lane reads the
441
+ // same truth there, and joins the hold it folded into — its posture over
442
+ // a foreign hold (D-31), which the truth read alone would not take.
443
+ const l = e !== null && t.folded;
444
+ if (l) {
445
+ const s = l(e, n);
446
+ if (s !== NOT_PENDING) return s;
447
+ }
438
448
  if (e !== null && e.l & CONFIG_AUTHORITATIVE) return GlobalQueue.rn(n);
439
- const t = n.m;
449
+ const u = n.m;
440
450
  // An errored member's lane value predates its error; the error is the answer (#4106).
441
- if (!s && t & STATUS_ERROR) return NOT_PENDING;
451
+ if (!s && u & STATUS_ERROR) return NOT_PENDING;
442
452
  if (e === null) return laneValueOf(n);
443
- const l = txOf(n);
444
453
  if (!tracking || e.l & CONFIG_CHILDREN_FORBIDDEN) {
445
454
  // An untracked read inside lane work derives from the lane like a
446
455
  // tracked one, minus the subscription — `untrack` is about
@@ -449,33 +458,33 @@ function supersede(e, n, s) {
449
458
  // lanes read by one pass link, as for tracked reads). Outside lane
450
459
  // work, the screen.
451
460
  if (passLane === null || e.l & CONFIG_CHILDREN_FORBIDDEN) return display(n);
452
- enterLane(l, e);
453
- if (!s && t & STATUS_PENDING) return NOT_PENDING;
461
+ enterLane(t, e);
462
+ if (!s && u & STATUS_PENDING) return NOT_PENDING;
454
463
  return laneValueOf(n);
455
464
  }
456
- if (e.i === EFFECT_RENDER && (passLane === null || !sameLane(passLane, l))) {
465
+ if (e.i === EFFECT_RENDER && (passLane === null || !sameLane(passLane, t))) {
457
466
  if (!s) {
458
467
  // A flight whose value has never shown: nothing to show — the leaf
459
468
  // waits on it, and its own frame holds (A15, #3334), not the lane's
460
469
  // parent. The landing lands the frame: its own pass (the flush
461
470
  // joined its transaction) reads the answer it waited on — the lane's
462
471
  // staging, revealed at this seam.
463
- if (t & STATUS_PENDING && n.T.v === NOT_PENDING) return NOT_PENDING;
472
+ if (u & STATUS_PENDING && n.T.v === NOT_PENDING) return NOT_PENDING;
464
473
  if (n.se !== NOT_PENDING && e.l & CONFIG_HELD && flushTransaction !== null && resolveTx(flushTransaction) === txOf(e)) return n.se;
465
474
  }
466
- if (l.ln || !s && t & STATUS_PENDING) {
467
- if (!s && !l.ke && t & STATUS_UNINITIALIZED) throw new NotReadyError(null);
475
+ if (t.ln || !s && u & STATUS_PENDING) {
476
+ if (!s && !t.ke && u & STATUS_UNINITIALIZED) throw new NotReadyError(null);
468
477
  // Re-derived at the reveal; once per pass (the pass may also be a stale
469
478
  // reader of a transaction, REACTIVE_FRAME_READ — both reruns apply).
470
479
  if (!(e.R & REACTIVE_SCREEN_READ)) {
471
480
  e.R |= REACTIVE_FRAME_READ | REACTIVE_SCREEN_READ;
472
- l.En.push(e);
481
+ t.En.push(e);
473
482
  }
474
483
  return display(n);
475
484
  }
476
485
  }
477
- enterLane(l, e);
478
- if (!s && t & STATUS_PENDING) return NOT_PENDING;
486
+ enterLane(t, e);
487
+ if (!s && u & STATUS_PENDING) return NOT_PENDING;
479
488
  return laneValueOf(n);
480
489
  }
481
490
 
@@ -598,10 +607,12 @@ function laneSeam(e, n) {
598
607
  // question is the one being asked (`question`: a slice's, a derive's
599
608
  // continuation's — the flight's); unstamped writes and mainline are
600
609
  // current.
601
- if (stale(e, question)) {
610
+ // A store re-bases a keyed arrangement guess over an older truth, and
611
+ // folds a current one beneath it (D-34): true.
612
+ const s = stale(e, question);
613
+ if (GlobalQueue.an?.(e, n, s) || s) {
602
614
  e.se = n;
603
615
  e.l |= CONFIG_HELD;
604
- GlobalQueue.an?.(e, n);
605
616
  schedule();
606
617
  return n;
607
618
  }
@@ -6,7 +6,7 @@ import { unobserved, clearDeps } from "./graph.js";
6
6
 
7
7
  import { deleteFromHeap } from "./heap.js";
8
8
 
9
- import { schedule, dirtyQueue } from "./scheduler.js";
9
+ import { globalQueue, schedule, dirtyQueue } from "./scheduler.js";
10
10
 
11
11
  // L2 (step 2): the parked frame. A pass parks the previous frame's children
12
12
  // and `_disposal` list on `_x._pendingFirstChild` / `_x._pendingDisposal`
@@ -35,17 +35,41 @@ function dispose(e) {
35
35
  unobserved(e);
36
36
  }
37
37
 
38
- function disposeChildren(e, n = false, t) {
39
- const i = e.R;
40
- if (i & REACTIVE_DISPOSED) return;
38
+ // The first error a cleanup threw, boxed so a thrown `undefined` still
39
+ // counts. A throwing cleanup stops neither its siblings nor the rest of the
40
+ // tree: every cleanup runs. The error is rethrown once the teardown is over:
41
+ // by a root's dispose call outside a flush (`disposeRootSelf`), otherwise when
42
+ // the flush ends (`flush`; `runCleanup` schedules one), so a flush is never
43
+ // left half-applied. A halt drops it: the error that halted is the one
44
+ // reported, and a stale one must not surface from a later, unrelated dispose.
45
+ let cleanupError;
46
+
47
+ /** Runs a cleanup or `unobserved` hook; a throw is collected, not raised. @internal */ function runCleanup(e) {
48
+ try {
49
+ e();
50
+ } catch (e) {
51
+ cleanupError ||= [ e ];
52
+ schedule();
53
+ }
54
+ }
55
+
56
+ /** Rethrows the error `runCleanup` collected, once; `drop` discards it. @internal */ function rethrowCleanupError(e) {
57
+ const n = cleanupError;
58
+ cleanupError = undefined;
59
+ if (n && !e) throw n[0];
60
+ }
61
+
62
+ function disposeChildren(e, n = false, r) {
63
+ const t = e.R;
64
+ if (t & REACTIVE_DISPOSED) return;
41
65
  // A parked frame dies with its owner (#3024): the commit that would retire
42
66
  // it never comes once the node is DISPOSED, so it drains here or its
43
67
  // cleanups never run and it stays subscribed, rerunning in a torn-down
44
68
  // tree (#3561). Death only (`self`): a re-pass leaves the frame reacting
45
69
  // until the commit.
46
- if (n && !t && e.T !== null && (e.T.Ue !== null || e.T.me !== null)) disposeChildren(e, false, true);
70
+ if (n && !r && e.T !== null && (e.T.Ue !== null || e.T.me !== null)) disposeChildren(e, false, true);
47
71
  if (n) {
48
- e.R = i | REACTIVE_DISPOSED;
72
+ e.R = t | REACTIVE_DISPOSED;
49
73
  }
50
74
  if (n && e.X) {
51
75
  if (e.T !== null) e.T.Se = null;
@@ -53,10 +77,10 @@ function disposeChildren(e, n = false, t) {
53
77
  // flight's — a transaction's or a lane's (its `_x._transaction`) — or a
54
78
  // stale reader's (REACTIVE_FRAME_READ — a frame that derived from one).
55
79
  // Nothing else re-examines a dead node, so make sure a seam comes.
56
- if (e.m & STATUS_PENDING && e.T?.Re != null || i & REACTIVE_FRAME_READ) schedule();
80
+ if (e.m & STATUS_PENDING && e.T?.Re != null || t & REACTIVE_FRAME_READ) schedule();
57
81
  }
58
- let l = t ? e.T.Ue : e.De;
59
- if (t) e.T.Ue = null; else e.De = null;
82
+ let l = r ? e.T.Ue : e.De;
83
+ if (r) e.T.Ue = null; else e.De = null;
60
84
  while (l) {
61
85
  const e = l;
62
86
  // Owner teardown is death regardless of the child's own lifecycle
@@ -88,34 +112,34 @@ function disposeChildren(e, n = false, t) {
88
112
  // would still be holding.
89
113
  l = l.pe;
90
114
  }
91
- if (!t) e.ye = 0;
115
+ if (!r) e.ye = 0;
92
116
  // O(1) splice out of parent's chain on individual dispose. Skipped during
93
117
  // batch dispose (parent already disposed) and for a zombie (it sits on the
94
118
  // parent's parked frame, drained whole by the commit). We leave
95
119
  // node._nextSibling intact so outer walks that already advanced past us
96
120
  // still reach later siblings.
97
- if (n && !t && !(i & REACTIVE_ZOMBIE) && e._parent !== null && !(e._parent.R & REACTIVE_DISPOSED)) {
121
+ if (n && !r && !(t & REACTIVE_ZOMBIE) && e._parent !== null && !(e._parent.R & REACTIVE_DISPOSED)) {
98
122
  const n = e.Ze;
99
- const t = e.pe;
100
- if (n !== null) n.pe = t; else e._parent.De = t;
101
- if (t !== null) t.Ze = n;
123
+ const r = e.pe;
124
+ if (n !== null) n.pe = r; else e._parent.De = r;
125
+ if (r !== null) r.Ze = n;
102
126
  e.Ze = null;
103
127
  }
104
- runDisposal(e, t);
128
+ runDisposal(e, r);
105
129
  // Final effect-returned cleanup fires at true disposal, after `_disposal`
106
130
  // to mirror rerun ordering (compute-phase teardown first, cleanup last).
107
131
  if (n && e.Je) {
108
132
  const n = e.Je;
109
133
  e.Je = undefined;
110
- n();
134
+ runCleanup(n);
111
135
  }
112
136
  }
113
137
 
114
138
  function linkChild(e, n) {
115
- const t = e.De;
139
+ const r = e.De;
116
140
  n.Ze = null;
117
- n.pe = t;
118
- if (t !== null) t.Ze = n;
141
+ n.pe = r;
142
+ if (r !== null) r.Ze = n;
119
143
  e.De = n;
120
144
  }
121
145
 
@@ -126,27 +150,26 @@ function runDisposal(e, n) {
126
150
  // walk ran every entry a second time. Detached, the re-entrant drain finds
127
151
  // nothing. The same shape latched a throwing cleanup: the list survived the
128
152
  // throw and every later drain re-ran and re-threw it.
129
- const t = n ? e.T.me : e.He;
130
- if (!t) return;
153
+ const r = n ? e.T.me : e.He;
154
+ if (!r) return;
131
155
  if (n) e.T.me = null; else e.He = null;
132
- if (Array.isArray(t)) {
156
+ if (Array.isArray(r)) {
133
157
  // Unwind order (#3572, restores 1.x #1562): later registrations run
134
158
  // before earlier ones. Children have already been disposed by the caller,
135
159
  // so with LIFO a body that registers cleanup before creating its children
136
160
  // tears down after them — the same order a per-component owner gives.
137
- for (let e = t.length - 1; e >= 0; e--) {
138
- const n = t[e];
139
- n.call(n);
161
+ for (let e = r.length - 1; e >= 0; e--) {
162
+ runCleanup(r[e]);
140
163
  }
141
164
  } else {
142
- t.call(t);
165
+ runCleanup(r);
143
166
  }
144
167
  }
145
168
 
146
169
  function childId(e, n) {
147
- let t = e;
148
- while (t.l & CONFIG_TRANSPARENT && t._parent) t = t._parent;
149
- if (t.id != null) return formatId(t.id, n ? t.ye++ : t.ye);
170
+ let r = e;
171
+ while (r.l & CONFIG_TRANSPARENT && r._parent) r = r._parent;
172
+ if (r.id != null) return formatId(r.id, n ? r.ye++ : r.ye);
150
173
  throw new Error("");
151
174
  }
152
175
 
@@ -163,8 +186,8 @@ function childId(e, n) {
163
186
  * The id a freshly-created node inherits: an explicit `options.id` wins;
164
187
  * transparent nodes share their parent's id; otherwise the parent's next
165
188
  * child id is consumed (or `undefined` outside an id-carrying tree).
166
- */ function inheritId(e, n, t) {
167
- return e?.id ?? (n ? t?.id : t?.id != null ? getNextChildId(t) : undefined);
189
+ */ function inheritId(e, n, r) {
190
+ return e?.id ?? (n ? r?.id : r?.id != null ? getNextChildId(r) : undefined);
168
191
  }
169
192
 
170
193
  /**
@@ -177,8 +200,8 @@ function childId(e, n) {
177
200
  }
178
201
 
179
202
  function formatId(e, n) {
180
- const t = n.toString(36), i = t.length - 1;
181
- return e + (i ? String.fromCharCode(64 + i) : "") + t;
203
+ const r = n.toString(36), t = r.length - 1;
204
+ return e + (t ? String.fromCharCode(64 + t) : "") + r;
182
205
  }
183
206
 
184
207
  /**
@@ -253,6 +276,9 @@ function formatId(e, n) {
253
276
 
254
277
  function disposeRootSelf(e = true) {
255
278
  disposeChildren(this, e);
279
+ // Outside a flush the dispose call rethrows; inside one, the flush does.
280
+ // Kept out of `disposeChildren`, whose hot path it slows.
281
+ if (cleanupError && !globalQueue.q) rethrowCleanupError();
256
282
  }
257
283
 
258
284
  /**
@@ -264,22 +290,22 @@ function disposeRootSelf(e = true) {
264
290
  * @internal
265
291
  */ function createOwner(e) {
266
292
  const n = context;
267
- const t = e?.transparent ?? false;
293
+ const r = e?.transparent ?? false;
268
294
  // Prod and observe boilerplates (see core.ts computed()). The observe
269
295
  // literal carries the `_name` and `_loc` slots the rendering layer fills
270
296
  // with the component label and JSX site (`owner._name = "<App>"`,
271
297
  // `owner._loc = "src/App.tsx:3:5"`) — slots, so labelling a root is a
272
298
  // plain store rather than a shape fork between labelled and plain roots.
273
- const i = {
274
- id: inheritId(e, t, n),
275
- l: t ? CONFIG_TRANSPARENT : 0,
299
+ const t = {
300
+ id: inheritId(e, r, n),
301
+ l: r ? CONFIG_TRANSPARENT : 0,
276
302
  // Present from birth: disposeChildren marks death here on roots too, and
277
303
  // a first store of a missing property is a shape transition plus a
278
304
  // properties-store allocation per root — on a 100×4 table teardown that
279
305
  // was 12 KB and most of disposal's time (2026-10-17).
280
306
  R: REACTIVE_NONE,
281
- je: true,
282
- Ye: n?.je ? n.Ye : n,
307
+ Qe: true,
308
+ Ye: n?.Qe ? n.Ye : n,
283
309
  De: null,
284
310
  pe: null,
285
311
  Ze: null,
@@ -290,8 +316,8 @@ function disposeRootSelf(e = true) {
290
316
  _parent: n,
291
317
  dispose: disposeRootSelf
292
318
  };
293
- if (n) linkChild(n, i);
294
- return i;
319
+ if (n) linkChild(n, t);
320
+ return t;
295
321
  }
296
322
 
297
323
  /**
@@ -329,11 +355,11 @@ function disposeRootSelf(e = true) {
329
355
  *
330
356
  * @description https://v2.solidjs.com/reference/solid-js/advanced/owner-introspection/create-root
331
357
  */ function createRoot(e, n) {
332
- const t = createOwner(n);
358
+ const r = createOwner(n);
333
359
  // A bound function, not an arrow: an arrow would share this scope, which
334
360
  // holds `init`, so keeping `dispose` would keep `init` and all it closes
335
361
  // over alive (#4095).
336
- return runWithOwner(t, () => e(disposeRootSelf.bind(t, true)));
362
+ return runWithOwner(r, () => e(disposeRootSelf.bind(r, true)));
337
363
  }
338
364
 
339
- export { cleanup, createOwner, createRoot, dispose, disposeChildren, getNextChildId, getObserver, getOwner, inheritId, isDisposed, linkChild, markDisposal, peekNextChildId };
365
+ export { cleanup, createOwner, createRoot, dispose, disposeChildren, getNextChildId, getObserver, getOwner, inheritId, isDisposed, linkChild, markDisposal, peekNextChildId, rethrowCleanupError, runCleanup };