@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.
- package/dist/dev.js +615 -125
- package/dist/node.cjs +1551 -1092
- package/dist/prod/core/core.js +106 -69
- package/dist/prod/core/effect.js +9 -9
- package/dist/prod/core/error.js +9 -0
- package/dist/prod/core/graph.js +3 -3
- package/dist/prod/core/heap.js +12 -12
- package/dist/prod/core/optimistic.js +6 -4
- package/dist/prod/core/owner.js +10 -10
- package/dist/prod/core/scheduler.js +6 -6
- package/dist/prod/core/verdict.js +2 -2
- package/dist/prod/map.js +53 -29
- package/dist/prod/store/optimistic.js +40 -31
- package/dist/prod/store/projection.js +25 -18
- package/dist/prod/store/reconcile.js +433 -208
- package/dist/prod/store/store.js +250 -116
- package/dist/prod/store/utils.js +34 -22
- package/dist/types/core/core.d.ts +15 -0
- package/dist/types/core/error.d.ts +1 -1
- package/dist/types/store/optimistic.d.ts +1 -1
- package/dist/types/store/reconcile.d.ts +17 -8
- package/dist/types/store/store.d.ts +20 -2
- package/dist/types-cjs/core/core.d.cts +15 -0
- package/dist/types-cjs/core/error.d.cts +1 -1
- package/dist/types-cjs/store/optimistic.d.cts +1 -1
- package/dist/types-cjs/store/reconcile.d.cts +17 -8
- package/dist/types-cjs/store/store.d.cts +20 -2
- package/package.json +1 -1
package/dist/prod/store/store.js
CHANGED
|
@@ -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
|
|
63
|
+
n[STORE_VALUE] = e;
|
|
64
|
+
initStoreFields(n);
|
|
40
65
|
} else {
|
|
41
66
|
n = {
|
|
42
|
-
|
|
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
|
-
|
|
68
|
-
if (
|
|
69
|
-
|
|
70
|
-
|
|
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] ||
|
|
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
|
|
101
|
-
const
|
|
102
|
-
t.set(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 (
|
|
206
|
+
if (O && e === "length") continue;
|
|
106
207
|
const n = e in o ? o[e] : i[e];
|
|
107
|
-
if (n !== $DELETED)
|
|
208
|
+
if (n !== $DELETED) s[e] = unwrapStoreValue(n, t, r);
|
|
108
209
|
}
|
|
109
|
-
if (
|
|
110
|
-
return
|
|
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
|
|
286
|
+
const O = signal(n, {
|
|
186
287
|
equals: o,
|
|
187
288
|
unobserved() {
|
|
188
|
-
if (t[r] ===
|
|
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
|
-
|
|
309
|
+
O.Ee = NOT_PENDING;
|
|
209
310
|
}
|
|
210
311
|
if (i && r in i) {
|
|
211
312
|
const e = i[r];
|
|
212
|
-
|
|
213
|
-
snapshotSources?.add(
|
|
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(
|
|
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
|
|
225
|
-
while (
|
|
226
|
-
|
|
227
|
-
|
|
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] =
|
|
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
|
|
266
|
-
const
|
|
267
|
-
if (o.has(
|
|
268
|
-
o.add(
|
|
269
|
-
t.scope.add(
|
|
270
|
-
let
|
|
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
|
-
|
|
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(
|
|
281
|
-
const e =
|
|
381
|
+
if (Array.isArray(O)) {
|
|
382
|
+
const e = s?.length ?? O.length;
|
|
282
383
|
for (let i = 0; i < e; i++) {
|
|
283
|
-
const e =
|
|
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
|
|
290
|
-
for (let e = 0, i =
|
|
291
|
-
const i =
|
|
292
|
-
const
|
|
293
|
-
if (!
|
|
294
|
-
walkAffectsScope(
|
|
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(
|
|
298
|
-
for (let i = 0,
|
|
299
|
-
const
|
|
300
|
-
if (!
|
|
301
|
-
walkAffectsScope(
|
|
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
|
|
593
|
+
const O = i ? STORE_OPTIMISTIC_OVERRIDE : STORE_OVERRIDE;
|
|
493
594
|
return {
|
|
494
595
|
base: o,
|
|
495
|
-
overrideKey:
|
|
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
|
|
530
|
-
registerTransientStoreNode(
|
|
531
|
-
return
|
|
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
|
|
539
|
-
const
|
|
540
|
-
const
|
|
541
|
-
if (
|
|
542
|
-
setSignal(
|
|
543
|
-
} else if (!
|
|
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,
|
|
646
|
+
setSignal(r, s);
|
|
546
647
|
}
|
|
547
|
-
const
|
|
648
|
+
const S = getNodes(e, STORE_NODE);
|
|
548
649
|
if (r === "set") {
|
|
549
|
-
if (
|
|
550
|
-
setSignal(
|
|
551
|
-
} else if (!
|
|
552
|
-
const r = upsertStoreNode(e,
|
|
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 (
|
|
557
|
-
setSignal(
|
|
558
|
-
delete
|
|
657
|
+
if (S[t]) {
|
|
658
|
+
setSignal(S[t], {});
|
|
659
|
+
delete S[t];
|
|
559
660
|
}
|
|
560
661
|
} else {
|
|
561
|
-
if (
|
|
562
|
-
setSignal(
|
|
563
|
-
} else if (!
|
|
564
|
-
const r = upsertStoreNode(e,
|
|
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
|
|
600
|
-
if (!i && !e[STORE_OVERRIDE] && !e[STORE_OPTIMISTIC_OVERRIDE] && !e[STORE_CUSTOM_PROTO] && !e[STORE_OPTIMISTIC] && !e[STORE_SNAPSHOT_PROPS] && !
|
|
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
|
|
604
|
-
const
|
|
605
|
-
const
|
|
606
|
-
const
|
|
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(
|
|
736
|
+
const n = Object.getOwnPropertyDescriptor(f, t);
|
|
609
737
|
if (n && n.get) return n.get.call(r);
|
|
610
|
-
if (!n && !
|
|
611
|
-
const e = unwrapStoreValue(
|
|
738
|
+
if (!n && !E && e[STORE_CUSTOM_PROTO]) {
|
|
739
|
+
const e = unwrapStoreValue(f);
|
|
612
740
|
if (hasInheritedAccessor(e, t)) {
|
|
613
|
-
return Reflect.get(
|
|
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 && (
|
|
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
|
|
627
|
-
|
|
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 (!
|
|
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 ?
|
|
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(
|
|
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(
|
|
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:
|
|
667
|
-
const
|
|
668
|
-
const
|
|
669
|
-
const
|
|
670
|
-
const
|
|
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
|
|
675
|
-
const
|
|
676
|
-
const
|
|
677
|
-
const u =
|
|
678
|
-
const
|
|
679
|
-
if (
|
|
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 (
|
|
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] =
|
|
820
|
+
r[t] = f;
|
|
687
821
|
stampOptimisticOwner(e, i, t);
|
|
688
|
-
if (
|
|
689
|
-
r.length =
|
|
822
|
+
if (a !== undefined) {
|
|
823
|
+
r.length = a;
|
|
690
824
|
stampOptimisticOwner(e, i, "length");
|
|
691
825
|
}
|
|
692
826
|
}
|
|
693
|
-
notifyStoreProperty(e, t, "set",
|
|
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(
|
|
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 =
|
|
833
|
+
for (let r = f; r < E; r++) {
|
|
700
834
|
if (t[r] === $DELETED) continue;
|
|
701
|
-
const n = r in t ? t[r] :
|
|
702
|
-
if (!(r in t) && !(r in
|
|
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(
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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 };
|