@solidjs/signals 2.0.0-rc.10 → 2.0.0-rc.11

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.
@@ -1,6 +1,6 @@
1
1
  import { NOT_PENDING, unwrapOverride, STATUS_UNINITIALIZED, REACTIVE_DIRTY, REACTIVE_CHECK, CONFIG_HAS_LANE, OVERRIDE_UNDEFINED, STATUS_PENDING, CONFIG_DERIVED_OVERRIDE, CONFIG_OPTIMISTIC, CONFIG_OVERRIDE_SUPERSEDED, EFFECT_RENDER, CONFIG_AUTHORITATIVE_OBSERVED, REACTIVE_MANUAL_WRITE, REACTIVE_OPTIMISTIC_DIRTY, LANE_RUN, EFFECT_USER } from "./constants.js";
2
2
 
3
- import { ext, hasActiveOverride, stale, currentOptimisticLane, enterStagedRead, latestReadActive } from "./core.js";
3
+ import { ext, hasActiveOverride, stale, currentOptimisticLane, enterStagedRead, setSignal, latestReadActive } from "./core.js";
4
4
 
5
5
  import { NotReadyError } from "./error.js";
6
6
 
@@ -25,17 +25,17 @@ import { GlobalQueue, activeTransition, globalQueue, origin, clock, insertSubs,
25
25
  */
26
26
  /** The optimistic half of setSignal, fired when `_overrideValue !== undefined`. */ function optimisticWrite(e, n) {
27
27
  const i = e.o?.Fe !== NOT_PENDING;
28
- const t = i ? unwrapOverride(e.o?.Fe) : e.ce;
29
- if (typeof n === "function") n = n(t);
30
- const r = !!(e.h & STATUS_UNINITIALIZED) ||
28
+ const r = i ? unwrapOverride(e.o?.Fe) : e.ce;
29
+ if (typeof n === "function") n = n(r);
30
+ const t = !!(e.h & STATUS_UNINITIALIZED) ||
31
31
  // A dirty node's _value is stale (its queued recompute hasn't run — e.g.
32
32
  // a latest() shadow marked by the previous landing's companion snap), so
33
33
  // equality against it must not swallow the write. Without this, a sync
34
34
  // push returning the shadow to that stale value was dropped, the snap
35
35
  // recompute then committed the parent's old value, and the banner showed
36
36
  // the previous transition's target (#3041 follow-up).
37
- !!((e.oe ?? 0) & (REACTIVE_DIRTY | REACTIVE_CHECK)) || !e.xe || !e.xe(t, n);
38
- if (!r) {
37
+ !!((e.oe ?? 0) & (REACTIVE_DIRTY | REACTIVE_CHECK)) || !e.xe || !e.xe(r, n);
38
+ if (!t) {
39
39
  // Same-value write with an active override still entangles the current
40
40
  // action's transition — the hold must outlast all overlapping actions —
41
41
  // and renews the override's PROVENANCE: the newer action re-asks the
@@ -162,7 +162,7 @@ function resolveOptimisticNodes(e) {
162
162
  // override-covered authoritative values hold in _pendingValue and
163
163
  // elevate on their OWN transition's schedule (A18 as re-ruled 2026-07-07).
164
164
  if (!(n.h & STATUS_PENDING)) n.h &= ~STATUS_UNINITIALIZED;
165
- const t = n.o?.Fe;
165
+ const r = n.o?.Fe;
166
166
  // A derived override (lanes stage, #3479) has no truth of its own: the
167
167
  // slot disarms — the memo is plain again — and the override PROMOTES to
168
168
  // `_value`. Not superseded, nothing it derives from told it otherwise
@@ -172,15 +172,15 @@ function resolveOptimisticNodes(e) {
172
172
  // the override IS what a recompute from the truth yields. Re-deriving
173
173
  // instead re-asked an async member's flight and held the transaction on
174
174
  // it — a waterfall after the reveal.
175
- const r = n.C & CONFIG_DERIVED_OVERRIDE;
176
- ext(n).Fe = r && !(n.C & CONFIG_OPTIMISTIC) ? undefined : NOT_PENDING;
175
+ const t = n.C & CONFIG_DERIVED_OVERRIDE;
176
+ ext(n).Fe = t && !(n.C & CONFIG_OPTIMISTIC) ? undefined : NOT_PENDING;
177
177
  // A superseded override's subscribers already re-derived from the truth
178
178
  // when it arrived (#3331) — the drop changes nothing they read. Everyone
179
179
  // else learns of the correction here: this drop IS their notification.
180
180
  const u = (n.C & CONFIG_OVERRIDE_SUPERSEDED) !== 0;
181
181
  n.C &= -8912897;
182
- if (!u && t !== NOT_PENDING && n.ce !== unwrapOverride(t)) {
183
- if (r) n.ce = unwrapOverride(t); else {
182
+ if (!u && r !== NOT_PENDING && n.ce !== unwrapOverride(r)) {
183
+ if (t) n.ce = unwrapOverride(r); else {
184
184
  insertSubs(n, true);
185
185
  }
186
186
  }
@@ -192,9 +192,9 @@ function resolveOptimisticNodes(e) {
192
192
  // transition that produced them (A19 — pending is a property of the data).
193
193
  for (let i = 0; i < n; i++) {
194
194
  const n = e[i];
195
- if (n.o?.Ze || n.o?.He) GlobalQueue.ti(n);
196
- const t = n.o?.kn;
197
- if (t && (t.o?.Ze === n || t.o?.He === n)) GlobalQueue.ti(t);
195
+ if (n.o?.Ze || n.o?.He) GlobalQueue.ri(n);
196
+ const r = n.o?.kn;
197
+ if (r && (r.o?.Ze === n || r.o?.He === n)) GlobalQueue.ri(r);
198
198
  }
199
199
  e.splice(0, n);
200
200
  }
@@ -248,14 +248,14 @@ function resolveOptimisticNodes(e) {
248
248
  const n = e.o?.me;
249
249
  if (n) {
250
250
  const i = findLane(n);
251
- const t = [ e ];
252
- while (t.length) {
253
- const e = t.pop();
251
+ const r = [ e ];
252
+ while (r.length) {
253
+ const e = r.pop();
254
254
  const n = e.o?.me;
255
255
  if (!n || findLane(n) !== i) continue;
256
256
  e.o.me = undefined;
257
257
  i.Qe.delete(e);
258
- for (let n = e.u; n !== null; n = n.Ae) t.push(n.ge);
258
+ for (let n = e.u; n !== null; n = n.Ae) r.push(n.ge);
259
259
  }
260
260
  }
261
261
  }
@@ -297,7 +297,7 @@ function resolveOptimisticNodes(e) {
297
297
  * the optimistic path so it flushes ahead of the hold — but they answer for
298
298
  * their owner and snap at settlement (`_snapCompanions`), not here.
299
299
  */ function endOptimism(e) {
300
- if (!e.Te || e.pe.length || !e.cn.length || e.ri.size || transitionBlocked(e)) return false;
300
+ if (!e.Te || e.pe.length || !e.cn.length || e.ti.size || transitionBlocked(e)) return false;
301
301
  for (const n of e.le.keys()) if (sourceObserved(e, n, e) && n.o?.ue?.has(n) && !resolveLane(n)) return false;
302
302
  let n = false;
303
303
  for (const i of e.cn) {
@@ -393,7 +393,49 @@ function resolveOptimisticNodes(e) {
393
393
  * action provenance, or an A17-silent confirmation (#3331). Before this the
394
394
  * landing took setSignal's plain path: a differing truth staged silently
395
395
  * under the override and the graph never moved until the commit.
396
+ *
397
+ * Slot arm (list-matrix F1): `mapArray`'s writes to its per-slot signals —
398
+ * the row accessors of index mode, the index accessors of keyed mode; never
399
+ * CONFIG_OPTIMISTIC, which is how the arm tells them from the store landings
400
+ * above — once a lane pass has run over the map. The list's frame lives in
401
+ * these writes as much as in the computed's result. Under a LANE pass the
402
+ * write is the lane's frame, as the computed's result is (lanes stage,
403
+ * `laneOverride`): the slot joins the lane carrying a DERIVED override —
404
+ * `_value` stays the committed row, the lane's readers and untracked reads
405
+ * see the override (A17), an off-lane render effect sees the committed frame
406
+ * (#3460), the revert promotes or drops it with the lane's transaction. The
407
+ * gate compares against the slot the pass publishes to (INV-11): the override
408
+ * when armed, the committed row otherwise. A plain `setSignal` here staged
409
+ * the write into the ACTION's transaction: `<For>` without `keyed` showed the
410
+ * pre-action list for the whole action while the keyed modes showed the
411
+ * optimistic one. A PLAIN pass over a slot still carrying the lane's frame —
412
+ * the landing, the reversion, a mainline re-pass — is the truth arriving
413
+ * under an override and takes the landing path below: a differing truth
414
+ * supersedes, an equal one confirms and the revert promotes. Every pass
415
+ * rewrites every slot whose value differs from the previous frame's (index
416
+ * mode rewrites every surviving slot), so a slot the landing does NOT write
417
+ * holds, by construction, what the truth yields. A slot with neither a lane
418
+ * nor an override is the plain write.
396
419
  */ function landOnOverride(e, n) {
420
+ if (!(e.C & CONFIG_OPTIMISTIC)) {
421
+ const i = currentOptimisticLane;
422
+ if (i === null) {
423
+ if (!hasActiveOverride(e)) return setSignal(e, n);
424
+ } else {
425
+ // INV-11: the gate compares against the slot this pass publishes to —
426
+ // the override when one is armed, the committed row otherwise.
427
+ if (!e.xe || !e.xe(hasActiveOverride(e) ? unwrapOverride(e.o.Fe) : e.ce, n)) {
428
+ // Membership before the publish: `laneOverride` files the slot under
429
+ // the lane's transaction, and the read path routes a tracked reader
430
+ // of a lane member through `overrideRead` (CONFIG_HAS_LANE).
431
+ assignOrMergeLane(e, i);
432
+ laneOverride(e, n, i);
433
+ insertSubs(e, true);
434
+ schedule();
435
+ }
436
+ return n;
437
+ }
438
+ }
397
439
  const i = e._e === NOT_PENDING ? e.ce : e._e;
398
440
  if (typeof n === "function") n = n(i);
399
441
  if (e._e === NOT_PENDING) queuePendingNode(e);
@@ -583,7 +625,7 @@ function cleanupCompletedLanes(e) {
583
625
 
584
626
  function trackOptimisticStore(e) {
585
627
  // After initTransition, globalQueue._batch IS activeTransition (same reference)
586
- globalQueue.m.ri.add(e);
628
+ globalQueue.m.ti.add(e);
587
629
  schedule();
588
630
  }
589
631
 
@@ -58,13 +58,13 @@ function disposeChildren(e, n = false, t) {
58
58
  // edge). Snap runs after the DISPOSED flag is set so the oracle reads
59
59
  // false, and notifies subscribers still watching the companion.
60
60
  const n = e;
61
- if (n.o?.Ze || n.o?.He) GlobalQueue.ti(n);
61
+ if (n.o?.Ze || n.o?.He) GlobalQueue.ri(n);
62
62
  // A firewall's leaves have no lifecycle of their own, so a companion on
63
63
  // one of them outlives its source the same way (INV-9's rationale). The
64
64
  // firewall knows which leaves carry companions (CONFIG_CHILD_COMPANIONS,
65
65
  // #3038): snap them with it — the snap retires a shadow whose firewall is
66
66
  // disposed (spec O5).
67
- if (n.C & CONFIG_CHILD_COMPANIONS) n.o.Un.forEach(GlobalQueue.ti);
67
+ if (n.C & CONFIG_CHILD_COMPANIONS) n.o.Un.forEach(GlobalQueue.ri);
68
68
  // A pending reader parked in a transaction may be the only thing holding
69
69
  // it (#3372): its death is a completion event the transaction must be
70
70
  // re-judged for, and nothing else re-enters a parked transaction.
@@ -100,7 +100,7 @@ const transientStoreNodes = new Set;
100
100
 
101
101
  function canUseSimpleSyncFlush(e) {
102
102
  const t = e.m;
103
- return transitions.size === 0 && activeLanes.size === 0 && e.ft.length === 0 && t.cn.length === 0 && t.A.length === 0 && t.ri.size === 0 && transientStoreNodes.size === 0 && pendingRearms.size === 0;
103
+ return transitions.size === 0 && activeLanes.size === 0 && e.ft.length === 0 && t.cn.length === 0 && t.A.length === 0 && t.ti.size === 0 && transientStoreNodes.size === 0 && pendingRearms.size === 0;
104
104
  }
105
105
 
106
106
  function sweepTransientStoreNodes() {
@@ -149,7 +149,7 @@ function setProjectionWriteActive(e) {
149
149
  le: new Map,
150
150
  cn: [],
151
151
  A: [],
152
- ri: new Set,
152
+ ti: new Set,
153
153
  pe: [],
154
154
  dt: {
155
155
  Tt: [ [], [] ],
@@ -180,7 +180,7 @@ function mergeTransitionState(e, t) {
180
180
  e.A.push(...t.A);
181
181
  t.A.length = 0;
182
182
  }
183
- for (const n of t.ri) e.ri.add(n);
183
+ for (const n of t.ti) e.ti.add(n);
184
184
  for (const [n, i] of t.le) {
185
185
  let t = e.le.get(n);
186
186
  if (!t) e.le.set(n, t = new Set);
@@ -547,7 +547,7 @@ class GlobalQueue extends Queue {
547
547
  static Ve=null;
548
548
  static Ge=null;
549
549
  static We=null;
550
- static ti=null;
550
+ static ri=null;
551
551
  static et=null;
552
552
  static nt=null;
553
553
  static rt=null;
@@ -625,7 +625,11 @@ class GlobalQueue extends Queue {
625
625
  * commit whatever its relation to the committed value — a landing equal to
626
626
  * committed still differs from the override — then _supersedeOverride
627
627
  * decides. Installed with the optimistic engine; only reachable on a node
628
- * that has an override. */
628
+ * that has an override — or, from `mapArray` once a lane pass has run over
629
+ * the map, on a per-slot signal (never CONFIG_OPTIMISTIC): the slot arm
630
+ * publishes a lane pass's write as the slot's derived override, lands a
631
+ * plain pass's write over one, and is the plain `setSignal` otherwise (F1,
632
+ * see optimistic.ts landOnOverride). */
629
633
  static st=null;
630
634
  static fi=null;
631
635
  flush() {
@@ -751,7 +755,7 @@ class GlobalQueue extends Queue {
751
755
  e.Gn = n.Gn;
752
756
  e.cn = n.cn;
753
757
  e.A = n.A;
754
- e.ri = n.ri;
758
+ e.ti = n.ti;
755
759
  currentBatch = this.m = e;
756
760
  }
757
761
  } else {
@@ -787,8 +791,13 @@ class GlobalQueue extends Queue {
787
791
  // sweep or recompute wrote a node it owns): effects computed under it
788
792
  // since are its to apply, not this flush's — runEffect leaves them queued
789
793
  // and the next pass parks them with it (#3319). Everything computed
790
- // mainline applies now.
791
- scheduled = dirtyQueue.EE >= dirtyQueue.ln || activeTransition !== null;
794
+ // mainline applies now. A write the finalize staged in the ambient
795
+ // batch with no subscriber to dirty (an optimistic store settle's
796
+ // keyset bump under a reader that never tracks the key set) is work
797
+ // too — the fast drain and the park exit already count it — so the
798
+ // next round commits it and the woken re-entry below does not adopt
799
+ // it into a parked transaction it never belonged to (matrix F6).
800
+ scheduled = dirtyQueue.EE >= dirtyQueue.ln || activeTransition !== null || this.m.Gn.length !== 0;
792
801
  // Run lane effects first (for ready lanes), then regular effects
793
802
  activeLanes.size && GlobalQueue.si(EFFECT_RENDER);
794
803
  this.run(EFFECT_RENDER);
@@ -802,9 +811,14 @@ class GlobalQueue extends Queue {
802
811
  // idle pass: entering adopts the ambient batch, and staged or dirty
803
812
  // ambient work would be held behind flights it never read. `scheduled`
804
813
  // is that test here — after the park exit as well as the normal one:
805
- // it was recomputed from the heap this pass, every write since re-armed
806
- // it, and optimistic ambient nodes reverted with the finalize — so a
807
- // wake in a pass with work simply falls to the next. Entering re-arms
814
+ // it was recomputed from the heap and the ambient batch's staged nodes
815
+ // this pass, every write since re-armed it, and optimistic ambient
816
+ // nodes reverted with the finalize — so a wake in a pass with work
817
+ // simply falls to the next. (A staged node with no subscriber — the
818
+ // finalize's keyset bump under a length-only reader — used to be
819
+ // missed here: the wake adopted it, stamped it, and a later ambient
820
+ // write to the same node joined the parked transaction and never
821
+ // reverted; matrix F6.) Entering re-arms
808
822
  // it itself; a dead (completed) wake is a bare return in
809
823
  // initTransition, and the loop moves on to the next.
810
824
  while (!scheduled && !activeTransition && wokenTransitions.length) this.initTransition(wokenTransitions.pop());
@@ -924,7 +938,7 @@ class GlobalQueue extends Queue {
924
938
  activeTransition.cn.push(n);
925
939
  }
926
940
  if (t.A.length) activeTransition.A.push(...t.A);
927
- for (const e of t.ri) activeTransition.ri.add(e);
941
+ for (const e of t.ti) activeTransition.ti.add(e);
928
942
  // Gated readers recorded against the ambient batch move with it: their
929
943
  // replay-at-commit now happens at the transaction's completion.
930
944
  if (t.In.size) {
@@ -1043,7 +1057,7 @@ function commitPendingNode(e) {
1043
1057
  e.ce = e._e;
1044
1058
  e._e = NOT_PENDING;
1045
1059
  }
1046
- if (e.C & CONFIG_HAS_COMPANIONS) GlobalQueue.ti(e);
1060
+ if (e.C & CONFIG_HAS_COMPANIONS) GlobalQueue.ri(e);
1047
1061
  return;
1048
1062
  }
1049
1063
  if (e._e !== NOT_PENDING) {
@@ -1081,7 +1095,7 @@ function commitPendingNode(e) {
1081
1095
  // against the frame. read()'s reveal carve-out keys on the mark (#3305).
1082
1096
  else e.C |= CONFIG_INPUTS_PUBLISHED;
1083
1097
  if (t.o != null && (t.o._n !== null || t.o.fn !== null)) GlobalQueue.en(t, false, true);
1084
- if (e.C & CONFIG_HAS_COMPANIONS) GlobalQueue.ti(e);
1098
+ if (e.C & CONFIG_HAS_COMPANIONS) GlobalQueue.ri(e);
1085
1099
  }
1086
1100
 
1087
1101
  // Store commit hook (INTERNALS-STORE-STATE.md §3): installed by the store
@@ -1213,7 +1227,7 @@ function finalizePureQueue(e = null, t = false) {
1213
1227
  // hook; the hook iterates, clears, and schedules (keeping the loop out of
1214
1228
  // core lets esbuild shake it — rollup already folds the null guard). The
1215
1229
  // completing transition scopes the clear to its own layer keys (#2899).
1216
- if (t.ri.size) GlobalQueue._t(t.ri, e);
1230
+ if (t.ti.size) GlobalQueue._t(t.ti, e);
1217
1231
  // Held-truth reveal wake (#3164), post-revert by construction: this
1218
1232
  // finalize committed confirming truth whose subscribers were masked all
1219
1233
  // hold — some re-derived against the committed view (the staging, or a
@@ -655,7 +655,7 @@ GlobalQueue.Ge = updatePendingSignal;
655
655
 
656
656
  GlobalQueue.We = updateChildCompanions;
657
657
 
658
- GlobalQueue.ti = snapCompanionsToState;
658
+ GlobalQueue.ri = snapCompanionsToState;
659
659
 
660
660
  GlobalQueue.et = latestRead;
661
661