@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.
- package/dist/dev.js +465 -81
- package/dist/node.cjs +990 -652
- package/dist/prod/core/core.js +110 -76
- package/dist/prod/core/effect.js +9 -9
- package/dist/prod/core/graph.js +3 -3
- package/dist/prod/core/heap.js +23 -23
- package/dist/prod/core/optimistic.js +6 -4
- package/dist/prod/core/owner.js +10 -10
- package/dist/prod/core/scheduler.js +12 -12
- package/dist/prod/core/verdict.js +3 -3
- package/dist/prod/store/optimistic.js +12 -5
- package/dist/prod/store/projection.js +25 -18
- package/dist/prod/store/reconcile.js +405 -211
- package/dist/prod/store/store.js +196 -102
- package/dist/prod/store/utils.js +22 -22
- package/dist/types/core/core.d.ts +15 -0
- package/dist/types/store/optimistic.d.ts +1 -1
- package/dist/types/store/store.d.ts +20 -2
- package/dist/types-cjs/core/core.d.cts +15 -0
- package/dist/types-cjs/store/optimistic.d.cts +1 -1
- 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,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
|
-
|
|
93
|
-
if (
|
|
94
|
-
|
|
95
|
-
|
|
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] ||
|
|
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
|
|
126
|
-
const
|
|
127
|
-
t.set(e,
|
|
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 (
|
|
206
|
+
if (O && e === "length") continue;
|
|
131
207
|
const n = e in o ? o[e] : i[e];
|
|
132
|
-
if (n !== $DELETED)
|
|
208
|
+
if (n !== $DELETED) s[e] = unwrapStoreValue(n, t, r);
|
|
133
209
|
}
|
|
134
|
-
if (
|
|
135
|
-
return
|
|
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
|
|
286
|
+
const O = signal(n, {
|
|
211
287
|
equals: o,
|
|
212
288
|
unobserved() {
|
|
213
|
-
if (t[r] ===
|
|
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
|
-
|
|
309
|
+
O.Ee = NOT_PENDING;
|
|
234
310
|
}
|
|
235
311
|
if (i && r in i) {
|
|
236
312
|
const e = i[r];
|
|
237
|
-
|
|
238
|
-
snapshotSources?.add(
|
|
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(
|
|
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
|
|
250
|
-
while (
|
|
251
|
-
|
|
252
|
-
|
|
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] =
|
|
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
|
|
291
|
-
const
|
|
292
|
-
if (o.has(
|
|
293
|
-
o.add(
|
|
294
|
-
t.scope.add(
|
|
295
|
-
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;
|
|
296
372
|
if (i) {
|
|
297
373
|
collectRecordNodes(i[STORE_NODE], r);
|
|
298
374
|
collectRecordNodes(i[STORE_HAS], r);
|
|
299
|
-
|
|
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(
|
|
306
|
-
const e =
|
|
381
|
+
if (Array.isArray(O)) {
|
|
382
|
+
const e = s?.length ?? O.length;
|
|
307
383
|
for (let i = 0; i < e; i++) {
|
|
308
|
-
const e =
|
|
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
|
|
315
|
-
for (let e = 0, i =
|
|
316
|
-
const i =
|
|
317
|
-
const S = getPropertyDescriptor(
|
|
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(
|
|
323
|
-
for (let i = 0,
|
|
324
|
-
const
|
|
325
|
-
if (!
|
|
326
|
-
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);
|
|
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
|
|
593
|
+
const O = i ? STORE_OPTIMISTIC_OVERRIDE : STORE_OVERRIDE;
|
|
518
594
|
return {
|
|
519
595
|
base: o,
|
|
520
|
-
overrideKey:
|
|
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
|
|
555
|
-
registerTransientStoreNode(
|
|
556
|
-
return
|
|
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
|
|
564
|
-
const
|
|
565
|
-
const
|
|
566
|
-
if (
|
|
567
|
-
setSignal(
|
|
568
|
-
} 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) {
|
|
569
645
|
const r = upsertStoreNode(e, getNodes(e, STORE_HAS), t, i);
|
|
570
|
-
setSignal(r,
|
|
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 (!
|
|
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 (!
|
|
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
|
-
|
|
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
|
|
640
|
-
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)) {
|
|
641
729
|
return read(getNode(e, o, t, undefined));
|
|
642
730
|
}
|
|
643
|
-
const
|
|
644
|
-
const
|
|
731
|
+
const s = getOverlayLayer(e, t);
|
|
732
|
+
const E = !!s;
|
|
645
733
|
const S = !!e[STORE_VALUE][$TARGET];
|
|
646
|
-
const
|
|
734
|
+
const f = s ?? e[STORE_VALUE];
|
|
647
735
|
if (!i) {
|
|
648
|
-
const n = Object.getOwnPropertyDescriptor(
|
|
736
|
+
const n = Object.getOwnPropertyDescriptor(f, t);
|
|
649
737
|
if (n && n.get) return n.get.call(r);
|
|
650
|
-
if (!n && !
|
|
651
|
-
const e = unwrapStoreValue(
|
|
738
|
+
if (!n && !E && e[STORE_CUSTOM_PROTO]) {
|
|
739
|
+
const e = unwrapStoreValue(f);
|
|
652
740
|
if (hasInheritedAccessor(e, t)) {
|
|
653
|
-
return Reflect.get(
|
|
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 && (
|
|
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
|
|
667
|
-
|
|
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 (!
|
|
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 ?
|
|
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(
|
|
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(
|
|
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:
|
|
707
|
-
const
|
|
708
|
-
const
|
|
709
|
-
const S =
|
|
710
|
-
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);
|
|
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
|
|
715
|
-
const R = Array.isArray(
|
|
716
|
-
const
|
|
717
|
-
const u = R && (getOverlayLayer(e, "length") ??
|
|
718
|
-
const
|
|
719
|
-
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;
|
|
720
814
|
armOptimisticStoreWrite(e, n);
|
|
721
|
-
if (
|
|
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] =
|
|
820
|
+
r[t] = f;
|
|
727
821
|
stampOptimisticOwner(e, i, t);
|
|
728
|
-
if (
|
|
729
|
-
r.length =
|
|
822
|
+
if (a !== undefined) {
|
|
823
|
+
r.length = a;
|
|
730
824
|
stampOptimisticOwner(e, i, "length");
|
|
731
825
|
}
|
|
732
826
|
}
|
|
733
|
-
notifyStoreProperty(e, t, "set",
|
|
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(
|
|
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 =
|
|
833
|
+
for (let r = f; r < E; r++) {
|
|
740
834
|
if (t[r] === $DELETED) continue;
|
|
741
|
-
const n = r in t ? t[r] :
|
|
742
|
-
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;
|
|
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(
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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 };
|
package/dist/prod/store/utils.js
CHANGED
|
@@ -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] ||
|
|
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
|
|
53
|
-
u = i &&
|
|
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[
|
|
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
|
|
64
|
-
for (let s = 0, l =
|
|
65
|
-
const l =
|
|
66
|
-
const
|
|
67
|
-
if (!
|
|
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,
|
|
87
|
-
const
|
|
88
|
-
const l = Object.getOwnPropertyDescriptor(e,
|
|
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[
|
|
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
|
|
105
|
-
let l = s[
|
|
106
|
-
const
|
|
107
|
-
if (
|
|
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
|
/**
|