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

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,107 @@ 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
+ // A store proxy is already tracked elsewhere: the shallow boundary passes
126
+ // it through by reference (replaced, never edited — same slot semantics
127
+ // as a raw) instead of claiming it raw. The sticky mark is global, so
128
+ // marking a live proxy would make wrap() serve it verbatim through every
129
+ // OTHER store too — downstream deep stores then captured it instead of
130
+ // wrapping it in their own family, and their writes landed in the
131
+ // upstream store's override layer (#2932).
132
+ if (e[$TARGET] !== undefined) return;
133
+ rawValuesUsed = true;
134
+ rawValues.add(e);
135
+ }
136
+ }
137
+
138
+ function markRawIngest(e) {
139
+ if (Array.isArray(e)) {
140
+ for (let t = 0, r = e.length; t < r; t++) markRawOne(e[t]);
141
+ } else {
142
+ for (const t in e) markRawOne(e[t]);
143
+ }
144
+ }
145
+
85
146
  function wrap(e, t) {
147
+ // Raw is raw in every family: the mark must preempt family wrapping too,
148
+ // or a shallow projection/optimistic store would proxy its raw records.
149
+ if (rawValuesUsed && rawValues.has(e)) return e;
86
150
  if (t?.[STORE_WRAP]) {
87
151
  const r = t[STORE_WRAP](e, t);
88
152
  const n = r[$TARGET];
89
153
  if (n && !n[STORE_PARENT] && n !== t) n[STORE_PARENT] = t;
90
154
  return r;
91
155
  }
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;
156
+ const r = storeLookup.get(e);
157
+ if (r !== undefined) return r[$PROXY];
158
+ let n = e[$PROXY];
159
+ if (!n) {
160
+ n = createStoreProxy(e);
161
+ const r = n[$TARGET];
162
+ storeLookup.set(e, r);
163
+ if (t) r[STORE_PARENT] = t;
164
+ }
165
+ return n;
166
+ }
167
+
168
+ // Shallow store root: the target itself is fully reactive (per-key nodes,
169
+ // membership, $TRACK); its values are served raw. Seed values are marked at
170
+ // creation; reconcile and the set trap mark on ingest.
171
+ function wrapShallow(e) {
172
+ const t = storeLookup.get(e);
173
+ if (t !== undefined) {
174
+ if (t[STORE_SHALLOW]) return t[$PROXY];
96
175
  }
176
+ const r = createStoreProxy(e);
177
+ const n = r[$TARGET];
178
+ n[STORE_SHALLOW] = true;
179
+ storeLookup.set(e, n);
180
+ markRawIngest(e);
97
181
  return r;
98
182
  }
99
183
 
@@ -115,24 +199,24 @@ function writeOnly(e) {
115
199
  }
116
200
 
117
201
  function unwrapStoreValue(e, t, r) {
118
- const n = e?.[$TARGET] || r?.get(e)?.[$TARGET];
202
+ const n = e?.[$TARGET] || lookupTarget(e, r);
119
203
  if (!n) return e;
120
204
  const o = n[STORE_OVERRIDE];
121
205
  if (!o) return n[STORE_VALUE];
122
206
  if (!t) t = new Map;
123
207
  if (t.has(e)) return t.get(e);
124
208
  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);
209
+ const O = Array.isArray(i);
210
+ const s = O ? [] : Object.create(Object.getPrototypeOf(i));
211
+ t.set(e, s);
128
212
  r = n[STORE_LOOKUP] ?? storeLookup;
129
213
  for (const e of getStoreKeys(i, o)) {
130
- if (E && e === "length") continue;
214
+ if (O && e === "length") continue;
131
215
  const n = e in o ? o[e] : i[e];
132
- if (n !== $DELETED) O[e] = unwrapStoreValue(n, t, r);
216
+ if (n !== $DELETED) s[e] = unwrapStoreValue(n, t, r);
133
217
  }
134
- if (E) O.length = o.length ?? i.length;
135
- return O;
218
+ if (O) s.length = o.length ?? i.length;
219
+ return s;
136
220
  }
137
221
 
