@solidjs/signals 2.0.0-beta.24 → 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,7 +28,7 @@ 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
 
@@ -52,6 +52,7 @@ function initStoreFields(e) {
52
52
  e[STORE_SNAPSHOT_PROPS] = undefined;
53
53
  e[STORE_PARENT] = undefined;
54
54
  e[STORE_DESC] = undefined;
55
+ e[STORE_SHALLOW] = undefined;
55
56
  e[$PROXY] = null;
56
57
  }
57
58
 
@@ -76,24 +77,99 @@ function createStoreProxy(e, t = storeTraps, r) {
76
77
  return n[$PROXY] = new Proxy(n, t);
77
78
  }
78
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().
79
86
  const storeLookup = new WeakMap;
80
87
 
81
88
  // Node records that hold at least one user (non-`$TRACK`) symbol-keyed node.
82
89
  // Lets reconcile enumerate symbols only for records that need it (#2851).
83
90
  const symbolKeyedRecords = new WeakSet;
84
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
+
85
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;
86
142
  if (t?.[STORE_WRAP]) {
87
143
  const r = t[STORE_WRAP](e, t);
88
144
  const n = r[$TARGET];
89
145
  if (n && !n[STORE_PARENT] && n !== t) n[STORE_PARENT] = t;
90
146
  return r;
91
147
  }
92
- let r = e[$PROXY] || storeLookup.get(e);
93
- if (!r) {
94
- storeLookup.set(e, r = createStoreProxy(e));
95
- 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];
96
167
  }
168
+ const r = createStoreProxy(e);
169
+ const n = r[$TARGET];
170
+ n[STORE_SHALLOW] = true;
171
+ storeLookup.set(e, n);
172
+ markRawIngest(e);
97
173
  return r;
98
174
  }
99
175
 
@@ -115,24 +191,24 @@ function writeOnly(e) {
115
191
  }
116
192
 
117
193
  function unwrapStoreValue(e, t, r) {
118
- const n = e?.[$TARGET] || r?.get(e)?.[$TARGET];
194
+ const n = e?.[$TARGET] || lookupTarget(e, r);
119
195
  if (!n) return e;
120
196
  const o = n[STORE_OVERRIDE];
121
197
  if (!o) return n[STORE_VALUE];
122
198
  if (!t) t = new Map;
123
199
  if (t.has(e)) return t.get(e);
124
200
  const i = n[STORE_VALUE];
125
- const E = Array.isArray(i);
126
- const O = E ? [] : Object.create(Object.getPrototypeOf(i));
127
- t.set(e, O);
201
+ const O = Array.isArray(i);
202
+ const s = O ? [] : Object.create(Object.getPrototypeOf(i));
203
+ t.set(e, s);
128
204
  r = n[STORE_LOOKUP] ?? storeLookup;
129
205
  for (const e of getStoreKeys(i, o)) {
130
- if (E && e === "length") continue;
206
+ if (O && e === "length") continue;
131
207
  const n = e in o ? o[e] : i[e];
132
- if (n !== $DELETED) O[e] = unwrapStoreValue(n, t, r);
208
+ if (n !== $DELETED) s[e] = unwrapStoreValue(n, t, r);
133
209
  }
134
- if (E) O.length = o.length ?? i.length;
135
- return O;
210
+ if (O) s.length = o.length ?? i.length;
211
+ return s;
136
212
  }
137
213
 
