@solidjs/signals 2.0.0-beta.23 → 2.0.0-beta.25

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 { $REFRESH, STORE_SNAPSHOT_PROPS, NOT_PENDING, unwrapOverride, STATUS_UNINITIALIZED, STATUS_PENDING, NO_SNAPSHOT } from "../core/constants.js";
2
2
 
3
- import { suppressComputedRecompute, isEqual, signal, pendingCheckActive, untrack, setSignal, read, snapshotCaptureActive, snapshotSources } from "../core/core.js";
3
+ import { suppressComputedRecompute, isEqual, signal, pendingCheckActive, untrack, setSignal, read, readNodeFast, READ_SLOW, snapshotCaptureActive, snapshotSources } from "../core/core.js";
4
4
 
5
5
  import { DEV } from "../core/dev.js";
6
6
 
@@ -28,19 +28,45 @@ import { createProjectionInternal } from "./projection.js";
28
28
  // the record witnesses it into the active isPending() probe.
29
29
  $AFFECTS = Symbol(0);
30
30
 
31
- const STORE_VALUE = "v", STORE_OVERRIDE = "o", STORE_OPTIMISTIC_OVERRIDE = "x", STORE_NODE = "n", STORE_HAS = "h", STORE_CUSTOM_PROTO = "c", STORE_WRAP = "w", STORE_LOOKUP = "l", STORE_FIREWALL = "f", STORE_OPTIMISTIC = "p", STORE_OPTIMISTIC_OWNERS = "t", STORE_PARENT = "u", STORE_DESC = "d";
31
+ const STORE_VALUE = "v", STORE_OVERRIDE = "o", STORE_OPTIMISTIC_OVERRIDE = "x", STORE_NODE = "n", STORE_HAS = "h", STORE_CUSTOM_PROTO = "c", STORE_WRAP = "w", STORE_LOOKUP = "l", STORE_FIREWALL = "f", STORE_OPTIMISTIC = "p", STORE_OPTIMISTIC_OWNERS = "t", STORE_PARENT = "u", STORE_DESC = "d", STORE_SHALLOW = "s";
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[STORE_SHALLOW] = undefined;
56
+ e[$PROXY] = null;
57
+ }
58
+
35
59
  function createStoreProxy(e, t = storeTraps, r) {
36
60
  let n;
37
61
  if (Array.isArray(e)) {
38
62
  n = [];
39
- n.v = e;
63
+ n[STORE_VALUE] = e;
64
+ initStoreFields(n);
40
65
  } else {
41
66
  n = {
42
- v: e
67
+ [STORE_VALUE]: e
43
68
  };
69
+ initStoreFields(n);
44
70
  const t = e?.[$TARGET]?.[STORE_VALUE] ?? e;
45
71
  const r = Object.getPrototypeOf(t);
46
72
  if (r !== null && r !== Object.prototype) {
@@ -51,24 +77,99 @@ function createStoreProxy(e, t = storeTraps, r) {
51
77
  return n[$PROXY] = new Proxy(n, t);
52
78
  }
53
79
 
80
+ // The global lookup maps raw value -> StoreNode TARGET (not proxy): reconcile
81
+ // and the unwrap/snapshot walks resolve targets through it constantly, and a
82
+ // target hit is a plain field read away from its proxy while a proxy hit
83
+ // costs a trap to get back to the target. Per-family STORE_LOOKUP maps
84
+ // (projections/optimistic) still map raw -> proxy — their wrap functions own
85
+ // that contract — so mixed-lookup consumers resolve through lookupTarget().
54
86
  const storeLookup = new WeakMap;
55
87
 
56
88
  // Node records that hold at least one user (non-`$TRACK`) symbol-keyed node.
57
89
  // Lets reconcile enumerate symbols only for records that need it (#2851).
58
90
  const symbolKeyedRecords = new WeakSet;
59
91
 
92
+ function lookupTarget(e, t) {
93
+ if (t !== undefined && t !== storeLookup) {
94
+ const r = t.get(e);
95
+ if (r !== undefined) return r[$TARGET];
96
+ }
97
+ return storeLookup.get(e);
98
+ }
99
+
100
+ // Values marked raw never acquire a proxy identity: wrap() serves them as-is
101
+ // everywhere — deep stores hold them as leaf values replaced by reference.
102
+ // Once raw, always raw (identity stays single, just unwrapped). Consulted
103
+ // only on wrap-creation and ingest paths; reads never touch it.
104
+ const rawValues = new WeakSet;
105
+
106
+ /**
107
+ * Marks a value as raw: no store will ever wrap it — every store presents it
108
+ * as-is, tracked by reference at whatever slot holds it and updated by
109
+ * replacement. Useful for class instances and external objects (editors,
110
+ * scene graphs, Maps) and for record-shaped data updated wholesale. Sticky
111
+ * for the value's lifetime.
112
+ */
113
+ // Flipped on the first mark and exported as a LIVE binding: reconcile
114
+ // consults it on every recursable pair, and importing the boolean directly
115
+ // lets those sites skip even the function call when no shallow store or raw
116
+ // mark exists anywhere in the app.
117
+ let rawValuesUsed = false;
118
+
119
+ function isRawValue(e) {
120
+ return rawValuesUsed && rawValues.has(e);
121
+ }
122
+
123
+ function markRawOne(e) {
124
+ if (isWrappable(e)) {
125
+ rawValuesUsed = true;
126
+ rawValues.add(e);
127
+ }
128
+ }
129
+
130
+ function markRawIngest(e) {
131
+ if (Array.isArray(e)) {
132
+ for (let t = 0, r = e.length; t < r; t++) markRawOne(e[t]);
133
+ } else {
134
+ for (const t in e) markRawOne(e[t]);
135
+ }
136
+ }
137
+
60
138
  function wrap(e, t) {
139
+ // Raw is raw in every family: the mark must preempt family wrapping too,
140
+ // or a shallow projection/optimistic store would proxy its raw records.
141
+ if (rawValuesUsed && rawValues.has(e)) return e;
61
142
  if (t?.[STORE_WRAP]) {
62
143
  const r = t[STORE_WRAP](e, t);
63
144
  const n = r[$TARGET];
64
145
  if (n && !n[STORE_PARENT] && n !== t) n[STORE_PARENT] = t;
65
146
  return r;
66
147
  }
67
- let r = e[$PROXY] || storeLookup.get(e);
68
- if (!r) {
69
- storeLookup.set(e, r = createStoreProxy(e));
70
- if (t) r[$TARGET][STORE_PARENT] = t;
148
+ const r = storeLookup.get(e);
149
+ if (r !== undefined) return r[$PROXY];
150
+ let n = e[$PROXY];
151
+ if (!n) {
152
+ n = createStoreProxy(e);
153
+ const r = n[$TARGET];
154
+ storeLookup.set(e, r);
155
+ if (t) r[STORE_PARENT] = t;
156
+ }
157
+ return n;
158
+ }
159
+
160
+ // Shallow store root: the target itself is fully reactive (per-key nodes,
161
+ // membership, $TRACK); its values are served raw. Seed values are marked at
162
+ // creation; reconcile and the set trap mark on ingest.
163
+ function wrapShallow(e) {
164
+ const t = storeLookup.get(e);
165
+ if (t !== undefined) {
166
+ if (t[STORE_SHALLOW]) return t[$PROXY];
71
167
  }
168
+ const r = createStoreProxy(e);
169
+ const n = r[$TARGET];
170
+ n[STORE_SHALLOW] = true;
171
+ storeLookup.set(e, n);
172
+ markRawIngest(e);
72
173
  return r;
73
174
  }
74
175
 
@@ -90,24 +191,24 @@ function writeOnly(e) {
90
191
  }
91
192
 
92
193
  function unwrapStoreValue(e, t, r) {
93
- const n = e?.[$TARGET] || r?.get(e)?.[$TARGET];
194
+ const n = e?.[$TARGET] || lookupTarget(e, r);
94
195
  if (!n) return e;
95
196
  const o = n[STORE_OVERRIDE];
96
197
  if (!o) return n[STORE_VALUE];
97
198
  if (!t) t = new Map;
98
199
  if (t.has(e)) return t.get(e);
99
200
  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);
201
+ const O = Array.isArray(i);
202
+ const s = O ? [] : Object.create(Object.getPrototypeOf(i));
203
+ t.set(e, s);
103
204
  r = n[STORE_LOOKUP] ?? storeLookup;
104
205
  for (const e of getStoreKeys(i, o)) {
105
- if (s && e === "length") continue;
206
+ if (O && e === "length") continue;
106
207
  const n = e in o ? o[e] : i[e];
107
- if (n !== $DELETED) E[e] = unwrapStoreValue(n, t, r);
208
+ if (n !== $DELETED) s[e] = unwrapStoreValue(n, t, r);
108
209
  }
109
- if (s) E.length = o.length ?? i.length;
110
- return E;
210
+ if (O) s.length = o.length ?? i.length;
211
+ return s;
111
212
  }
112
213
 
113
214
  function isPrototypePollutionKey(e) {
@@ -182,10 +283,10 @@ function getNodes(e, t) {
182
283
 
183
284
  function getNode(e, t, r, n, o = isEqual, i) {
184
285
  if (t[r]) return t[r];
185
- const s = signal(n, {
286
+ const O = signal(n, {
186
287
  equals: o,
187
288
  unobserved() {
188
- if (t[r] === s) {
289
+ if (t[r] === O) {
189
290
  delete t[r];
190
291
  // Drop the symbol-record mark once the last user symbol node is
191
292
  // gone, so reconcile's fast path stops probing a now string-only
@@ -205,28 +306,28 @@ function getNode(e, t, r, n, o = isEqual, i) {
205
306
  }
206
307
  }, e[STORE_FIREWALL]);
207
308
  if (e[STORE_OPTIMISTIC]) {
208
- s.Ee = NOT_PENDING;
309
+ O.Ee = NOT_PENDING;
209
310
  }
210
311
  if (i && r in i) {
211
312
  const e = i[r];
212
- s.Ve = e === undefined ? NO_SNAPSHOT : e;
213
- snapshotSources?.add(s);
313
+ O.Ve = e === undefined ? NO_SNAPSHOT : e;
314
+ snapshotSources?.add(O);
214
315
  }
215
316
  if (typeof r === "symbol" && r !== $TRACK && r !== $AFFECTS) symbolKeyedRecords.add(t);
216
317
  // A node born inside a live mark's identity scope inherits the mark
217
318
  // (the declaration walk could only cover nodes that existed then). The
218
319
  // 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);
320
+ if (r !== $AFFECTS && affectsScopes.size) inheritAffectsMarks(O, e[STORE_VALUE], r);
220
321
  // Node presence bubbles up the wrap chain (sticky), so reconcile can see
221
322
  // "subscribers live somewhere below" through node-less intermediate
222
323
  // records — the captured-proxy diff gate (#2902). Amortized O(1): stops at
223
324
  // 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];
325
+ let s = e;
326
+ while (s && !s[STORE_DESC]) {
327
+ s[STORE_DESC] = true;
328
+ s = s[STORE_PARENT];
228
329
  }
229
- return t[r] = s;
330
+ return t[r] = O;
230
331
  }
231
332
 
232
333
  /**
@@ -262,43 +363,43 @@ const affectsScopes = new Map;
262
363
  // holds the root, and must still descend to pick up records added since.
263
364
  o) {
264
365
  if (!isWrappable(e)) return;
265
- 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;
366
+ const i = e[$TARGET] || lookupTarget(e, n);
367
+ const O = i ? i[STORE_VALUE] : e;
368
+ if (o.has(O)) return;
369
+ o.add(O);
370
+ t.scope.add(O);
371
+ let s;
271
372
  if (i) {
272
373
  collectRecordNodes(i[STORE_NODE], r);
273
374
  collectRecordNodes(i[STORE_HAS], r);
274
- E = mergedOverlay(i);
375
+ s = mergedOverlay(i);
275
376
  // Carry the effective lookup into untouched descendants. Default stores
276
377
  // use the global lookup just like snapshotImpl; without it, nested raw
277
378
  // objects fall back to string-only enumeration and symbol branches vanish.
278
379
  n = i[STORE_LOOKUP] ?? n ?? storeLookup;
279
380
  }
280
- if (Array.isArray(s)) {
281
- const e = E?.length ?? s.length;
381
+ if (Array.isArray(O)) {
382
+ const e = s?.length ?? O.length;
282
383
  for (let i = 0; i < e; i++) {
283
- const e = E && i in E ? E[i] : s[i];
384
+ const e = s && i in s ? s[i] : O[i];
284
385
  if (e !== $DELETED) walkAffectsScope(e, t, r, n, o);
285
386
  }
286
387
  // Arrays can also carry symbol metadata. Enumerate symbols separately to
287
388
  // avoid scanning large index lists twice. Outside a store tree, retain the
288
389
  // 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);
390
+ const E = i || n ? getStoreSymbols(O, s) : [];
391
+ for (let e = 0, i = E.length; e < i; e++) {
392
+ const i = E[e];
393
+ const S = getPropertyDescriptor(O, s, i);
394
+ if (!S || S.get) continue;
395
+ walkAffectsScope(S.value, t, r, n, o);
295
396
  }
296
397
  } 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);
398
+ const e = i || n ? getStoreKeys(O, s) : getKeys(O, s);
399
+ for (let i = 0, E = e.length; i < E; i++) {
400
+ const E = getPropertyDescriptor(O, s, e[i]);
401
+ if (!E || E.get) continue;
402
+ walkAffectsScope(E.value, t, r, n, o);
302
403
  }
303
404
  }
304
405
  }
@@ -489,10 +590,10 @@ function prepareStoreWrite(e, t, r) {
489
590
  }
490
591
  }
491
592
  const i = e[STORE_OPTIMISTIC] && !projectionWriteActive;
492
- const s = i ? STORE_OPTIMISTIC_OVERRIDE : STORE_OVERRIDE;
593
+ const O = i ? STORE_OPTIMISTIC_OVERRIDE : STORE_OVERRIDE;
493
594
  return {
494
595
  base: o,
495
- overrideKey: s,
596
+ overrideKey: O,
496
597
  state: n
497
598
  };
498
599
  }
@@ -526,42 +627,42 @@ function prepareStoreWrite(e, t, r) {
526
627
  function upsertStoreNode(e, t, r, n, o) {
527
628
  if (t[r]) return t[r];
528
629
  const i = isWrappable(n) ? wrap(n, e) : n;
529
- const s = getNode(e, t, r, i, isEqual, o);
530
- registerTransientStoreNode(s);
531
- return s;
630
+ const O = getNode(e, t, r, i, isEqual, o);
631
+ registerTransientStoreNode(O);
632
+ return O;
532
633
  }
533
634
 
534
635
  function notifyStoreProperty(e, t, r, n, o, i) {
535
636
  // Cold writes upsert a transient pending node so untracked reads batch like signals.
536
637
  // Skip for projection writes (different commit semantics) and for optimistic stores
537
638
  // (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) {
639
+ const O = projectionWriteActive || e[STORE_OPTIMISTIC];
640
+ const s = r !== "delete";
641
+ const E = e[STORE_HAS]?.[t];
642
+ if (E) {
643
+ setSignal(E, s);
644
+ } else if (!O && r !== "invalidate" && i !== s) {
544
645
  const r = upsertStoreNode(e, getNodes(e, STORE_HAS), t, i);
545
- setSignal(r, E);
646
+ setSignal(r, s);
546
647
  }
547
- const c = getNodes(e, STORE_NODE);
648
+ const S = getNodes(e, STORE_NODE);
548
649
  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]);
650
+ if (S[t]) {
651
+ setSignal(S[t], () => isWrappable(n) ? wrap(n, e) : n);
652
+ } else if (!O) {
653
+ const r = upsertStoreNode(e, S, t, o, e[STORE_SNAPSHOT_PROPS]);
553
654
  setSignal(r, () => isWrappable(n) ? wrap(n, e) : n);
554
655
  }
555
656
  } else if (r === "invalidate") {
556
- if (c[t]) {
557
- setSignal(c[t], {});
558
- delete c[t];
657
+ if (S[t]) {
658
+ setSignal(S[t], {});
659
+ delete S[t];
559
660
  }
560
661
  } 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]);
662
+ if (S[t]) {
663
+ setSignal(S[t], undefined);
664
+ } else if (!O) {
665
+ const r = upsertStoreNode(e, S, t, o, e[STORE_SNAPSHOT_PROPS]);
565
666
  setSignal(r, undefined);
566
667
  }
567
668
  }
@@ -593,50 +694,82 @@ const storeTraps = {
593
694
  trackSelf(e);
594
695
  return r;
595
696
  }
697
+ // Hot path: an existing node on a plain target — no firewall (so neither
698
+ // selfRead nor the uninitialized guard can apply), no override layers,
699
+ // no write scope, and a raw (non-proxy) source. This is the shape of
700
+ // every effect re-read of a settled store; it pays one node read and the
701
+ // wrap check, nothing else. Any exotic bit falls through to the full
702
+ // resolution below, and dev strictRead keeps its warning path.
703
+ if (e[STORE_FIREWALL] === undefined && e[STORE_OVERRIDE] === undefined && e[STORE_OPTIMISTIC_OVERRIDE] === undefined && !writeOverride && (Writing === null || !Writing.has(r))) {
704
+ const r = e[STORE_NODE];
705
+ const n = r && r[t];
706
+ if (n !== undefined && e[STORE_VALUE][$TARGET] === undefined) {
707
+ // readNodeFast is read()'s plain-signal fast path hoisted over the
708
+ // call; READ_SLOW means a global read window (latest/pending-check/
709
+ // transition/lane/snapshot capture) or a node layer is active, and
710
+ // only then does the full read() resolution have anything to do.
711
+ let t = readNodeFast(n);
712
+ if (t === READ_SLOW) t = read(n);
713
+ if (t === $DELETED) t = undefined;
714
+ // Every node-writing site wraps wrappables before setSignal (see the
715
+ // dev assertion below), so re-wrapping on read is redundant — except
716
+ // during snapshot capture, where read() can surface a raw captured
717
+ // value seeded from snapshot props.
718
+ if (!snapshotCaptureActive) {
719
+ return t;
720
+ }
721
+ return isWrappable(t) ? wrap(t, e) : t;
722
+ }
723
+ }
596
724
  const n = getObserver() === e[STORE_FIREWALL];
597
725
  const o = getNodes(e, STORE_NODE);
598
726
  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)) {
727
+ const O = e[STORE_VALUE];
728
+ if (!i && !e[STORE_OVERRIDE] && !e[STORE_OPTIMISTIC_OVERRIDE] && !e[STORE_CUSTOM_PROTO] && !e[STORE_OPTIMISTIC] && !e[STORE_SNAPSHOT_PROPS] && !O[$TARGET] && !(t in O) && getObserver() && !n && !writeOnly(r)) {
601
729
  return read(getNode(e, o, t, undefined));
602
730
  }
603
- const E = getOverlayLayer(e, t);
604
- const O = !!E;
605
- const c = !!e[STORE_VALUE][$TARGET];
606
- const S = E ?? e[STORE_VALUE];
731
+ const s = getOverlayLayer(e, t);
732
+ const E = !!s;
733
+ const S = !!e[STORE_VALUE][$TARGET];
734
+ const f = s ?? e[STORE_VALUE];
607
735
  if (!i) {
608
- const n = Object.getOwnPropertyDescriptor(S, t);
736
+ const n = Object.getOwnPropertyDescriptor(f, t);
609
737
  if (n && n.get) return n.get.call(r);
610
- if (!n && !O && e[STORE_CUSTOM_PROTO]) {
611
- const e = unwrapStoreValue(S);
738
+ if (!n && !E && e[STORE_CUSTOM_PROTO]) {
739
+ const e = unwrapStoreValue(f);
612
740
  if (hasInheritedAccessor(e, t)) {
613
- return Reflect.get(S, t, r);
741
+ return Reflect.get(f, t, r);
614
742
  }
615
743
  }
616
744
  }
617
745
  if (writeOnly(r)) {
618
746
  if (isPrototypePollutionKey(t) && !hasOwnStoreProperty(e, t)) return undefined;
619
- let r = i && (O || !c) ? visibleNodeValue(i) : S[t];
747
+ let r = i && (E || !S) ? visibleNodeValue(i) : f[t];
620
748
  r === $DELETED && (r = undefined);
621
749
  if (!isWrappable(r)) return r;
750
+ // Shallow boundary: records are replaced, never edited in place. Reads
751
+ // inside a setter serve the raw so read-then-replace, filter/pop and
752
+ // projection derives all work; in-place mutation of a raw is inert by
753
+ // construction — the same contract as a markRaw child in a deep store.
754
+ if (e[STORE_SHALLOW]) return r;
622
755
  const n = wrap(r, e);
623
756
  Writing?.add(n);
624
757
  return n;
625
758
  }
626
- let f = i ? O || !c ? read(o[t]) : (read(o[t]), S[t]) : S[t];
627
- f === $DELETED && (f = undefined);
759
+ let c = i ? E || !S ? read(o[t]) : (read(o[t]), f[t]) : f[t];
760
+ c === $DELETED && (c = undefined);
628
761
  if (!i) {
629
- if (!O && typeof f === "function" && !Object.prototype.hasOwnProperty.call(S, t)) {
762
+ if (!E && typeof c === "function" && !Object.prototype.hasOwnProperty.call(f, t)) {
630
763
  let t;
631
- return !Array.isArray(e[STORE_VALUE]) && (t = Object.getPrototypeOf(e[STORE_VALUE])) && t !== Object.prototype ? f.bind(S) : f;
764
+ return !Array.isArray(e[STORE_VALUE]) && (t = Object.getPrototypeOf(e[STORE_VALUE])) && t !== Object.prototype ? c.bind(f) : c;
632
765
  } else if (getObserver() && !n) {
633
- return read(getNode(e, o, t, isWrappable(f) ? wrap(f, e) : f, isEqual, e[STORE_SNAPSHOT_PROPS]));
766
+ return read(getNode(e, o, t, isWrappable(c) ? wrap(c, e) : c, isEqual, e[STORE_SNAPSHOT_PROPS]));
634
767
  }
635
768
  }
636
769
  // Untracked fall-through (tracked reads already threw via their node in
637
770
  // read(); the dev strictRead error above wins first for memo parity).
638
771
  if (!n) throwIfUninitialized(e);
639
- return isWrappable(f) ? wrap(f, e) : f;
772
+ return isWrappable(c) ? wrap(c, e) : c;
640
773
  },
641
774
  has(e, t) {
642
775
  if (t === $PROXY || t === $TRACK || t === "__proto__") return true;
@@ -663,56 +796,57 @@ const storeTraps = {
663
796
  const n = e[$PROXY];
664
797
  if (writeOnly(n)) {
665
798
  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);
799
+ const {base: o, overrideKey: i, state: O} = prepareStoreWrite(e, n, t);
800
+ const s = getOverlayLayer(e, t);
801
+ const E = s ? s[t] : o;
802
+ const S = s ? s[t] !== $DELETED : t in e[STORE_VALUE];
803
+ const f = unwrapStoreValue(r);
804
+ if (e[STORE_SHALLOW] && isWrappable(f)) rawValues.add(f);
671
805
  // Symbol-keyed writes on arrays are metadata, not index writes — never run
672
806
  // them through the numeric index/length machinery (`parseInt` on a symbol
673
807
  // 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;
808
+ const c = typeof t === "string" ? Number(t) : -1;
809
+ const R = Array.isArray(O) && Number.isInteger(c) && c >= 0 && c < 4294967295 && String(c) === t;
810
+ const T = R ? c + 1 : 0;
811
+ const u = R && (getOverlayLayer(e, "length") ?? O).length;
812
+ const a = R && T > u ? T : undefined;
813
+ if (E === f && a === undefined) return true;
680
814
  armOptimisticStoreWrite(e, n);
681
- if (S !== undefined && S === o && l === undefined) {
815
+ if (f !== undefined && f === o && a === undefined) {
682
816
  delete e[i]?.[t];
683
817
  if (i === STORE_OPTIMISTIC_OVERRIDE) delete e[STORE_OPTIMISTIC_OWNERS]?.[t];
684
818
  } else {
685
819
  const r = e[i] || (e[i] = Object.create(null));
686
- r[t] = S;
820
+ r[t] = f;
687
821
  stampOptimisticOwner(e, i, t);
688
- if (l !== undefined) {
689
- r.length = l;
822
+ if (a !== undefined) {
823
+ r.length = a;
690
824
  stampOptimisticOwner(e, i, "length");
691
825
  }
692
826
  }
693
- notifyStoreProperty(e, t, "set", S, O, c);
827
+ notifyStoreProperty(e, t, "set", f, E, S);
694
828
  // Shrinking an array's length must remove the truncated indices, otherwise
695
829
  // they leak through `has`, `ownKeys`, and (tracked) index reads from the
696
830
  // 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) {
831
+ if (Array.isArray(O) && t === "length" && typeof f === "number" && typeof E === "number" && f < E) {
698
832
  const t = e[i] || (e[i] = Object.create(null));
699
- for (let r = S; r < O; r++) {
833
+ for (let r = f; r < E; r++) {
700
834
  if (t[r] === $DELETED) continue;
701
- const n = r in t ? t[r] : s[r];
702
- if (!(r in t) && !(r in s)) continue;
835
+ const n = r in t ? t[r] : O[r];
836
+ if (!(r in t) && !(r in O)) continue;
703
837
  t[r] = $DELETED;
704
838
  stampOptimisticOwner(e, i, r);
705
839
  notifyStoreProperty(e, r, "delete", undefined, n, true);
706
840
  }
707
841
  }
708
842
  // notify length change
709
- if (Array.isArray(s) && t !== "length" && l !== undefined) {
843
+ if (Array.isArray(O) && t !== "length" && a !== undefined) {
710
844
  const t = getNodes(e, STORE_NODE);
711
845
  if (t.length) {
712
- setSignal(t.length, l);
846
+ setSignal(t.length, a);
713
847
  } else if (!projectionWriteActive && !e[STORE_OPTIMISTIC]) {
714
848
  const r = upsertStoreNode(e, t, "length", u, e[STORE_SNAPSHOT_PROPS]);
715
- setSignal(r, l);
849
+ setSignal(r, a);
716
850
  }
717
851
  }
718
852
  if (false) ;
@@ -727,11 +861,11 @@ const storeTraps = {
727
861
  untrack(() => {
728
862
  const {base: o, overrideKey: i} = prepareStoreWrite(e, n, t);
729
863
  armOptimisticStoreWrite(e, n);
730
- const s = "value" in r ? {
864
+ const O = "value" in r ? {
731
865
  ...r,
732
866
  value: unwrapStoreValue(r.value)
733
867
  } : r;
734
- Object.defineProperty(e[i] || (e[i] = Object.create(null)), t, s);
868
+ Object.defineProperty(e[i] || (e[i] = Object.create(null)), t, O);
735
869
  stampOptimisticOwner(e, i, t);
736
870
  notifyStoreProperty(e, t, "invalidate");
737
871
  if (false) ;
@@ -859,7 +993,7 @@ function storeSetter(e, t) {
859
993
  }
860
994
 
861
995
  function createStore(e, t, r) {
862
- const n = typeof e === "function", o = n ? createProjectionInternal(e, t, r).store : wrap(e);
996
+ const n = typeof e === "function", o = n ? createProjectionInternal(e, t, r).store : t?.shallow ? wrapShallow(e) : wrap(e);
863
997
  return [ o, n ? e => {
864
998
  // Mark the projection as manually written before notifying property nodes.
865
999
  suppressComputedRecompute(o[$REFRESH]);
@@ -867,4 +1001,4 @@ function createStore(e, t, r) {
867
1001
  } : e => storeSetter(o, e) ];
868
1002
  }
869
1003
 
870
- export { $AFFECTS, $DELETED, $PROXY, $TARGET, $TRACK, STORE_CUSTOM_PROTO, STORE_DESC, STORE_FIREWALL, STORE_HAS, STORE_LOOKUP, STORE_NODE, STORE_OPTIMISTIC, STORE_OPTIMISTIC_OVERRIDE, STORE_OPTIMISTIC_OWNERS, STORE_OVERRIDE, STORE_PARENT, STORE_VALUE, STORE_WRAP, createStore, createStoreProxy, getKeys, getOverlayLayer, getPropertyDescriptor, getStoreAffectsNodes, getStoreKeys, getStoreSymbols, isWrappable, mergedOverlay, notifySelf, ownEnumerableKeys, setWriteOverride, storeLookup, storeSetter, storeTraps, symbolKeyedRecords, trackSelf, visibleNodeValue, witnessAffectsMark, wrap };
1004
+ export { $AFFECTS, $DELETED, $PROXY, $TARGET, $TRACK, STORE_CUSTOM_PROTO, STORE_DESC, STORE_FIREWALL, STORE_HAS, STORE_LOOKUP, STORE_NODE, STORE_OPTIMISTIC, STORE_OPTIMISTIC_OVERRIDE, STORE_OPTIMISTIC_OWNERS, STORE_OVERRIDE, STORE_PARENT, STORE_SHALLOW, STORE_VALUE, STORE_WRAP, createStore, createStoreProxy, getKeys, getOverlayLayer, getPropertyDescriptor, getStoreAffectsNodes, getStoreKeys, getStoreSymbols, isRawValue, isWrappable, lookupTarget, markRawIngest, mergedOverlay, notifySelf, ownEnumerableKeys, rawValuesUsed, setWriteOverride, storeLookup, storeSetter, storeTraps, symbolKeyedRecords, trackSelf, visibleNodeValue, witnessAffectsMark, wrap, wrapShallow };