138
222
  function isPrototypePollutionKey(e) {
@@ -179,7 +263,7 @@ function ownEnumerableKeysPlain(e) {
179
263
  * The value a store leaf's backing signal currently shows to readers: active
180
264
  * override, else held pending value, else committed value.
181
265
  */ function visibleNodeValue(e) {
182
- return e.Ee !== undefined && e.Ee !== NOT_PENDING ? unwrapOverride(e.Ee) : e.Ne !== NOT_PENDING ? e.Ne : e.Pe;
266
+ return e.Ae !== undefined && e.Ae !== NOT_PENDING ? unwrapOverride(e.Ae) : e._e !== NOT_PENDING ? e._e : e.Ue;
183
267
  }
184
268
 
185
269
  function hasOwnStoreProperty(e, t) {
@@ -207,10 +291,10 @@ function getNodes(e, t) {
207
291
 
208
292
  function getNode(e, t, r, n, o = isEqual, i) {
209
293
  if (t[r]) return t[r];
210
- const E = signal(n, {
294
+ const O = signal(n, {
211
295
  equals: o,
212
296
  unobserved() {
213
- if (t[r] === E) {
297
+ if (t[r] === O) {
214
298
  delete t[r];
215
299
  // Drop the symbol-record mark once the last user symbol node is
216
300
  // gone, so reconcile's fast path stops probing a now string-only
@@ -230,28 +314,28 @@ function getNode(e, t, r, n, o = isEqual, i) {
230
314
  }
231
315
  }, e[STORE_FIREWALL]);
232
316
  if (e[STORE_OPTIMISTIC]) {
233
- E.Ee = NOT_PENDING;
317
+ O.Ae = NOT_PENDING;
234
318
  }
235
319
  if (i && r in i) {
236
320
  const e = i[r];
237
- E.Ve = e === undefined ? NO_SNAPSHOT : e;
238
- snapshotSources?.add(E);
321
+ O.xe = e === undefined ? NO_SNAPSHOT : e;
322
+ snapshotSources?.add(O);
239
323
  }
240
324
  if (typeof r === "symbol" && r !== $TRACK && r !== $AFFECTS) symbolKeyedRecords.add(t);
241
325
  // A node born inside a live mark's identity scope inherits the mark
242
326
  // (the declaration walk could only cover nodes that existed then). The
243
327
  // 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);
328
+ if (r !== $AFFECTS && affectsScopes.size) inheritAffectsMarks(O, e[STORE_VALUE], r);
245
329
  // Node presence bubbles up the wrap chain (sticky), so reconcile can see
246
330
  // "subscribers live somewhere below" through node-less intermediate
247
331
  // records — the captured-proxy diff gate (#2902). Amortized O(1): stops at
248
332
  // 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];
333
+ let s = e;
334
+ while (s && !s[STORE_DESC]) {
335
+ s[STORE_DESC] = true;
336
+ s = s[STORE_PARENT];
253
337
  }
254
- return t[r] = E;
338
+ return t[r] = O;
255
339
  }
256
340
 
257
341
  /**
@@ -287,43 +371,43 @@ const affectsScopes = new Map;
287
371
  // holds the root, and must still descend to pick up records added since.
288
372
  o) {
289
373
  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;
374
+ const i = e[$TARGET] || lookupTarget(e, n);
375
+ const O = i ? i[STORE_VALUE] : e;
376
+ if (o.has(O)) return;
377
+ o.add(O);
378
+ t.scope.add(O);
379
+ let s;
296
380
  if (i) {
297
381
  collectRecordNodes(i[STORE_NODE], r);
298
382
  collectRecordNodes(i[STORE_HAS], r);
299
- O = mergedOverlay(i);
383
+ s = mergedOverlay(i);
300
384
  // Carry the effective lookup into untouched descendants. Default stores
301
385
  // use the global lookup just like snapshotImpl; without it, nested raw
302
386
  // objects fall back to string-only enumeration and symbol branches vanish.
303
387
  n = i[STORE_LOOKUP] ?? n ?? storeLookup;
304
388
  }
305
- if (Array.isArray(E)) {
306
- const e = O?.length ?? E.length;
389
+ if (Array.isArray(O)) {
390
+ const e = s?.length ?? O.length;
307
391
  for (let i = 0; i < e; i++) {
308
- const e = O && i in O ? O[i] : E[i];
392
+ const e = s && i in s ? s[i] : O[i];
309
393
  if (e !== $DELETED) walkAffectsScope(e, t, r, n, o);
310
394
  }
311
395
  // Arrays can also carry symbol metadata. Enumerate symbols separately to
312
396
  // avoid scanning large index lists twice. Outside a store tree, retain the
313
397
  // 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);
398
+ const E = i || n ? getStoreSymbols(O, s) : [];
399
+ for (let e = 0, i = E.length; e < i; e++) {
400
+ const i = E[e];
401
+ const S = getPropertyDescriptor(O, s, i);
318
402
  if (!S || S.get) continue;
319
403
  walkAffectsScope(S.value, t, r, n, o);
320
404
  }
321
405
  } 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);
406
+ const e = i || n ? getStoreKeys(O, s) : getKeys(O, s);
407
+ for (let i = 0, E = e.length; i < E; i++) {
408
+ const E = getPropertyDescriptor(O, s, e[i]);
409
+ if (!E || E.get) continue;
410
+ walkAffectsScope(E.value, t, r, n, o);
327
411
  }
328
412
  }
329
413
  }
@@ -352,11 +436,11 @@ o) {
352
436
  // Callers guard on `pendingCheckActive`, which only flips inside
353
437
  // isPending() — the verdict layer is loaded and its hook installed.
354
438
  const r = e[STORE_NODE]?.[$AFFECTS];
355
- if (r?.t) GlobalQueue.Lt(r);
439
+ if (r?.t) GlobalQueue.Dt(r);
356
440
  if (affectsScopes.size) {
357
441
  const n = e[STORE_VALUE];
358
442
  for (const [e, o] of affectsScopes) {
359
- if (e !== r && e.t && o.scope.has(n) && (o.key === undefined || o.key === t)) GlobalQueue.Lt(e);
443
+ if (e !== r && e.t && o.scope.has(n) && (o.key === undefined || o.key === t)) GlobalQueue.Dt(e);
360
444
  }
361
445
  }
362
446
  }
@@ -498,8 +582,8 @@ function getPropertyDescriptor(e, t, r) {
498
582
  function prepareStoreWrite(e, t, r) {
499
583
  if (e[STORE_OPTIMISTIC]) {
500
584
  const t = e[STORE_FIREWALL];
501
- if (t?.Ue) {
502
- globalQueue.initTransition(t.Ue);
585
+ if (t?.Ie) {
586
+ globalQueue.initTransition(t.Ie);
503
587
  }
504
588
  }
505
589
  const n = e[STORE_VALUE];
@@ -514,10 +598,10 @@ function prepareStoreWrite(e, t, r) {
514
598
  }
515
599
  }
516
600
  const i = e[STORE_OPTIMISTIC] && !projectionWriteActive;
517
- const E = i ? STORE_OPTIMISTIC_OVERRIDE : STORE_OVERRIDE;
601
+ const O = i ? STORE_OPTIMISTIC_OVERRIDE : STORE_OVERRIDE;
518
602
  return {
519
603
  base: o,
520
- overrideKey: E,
604
+ overrideKey: O,
521
605
  state: n
522
606
  };
523
607
  }
@@ -551,29 +635,29 @@ function prepareStoreWrite(e, t, r) {
551
635
  function upsertStoreNode(e, t, r, n, o) {
552
636
  if (t[r]) return t[r];
553
637
  const i = isWrappable(n) ? wrap(n, e) : n;
554
- const E = getNode(e, t, r, i, isEqual, o);
555
- registerTransientStoreNode(E);
556
- return E;
638
+ const O = getNode(e, t, r, i, isEqual, o);
639
+ registerTransientStoreNode(O);
640
+ return O;
557
641
  }
558
642
 
559
643
  function notifyStoreProperty(e, t, r, n, o, i) {
560
644
  // Cold writes upsert a transient pending node so untracked reads batch like signals.
561
645
  // Skip for projection writes (different commit semantics) and for optimistic stores
562
646
  // (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) {
647
+ const O = projectionWriteActive || e[STORE_OPTIMISTIC];
648
+ const s = r !== "delete";
649
+ const E = e[STORE_HAS]?.[t];
650
+ if (E) {
651
+ setSignal(E, s);
652
+ } else if (!O && r !== "invalidate" && i !== s) {
569
653
  const r = upsertStoreNode(e, getNodes(e, STORE_HAS), t, i);
570
- setSignal(r, O);
654
+ setSignal(r, s);
571
655
  }
572
656
  const S = getNodes(e, STORE_NODE);
573
657
  if (r === "set") {
574
658
  if (S[t]) {
575
659
  setSignal(S[t], () => isWrappable(n) ? wrap(n, e) : n);
576
- } else if (!E) {
660
+ } else if (!O) {
577
661
  const r = upsertStoreNode(e, S, t, o, e[STORE_SNAPSHOT_PROPS]);
578
662
  setSignal(r, () => isWrappable(n) ? wrap(n, e) : n);
579
663
  }
@@ -585,7 +669,7 @@ function notifyStoreProperty(e, t, r, n, o, i) {
585
669
  } else {
586
670
  if (S[t]) {
587
671
  setSignal(S[t], undefined);
588
- } else if (!E) {
672
+ } else if (!O) {
589
673
  const r = upsertStoreNode(e, S, t, o, e[STORE_SNAPSHOT_PROPS]);
590
674
  setSignal(r, undefined);
591
675
  }
@@ -628,55 +712,79 @@ const storeTraps = {
628
712
  const r = e[STORE_NODE];
629
713
  const n = r && r[t];
630
714
  if (n !== undefined && e[STORE_VALUE][$TARGET] === undefined) {
631
- let t = read(n);
715
+ // readNodeFast is read()'s plain-signal fast path hoisted over the
716
+ // call; READ_SLOW means a global read window (latest/pending-check/
717
+ // transition/lane/snapshot capture) or a node layer is active, and
718
+ // only then does the full read() resolution have anything to do.
719
+ let t = readNodeFast(n);
720
+ if (t === READ_SLOW) t = read(n);
632
721
  if (t === $DELETED) t = undefined;
722
+ // Every node-writing site wraps wrappables before setSignal (see the
723
+ // dev assertion below), so re-wrapping on read is redundant — except
724
+ // during snapshot capture, where read() can surface a raw captured
725
+ // value seeded from snapshot props.
726
+ if (!snapshotCaptureActive) {
727
+ return t;
728
+ }
633
729
  return isWrappable(t) ? wrap(t, e) : t;
634
730
  }
635
731
  }
636
732
  const n = getObserver() === e[STORE_FIREWALL];
637
733
  const o = getNodes(e, STORE_NODE);
638
734
  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)) {
735
+ const O = e[STORE_VALUE];
736
+ 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
737
  return read(getNode(e, o, t, undefined));
642
738
  }
643
- const O = getOverlayLayer(e, t);
644
- const s = !!O;
739
+ const s = getOverlayLayer(e, t);
740
+ const E = !!s;
645
741
  const S = !!e[STORE_VALUE][$TARGET];
646
- const c = O ?? e[STORE_VALUE];
742
+ const f = s ?? e[STORE_VALUE];
647
743
  if (!i) {
648
- const n = Object.getOwnPropertyDescriptor(c, t);
744
+ const n = Object.getOwnPropertyDescriptor(f, t);
649
745
  if (n && n.get) return n.get.call(r);
650
- if (!n && !s && e[STORE_CUSTOM_PROTO]) {
651
- const e = unwrapStoreValue(c);
746
+ if (!n && !E && e[STORE_CUSTOM_PROTO]) {
747
+ const e = unwrapStoreValue(f);
652
748
  if (hasInheritedAccessor(e, t)) {
653
- return Reflect.get(c, t, r);
749
+ return Reflect.get(f, t, r);
654
750
  }
655
751
  }
656
752
  }
657
753
  if (writeOnly(r)) {
658
754
  if (isPrototypePollutionKey(t) && !hasOwnStoreProperty(e, t)) return undefined;
659
- let r = i && (s || !S) ? visibleNodeValue(i) : c[t];
755
+ let r = i && (E || !S) ? visibleNodeValue(i) : f[t];
660
756
  r === $DELETED && (r = undefined);
661
757
  if (!isWrappable(r)) return r;
758
+ // Shallow boundary: records are replaced, never edited in place. Reads
759
+ // inside a setter serve the raw so read-then-replace, filter/pop and
760
+ // projection derives all work; in-place mutation of a raw is inert by
761
+ // construction — the same contract as a markRaw child in a deep store.
762
+ if (e[STORE_SHALLOW]) return r;
662
763
  const n = wrap(r, e);
663
764
  Writing?.add(n);
664
765
  return n;
665
766
  }
666
- let T = i ? s || !S ? read(o[t]) : (read(o[t]), c[t]) : c[t];
667
- T === $DELETED && (T = undefined);
767
+ let R = i ? E || !S ? read(o[t]) : (read(o[t]), f[t]) : f[t];
768
+ R === $DELETED && (R = undefined);
668
769
  if (!i) {
669
- if (!s && typeof T === "function" && !Object.prototype.hasOwnProperty.call(c, t)) {
770
+ if (!E && typeof R === "function" && !Object.prototype.hasOwnProperty.call(f, t)) {
670
771
  let t;
671
- return !Array.isArray(e[STORE_VALUE]) && (t = Object.getPrototypeOf(e[STORE_VALUE])) && t !== Object.prototype ? T.bind(c) : T;
772
+ return !Array.isArray(e[STORE_VALUE]) && (t = Object.getPrototypeOf(e[STORE_VALUE])) && t !== Object.prototype ? R.bind(f) : R;
672
773
  } else if (getObserver() && !n) {
673
- return read(getNode(e, o, t, isWrappable(T) ? wrap(T, e) : T, isEqual, e[STORE_SNAPSHOT_PROPS]));
774
+ return read(getNode(e, o, t, isWrappable(R) ? wrap(R, e) : R, isEqual, e[STORE_SNAPSHOT_PROPS]));
674
775
  }
675
776
  }
676
777
  // Untracked fall-through (tracked reads already threw via their node in
677
778
  // read(); the dev strictRead error above wins first for memo parity).
678
- if (!n) throwIfUninitialized(e);
679
- return isWrappable(T) ? wrap(T, e) : T;
779
+ // Observer-present reads must NOT re-consult the flag here: during the
780
+ // settle flush the firewall has recomputed (first values live on the
781
+ // pending rail, served by read() above) but its UNINITIALIZED clear is
782
+ // deferred to batch commit — vetoing read()'s verdict with the stale flag
783
+ // threw a fresh NotReadyError for an already-settled source, which no
784
+ // sweep would ever release (#2938: projection over an async store wedged
785
+ // its Loading boundary on `undefined`).
786
+ if (!n && !getObserver()) throwIfUninitialized(e);
787
+ return isWrappable(R) ? wrap(R, e) : R;
680
788
  },
681
789
  has(e, t) {
682
790
  if (t === $PROXY || t === $TRACK || t === "__proto__") return true;
@@ -703,55 +811,67 @@ const storeTraps = {
703
811
  const n = e[$PROXY];
704
812
  if (writeOnly(n)) {
705
813
  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);
814
+ const {base: o, overrideKey: i, state: O} = prepareStoreWrite(e, n, t);
815
+ const s = getOverlayLayer(e, t);
816
+ const E = s ? s[t] : o;
817
+ const S = s ? s[t] !== $DELETED : t in e[STORE_VALUE];
818
+ // Shallow slots hold store proxies verbatim (pass-through reference,
819
+ // never raw-marked — see markRawOne/#2932); everything else unwraps
820
+ // and marks as usual.
821
+ const f = !!e[STORE_SHALLOW] && r?.[$TARGET] !== undefined;
822
+ const R = f ? r : unwrapStoreValue(r);
823
+ if (e[STORE_SHALLOW] && !f && isWrappable(R)) {
824
+ // Flip the live gate too: a bare add was inert unless something else
825
+ // had already marked a raw somewhere (wrap() checks rawValuesUsed
826
+ // first), so the documented set-trap ingest mark silently no-oped in
827
+ // apps whose only shallow data arrived through writes.
828
+ rawValuesUsed = true;
829
+ rawValues.add(R);
830
+ }
711
831
  // Symbol-keyed writes on arrays are metadata, not index writes — never run
712
832
  // them through the numeric index/length machinery (`parseInt` on a symbol
713
833
  // 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;
834
+ const c = typeof t === "string" ? Number(t) : -1;
835
+ const T = Array.isArray(O) && Number.isInteger(c) && c >= 0 && c < 4294967295 && String(c) === t;
836
+ const u = T ? c + 1 : 0;
837
+ const a = T && (getOverlayLayer(e, "length") ?? O).length;
838
+ const l = T && u > a ? u : undefined;
839
+ if (E === R && l === undefined) return true;
720
840
  armOptimisticStoreWrite(e, n);
721
- if (c !== undefined && c === o && l === undefined) {
841
+ if (R !== undefined && R === o && l === undefined) {
722
842
  delete e[i]?.[t];
723
843
  if (i === STORE_OPTIMISTIC_OVERRIDE) delete e[STORE_OPTIMISTIC_OWNERS]?.[t];
724
844
  } else {
725
845
  const r = e[i] || (e[i] = Object.create(null));
726
- r[t] = c;
846
+ r[t] = R;
727
847
  stampOptimisticOwner(e, i, t);
728
848
  if (l !== undefined) {
729
849
  r.length = l;
730
850
  stampOptimisticOwner(e, i, "length");
731
851
  }
732
852
  }
733
- notifyStoreProperty(e, t, "set", c, s, S);
853
+ notifyStoreProperty(e, t, "set", R, E, S);
734
854
  // Shrinking an array's length must remove the truncated indices, otherwise
735
855
  // they leak through `has`, `ownKeys`, and (tracked) index reads from the
736
856
  // 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) {
857
+ if (Array.isArray(O) && t === "length" && typeof R === "number" && typeof E === "number" && R < E) {
738
858
  const t = e[i] || (e[i] = Object.create(null));
739
- for (let r = c; r < s; r++) {
859
+ for (let r = R; r < E; r++) {
740
860
  if (t[r] === $DELETED) continue;
741
- const n = r in t ? t[r] : E[r];
742
- if (!(r in t) && !(r in E)) continue;
861
+ const n = r in t ? t[r] : O[r];
862
+ if (!(r in t) && !(r in O)) continue;
743
863
  t[r] = $DELETED;
744
864
  stampOptimisticOwner(e, i, r);
745
865
  notifyStoreProperty(e, r, "delete", undefined, n, true);
746
866
  }
747
867
  }
748
868
  // notify length change
749
- if (Array.isArray(E) && t !== "length" && l !== undefined) {
869
+ if (Array.isArray(O) && t !== "length" && l !== undefined) {
750
870
  const t = getNodes(e, STORE_NODE);
751
871
  if (t.length) {
752
872
  setSignal(t.length, l);
753
873
  } else if (!projectionWriteActive && !e[STORE_OPTIMISTIC]) {
754
- const r = upsertStoreNode(e, t, "length", u, e[STORE_SNAPSHOT_PROPS]);
874
+ const r = upsertStoreNode(e, t, "length", a, e[STORE_SNAPSHOT_PROPS]);
755
875
  setSignal(r, l);
756
876
  }
757
877
  }
@@ -767,11 +887,11 @@ const storeTraps = {
767
887
  untrack(() => {
768
888
  const {base: o, overrideKey: i} = prepareStoreWrite(e, n, t);
769
889
  armOptimisticStoreWrite(e, n);
770
- const E = "value" in r ? {
890
+ const O = "value" in r ? {
771
891
  ...r,
772
892
  value: unwrapStoreValue(r.value)
773
893
  } : r;
774
- Object.defineProperty(e[i] || (e[i] = Object.create(null)), t, E);
894
+ Object.defineProperty(e[i] || (e[i] = Object.create(null)), t, O);
775
895
  stampOptimisticOwner(e, i, t);
776
896
  notifyStoreProperty(e, t, "invalidate");
777
897
  if (false) ;
@@ -899,7 +1019,7 @@ function storeSetter(e, t) {
899
1019
  }
900
1020
 
901
1021
  function createStore(e, t, r) {
902
- const n = typeof e === "function", o = n ? createProjectionInternal(e, t, r).store : wrap(e);
1022
+ const n = typeof e === "function", o = n ? createProjectionInternal(e, t, r).store : t?.shallow ? wrapShallow(e) : wrap(e);
903
1023
  return [ o, n ? e => {
904
1024
  // Mark the projection as manually written before notifying property nodes.
905
1025
  suppressComputedRecompute(o[$REFRESH]);
@@ -907,4 +1027,4 @@ function createStore(e, t, r) {
907
1027
  } : e => storeSetter(o, e) ];
908
1028
  }
909
1029
 
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 };
1030
+ 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 };