@solidjs/signals 2.0.0-beta.22 → 2.0.0-beta.24

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.
@@ -32,15 +32,40 @@ const STORE_VALUE = "v", STORE_OVERRIDE = "o", STORE_OPTIMISTIC_OVERRIDE = "x",
32
32
 
33
33
  const STORE_SELF_PENDING = Symbol(0);
34
34
 
35
+ // Every StoreNode field is initialized up front, in one fixed order, so all
36
+ // targets share a single hidden class. The traps and reconcile read these
37
+ // fields on their hottest paths; fields added lazily in varying orders made
38
+ // those loads megamorphic across a large store graph (dictionary-mode probes
39
+ // on every trap hit). Assignments elsewhere must never `delete` a field —
40
+ // write `undefined` instead, or the shape degrades again.
41
+ function initStoreFields(e) {
42
+ e[STORE_OVERRIDE] = undefined;
43
+ e[STORE_OPTIMISTIC_OVERRIDE] = undefined;
44
+ e[STORE_OPTIMISTIC_OWNERS] = undefined;
45
+ e[STORE_NODE] = undefined;
46
+ e[STORE_HAS] = undefined;
47
+ e[STORE_CUSTOM_PROTO] = undefined;
48
+ e[STORE_WRAP] = undefined;
49
+ e[STORE_LOOKUP] = undefined;
50
+ e[STORE_FIREWALL] = undefined;
51
+ e[STORE_OPTIMISTIC] = undefined;
52
+ e[STORE_SNAPSHOT_PROPS] = undefined;
53
+ e[STORE_PARENT] = undefined;
54
+ e[STORE_DESC] = undefined;
55
+ e[$PROXY] = null;
56
+ }
57
+
35
58
  function createStoreProxy(e, t = storeTraps, r) {
36
59
  let n;
37
60
  if (Array.isArray(e)) {
38
61
  n = [];
39
- n.v = e;
62
+ n[STORE_VALUE] = e;
63
+ initStoreFields(n);
40
64
  } else {
41
65
  n = {
42
- v: e
66
+ [STORE_VALUE]: e
43
67
  };
68
+ initStoreFields(n);
44
69
  const t = e?.[$TARGET]?.[STORE_VALUE] ?? e;
45
70
  const r = Object.getPrototypeOf(t);
46
71
  if (r !== null && r !== Object.prototype) {
@@ -97,17 +122,17 @@ function unwrapStoreValue(e, t, r) {
97
122
  if (!t) t = new Map;
98
123
  if (t.has(e)) return t.get(e);
99
124
  const i = n[STORE_VALUE];
100
- const s = Array.isArray(i);
101
- const E = s ? [] : Object.create(Object.getPrototypeOf(i));
102
- t.set(e, E);
125
+ const E = Array.isArray(i);
126
+ const O = E ? [] : Object.create(Object.getPrototypeOf(i));
127
+ t.set(e, O);
103
128
  r = n[STORE_LOOKUP] ?? storeLookup;
104
129
  for (const e of getStoreKeys(i, o)) {
105
- if (s && e === "length") continue;
130
+ if (E && e === "length") continue;
106
131
  const n = e in o ? o[e] : i[e];
107
- if (n !== $DELETED) E[e] = unwrapStoreValue(n, t, r);
132
+ if (n !== $DELETED) O[e] = unwrapStoreValue(n, t, r);
108
133
  }
109
- if (s) E.length = o.length ?? i.length;
110
- return E;
134
+ if (E) O.length = o.length ?? i.length;
135
+ return O;
111
136
  }
112
137
 
113
138
  function isPrototypePollutionKey(e) {
@@ -154,7 +179,7 @@ function ownEnumerableKeysPlain(e) {
154
179
  * The value a store leaf's backing signal currently shows to readers: active
155
180
  * override, else held pending value, else committed value.
156
181
  */ function visibleNodeValue(e) {
157
- return e.Ee !== undefined && e.Ee !== NOT_PENDING ? unwrapOverride(e.Ee) : e.Ne !== NOT_PENDING ? e.Ne : e.Re;
182
+ return e.Ee !== undefined && e.Ee !== NOT_PENDING ? unwrapOverride(e.Ee) : e.Ne !== NOT_PENDING ? e.Ne : e.Pe;
158
183
  }
159
184
 
160
185
  function hasOwnStoreProperty(e, t) {
@@ -182,10 +207,10 @@ function getNodes(e, t) {
182
207
 
183
208
  function getNode(e, t, r, n, o = isEqual, i) {
184
209
  if (t[r]) return t[r];
185
- const s = signal(n, {
210
+ const E = signal(n, {
186
211
  equals: o,
187
212
  unobserved() {
188
- if (t[r] === s) {
213
+ if (t[r] === E) {
189
214
  delete t[r];
190
215
  // Drop the symbol-record mark once the last user symbol node is
191
216
  // gone, so reconcile's fast path stops probing a now string-only
@@ -205,28 +230,28 @@ function getNode(e, t, r, n, o = isEqual, i) {
205
230
  }
206
231
  }, e[STORE_FIREWALL]);
207
232
  if (e[STORE_OPTIMISTIC]) {
208
- s.Ee = NOT_PENDING;
233
+ E.Ee = NOT_PENDING;
209
234
  }
210
235
  if (i && r in i) {
211
236
  const e = i[r];
212
- s.Ve = e === undefined ? NO_SNAPSHOT : e;
213
- snapshotSources?.add(s);
237
+ E.Ve = e === undefined ? NO_SNAPSHOT : e;
238
+ snapshotSources?.add(E);
214
239
  }
215
240
  if (typeof r === "symbol" && r !== $TRACK && r !== $AFFECTS) symbolKeyedRecords.add(t);
216
241
  // A node born inside a live mark's identity scope inherits the mark
217
242
  // (the declaration walk could only cover nodes that existed then). The
218
243
  // record's own $AFFECTS carrier is the mark's channel, never a member.
219
- if (r !== $AFFECTS && affectsScopes.size) inheritAffectsMarks(s, e[STORE_VALUE], r);
244
+ if (r !== $AFFECTS && affectsScopes.size) inheritAffectsMarks(E, e[STORE_VALUE], r);
220
245
  // Node presence bubbles up the wrap chain (sticky), so reconcile can see
221
246
  // "subscribers live somewhere below" through node-less intermediate
222
247
  // records — the captured-proxy diff gate (#2902). Amortized O(1): stops at
223
248
  // the first already-flagged ancestor.
224
- let E = e;
225
- while (E && !E[STORE_DESC]) {
226
- E[STORE_DESC] = true;
227
- E = E[STORE_PARENT];
249
+ let O = e;
250
+ while (O && !O[STORE_DESC]) {
251
+ O[STORE_DESC] = true;
252
+ O = O[STORE_PARENT];
228
253
  }
229
- return t[r] = s;
254
+ return t[r] = E;
230
255
  }
231
256
 
232
257
  /**
@@ -263,42 +288,42 @@ const affectsScopes = new Map;
263
288
  o) {
264
289
  if (!isWrappable(e)) return;
265
290
  const i = e[$TARGET] || (n ?? storeLookup).get(e)?.[$TARGET];
266
- const s = i ? i[STORE_VALUE] : e;
267
- if (o.has(s)) return;
268
- o.add(s);
269
- t.scope.add(s);
270
- let E;
291
+ const E = i ? i[STORE_VALUE] : e;
292
+ if (o.has(E)) return;
293
+ o.add(E);
294
+ t.scope.add(E);
295
+ let O;
271
296
  if (i) {
272
297
  collectRecordNodes(i[STORE_NODE], r);
273
298
  collectRecordNodes(i[STORE_HAS], r);
274
- E = mergedOverlay(i);
299
+ O = mergedOverlay(i);
275
300
  // Carry the effective lookup into untouched descendants. Default stores
276
301
  // use the global lookup just like snapshotImpl; without it, nested raw
277
302
  // objects fall back to string-only enumeration and symbol branches vanish.
278
303
  n = i[STORE_LOOKUP] ?? n ?? storeLookup;
279
304
  }
280
- if (Array.isArray(s)) {
281
- const e = E?.length ?? s.length;
305
+ if (Array.isArray(E)) {
306
+ const e = O?.length ?? E.length;
282
307
  for (let i = 0; i < e; i++) {
283
- const e = E && i in E ? E[i] : s[i];
308
+ const e = O && i in O ? O[i] : E[i];
284
309
  if (e !== $DELETED) walkAffectsScope(e, t, r, n, o);
285
310
  }
286
311
  // Arrays can also carry symbol metadata. Enumerate symbols separately to
287
312
  // avoid scanning large index lists twice. Outside a store tree, retain the
288
313
  // existing index-only walk.
289
- const O = i || n ? getStoreSymbols(s, E) : [];
290
- for (let e = 0, i = O.length; e < i; e++) {
291
- const i = O[e];
292
- const c = getPropertyDescriptor(s, E, i);
293
- if (!c || c.get) continue;
294
- walkAffectsScope(c.value, t, r, n, o);
314
+ const s = i || n ? getStoreSymbols(E, O) : [];
315
+ for (let e = 0, i = s.length; e < i; e++) {
316
+ const i = s[e];
317
+ const S = getPropertyDescriptor(E, O, i);
318
+ if (!S || S.get) continue;
319
+ walkAffectsScope(S.value, t, r, n, o);
295
320
  }
296
321
  } else {
297
- const e = i || n ? getStoreKeys(s, E) : getKeys(s, E);
298
- for (let i = 0, O = e.length; i < O; i++) {
299
- const O = getPropertyDescriptor(s, E, e[i]);
300
- if (!O || O.get) continue;
301
- walkAffectsScope(O.value, t, r, n, o);
322
+ const e = i || n ? getStoreKeys(E, O) : getKeys(E, O);
323
+ for (let i = 0, s = e.length; i < s; i++) {
324
+ const s = getPropertyDescriptor(E, O, e[i]);
325
+ if (!s || s.get) continue;
326
+ walkAffectsScope(s.value, t, r, n, o);
302
327
  }
303
328
  }
304
329
  }
@@ -489,10 +514,10 @@ function prepareStoreWrite(e, t, r) {
489
514
  }
490
515
  }
491
516
  const i = e[STORE_OPTIMISTIC] && !projectionWriteActive;
492
- const s = i ? STORE_OPTIMISTIC_OVERRIDE : STORE_OVERRIDE;
517
+ const E = i ? STORE_OPTIMISTIC_OVERRIDE : STORE_OVERRIDE;
493
518
  return {
494
519
  base: o,
495
- overrideKey: s,
520
+ overrideKey: E,
496
521
  state: n
497
522
  };
498
523
  }
@@ -526,42 +551,42 @@ function prepareStoreWrite(e, t, r) {
526
551
  function upsertStoreNode(e, t, r, n, o) {
527
552
  if (t[r]) return t[r];
528
553
  const i = isWrappable(n) ? wrap(n, e) : n;
529
- const s = getNode(e, t, r, i, isEqual, o);
530
- registerTransientStoreNode(s);
531
- return s;
554
+ const E = getNode(e, t, r, i, isEqual, o);
555
+ registerTransientStoreNode(E);
556
+ return E;
532
557
  }
533
558
 
534
559
  function notifyStoreProperty(e, t, r, n, o, i) {
535
560
  // Cold writes upsert a transient pending node so untracked reads batch like signals.
536
561
  // Skip for projection writes (different commit semantics) and for optimistic stores
537
562
  // (whose whole purpose is immediate visibility via STORE_OPTIMISTIC_OVERRIDE).
538
- const s = projectionWriteActive || e[STORE_OPTIMISTIC];
539
- const E = r !== "delete";
540
- const O = e[STORE_HAS]?.[t];
541
- if (O) {
542
- setSignal(O, E);
543
- } else if (!s && r !== "invalidate" && i !== E) {
563
+ const E = projectionWriteActive || e[STORE_OPTIMISTIC];
564
+ const O = r !== "delete";
565
+ const s = e[STORE_HAS]?.[t];
566
+ if (s) {
567
+ setSignal(s, O);
568
+ } else if (!E && r !== "invalidate" && i !== O) {
544
569
  const r = upsertStoreNode(e, getNodes(e, STORE_HAS), t, i);
545
- setSignal(r, E);
570
+ setSignal(r, O);
546
571
  }
547
- const c = getNodes(e, STORE_NODE);
572
+ const S = getNodes(e, STORE_NODE);
548
573
  if (r === "set") {
549
- if (c[t]) {
550
- setSignal(c[t], () => isWrappable(n) ? wrap(n, e) : n);
551
- } else if (!s) {
552
- const r = upsertStoreNode(e, c, t, o, e[STORE_SNAPSHOT_PROPS]);
574
+ if (S[t]) {
575
+ setSignal(S[t], () => isWrappable(n) ? wrap(n, e) : n);
576
+ } else if (!E) {
577
+ const r = upsertStoreNode(e, S, t, o, e[STORE_SNAPSHOT_PROPS]);
553
578
  setSignal(r, () => isWrappable(n) ? wrap(n, e) : n);
554
579
  }
555
580
  } else if (r === "invalidate") {
556
- if (c[t]) {
557
- setSignal(c[t], {});
558
- delete c[t];
581
+ if (S[t]) {
582
+ setSignal(S[t], {});
583
+ delete S[t];
559
584
  }
560
585
  } else {
561
- if (c[t]) {
562
- setSignal(c[t], undefined);
563
- } else if (!s) {
564
- const r = upsertStoreNode(e, c, t, o, e[STORE_SNAPSHOT_PROPS]);
586
+ if (S[t]) {
587
+ setSignal(S[t], undefined);
588
+ } else if (!E) {
589
+ const r = upsertStoreNode(e, S, t, o, e[STORE_SNAPSHOT_PROPS]);
565
590
  setSignal(r, undefined);
566
591
  }
567
592
  }
@@ -593,50 +618,65 @@ const storeTraps = {
593
618
  trackSelf(e);
594
619
  return r;
595
620
  }
621
+ // Hot path: an existing node on a plain target — no firewall (so neither
622
+ // selfRead nor the uninitialized guard can apply), no override layers,
623
+ // no write scope, and a raw (non-proxy) source. This is the shape of
624
+ // every effect re-read of a settled store; it pays one node read and the
625
+ // wrap check, nothing else. Any exotic bit falls through to the full
626
+ // resolution below, and dev strictRead keeps its warning path.
627
+ if (e[STORE_FIREWALL] === undefined && e[STORE_OVERRIDE] === undefined && e[STORE_OPTIMISTIC_OVERRIDE] === undefined && !writeOverride && (Writing === null || !Writing.has(r))) {
628
+ const r = e[STORE_NODE];
629
+ const n = r && r[t];
630
+ if (n !== undefined && e[STORE_VALUE][$TARGET] === undefined) {
631
+ let t = read(n);
632
+ if (t === $DELETED) t = undefined;
633
+ return isWrappable(t) ? wrap(t, e) : t;
634
+ }
635
+ }
596
636
  const n = getObserver() === e[STORE_FIREWALL];
597
637
  const o = getNodes(e, STORE_NODE);
598
638
  const i = n ? undefined : o[t];
599
- const s = e[STORE_VALUE];
600
- if (!i && !e[STORE_OVERRIDE] && !e[STORE_OPTIMISTIC_OVERRIDE] && !e[STORE_CUSTOM_PROTO] && !e[STORE_OPTIMISTIC] && !e[STORE_SNAPSHOT_PROPS] && !s[$TARGET] && !(t in s) && getObserver() && !n && !writeOnly(r)) {
639
+ const E = e[STORE_VALUE];
640
+ if (!i && !e[STORE_OVERRIDE] && !e[STORE_OPTIMISTIC_OVERRIDE] && !e[STORE_CUSTOM_PROTO] && !e[STORE_OPTIMISTIC] && !e[STORE_SNAPSHOT_PROPS] && !E[$TARGET] && !(t in E) && getObserver() && !n && !writeOnly(r)) {
601
641
  return read(getNode(e, o, t, undefined));
602
642
  }
603
- const E = getOverlayLayer(e, t);
604
- const O = !!E;
605
- const c = !!e[STORE_VALUE][$TARGET];
606
- const S = E ?? e[STORE_VALUE];
643
+ const O = getOverlayLayer(e, t);
644
+ const s = !!O;
645
+ const S = !!e[STORE_VALUE][$TARGET];
646
+ const c = O ?? e[STORE_VALUE];
607
647
  if (!i) {
608
- const n = Object.getOwnPropertyDescriptor(S, t);
648
+ const n = Object.getOwnPropertyDescriptor(c, t);
609
649
  if (n && n.get) return n.get.call(r);
610
- if (!n && !O && e[STORE_CUSTOM_PROTO]) {
611
- const e = unwrapStoreValue(S);
650
+ if (!n && !s && e[STORE_CUSTOM_PROTO]) {
651
+ const e = unwrapStoreValue(c);
612
652
  if (hasInheritedAccessor(e, t)) {
613
- return Reflect.get(S, t, r);
653
+ return Reflect.get(c, t, r);
614
654
  }
615
655
  }
616
656
  }
617
657
  if (writeOnly(r)) {
618
658
  if (isPrototypePollutionKey(t) && !hasOwnStoreProperty(e, t)) return undefined;
619
- let r = i && (O || !c) ? visibleNodeValue(i) : S[t];
659
+ let r = i && (s || !S) ? visibleNodeValue(i) : c[t];
620
660
  r === $DELETED && (r = undefined);
621
661
  if (!isWrappable(r)) return r;
622
662
  const n = wrap(r, e);
623
663
  Writing?.add(n);
624
664
  return n;
625
665
  }
626
- let f = i ? O || !c ? read(o[t]) : (read(o[t]), S[t]) : S[t];
627
- f === $DELETED && (f = undefined);
666
+ let T = i ? s || !S ? read(o[t]) : (read(o[t]), c[t]) : c[t];
667
+ T === $DELETED && (T = undefined);
628
668
  if (!i) {
629
- if (!O && typeof f === "function" && !Object.prototype.hasOwnProperty.call(S, t)) {
669
+ if (!s && typeof T === "function" && !Object.prototype.hasOwnProperty.call(c, t)) {
630
670
  let t;
631
- return !Array.isArray(e[STORE_VALUE]) && (t = Object.getPrototypeOf(e[STORE_VALUE])) && t !== Object.prototype ? f.bind(S) : f;
671
+ return !Array.isArray(e[STORE_VALUE]) && (t = Object.getPrototypeOf(e[STORE_VALUE])) && t !== Object.prototype ? T.bind(c) : T;
632
672
  } else if (getObserver() && !n) {
633
- return read(getNode(e, o, t, isWrappable(f) ? wrap(f, e) : f, isEqual, e[STORE_SNAPSHOT_PROPS]));
673
+ return read(getNode(e, o, t, isWrappable(T) ? wrap(T, e) : T, isEqual, e[STORE_SNAPSHOT_PROPS]));
634
674
  }
635
675
  }
636
676
  // Untracked fall-through (tracked reads already threw via their node in
637
677
  // read(); the dev strictRead error above wins first for memo parity).
638
678
  if (!n) throwIfUninitialized(e);
639
- return isWrappable(f) ? wrap(f, e) : f;
679
+ return isWrappable(T) ? wrap(T, e) : T;
640
680
  },
641
681
  has(e, t) {
642
682
  if (t === $PROXY || t === $TRACK || t === "__proto__") return true;
@@ -663,50 +703,50 @@ const storeTraps = {
663
703
  const n = e[$PROXY];
664
704
  if (writeOnly(n)) {
665
705
  untrack(() => {
666
- const {base: o, overrideKey: i, state: s} = prepareStoreWrite(e, n, t);
667
- const E = getOverlayLayer(e, t);
668
- const O = E ? E[t] : o;
669
- const c = E ? E[t] !== $DELETED : t in e[STORE_VALUE];
670
- const S = unwrapStoreValue(r);
706
+ const {base: o, overrideKey: i, state: E} = prepareStoreWrite(e, n, t);
707
+ const O = getOverlayLayer(e, t);
708
+ const s = O ? O[t] : o;
709
+ const S = O ? O[t] !== $DELETED : t in e[STORE_VALUE];
710
+ const c = unwrapStoreValue(r);
671
711
  // Symbol-keyed writes on arrays are metadata, not index writes — never run
672
712
  // them through the numeric index/length machinery (`parseInt` on a symbol
673
713
  // throws). #2769
674
- const f = typeof t === "string" ? Number(t) : -1;
675
- const T = Array.isArray(s) && Number.isInteger(f) && f >= 0 && f < 4294967295 && String(f) === t;
676
- const R = T ? f + 1 : 0;
677
- const u = T && (getOverlayLayer(e, "length") ?? s).length;
678
- const l = T && R > u ? R : undefined;
679
- if (O === S && l === undefined) return true;
714
+ const T = typeof t === "string" ? Number(t) : -1;
715
+ const R = Array.isArray(E) && Number.isInteger(T) && T >= 0 && T < 4294967295 && String(T) === t;
716
+ const f = R ? T + 1 : 0;
717
+ const u = R && (getOverlayLayer(e, "length") ?? E).length;
718
+ const l = R && f > u ? f : undefined;
719
+ if (s === c && l === undefined) return true;
680
720
  armOptimisticStoreWrite(e, n);
681
- if (S !== undefined && S === o && l === undefined) {
721
+ if (c !== undefined && c === o && l === undefined) {
682
722
  delete e[i]?.[t];
683
723
  if (i === STORE_OPTIMISTIC_OVERRIDE) delete e[STORE_OPTIMISTIC_OWNERS]?.[t];
684
724
  } else {
685
725
  const r = e[i] || (e[i] = Object.create(null));
686
- r[t] = S;
726
+ r[t] = c;
687
727
  stampOptimisticOwner(e, i, t);
688
728
  if (l !== undefined) {
689
729
  r.length = l;
690
730
  stampOptimisticOwner(e, i, "length");
691
731
  }
692
732
  }
693
- notifyStoreProperty(e, t, "set", S, O, c);
733
+ notifyStoreProperty(e, t, "set", c, s, S);
694
734
  // Shrinking an array's length must remove the truncated indices, otherwise
695
735
  // they leak through `has`, `ownKeys`, and (tracked) index reads from the
696
736
  // underlying value. Mark each as deleted and notify so reactive reads update. #2768
697
- if (Array.isArray(s) && t === "length" && typeof S === "number" && typeof O === "number" && S < O) {
737
+ if (Array.isArray(E) && t === "length" && typeof c === "number" && typeof s === "number" && c < s) {
698
738
  const t = e[i] || (e[i] = Object.create(null));
699
- for (let r = S; r < O; r++) {
739
+ for (let r = c; r < s; r++) {
700
740
  if (t[r] === $DELETED) continue;
701
- const n = r in t ? t[r] : s[r];
702
- if (!(r in t) && !(r in s)) continue;
741
+ const n = r in t ? t[r] : E[r];
742
+ if (!(r in t) && !(r in E)) continue;
703
743
  t[r] = $DELETED;
704
744
  stampOptimisticOwner(e, i, r);
705
745
  notifyStoreProperty(e, r, "delete", undefined, n, true);
706
746
  }
707
747
  }
708
748
  // notify length change
709
- if (Array.isArray(s) && t !== "length" && l !== undefined) {
749
+ if (Array.isArray(E) && t !== "length" && l !== undefined) {
710
750
  const t = getNodes(e, STORE_NODE);
711
751
  if (t.length) {
712
752
  setSignal(t.length, l);
@@ -727,11 +767,11 @@ const storeTraps = {
727
767
  untrack(() => {
728
768
  const {base: o, overrideKey: i} = prepareStoreWrite(e, n, t);
729
769
  armOptimisticStoreWrite(e, n);
730
- const s = "value" in r ? {
770
+ const E = "value" in r ? {
731
771
  ...r,
732
772
  value: unwrapStoreValue(r.value)
733
773
  } : r;
734
- Object.defineProperty(e[i] || (e[i] = Object.create(null)), t, s);
774
+ Object.defineProperty(e[i] || (e[i] = Object.create(null)), t, E);
735
775
  stampOptimisticOwner(e, i, t);
736
776
  notifyStoreProperty(e, t, "invalidate");
737
777
  if (false) ;
@@ -27,6 +27,18 @@ function snapshotImpl(e, t, r, n) {
27
27
  if (pendingCheckActive) witnessAffectsMark(o);
28
28
  }
29
29
  i = mergedOverlay(o);
30
+ // A derived store's STORE_VALUE is the inner store's live proxy
31
+ // (store-in-store: createOptimisticStore/createProjection over a store).
32
+ // Without an overlay of its own, the fast path below would map to — and
33
+ // could return — that proxy verbatim. Recurse instead so the
34
+ // inner store's own target branch unwraps it (chains of any depth).
35
+ // With an overlay, a fresh `result` is always built, so the walk over the
36
+ // inner proxy already copies plain values.
37
+ if (!i && o[STORE_VALUE][$TARGET]) {
38
+ f = snapshotImpl(o[STORE_VALUE], t, r, n);
39
+ r.set(e, f);
40
+ return f;
41
+ }
30
42
  s = Array.isArray(o[STORE_VALUE]);
31
43
  r.set(e, i ? c = s ? [] : Object.create(Object.getPrototypeOf(o[STORE_VALUE])) : o[STORE_VALUE]);
32
44
  e = o[STORE_VALUE];
@@ -23,7 +23,6 @@
23
23
  * ```
24
24
  */
25
25
  export declare class NotReadyError extends Error {
26
- source: any;
27
26
  /**
28
27
  * Tags a visibility-only notification on the affects() boundary channel:
29
28
  * boundaries update display state from it, but the root queue never
@@ -31,6 +30,7 @@ export declare class NotReadyError extends Error {
31
30
  * construction.
32
31
  */
33
32
  _markVisual?: boolean;
33
+ source: any;
34
34
  constructor(source: any);
35
35
  }
36
36
  export declare class StatusError extends Error {
@@ -1,14 +1,20 @@
1
1
  /**
2
2
  * Returns a draft-mutating function that smart-merges `value` into a store,
3
- * preserving the identity of items whose `key` field matches between old and
4
- * new states. Useful when applying server payloads or full-replacement data
5
- * onto an existing store without losing fine-grained reactivity.
3
+ * preserving fine-grained reactivity: only changed leaves trigger updates.
6
4
  *
7
- * Items with the same key are updated in place (only changed properties
8
- * trigger updates). Items added or removed update the corresponding signals.
5
+ * With a `key` (default `"id"`), array items whose key matches between old
6
+ * and new states keep their identity (updated in place, moves and removals
7
+ * update the corresponding signals) — the shape for keyed server payloads.
8
+ * Items without the key field fall back to positional matching.
9
+ *
10
+ * With `key: null`, matching is purely positional: index N of the new array
11
+ * merges into index N of the old, and object properties merge recursively —
12
+ * the classic pattern for fixed-shape data that churns in place (dashboards,
13
+ * monitors), where no keyed diff pass is needed or wanted.
9
14
  *
10
15
  * @param value the next state to merge in
11
- * @param key property name (string) or extractor function for stable identity
16
+ * @param key property name (string) or extractor function for stable
17
+ * identity (default `"id"`); pass `null` for positional merging
12
18
  *
13
19
  * @example
14
20
  * ```ts
@@ -16,8 +22,11 @@
16
22
  *
17
23
  * async function refresh() {
18
24
  * const fresh = await api.getTodos();
19
- * setTodos(reconcile(fresh, "id")); // diff-merge by `id`
25
+ * setTodos(reconcile(fresh)); // diff-merge by `id`
20
26
  * }
27
+ *
28
+ * // fixed-shape polling data — positional merge
29
+ * setStats(reconcile(nextStats, null));
21
30
  * ```
22
31
  */
23
- export declare function reconcile<T extends U, U>(value: T, key: string | ((item: NonNullable<any>) => any)): (state: U) => void;
32
+ export declare function reconcile<T extends U, U>(value: T, key?: string | ((item: NonNullable<any>) => any) | null): (state: U) => void;
@@ -23,7 +23,6 @@
23
23
  * ```
24
24
  */
25
25
  export declare class NotReadyError extends Error {
26
- source: any;
27
26
  /**
28
27
  * Tags a visibility-only notification on the affects() boundary channel:
29
28
  * boundaries update display state from it, but the root queue never
@@ -31,6 +30,7 @@ export declare class NotReadyError extends Error {
31
30
  * construction.
32
31
  */
33
32
  _markVisual?: boolean;
33
+ source: any;
34
34
  constructor(source: any);
35
35
  }
36
36
  export declare class StatusError extends Error {
@@ -1,14 +1,20 @@
1
1
  /**
2
2
  * Returns a draft-mutating function that smart-merges `value` into a store,
3
- * preserving the identity of items whose `key` field matches between old and
4
- * new states. Useful when applying server payloads or full-replacement data
5
- * onto an existing store without losing fine-grained reactivity.
3
+ * preserving fine-grained reactivity: only changed leaves trigger updates.
6
4
  *
7
- * Items with the same key are updated in place (only changed properties
8
- * trigger updates). Items added or removed update the corresponding signals.
5
+ * With a `key` (default `"id"`), array items whose key matches between old
6
+ * and new states keep their identity (updated in place, moves and removals
7
+ * update the corresponding signals) — the shape for keyed server payloads.
8
+ * Items without the key field fall back to positional matching.
9
+ *
10
+ * With `key: null`, matching is purely positional: index N of the new array
11
+ * merges into index N of the old, and object properties merge recursively —
12
+ * the classic pattern for fixed-shape data that churns in place (dashboards,
13
+ * monitors), where no keyed diff pass is needed or wanted.
9
14
  *
10
15
  * @param value the next state to merge in
11
- * @param key property name (string) or extractor function for stable identity
16
+ * @param key property name (string) or extractor function for stable
17
+ * identity (default `"id"`); pass `null` for positional merging
12
18
  *
13
19
  * @example
14
20
  * ```ts
@@ -16,8 +22,11 @@
16
22
  *
17
23
  * async function refresh() {
18
24
  * const fresh = await api.getTodos();
19
- * setTodos(reconcile(fresh, "id")); // diff-merge by `id`
25
+ * setTodos(reconcile(fresh)); // diff-merge by `id`
20
26
  * }
27
+ *
28
+ * // fixed-shape polling data — positional merge
29
+ * setStats(reconcile(nextStats, null));
21
30
  * ```
22
31
  */
23
- export declare function reconcile<T extends U, U>(value: T, key: string | ((item: NonNullable<any>) => any)): (state: U) => void;
32
+ export declare function reconcile<T extends U, U>(value: T, key?: string | ((item: NonNullable<any>) => any) | null): (state: U) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidjs/signals",
3
- "version": "2.0.0-beta.22",
3
+ "version": "2.0.0-beta.24",
4
4
  "description": "Solid's reactive primitives: signals, memos, effects, stores, and async-aware computations.",
5
5
  "author": "Ryan Carniato",
6
6
  "license": "MIT",