138
214
  function isPrototypePollutionKey(e) {
@@ -207,10 +283,10 @@ function getNodes(e, t) {
207
283
 
208
284
  function getNode(e, t, r, n, o = isEqual, i) {
209
285
  if (t[r]) return t[r];
210
- const E = signal(n, {
286
+ const O = signal(n, {
211
287
  equals: o,
212
288
  unobserved() {
213
- if (t[r] === E) {
289
+ if (t[r] === O) {
214
290
  delete t[r];
215
291
  // Drop the symbol-record mark once the last user symbol node is
216
292
  // gone, so reconcile's fast path stops probing a now string-only
@@ -230,28 +306,28 @@ function getNode(e, t, r, n, o = isEqual, i) {
230
306
  }
231
307
  }, e[STORE_FIREWALL]);
232
308
  if (e[STORE_OPTIMISTIC]) {
233
- E.Ee = NOT_PENDING;
309
+ O.Ee = NOT_PENDING;
234
310
  }
235
311
  if (i && r in i) {
236
312
  const e = i[r];
237
- E.Ve = e === undefined ? NO_SNAPSHOT : e;
238
- snapshotSources?.add(E);
313
+ O.Ve = e === undefined ? NO_SNAPSHOT : e;
314
+ snapshotSources?.add(O);
239
315
  }
240
316
  if (typeof r === "symbol" && r !== $TRACK && r !== $AFFECTS) symbolKeyedRecords.add(t);
241
317
  // A node born inside a live mark's identity scope inherits the mark
242
318
  // (the declaration walk could only cover nodes that existed then). The
243
319
  // record's own $AFFECTS carrier is the mark's channel, never a member.
244
- if (r !== $AFFECTS && affectsScopes.size) inheritAffectsMarks(E, e[STORE_VALUE], r);
320
+ if (r !== $AFFECTS && affectsScopes.size) inheritAffectsMarks(O, e[STORE_VALUE], r);
245
321
  // Node presence bubbles up the wrap chain (sticky), so reconcile can see
246
322
  // "subscribers live somewhere below" through node-less intermediate
247
323
  // records — the captured-proxy diff gate (#2902). Amortized O(1): stops at
248
324
  // the first already-flagged ancestor.
249
- let O = e;
250
- while (O && !O[STORE_DESC]) {
251
- O[STORE_DESC] = true;
252
- O = O[STORE_PARENT];
325
+ let s = e;
326
+ while (s && !s[STORE_DESC]) {
327
+ s[STORE_DESC] = true;
328
+ s = s[STORE_PARENT];
253
329
  }
254
- return t[r] = E;
330
+ return t[r] = O;
255
331
  }
256
332
 
257
333
  /**
@@ -287,43 +363,43 @@ const affectsScopes = new Map;
287
363
  // holds the root, and must still descend to pick up records added since.
288
364
  o) {
289
365
  if (!isWrappable(e)) return;
290
- const i = e[$TARGET] || (n ?? storeLookup).get(e)?.[$TARGET];
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;
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;
296
372
  if (i) {
297
373
  collectRecordNodes(i[STORE_NODE], r);
298
374
  collectRecordNodes(i[STORE_HAS], r);
299
- O = mergedOverlay(i);
375
+ s = mergedOverlay(i);
300
376
  // Carry the effective lookup into untouched descendants. Default stores
301
377
  // use the global lookup just like snapshotImpl; without it, nested raw
302
378
  // objects fall back to string-only enumeration and symbol branches vanish.
303
379
  n = i[STORE_LOOKUP] ?? n ?? storeLookup;
304
380
  }
305
- if (Array.isArray(E)) {
306
- const e = O?.length ?? E.length;
381
+ if (Array.isArray(O)) {
382
+ const e = s?.length ?? O.length;
307
383
  for (let i = 0; i < e; i++) {
308
- const e = O && i in O ? O[i] : E[i];
384
+ const e = s && i in s ? s[i] : O[i];
309
385
  if (e !== $DELETED) walkAffectsScope(e, t, r, n, o);
310
386
  }
311
387
  // Arrays can also carry symbol metadata. Enumerate symbols separately to
312
388
  // avoid scanning large index lists twice. Outside a store tree, retain the
313
389
  // existing index-only walk.
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);
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);
318
394
  if (!S || S.get) continue;
319
395
  walkAffectsScope(S.value, t, r, n, o);
320
396
  }
321
397
  } else {
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);
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);
327
403
  }
328
404
  }
329
405
  }
@@ -514,10 +590,10 @@ function prepareStoreWrite(e, t, r) {
514
590
  }
515
591
  }
516
592
  const i = e[STORE_OPTIMISTIC] && !projectionWriteActive;
517
- const E = i ? STORE_OPTIMISTIC_OVERRIDE : STORE_OVERRIDE;
593
+ const O = i ? STORE_OPTIMISTIC_OVERRIDE : STORE_OVERRIDE;
518
594
  return {
519
595
  base: o,
520
- overrideKey: E,
596
+ overrideKey: O,
521
597
  state: n
522
598
  };
523
599
  }
@@ -551,29 +627,29 @@ function prepareStoreWrite(e, t, r) {
551
627
  function upsertStoreNode(e, t, r, n, o) {
552
628
  if (t[r]) return t[r];
553
629
  const i = isWrappable(n) ? wrap(n, e) : n;
554
- const E = getNode(e, t, r, i, isEqual, o);
555
- registerTransientStoreNode(E);
556
- return E;
630
+ const O = getNode(e, t, r, i, isEqual, o);
631
+ registerTransientStoreNode(O);
632
+ return O;
557
633
  }
558
634
 
559
635
  function notifyStoreProperty(e, t, r, n, o, i) {
560
636
  // Cold writes upsert a transient pending node so untracked reads batch like signals.
561
637
  // Skip for projection writes (different commit semantics) and for optimistic stores
562
638
  // (whose whole purpose is immediate visibility via STORE_OPTIMISTIC_OVERRIDE).
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) {
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) {
569
645
  const r = upsertStoreNode(e, getNodes(e, STORE_HAS), t, i);
570
- setSignal(r, O);
646
+ setSignal(r, s);
571
647
  }
572
648
  const S = getNodes(e, STORE_NODE);
573
649
  if (r === "set") {
574
650
  if (S[t]) {
575
651
  setSignal(S[t], () => isWrappable(n) ? wrap(n, e) : n);
576
- } else if (!E) {
652
+ } else if (!O) {
577
653
  const r = upsertStoreNode(e, S, t, o, e[STORE_SNAPSHOT_PROPS]);
578
654
  setSignal(r, () => isWrappable(n) ? wrap(n, e) : n);
579
655
  }
@@ -585,7 +661,7 @@ function notifyStoreProperty(e, t, r, n, o, i) {
585
661
  } else {
586
662
  if (S[t]) {
587
663
  setSignal(S[t], undefined);
588
- } else if (!E) {
664
+ } else if (!O) {
589
665
  const r = upsertStoreNode(e, S, t, o, e[STORE_SNAPSHOT_PROPS]);
590
666
  setSignal(r, undefined);
591
667
  }
@@ -628,55 +704,72 @@ const storeTraps = {
628
704
  const r = e[STORE_NODE];
629
705
  const n = r && r[t];
630
706
  if (n !== undefined && e[STORE_VALUE][$TARGET] === undefined) {
631
- let t = read(n);
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);
632
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
+ }
633
721
  return isWrappable(t) ? wrap(t, e) : t;
634
722
  }
635
723
  }
636
724
  const n = getObserver() === e[STORE_FIREWALL];
637
725
  const o = getNodes(e, STORE_NODE);
638
726
  const i = n ? undefined : o[t];
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)) {
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)) {
641
729
  return read(getNode(e, o, t, undefined));
642
730
  }
643
- const O = getOverlayLayer(e, t);
644
- const s = !!O;
731
+ const s = getOverlayLayer(e, t);
732
+ const E = !!s;
645
733
  const S = !!e[STORE_VALUE][$TARGET];
646
- const c = O ?? e[STORE_VALUE];
734
+ const f = s ?? e[STORE_VALUE];
647
735
  if (!i) {
648
- const n = Object.getOwnPropertyDescriptor(c, t);
736
+ const n = Object.getOwnPropertyDescriptor(f, t);
649
737
  if (n && n.get) return n.get.call(r);
650
- if (!n && !s && e[STORE_CUSTOM_PROTO]) {
651
- const e = unwrapStoreValue(c);
738
+ if (!n && !E && e[STORE_CUSTOM_PROTO]) {
739
+ const e = unwrapStoreValue(f);
652
740
  if (hasInheritedAccessor(e, t)) {
653
- return Reflect.get(c, t, r);
741
+ return Reflect.get(f, t, r);
654
742
  }
655
743
  }
656
744
  }
657
745
  if (writeOnly(r)) {
658
746
  if (isPrototypePollutionKey(t) && !hasOwnStoreProperty(e, t)) return undefined;
659
- let r = i && (s || !S) ? visibleNodeValue(i) : c[t];
747
+ let r = i && (E || !S) ? visibleNodeValue(i) : f[t];
660
748
  r === $DELETED && (r = undefined);
661
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;
662
755
  const n = wrap(r, e);
663
756
  Writing?.add(n);
664
757
  return n;
665
758
  }
666
- let T = i ? s || !S ? read(o[t]) : (read(o[t]), c[t]) : c[t];
667
- T === $DELETED && (T = undefined);
759
+ let c = i ? E || !S ? read(o[t]) : (read(o[t]), f[t]) : f[t];
760
+ c === $DELETED && (c = undefined);
668
761
  if (!i) {
669
- if (!s && typeof T === "function" && !Object.prototype.hasOwnProperty.call(c, t)) {
762
+ if (!E && typeof c === "function" && !Object.prototype.hasOwnProperty.call(f, t)) {
670
763
  let t;
671
- return !Array.isArray(e[STORE_VALUE]) && (t = Object.getPrototypeOf(e[STORE_VALUE])) && t !== Object.prototype ? T.bind(c) : T;
764
+ return !Array.isArray(e[STORE_VALUE]) && (t = Object.getPrototypeOf(e[STORE_VALUE])) && t !== Object.prototype ? c.bind(f) : c;
672
765
  } else if (getObserver() && !n) {
673
- return read(getNode(e, o, t, isWrappable(T) ? wrap(T, e) : T, isEqual, e[STORE_SNAPSHOT_PROPS]));
766
+ return read(getNode(e, o, t, isWrappable(c) ? wrap(c, e) : c, isEqual, e[STORE_SNAPSHOT_PROPS]));
674
767
  }
675
768
  }
676
769
  // Untracked fall-through (tracked reads already threw via their node in
677
770
  // read(); the dev strictRead error above wins first for memo parity).
678
771
  if (!n) throwIfUninitialized(e);
679
- return isWrappable(T) ? wrap(T, e) : T;
772
+ return isWrappable(c) ? wrap(c, e) : c;
680
773
  },
681
774
  has(e, t) {
682
775
  if (t === $PROXY || t === $TRACK || t === "__proto__") return true;
@@ -703,56 +796,57 @@ const storeTraps = {
703
796
  const n = e[$PROXY];
704
797
  if (writeOnly(n)) {
705
798
  untrack(() => {
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);
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);
711
805
  // Symbol-keyed writes on arrays are metadata, not index writes — never run
712
806
  // them through the numeric index/length machinery (`parseInt` on a symbol
713
807
  // throws). #2769
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;
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;
720
814
  armOptimisticStoreWrite(e, n);
721
- if (c !== undefined && c === o && l === undefined) {
815
+ if (f !== undefined && f === o && a === undefined) {
722
816
  delete e[i]?.[t];
723
817
  if (i === STORE_OPTIMISTIC_OVERRIDE) delete e[STORE_OPTIMISTIC_OWNERS]?.[t];
724
818
  } else {
725
819
  const r = e[i] || (e[i] = Object.create(null));
726
- r[t] = c;
820
+ r[t] = f;
727
821
  stampOptimisticOwner(e, i, t);
728
- if (l !== undefined) {
729
- r.length = l;
822
+ if (a !== undefined) {
823
+ r.length = a;
730
824
  stampOptimisticOwner(e, i, "length");
731
825
  }
732
826
  }
733
- notifyStoreProperty(e, t, "set", c, s, S);
827
+ notifyStoreProperty(e, t, "set", f, E, S);
734
828
  // Shrinking an array's length must remove the truncated indices, otherwise
735
829
  // they leak through `has`, `ownKeys`, and (tracked) index reads from the
736
830
  // underlying value. Mark each as deleted and notify so reactive reads update. #2768
737
- if (Array.isArray(E) && t === "length" && typeof c === "number" && typeof s === "number" && c < s) {
831
+ if (Array.isArray(O) && t === "length" && typeof f === "number" && typeof E === "number" && f < E) {
738
832
  const t = e[i] || (e[i] = Object.create(null));
739
- for (let r = c; r < s; r++) {
833
+ for (let r = f; r < E; r++) {
740
834
  if (t[r] === $DELETED) continue;
741
- const n = r in t ? t[r] : E[r];
742
- if (!(r in t) && !(r in E)) continue;
835
+ const n = r in t ? t[r] : O[r];
836
+ if (!(r in t) && !(r in O)) continue;
743
837
  t[r] = $DELETED;
744
838
  stampOptimisticOwner(e, i, r);
745
839
  notifyStoreProperty(e, r, "delete", undefined, n, true);
746
840
  }
747
841
  }
748
842
  // notify length change
749
- if (Array.isArray(E) && t !== "length" && l !== undefined) {
843
+ if (Array.isArray(O) && t !== "length" && a !== undefined) {
750
844
  const t = getNodes(e, STORE_NODE);
751
845
  if (t.length) {
752
- setSignal(t.length, l);
846
+ setSignal(t.length, a);
753
847
  } else if (!projectionWriteActive && !e[STORE_OPTIMISTIC]) {
754
848
  const r = upsertStoreNode(e, t, "length", u, e[STORE_SNAPSHOT_PROPS]);
755
- setSignal(r, l);
849
+ setSignal(r, a);
756
850
  }
757
851
  }
758
852
  if (false) ;
@@ -767,11 +861,11 @@ const storeTraps = {
767
861
  untrack(() => {
768
862
  const {base: o, overrideKey: i} = prepareStoreWrite(e, n, t);
769
863
  armOptimisticStoreWrite(e, n);
770
- const E = "value" in r ? {
864
+ const O = "value" in r ? {
771
865
  ...r,
772
866
  value: unwrapStoreValue(r.value)
773
867
  } : r;
774
- Object.defineProperty(e[i] || (e[i] = Object.create(null)), t, E);
868
+ Object.defineProperty(e[i] || (e[i] = Object.create(null)), t, O);
775
869
  stampOptimisticOwner(e, i, t);
776
870
  notifyStoreProperty(e, t, "invalidate");
777
871
  if (false) ;
@@ -899,7 +993,7 @@ function storeSetter(e, t) {
899
993
  }
900
994
 
901
995
  function createStore(e, t, r) {
902
- 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);
903
997
  return [ o, n ? e => {
904
998
  // Mark the projection as manually written before notifying property nodes.
905
999
  suppressComputedRecompute(o[$REFRESH]);
@@ -907,4 +1001,4 @@ function createStore(e, t, r) {
907
1001
  } : e => storeSetter(o, e) ];
908
1002
  }
909
1003
 
910
- 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 };
@@ -12,14 +12,14 @@ import "../core/effect.js";
12
12
 
13
13
  import { createMemo } from "../signals.js";
14
14
 
15
- import { ownEnumerableKeys, $PROXY, isWrappable, $TARGET, trackSelf, witnessAffectsMark, mergedOverlay, STORE_VALUE, storeLookup, STORE_LOOKUP, $DELETED, wrap, getStoreSymbols, getPropertyDescriptor, getStoreKeys, getKeys, $TRACK } from "./store.js";
15
+ import { ownEnumerableKeys, $PROXY, isWrappable, $TARGET, lookupTarget, trackSelf, witnessAffectsMark, mergedOverlay, STORE_VALUE, storeLookup, STORE_LOOKUP, $DELETED, wrap, getStoreSymbols, getPropertyDescriptor, getStoreKeys, getKeys, $TRACK, rawValuesUsed, isRawValue } from "./store.js";
16
16
 
17
17
  function snapshotImpl(e, t, r, n) {
18
18
  let o, s, i, c, f, u;
19
19
  if (!isWrappable(e)) return e;
20
20
  if (r && r.has(e)) return r.get(e);
21
21
  if (!r) r = new Map;
22
- if (o = e[$TARGET] || n?.get(e)?.[$TARGET]) {
22
+ if (o = e[$TARGET] || lookupTarget(e, n)) {
23
23
  if (t) {
24
24
  trackSelf(o, $TRACK);
25
25
  // A tracked walk reads THROUGH the record without touching the proxy
@@ -49,24 +49,24 @@ function snapshotImpl(e, t, r, n) {
49
49
  }
50
50
  if (s) {
51
51
  const s = i?.length ?? e.length;
52
- for (let p = 0; p < s; p++) {
53
- u = i && p in i ? i[p] : e[p];
52
+ for (let a = 0; a < s; a++) {
53
+ u = i && a in i ? i[a] : e[a];
54
54
  if (u === $DELETED) continue;
55
- if (t && isWrappable(u)) wrap(u, o);
55
+ if (t && isWrappable(u) && !(rawValuesUsed && isRawValue(u))) wrap(u, o);
56
56
  if ((f = snapshotImpl(u, t, r, n)) !== u || c) {
57
57
  if (!c) r.set(e, c = [ ...e ]);
58
- c[p] = f;
58
+ c[a] = f;
59
59
  }
60
60
  }
61
61
  // Enumerate array symbols separately to avoid scanning indices twice.
62
62
  // Spread copies omit symbols, so assign them after the numeric walk.
63
- const p = n ? getStoreSymbols(e, i) : [];
64
- for (let s = 0, l = p.length; s < l; s++) {
65
- const l = p[s];
66
- const a = getPropertyDescriptor(e, i, l);
67
- if (!a || a.get) continue;
63
+ const a = n ? getStoreSymbols(e, i) : [];
64
+ for (let s = 0, l = a.length; s < l; s++) {
65
+ const l = a[s];
66
+ const p = getPropertyDescriptor(e, i, l);
67
+ if (!p || p.get) continue;
68
68
  u = i && l in i ? i[l] : e[l];
69
- if (t && isWrappable(u)) wrap(u, o);
69
+ if (t && isWrappable(u) && !(rawValuesUsed && isRawValue(u))) wrap(u, o);
70
70
  f = snapshotImpl(u, t, r, n);
71
71
  if (f !== u || c) {
72
72
  if (!c) r.set(e, c = Object.assign([ ...e ], e));
@@ -83,30 +83,30 @@ function snapshotImpl(e, t, r, n) {
83
83
  // A lookup means this object belongs to an immutable store backing tree,
84
84
  // even if that nested value has not needed its own proxy yet.
85
85
  const s = n ? getStoreKeys(e, undefined) : getKeys(e, undefined);
86
- for (let i = 0, p = s.length; i < p; i++) {
87
- const p = s[i];
88
- const l = Object.getOwnPropertyDescriptor(e, p);
86
+ for (let i = 0, a = s.length; i < a; i++) {
87
+ const a = s[i];
88
+ const l = Object.getOwnPropertyDescriptor(e, a);
89
89
  if (l.get) continue;
90
90
  u = l.value;
91
- if (t && isWrappable(u)) wrap(u, o);
91
+ if (t && isWrappable(u) && !(rawValuesUsed && isRawValue(u))) wrap(u, o);
92
92
  if ((f = snapshotImpl(u, t, r, n)) !== u || c) {
93
93
  if (!c) {
94
94
  c = Object.create(Object.getPrototypeOf(e));
95
95
  Object.assign(c, e);
96
96
  }
97
- c[p] = f;
97
+ c[a] = f;
98
98
  }
99
99
  }
100
100
  } else {
101
101
  // An override only exists on a store record, and the target branch above
102
102
  // always set `lookup` alongside it — so this branch is always store-keyed.
103
103
  const s = getStoreKeys(e, i);
104
- for (let p = 0, l = s.length; p < l; p++) {
105
- let l = s[p];
106
- const a = getPropertyDescriptor(e, i, l);
107
- if (a.get) continue;
104
+ for (let a = 0, l = s.length; a < l; a++) {
105
+ let l = s[a];
106
+ const p = getPropertyDescriptor(e, i, l);
107
+ if (p.get) continue;
108
108
  u = l in i ? i[l] : e[l];
109
- if (t && isWrappable(u)) wrap(u, o);
109
+ if (t && isWrappable(u) && !(rawValuesUsed && isRawValue(u))) wrap(u, o);
110
110
  if ((f = snapshotImpl(u, t, r, n)) !== e[l] || c) {
111
111
  if (!c) {
112
112
  c = Object.create(Object.getPrototypeOf(e));
@@ -72,6 +72,21 @@ export declare function untrack<T>(fn: () => T, strictReadLabel?: string | false
72
72
  * date so its status flags reflect the current graph.
73
73
  */
74
74
  export declare function prepareComputed(comp: Computed<unknown>, refresh: boolean): void;
75
+ /**
76
+ * Sentinel returned by readNodeFast when the plain-signal fast path does not
77
+ * apply and the caller must fall back to the full read().
78
+ */
79
+ export declare const READ_SLOW: unique symbol;
80
+ /**
81
+ * read()'s plain-signal fast path as a standalone entry for hot callers
82
+ * (store traps). Safe to substitute for read() only because the bail
83
+ * conditions mirror read()'s prelude and fast-path guard exactly: the
84
+ * latestRead and pendingCheck windows run side-effectful hooks before the
85
+ * fast path, `_fn` nodes need prepareComputed, and firewall / override /
86
+ * snapshot / transition / lane / dev-strictRead state all take the full
87
+ * resolution. Anything slow returns READ_SLOW; the caller then calls read().
88
+ */
89
+ export declare function readNodeFast<T>(el: Signal<T>): T | typeof READ_SLOW;
75
90
  export declare function read<T>(el: Signal<T> | Computed<T>): T;
76
91
  export declare function setSignal<T>(el: Signal<T> | Computed<T>, v: T | ((prev: T) => T)): T;
77
92
  /**