@solidjs/signals 2.0.0-beta.23 → 2.0.0-beta.24
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 +171 -65
- package/dist/node.cjs +1116 -995
- package/dist/prod/core/core.js +14 -11
- package/dist/prod/core/error.js +9 -0
- package/dist/prod/core/heap.js +12 -12
- package/dist/prod/core/scheduler.js +6 -6
- package/dist/prod/core/verdict.js +1 -1
- package/dist/prod/map.js +53 -29
- package/dist/prod/store/optimistic.js +29 -27
- package/dist/prod/store/reconcile.js +92 -61
- package/dist/prod/store/store.js +142 -102
- package/dist/prod/store/utils.js +12 -0
- package/dist/types/core/error.d.ts +1 -1
- package/dist/types/store/reconcile.d.ts +17 -8
- package/dist/types-cjs/core/error.d.cts +1 -1
- package/dist/types-cjs/store/reconcile.d.cts +17 -8
- package/package.json +1 -1
package/dist/prod/store/store.js
CHANGED
|
@@ -32,15 +32,40 @@ const STORE_VALUE = "v", STORE_OVERRIDE = "o", STORE_OPTIMISTIC_OVERRIDE = "x",
|
|
|
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[$PROXY] = null;
|
|
56
|
+
}
|
|
57
|
+
|
|
35
58
|
function createStoreProxy(e, t = storeTraps, r) {
|
|
36
59
|
let n;
|
|
37
60
|
if (Array.isArray(e)) {
|
|
38
61
|
n = [];
|
|
39
|
-
n
|
|
62
|
+
n[STORE_VALUE] = e;
|
|
63
|
+
initStoreFields(n);
|
|
40
64
|
} else {
|
|
41
65
|
n = {
|
|
42
|
-
|
|
66
|
+
[STORE_VALUE]: e
|
|
43
67
|
};
|
|
68
|
+
initStoreFields(n);
|
|
44
69
|
const t = e?.[$TARGET]?.[STORE_VALUE] ?? e;
|
|
45
70
|
const r = Object.getPrototypeOf(t);
|
|
46
71
|
if (r !== null && r !== Object.prototype) {
|
|
@@ -97,17 +122,17 @@ function unwrapStoreValue(e, t, r) {
|
|
|
97
122
|
if (!t) t = new Map;
|
|
98
123
|
if (t.has(e)) return t.get(e);
|
|
99
124
|
const i = n[STORE_VALUE];
|
|
100
|
-
const
|
|
101
|
-
const
|
|
102
|
-
t.set(e,
|
|
125
|
+
const E = Array.isArray(i);
|
|
126
|
+
const O = E ? [] : Object.create(Object.getPrototypeOf(i));
|
|
127
|
+
t.set(e, O);
|
|
103
128
|
r = n[STORE_LOOKUP] ?? storeLookup;
|
|
104
129
|
for (const e of getStoreKeys(i, o)) {
|
|
105
|
-
if (
|
|
130
|
+
if (E && e === "length") continue;
|
|
106
131
|
const n = e in o ? o[e] : i[e];
|
|
107
|
-
if (n !== $DELETED)
|
|
132
|
+
if (n !== $DELETED) O[e] = unwrapStoreValue(n, t, r);
|
|
108
133
|
}
|
|
109
|
-
if (
|
|
110
|
-
return
|
|
134
|
+
if (E) O.length = o.length ?? i.length;
|
|
135
|
+
return O;
|
|
111
136
|
}
|
|
112
137
|
|
|
113
138
|
function isPrototypePollutionKey(e) {
|
|
@@ -182,10 +207,10 @@ function getNodes(e, t) {
|
|
|
182
207
|
|
|
183
208
|
function getNode(e, t, r, n, o = isEqual, i) {
|
|
184
209
|
if (t[r]) return t[r];
|
|
185
|
-
const
|
|
210
|
+
const E = signal(n, {
|
|
186
211
|
equals: o,
|
|
187
212
|
unobserved() {
|
|
188
|
-
if (t[r] ===
|
|
213
|
+
if (t[r] === E) {
|
|
189
214
|
delete t[r];
|
|
190
215
|
// Drop the symbol-record mark once the last user symbol node is
|
|
191
216
|
// gone, so reconcile's fast path stops probing a now string-only
|
|
@@ -205,28 +230,28 @@ function getNode(e, t, r, n, o = isEqual, i) {
|
|
|
205
230
|
}
|
|
206
231
|
}, e[STORE_FIREWALL]);
|
|
207
232
|
if (e[STORE_OPTIMISTIC]) {
|
|
208
|
-
|
|
233
|
+
E.Ee = NOT_PENDING;
|
|
209
234
|
}
|
|
210
235
|
if (i && r in i) {
|
|
211
236
|
const e = i[r];
|
|
212
|
-
|
|
213
|
-
snapshotSources?.add(
|
|
237
|
+
E.Ve = e === undefined ? NO_SNAPSHOT : e;
|
|
238
|
+
snapshotSources?.add(E);
|
|
214
239
|
}
|
|
215
240
|
if (typeof r === "symbol" && r !== $TRACK && r !== $AFFECTS) symbolKeyedRecords.add(t);
|
|
216
241
|
// A node born inside a live mark's identity scope inherits the mark
|
|
217
242
|
// (the declaration walk could only cover nodes that existed then). The
|
|
218
243
|
// record's own $AFFECTS carrier is the mark's channel, never a member.
|
|
219
|
-
if (r !== $AFFECTS && affectsScopes.size) inheritAffectsMarks(
|
|
244
|
+
if (r !== $AFFECTS && affectsScopes.size) inheritAffectsMarks(E, e[STORE_VALUE], r);
|
|
220
245
|
// Node presence bubbles up the wrap chain (sticky), so reconcile can see
|
|
221
246
|
// "subscribers live somewhere below" through node-less intermediate
|
|
222
247
|
// records — the captured-proxy diff gate (#2902). Amortized O(1): stops at
|
|
223
248
|
// the first already-flagged ancestor.
|
|
224
|
-
let
|
|
225
|
-
while (
|
|
226
|
-
|
|
227
|
-
|
|
249
|
+
let O = e;
|
|
250
|
+
while (O && !O[STORE_DESC]) {
|
|
251
|
+
O[STORE_DESC] = true;
|
|
252
|
+
O = O[STORE_PARENT];
|
|
228
253
|
}
|
|
229
|
-
return t[r] =
|
|
254
|
+
return t[r] = E;
|
|
230
255
|
}
|
|
231
256
|
|
|
232
257
|
/**
|
|
@@ -263,42 +288,42 @@ const affectsScopes = new Map;
|
|
|
263
288
|
o) {
|
|
264
289
|
if (!isWrappable(e)) return;
|
|
265
290
|
const i = e[$TARGET] || (n ?? storeLookup).get(e)?.[$TARGET];
|
|
266
|
-
const
|
|
267
|
-
if (o.has(
|
|
268
|
-
o.add(
|
|
269
|
-
t.scope.add(
|
|
270
|
-
let
|
|
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;
|
|
271
296
|
if (i) {
|
|
272
297
|
collectRecordNodes(i[STORE_NODE], r);
|
|
273
298
|
collectRecordNodes(i[STORE_HAS], r);
|
|
274
|
-
|
|
299
|
+
O = mergedOverlay(i);
|
|
275
300
|
// Carry the effective lookup into untouched descendants. Default stores
|
|
276
301
|
// use the global lookup just like snapshotImpl; without it, nested raw
|
|
277
302
|
// objects fall back to string-only enumeration and symbol branches vanish.
|
|
278
303
|
n = i[STORE_LOOKUP] ?? n ?? storeLookup;
|
|
279
304
|
}
|
|
280
|
-
if (Array.isArray(
|
|
281
|
-
const e =
|
|
305
|
+
if (Array.isArray(E)) {
|
|
306
|
+
const e = O?.length ?? E.length;
|
|
282
307
|
for (let i = 0; i < e; i++) {
|
|
283
|
-
const e =
|
|
308
|
+
const e = O && i in O ? O[i] : E[i];
|
|
284
309
|
if (e !== $DELETED) walkAffectsScope(e, t, r, n, o);
|
|
285
310
|
}
|
|
286
311
|
// Arrays can also carry symbol metadata. Enumerate symbols separately to
|
|
287
312
|
// avoid scanning large index lists twice. Outside a store tree, retain the
|
|
288
313
|
// existing index-only walk.
|
|
289
|
-
const
|
|
290
|
-
for (let e = 0, i =
|
|
291
|
-
const i =
|
|
292
|
-
const
|
|
293
|
-
if (!
|
|
294
|
-
walkAffectsScope(
|
|
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);
|
|
318
|
+
if (!S || S.get) continue;
|
|
319
|
+
walkAffectsScope(S.value, t, r, n, o);
|
|
295
320
|
}
|
|
296
321
|
} else {
|
|
297
|
-
const e = i || n ? getStoreKeys(
|
|
298
|
-
for (let i = 0,
|
|
299
|
-
const
|
|
300
|
-
if (!
|
|
301
|
-
walkAffectsScope(
|
|
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);
|
|
302
327
|
}
|
|
303
328
|
}
|
|
304
329
|
}
|
|
@@ -489,10 +514,10 @@ function prepareStoreWrite(e, t, r) {
|
|
|
489
514
|
}
|
|
490
515
|
}
|
|
491
516
|
const i = e[STORE_OPTIMISTIC] && !projectionWriteActive;
|
|
492
|
-
const
|
|
517
|
+
const E = i ? STORE_OPTIMISTIC_OVERRIDE : STORE_OVERRIDE;
|
|
493
518
|
return {
|
|
494
519
|
base: o,
|
|
495
|
-
overrideKey:
|
|
520
|
+
overrideKey: E,
|
|
496
521
|
state: n
|
|
497
522
|
};
|
|
498
523
|
}
|
|
@@ -526,42 +551,42 @@ function prepareStoreWrite(e, t, r) {
|
|
|
526
551
|
function upsertStoreNode(e, t, r, n, o) {
|
|
527
552
|
if (t[r]) return t[r];
|
|
528
553
|
const i = isWrappable(n) ? wrap(n, e) : n;
|
|
529
|
-
const
|
|
530
|
-
registerTransientStoreNode(
|
|
531
|
-
return
|
|
554
|
+
const E = getNode(e, t, r, i, isEqual, o);
|
|
555
|
+
registerTransientStoreNode(E);
|
|
556
|
+
return E;
|
|
532
557
|
}
|
|
533
558
|
|
|
534
559
|
function notifyStoreProperty(e, t, r, n, o, i) {
|
|
535
560
|
// Cold writes upsert a transient pending node so untracked reads batch like signals.
|
|
536
561
|
// Skip for projection writes (different commit semantics) and for optimistic stores
|
|
537
562
|
// (whose whole purpose is immediate visibility via STORE_OPTIMISTIC_OVERRIDE).
|
|
538
|
-
const
|
|
539
|
-
const
|
|
540
|
-
const
|
|
541
|
-
if (
|
|
542
|
-
setSignal(
|
|
543
|
-
} else if (!
|
|
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) {
|
|
544
569
|
const r = upsertStoreNode(e, getNodes(e, STORE_HAS), t, i);
|
|
545
|
-
setSignal(r,
|
|
570
|
+
setSignal(r, O);
|
|
546
571
|
}
|
|
547
|
-
const
|
|
572
|
+
const S = getNodes(e, STORE_NODE);
|
|
548
573
|
if (r === "set") {
|
|
549
|
-
if (
|
|
550
|
-
setSignal(
|
|
551
|
-
} else if (!
|
|
552
|
-
const r = upsertStoreNode(e,
|
|
574
|
+
if (S[t]) {
|
|
575
|
+
setSignal(S[t], () => isWrappable(n) ? wrap(n, e) : n);
|
|
576
|
+
} else if (!E) {
|
|
577
|
+
const r = upsertStoreNode(e, S, t, o, e[STORE_SNAPSHOT_PROPS]);
|
|
553
578
|
setSignal(r, () => isWrappable(n) ? wrap(n, e) : n);
|
|
554
579
|
}
|
|
555
580
|
} else if (r === "invalidate") {
|
|
556
|
-
if (
|
|
557
|
-
setSignal(
|
|
558
|
-
delete
|
|
581
|
+
if (S[t]) {
|
|
582
|
+
setSignal(S[t], {});
|
|
583
|
+
delete S[t];
|
|
559
584
|
}
|
|
560
585
|
} else {
|
|
561
|
-
if (
|
|
562
|
-
setSignal(
|
|
563
|
-
} else if (!
|
|
564
|
-
const r = upsertStoreNode(e,
|
|
586
|
+
if (S[t]) {
|
|
587
|
+
setSignal(S[t], undefined);
|
|
588
|
+
} else if (!E) {
|
|
589
|
+
const r = upsertStoreNode(e, S, t, o, e[STORE_SNAPSHOT_PROPS]);
|
|
565
590
|
setSignal(r, undefined);
|
|
566
591
|
}
|
|
567
592
|
}
|
|
@@ -593,50 +618,65 @@ const storeTraps = {
|
|
|
593
618
|
trackSelf(e);
|
|
594
619
|
return r;
|
|
595
620
|
}
|
|
621
|
+
// Hot path: an existing node on a plain target — no firewall (so neither
|
|
622
|
+
// selfRead nor the uninitialized guard can apply), no override layers,
|
|
623
|
+
// no write scope, and a raw (non-proxy) source. This is the shape of
|
|
624
|
+
// every effect re-read of a settled store; it pays one node read and the
|
|
625
|
+
// wrap check, nothing else. Any exotic bit falls through to the full
|
|
626
|
+
// resolution below, and dev strictRead keeps its warning path.
|
|
627
|
+
if (e[STORE_FIREWALL] === undefined && e[STORE_OVERRIDE] === undefined && e[STORE_OPTIMISTIC_OVERRIDE] === undefined && !writeOverride && (Writing === null || !Writing.has(r))) {
|
|
628
|
+
const r = e[STORE_NODE];
|
|
629
|
+
const n = r && r[t];
|
|
630
|
+
if (n !== undefined && e[STORE_VALUE][$TARGET] === undefined) {
|
|
631
|
+
let t = read(n);
|
|
632
|
+
if (t === $DELETED) t = undefined;
|
|
633
|
+
return isWrappable(t) ? wrap(t, e) : t;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
596
636
|
const n = getObserver() === e[STORE_FIREWALL];
|
|
597
637
|
const o = getNodes(e, STORE_NODE);
|
|
598
638
|
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] && !
|
|
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)) {
|
|
601
641
|
return read(getNode(e, o, t, undefined));
|
|
602
642
|
}
|
|
603
|
-
const
|
|
604
|
-
const
|
|
605
|
-
const
|
|
606
|
-
const
|
|
643
|
+
const O = getOverlayLayer(e, t);
|
|
644
|
+
const s = !!O;
|
|
645
|
+
const S = !!e[STORE_VALUE][$TARGET];
|
|
646
|
+
const c = O ?? e[STORE_VALUE];
|
|
607
647
|
if (!i) {
|
|
608
|
-
const n = Object.getOwnPropertyDescriptor(
|
|
648
|
+
const n = Object.getOwnPropertyDescriptor(c, t);
|
|
609
649
|
if (n && n.get) return n.get.call(r);
|
|
610
|
-
if (!n && !
|
|
611
|
-
const e = unwrapStoreValue(
|
|
650
|
+
if (!n && !s && e[STORE_CUSTOM_PROTO]) {
|
|
651
|
+
const e = unwrapStoreValue(c);
|
|
612
652
|
if (hasInheritedAccessor(e, t)) {
|
|
613
|
-
return Reflect.get(
|
|
653
|
+
return Reflect.get(c, t, r);
|
|
614
654
|
}
|
|
615
655
|
}
|
|
616
656
|
}
|
|
617
657
|
if (writeOnly(r)) {
|
|
618
658
|
if (isPrototypePollutionKey(t) && !hasOwnStoreProperty(e, t)) return undefined;
|
|
619
|
-
let r = i && (
|
|
659
|
+
let r = i && (s || !S) ? visibleNodeValue(i) : c[t];
|
|
620
660
|
r === $DELETED && (r = undefined);
|
|
621
661
|
if (!isWrappable(r)) return r;
|
|
622
662
|
const n = wrap(r, e);
|
|
623
663
|
Writing?.add(n);
|
|
624
664
|
return n;
|
|
625
665
|
}
|
|
626
|
-
let
|
|
627
|
-
|
|
666
|
+
let T = i ? s || !S ? read(o[t]) : (read(o[t]), c[t]) : c[t];
|
|
667
|
+
T === $DELETED && (T = undefined);
|
|
628
668
|
if (!i) {
|
|
629
|
-
if (!
|
|
669
|
+
if (!s && typeof T === "function" && !Object.prototype.hasOwnProperty.call(c, t)) {
|
|
630
670
|
let t;
|
|
631
|
-
return !Array.isArray(e[STORE_VALUE]) && (t = Object.getPrototypeOf(e[STORE_VALUE])) && t !== Object.prototype ?
|
|
671
|
+
return !Array.isArray(e[STORE_VALUE]) && (t = Object.getPrototypeOf(e[STORE_VALUE])) && t !== Object.prototype ? T.bind(c) : T;
|
|
632
672
|
} else if (getObserver() && !n) {
|
|
633
|
-
return read(getNode(e, o, t, isWrappable(
|
|
673
|
+
return read(getNode(e, o, t, isWrappable(T) ? wrap(T, e) : T, isEqual, e[STORE_SNAPSHOT_PROPS]));
|
|
634
674
|
}
|
|
635
675
|
}
|
|
636
676
|
// Untracked fall-through (tracked reads already threw via their node in
|
|
637
677
|
// read(); the dev strictRead error above wins first for memo parity).
|
|
638
678
|
if (!n) throwIfUninitialized(e);
|
|
639
|
-
return isWrappable(
|
|
679
|
+
return isWrappable(T) ? wrap(T, e) : T;
|
|
640
680
|
},
|
|
641
681
|
has(e, t) {
|
|
642
682
|
if (t === $PROXY || t === $TRACK || t === "__proto__") return true;
|
|
@@ -663,50 +703,50 @@ const storeTraps = {
|
|
|
663
703
|
const n = e[$PROXY];
|
|
664
704
|
if (writeOnly(n)) {
|
|
665
705
|
untrack(() => {
|
|
666
|
-
const {base: o, overrideKey: i, state:
|
|
667
|
-
const
|
|
668
|
-
const
|
|
669
|
-
const
|
|
670
|
-
const
|
|
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);
|
|
671
711
|
// Symbol-keyed writes on arrays are metadata, not index writes — never run
|
|
672
712
|
// them through the numeric index/length machinery (`parseInt` on a symbol
|
|
673
713
|
// throws). #2769
|
|
674
|
-
const
|
|
675
|
-
const
|
|
676
|
-
const
|
|
677
|
-
const u =
|
|
678
|
-
const l =
|
|
679
|
-
if (
|
|
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;
|
|
680
720
|
armOptimisticStoreWrite(e, n);
|
|
681
|
-
if (
|
|
721
|
+
if (c !== undefined && c === o && l === undefined) {
|
|
682
722
|
delete e[i]?.[t];
|
|
683
723
|
if (i === STORE_OPTIMISTIC_OVERRIDE) delete e[STORE_OPTIMISTIC_OWNERS]?.[t];
|
|
684
724
|
} else {
|
|
685
725
|
const r = e[i] || (e[i] = Object.create(null));
|
|
686
|
-
r[t] =
|
|
726
|
+
r[t] = c;
|
|
687
727
|
stampOptimisticOwner(e, i, t);
|
|
688
728
|
if (l !== undefined) {
|
|
689
729
|
r.length = l;
|
|
690
730
|
stampOptimisticOwner(e, i, "length");
|
|
691
731
|
}
|
|
692
732
|
}
|
|
693
|
-
notifyStoreProperty(e, t, "set",
|
|
733
|
+
notifyStoreProperty(e, t, "set", c, s, S);
|
|
694
734
|
// Shrinking an array's length must remove the truncated indices, otherwise
|
|
695
735
|
// they leak through `has`, `ownKeys`, and (tracked) index reads from the
|
|
696
736
|
// underlying value. Mark each as deleted and notify so reactive reads update. #2768
|
|
697
|
-
if (Array.isArray(
|
|
737
|
+
if (Array.isArray(E) && t === "length" && typeof c === "number" && typeof s === "number" && c < s) {
|
|
698
738
|
const t = e[i] || (e[i] = Object.create(null));
|
|
699
|
-
for (let r =
|
|
739
|
+
for (let r = c; r < s; r++) {
|
|
700
740
|
if (t[r] === $DELETED) continue;
|
|
701
|
-
const n = r in t ? t[r] :
|
|
702
|
-
if (!(r in t) && !(r in
|
|
741
|
+
const n = r in t ? t[r] : E[r];
|
|
742
|
+
if (!(r in t) && !(r in E)) continue;
|
|
703
743
|
t[r] = $DELETED;
|
|
704
744
|
stampOptimisticOwner(e, i, r);
|
|
705
745
|
notifyStoreProperty(e, r, "delete", undefined, n, true);
|
|
706
746
|
}
|
|
707
747
|
}
|
|
708
748
|
// notify length change
|
|
709
|
-
if (Array.isArray(
|
|
749
|
+
if (Array.isArray(E) && t !== "length" && l !== undefined) {
|
|
710
750
|
const t = getNodes(e, STORE_NODE);
|
|
711
751
|
if (t.length) {
|
|
712
752
|
setSignal(t.length, l);
|
|
@@ -727,11 +767,11 @@ const storeTraps = {
|
|
|
727
767
|
untrack(() => {
|
|
728
768
|
const {base: o, overrideKey: i} = prepareStoreWrite(e, n, t);
|
|
729
769
|
armOptimisticStoreWrite(e, n);
|
|
730
|
-
const
|
|
770
|
+
const E = "value" in r ? {
|
|
731
771
|
...r,
|
|
732
772
|
value: unwrapStoreValue(r.value)
|
|
733
773
|
} : r;
|
|
734
|
-
Object.defineProperty(e[i] || (e[i] = Object.create(null)), t,
|
|
774
|
+
Object.defineProperty(e[i] || (e[i] = Object.create(null)), t, E);
|
|
735
775
|
stampOptimisticOwner(e, i, t);
|
|
736
776
|
notifyStoreProperty(e, t, "invalidate");
|
|
737
777
|
if (false) ;
|
package/dist/prod/store/utils.js
CHANGED
|
@@ -27,6 +27,18 @@ function snapshotImpl(e, t, r, n) {
|
|
|
27
27
|
if (pendingCheckActive) witnessAffectsMark(o);
|
|
28
28
|
}
|
|
29
29
|
i = mergedOverlay(o);
|
|
30
|
+
// A derived store's STORE_VALUE is the inner store's live proxy
|
|
31
|
+
// (store-in-store: createOptimisticStore/createProjection over a store).
|
|
32
|
+
// Without an overlay of its own, the fast path below would map to — and
|
|
33
|
+
// could return — that proxy verbatim. Recurse instead so the
|
|
34
|
+
// inner store's own target branch unwraps it (chains of any depth).
|
|
35
|
+
// With an overlay, a fresh `result` is always built, so the walk over the
|
|
36
|
+
// inner proxy already copies plain values.
|
|
37
|
+
if (!i && o[STORE_VALUE][$TARGET]) {
|
|
38
|
+
f = snapshotImpl(o[STORE_VALUE], t, r, n);
|
|
39
|
+
r.set(e, f);
|
|
40
|
+
return f;
|
|
41
|
+
}
|
|
30
42
|
s = Array.isArray(o[STORE_VALUE]);
|
|
31
43
|
r.set(e, i ? c = s ? [] : Object.create(Object.getPrototypeOf(o[STORE_VALUE])) : o[STORE_VALUE]);
|
|
32
44
|
e = o[STORE_VALUE];
|
|
@@ -23,7 +23,6 @@
|
|
|
23
23
|
* ```
|
|
24
24
|
*/
|
|
25
25
|
export declare class NotReadyError extends Error {
|
|
26
|
-
source: any;
|
|
27
26
|
/**
|
|
28
27
|
* Tags a visibility-only notification on the affects() boundary channel:
|
|
29
28
|
* boundaries update display state from it, but the root queue never
|
|
@@ -31,6 +30,7 @@ export declare class NotReadyError extends Error {
|
|
|
31
30
|
* construction.
|
|
32
31
|
*/
|
|
33
32
|
_markVisual?: boolean;
|
|
33
|
+
source: any;
|
|
34
34
|
constructor(source: any);
|
|
35
35
|
}
|
|
36
36
|
export declare class StatusError extends Error {
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Returns a draft-mutating function that smart-merges `value` into a store,
|
|
3
|
-
* preserving
|
|
4
|
-
* new states. Useful when applying server payloads or full-replacement data
|
|
5
|
-
* onto an existing store without losing fine-grained reactivity.
|
|
3
|
+
* preserving fine-grained reactivity: only changed leaves trigger updates.
|
|
6
4
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* With a `key` (default `"id"`), array items whose key matches between old
|
|
6
|
+
* and new states keep their identity (updated in place, moves and removals
|
|
7
|
+
* update the corresponding signals) — the shape for keyed server payloads.
|
|
8
|
+
* Items without the key field fall back to positional matching.
|
|
9
|
+
*
|
|
10
|
+
* With `key: null`, matching is purely positional: index N of the new array
|
|
11
|
+
* merges into index N of the old, and object properties merge recursively —
|
|
12
|
+
* the classic pattern for fixed-shape data that churns in place (dashboards,
|
|
13
|
+
* monitors), where no keyed diff pass is needed or wanted.
|
|
9
14
|
*
|
|
10
15
|
* @param value the next state to merge in
|
|
11
|
-
* @param key property name (string) or extractor function for stable
|
|
16
|
+
* @param key property name (string) or extractor function for stable
|
|
17
|
+
* identity (default `"id"`); pass `null` for positional merging
|
|
12
18
|
*
|
|
13
19
|
* @example
|
|
14
20
|
* ```ts
|
|
@@ -16,8 +22,11 @@
|
|
|
16
22
|
*
|
|
17
23
|
* async function refresh() {
|
|
18
24
|
* const fresh = await api.getTodos();
|
|
19
|
-
* setTodos(reconcile(fresh
|
|
25
|
+
* setTodos(reconcile(fresh)); // diff-merge by `id`
|
|
20
26
|
* }
|
|
27
|
+
*
|
|
28
|
+
* // fixed-shape polling data — positional merge
|
|
29
|
+
* setStats(reconcile(nextStats, null));
|
|
21
30
|
* ```
|
|
22
31
|
*/
|
|
23
|
-
export declare function reconcile<T extends U, U>(value: T, key
|
|
32
|
+
export declare function reconcile<T extends U, U>(value: T, key?: string | ((item: NonNullable<any>) => any) | null): (state: U) => void;
|
|
@@ -23,7 +23,6 @@
|
|
|
23
23
|
* ```
|
|
24
24
|
*/
|
|
25
25
|
export declare class NotReadyError extends Error {
|
|
26
|
-
source: any;
|
|
27
26
|
/**
|
|
28
27
|
* Tags a visibility-only notification on the affects() boundary channel:
|
|
29
28
|
* boundaries update display state from it, but the root queue never
|
|
@@ -31,6 +30,7 @@ export declare class NotReadyError extends Error {
|
|
|
31
30
|
* construction.
|
|
32
31
|
*/
|
|
33
32
|
_markVisual?: boolean;
|
|
33
|
+
source: any;
|
|
34
34
|
constructor(source: any);
|
|
35
35
|
}
|
|
36
36
|
export declare class StatusError extends Error {
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Returns a draft-mutating function that smart-merges `value` into a store,
|
|
3
|
-
* preserving
|
|
4
|
-
* new states. Useful when applying server payloads or full-replacement data
|
|
5
|
-
* onto an existing store without losing fine-grained reactivity.
|
|
3
|
+
* preserving fine-grained reactivity: only changed leaves trigger updates.
|
|
6
4
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* With a `key` (default `"id"`), array items whose key matches between old
|
|
6
|
+
* and new states keep their identity (updated in place, moves and removals
|
|
7
|
+
* update the corresponding signals) — the shape for keyed server payloads.
|
|
8
|
+
* Items without the key field fall back to positional matching.
|
|
9
|
+
*
|
|
10
|
+
* With `key: null`, matching is purely positional: index N of the new array
|
|
11
|
+
* merges into index N of the old, and object properties merge recursively —
|
|
12
|
+
* the classic pattern for fixed-shape data that churns in place (dashboards,
|
|
13
|
+
* monitors), where no keyed diff pass is needed or wanted.
|
|
9
14
|
*
|
|
10
15
|
* @param value the next state to merge in
|
|
11
|
-
* @param key property name (string) or extractor function for stable
|
|
16
|
+
* @param key property name (string) or extractor function for stable
|
|
17
|
+
* identity (default `"id"`); pass `null` for positional merging
|
|
12
18
|
*
|
|
13
19
|
* @example
|
|
14
20
|
* ```ts
|
|
@@ -16,8 +22,11 @@
|
|
|
16
22
|
*
|
|
17
23
|
* async function refresh() {
|
|
18
24
|
* const fresh = await api.getTodos();
|
|
19
|
-
* setTodos(reconcile(fresh
|
|
25
|
+
* setTodos(reconcile(fresh)); // diff-merge by `id`
|
|
20
26
|
* }
|
|
27
|
+
*
|
|
28
|
+
* // fixed-shape polling data — positional merge
|
|
29
|
+
* setStats(reconcile(nextStats, null));
|
|
21
30
|
* ```
|
|
22
31
|
*/
|
|
23
|
-
export declare function reconcile<T extends U, U>(value: T, key
|
|
32
|
+
export declare function reconcile<T extends U, U>(value: T, key?: string | ((item: NonNullable<any>) => any) | null): (state: U) => void;
|
package/package.json
CHANGED