@solidjs/signals 2.0.0-beta.8 → 2.0.0-rc.0
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/README.md +13 -9
- package/dist/dev.js +6078 -1440
- package/dist/node.cjs +7684 -3248
- package/dist/prod/affects.js +126 -0
- package/dist/prod/boundaries.js +572 -0
- package/dist/prod/core/action.js +139 -0
- package/dist/prod/core/async.js +553 -0
- package/dist/prod/core/constants.js +92 -0
- package/dist/prod/core/context.js +67 -0
- package/dist/prod/core/core.js +817 -0
- package/dist/prod/core/dev.js +3 -0
- package/dist/prod/core/effect.js +145 -0
- package/dist/prod/core/error.js +68 -0
- package/dist/prod/core/external.js +98 -0
- package/dist/prod/core/graph.js +104 -0
- package/dist/prod/core/heap.js +140 -0
- package/dist/prod/core/invariants.js +42 -0
- package/dist/prod/core/lanes.js +140 -0
- package/dist/prod/core/optimistic.js +245 -0
- package/dist/prod/core/owner.js +302 -0
- package/dist/prod/core/scheduler.js +749 -0
- package/dist/prod/core/verdict.js +369 -0
- package/dist/prod/index.js +45 -0
- package/dist/prod/map.js +317 -0
- package/dist/prod/signals.js +375 -0
- package/dist/prod/store/optimistic.js +217 -0
- package/dist/prod/store/projection.js +231 -0
- package/dist/prod/store/reconcile.js +707 -0
- package/dist/prod/store/store.js +1081 -0
- package/dist/prod/store/storePath.js +103 -0
- package/dist/prod/store/utils.js +328 -0
- package/dist/types/affects.d.ts +47 -0
- package/dist/types/boundaries.d.ts +93 -11
- package/dist/types/core/action.d.ts +63 -0
- package/dist/types/core/async.d.ts +16 -1
- package/dist/types/core/constants.d.ts +46 -0
- package/dist/types/core/context.d.ts +10 -2
- package/dist/types/core/core.d.ts +111 -17
- package/dist/types/core/dev.d.ts +17 -2
- package/dist/types/core/effect.d.ts +1 -2
- package/dist/types/core/error.d.ts +33 -0
- package/dist/types/core/external.d.ts +0 -11
- package/dist/types/core/graph.d.ts +2 -1
- package/dist/types/core/heap.d.ts +8 -0
- package/dist/types/core/index.d.ts +3 -2
- package/dist/types/core/invariants.d.ts +59 -0
- package/dist/types/core/lanes.d.ts +2 -0
- package/dist/types/core/optimistic.d.ts +6 -0
- package/dist/types/core/owner.d.ts +101 -3
- package/dist/types/core/scheduler.d.ts +97 -9
- package/dist/types/core/types.d.ts +68 -6
- package/dist/types/core/verdict.d.ts +2 -0
- package/dist/types/index.d.ts +4 -3
- package/dist/types/map.d.ts +50 -5
- package/dist/types/signals.d.ts +424 -19
- package/dist/types/store/index.d.ts +1 -1
- package/dist/types/store/optimistic.d.ts +39 -13
- package/dist/types/store/projection.d.ts +48 -18
- package/dist/types/store/reconcile.d.ts +46 -1
- package/dist/types/store/store.d.ts +164 -19
- package/dist/types/store/storePath.d.ts +28 -0
- package/dist/types/store/utils.d.ts +78 -7
- package/dist/types-cjs/affects.d.cts +47 -0
- package/dist/types-cjs/boundaries.d.cts +93 -11
- package/dist/types-cjs/core/action.d.cts +63 -0
- package/dist/types-cjs/core/async.d.cts +16 -1
- package/dist/types-cjs/core/constants.d.cts +46 -0
- package/dist/types-cjs/core/context.d.cts +10 -2
- package/dist/types-cjs/core/core.d.cts +111 -17
- package/dist/types-cjs/core/dev.d.cts +17 -2
- package/dist/types-cjs/core/effect.d.cts +1 -2
- package/dist/types-cjs/core/error.d.cts +33 -0
- package/dist/types-cjs/core/external.d.cts +0 -11
- package/dist/types-cjs/core/graph.d.cts +2 -1
- package/dist/types-cjs/core/heap.d.cts +8 -0
- package/dist/types-cjs/core/index.d.cts +3 -2
- package/dist/types-cjs/core/invariants.d.cts +59 -0
- package/dist/types-cjs/core/lanes.d.cts +2 -0
- package/dist/types-cjs/core/optimistic.d.cts +6 -0
- package/dist/types-cjs/core/owner.d.cts +101 -3
- package/dist/types-cjs/core/scheduler.d.cts +97 -9
- package/dist/types-cjs/core/types.d.cts +68 -6
- package/dist/types-cjs/core/verdict.d.cts +2 -0
- package/dist/types-cjs/index.d.cts +4 -3
- package/dist/types-cjs/map.d.cts +50 -5
- package/dist/types-cjs/signals.d.cts +424 -19
- package/dist/types-cjs/store/index.d.cts +1 -1
- package/dist/types-cjs/store/optimistic.d.cts +39 -13
- package/dist/types-cjs/store/projection.d.cts +48 -18
- package/dist/types-cjs/store/reconcile.d.cts +46 -1
- package/dist/types-cjs/store/store.d.cts +164 -19
- package/dist/types-cjs/store/storePath.d.cts +28 -0
- package/dist/types-cjs/store/utils.d.cts +78 -7
- package/package.json +14 -11
- package/dist/prod.js +0 -3577
|
@@ -0,0 +1,707 @@
|
|
|
1
|
+
import { setSignal, untrack } from "../core/core.js";
|
|
2
|
+
|
|
3
|
+
import "../core/scheduler.js";
|
|
4
|
+
|
|
5
|
+
import "../core/invariants.js";
|
|
6
|
+
|
|
7
|
+
import "../core/verdict.js";
|
|
8
|
+
|
|
9
|
+
import "../core/effect.js";
|
|
10
|
+
|
|
11
|
+
import { $TARGET, STORE_SHALLOW, STORE_OVERRIDE, STORE_OPTIMISTIC_OVERRIDE, STORE_VALUE, STORE_LOOKUP, $PROXY, storeLookup, markRawIngest, STORE_NODE, $TRACK, STORE_HAS, notifySelf, isWrappable, wrap, rawValuesUsed, isRawValue, STORE_DESC, symbolKeyedRecords, $DELETED, getKeys, getStoreSymbols, STORE_WRAP, lookupTarget } from "./store.js";
|
|
12
|
+
|
|
13
|
+
// Enumerate a node record's keys. Keeps the common string-key path on the
|
|
14
|
+
// `Object.keys` fast path; only records currently holding a user symbol node
|
|
15
|
+
// (marked by `getNode`) pay for symbol enumeration. `$TRACK` is the only
|
|
16
|
+
// internal symbol a node record can hold and callers handle it separately.
|
|
17
|
+
function nodeKeys(e) {
|
|
18
|
+
const t = Object.keys(e);
|
|
19
|
+
if (symbolKeyedRecords.has(e)) {
|
|
20
|
+
const n = Object.getOwnPropertySymbols(e);
|
|
21
|
+
for (let e = 0, r = n.length; e < r; e++) {
|
|
22
|
+
if (n[e] !== $TRACK) t.push(n[e]);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return t;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function unwrap(e) {
|
|
29
|
+
// Primitives can't be store proxies; skip the symbol lookups (which box the
|
|
30
|
+
// primitive) for the common leaf case.
|
|
31
|
+
if (e === null || typeof e !== "object") return e;
|
|
32
|
+
return e[$TARGET]?.[STORE_VALUE] ?? e;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getOverrideValue(e, t, n, r) {
|
|
36
|
+
if (r && n in r) return r[n];
|
|
37
|
+
return t && n in t ? t[n] : e[n];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Append `o`'s *enumerable* own symbol keys from a pre-fetched symbol list.
|
|
41
|
+
function addEnumSymbols(e, t, n) {
|
|
42
|
+
for (let r = 0, a = t.length; r < a; r++) {
|
|
43
|
+
if (Object.prototype.propertyIsEnumerable.call(e, t[r])) n.add(t[r]);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getAllKeys(e, t, n) {
|
|
48
|
+
// Symbols are merged explicitly below; keep the common string-key path on
|
|
49
|
+
// Object.keys() and avoid reflecting the base symbols twice.
|
|
50
|
+
const r = getKeys(e, t);
|
|
51
|
+
const a = Object.keys(n);
|
|
52
|
+
// `value` can be a wrapped store (store-in-store) whose ownKeys trap tracks;
|
|
53
|
+
// mirror `getKeys` and enumerate its symbols untracked in that case.
|
|
54
|
+
const i = e[$TARGET] ? untrack(() => Object.getOwnPropertySymbols(e)) : Object.getOwnPropertySymbols(e);
|
|
55
|
+
const l = Object.getOwnPropertySymbols(n);
|
|
56
|
+
// Symbol-free diff (the overwhelmingly common case) stays on the exact
|
|
57
|
+
// pre-#2851 path, including the identical-key-sets fast path from #2756.
|
|
58
|
+
if (i.length === 0 && l.length === 0) {
|
|
59
|
+
if (r.length === a.length) {
|
|
60
|
+
let e = true;
|
|
61
|
+
for (let t = 0; t < r.length; t++) {
|
|
62
|
+
if (r[t] !== a[t]) {
|
|
63
|
+
e = false;
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (e) return r;
|
|
68
|
+
}
|
|
69
|
+
const e = new Set(r);
|
|
70
|
+
for (let t = 0; t < a.length; t++) e.add(a[t]);
|
|
71
|
+
return Array.from(e);
|
|
72
|
+
}
|
|
73
|
+
// Symbol-aware diff (#2851): base symbols join the set, then override
|
|
74
|
+
// adds/deletes are re-applied so a `$DELETED` symbol stays deleted, then
|
|
75
|
+
// `next`'s keys (a key present in next is never deleted by the diff).
|
|
76
|
+
const s = new Set(r);
|
|
77
|
+
addEnumSymbols(e, i, s);
|
|
78
|
+
if (t) {
|
|
79
|
+
for (const e of Reflect.ownKeys(t)) {
|
|
80
|
+
t[e] === $DELETED ? s.delete(e) : s.add(e);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
for (let e = 0; e < a.length; e++) s.add(a[e]);
|
|
84
|
+
addEnumSymbols(n, l, s);
|
|
85
|
+
return Array.from(s);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Array entries can be `null`/`undefined`/primitives, not just keyed objects.
|
|
89
|
+
// These helpers keep the keyed paths from passing a non-object to `keyFn` (which
|
|
90
|
+
// assumes an object) or to `wrap()` (which assumes a wrappable value).
|
|
91
|
+
function wrapValue(e, t) {
|
|
92
|
+
return isWrappable(e) ? wrap(e, t) : e;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function itemKey(e, t) {
|
|
96
|
+
return isWrappable(e) ? t(e) : e;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function keyedMatch(e, t, n) {
|
|
100
|
+
return e === t || isWrappable(e) && isWrappable(t) && n(e) === n(t);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// A pair of array slots may only be merged into when both sides are real store
|
|
104
|
+
// children of the SAME container kind. An array and an object are different
|
|
105
|
+
// shapes, and merging one into the other leaves the slot's proxy permanently
|
|
106
|
+
// mismatched with its value (an array target holding an object, so
|
|
107
|
+
// `Array.isArray`/spread/`map` lie). The object diff has always applied this
|
|
108
|
+
// rule; the array paths reach the same recursion through `keyedMatch` /
|
|
109
|
+
// positional pairing, where two keyless wrappables "match" regardless of kind.
|
|
110
|
+
function recursablePair(e, t) {
|
|
111
|
+
return isWrappable(e) && isWrappable(t) && !(rawValuesUsed && (isRawValue(e) || isRawValue(t))) && Array.isArray(e) === Array.isArray(t);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Array reconciliation updates the slots it visits, then swaps STORE_VALUE.
|
|
115
|
+
// Previously tracked keys that are absent from `next` still need invalidating,
|
|
116
|
+
// and `in` dependencies should follow the new value's membership. Use
|
|
117
|
+
// membership rather than length arithmetic so sparse arrays and named array
|
|
118
|
+
// props behave like normal property reads.
|
|
119
|
+
// Inline key iteration on purpose: these loops run per array target per
|
|
120
|
+
// reconcile pass, and a shared helper means a closure + per-key call in
|
|
121
|
+
// instruction counts. String-only records (the common case) iterate in
|
|
122
|
+
// place; symbol-bearing records take the nodeKeys array path.
|
|
123
|
+
function syncArrayNodeMembership(e, t) {
|
|
124
|
+
let n = e[STORE_NODE];
|
|
125
|
+
if (n) {
|
|
126
|
+
if (symbolKeyedRecords.has(n)) {
|
|
127
|
+
const e = nodeKeys(n);
|
|
128
|
+
for (let r = 0, a = e.length; r < a; r++) {
|
|
129
|
+
e[r] in t || setSignal(n[e[r]], undefined);
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
for (const e in n) {
|
|
133
|
+
e in t || setSignal(n[e], undefined);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (n = e[STORE_HAS]) {
|
|
138
|
+
if (symbolKeyedRecords.has(n)) {
|
|
139
|
+
const e = nodeKeys(n);
|
|
140
|
+
for (let r = 0, a = e.length; r < a; r++) {
|
|
141
|
+
setSignal(n[e[r]], e[r] in t);
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
for (const e in n) {
|
|
145
|
+
setSignal(n[e], e in t);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Recurse into a matched wrappable child pair without manufacturing a proxy:
|
|
152
|
+
// resolve the child's target through the lookup and dispatch directly. A
|
|
153
|
+
// lookup miss means the child was never observed — no proxy, no nodes, no
|
|
154
|
+
// subscribers anywhere below — so the parent's swap making `next`
|
|
155
|
+
// authoritative IS the whole update; a later read wraps next's child on
|
|
156
|
+
// demand. This turns the diff from O(previous graph) into O(observed graph)
|
|
157
|
+
// and drops the wrap()/$PROXY/$TARGET round-trip per visited child.
|
|
158
|
+
// Wrap-family stores (projections/optimistic) own child proxy creation and
|
|
159
|
+
// keep the proxy-based recursion.
|
|
160
|
+
function applyStateChild(e, t, n, r) {
|
|
161
|
+
if (n[STORE_WRAP] !== undefined) {
|
|
162
|
+
applyState(e, wrap(t, n), r);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const a = t[$TARGET] ?? storeLookup.get(t);
|
|
166
|
+
if (a === undefined) return;
|
|
167
|
+
e = unwrap(e);
|
|
168
|
+
if (a[STORE_SHALLOW]) {
|
|
169
|
+
applyStateShallow(e, a);
|
|
170
|
+
} else if (a[STORE_OVERRIDE] || a[STORE_OPTIMISTIC_OVERRIDE]) {
|
|
171
|
+
applyStateSlow(e, a, r);
|
|
172
|
+
} else {
|
|
173
|
+
applyStateFast(e, a, r);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Reconcile a single array slot: recurse into a wrappable pair, otherwise replace
|
|
178
|
+
// the node's value outright (covers object→primitive and primitive→object).
|
|
179
|
+
function applyArrayItem(e, t, n, r, a) {
|
|
180
|
+
if (recursablePair(t, e)) {
|
|
181
|
+
const i = wrap(t, n);
|
|
182
|
+
r && setSignal(r, i);
|
|
183
|
+
applyState(e, i, a);
|
|
184
|
+
} else r && setSignal(r, wrapValue(e, n));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* The captured-proxy half of the object diff (#2902): descend into keyed-
|
|
189
|
+
* matching children that have NO node at this level but shelter subscribers
|
|
190
|
+
* somewhere below (their target's sticky `STORE_DESC` flag, bubbled up by
|
|
191
|
+
* `getNode`). Without this, a proxy captured through untracked reads — a
|
|
192
|
+
* `<For>` row handed to a child component — detaches from the diff the
|
|
193
|
+
* moment no intermediate level happens to be tracked, and its live
|
|
194
|
+
* subscribers go permanently stale. Never-subscribed branches have no flag
|
|
195
|
+
* and stay pruned exactly as before; keys the main loop already visited
|
|
196
|
+
* (node present) are skipped. Callers gate on the parent's own flag and on
|
|
197
|
+
* `$TRACK` absence (an enumeration-tracked record already diffs every key).
|
|
198
|
+
*/ function applyDescendants(e, t, n, r, a, i, l) {
|
|
199
|
+
const s = n[STORE_LOOKUP] || storeLookup;
|
|
200
|
+
if (i) {
|
|
201
|
+
const n = getKeys(e, i).concat(getStoreSymbols(e, i));
|
|
202
|
+
for (let o = 0, f = n.length; o < f; o++) {
|
|
203
|
+
const f = n[o];
|
|
204
|
+
if (r?.[f]) continue;
|
|
205
|
+
// main loop already diffed this slot
|
|
206
|
+
const u = unwrap(getOverrideValue(e, i, f, l));
|
|
207
|
+
if (!isWrappable(u)) continue;
|
|
208
|
+
descendInto(u, t[f], s, a);
|
|
209
|
+
}
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
// No-override path (every applyStateFast call): iterate in place instead of
|
|
213
|
+
// building keys + symbols + concat arrays per object per pass. The cheap
|
|
214
|
+
// bails (noded key, primitive value) stay inline — only genuine descent
|
|
215
|
+
// candidates pay a call.
|
|
216
|
+
for (const n in e) {
|
|
217
|
+
if (r?.[n]) continue;
|
|
218
|
+
// main loop already diffed this slot
|
|
219
|
+
const i = unwrap(e[n]);
|
|
220
|
+
if (!isWrappable(i)) continue;
|
|
221
|
+
descendInto(i, t[n], s, a);
|
|
222
|
+
}
|
|
223
|
+
const o = Object.getOwnPropertySymbols(e);
|
|
224
|
+
for (let n = 0, i = o.length; n < i; n++) {
|
|
225
|
+
if (Object.prototype.propertyIsEnumerable.call(e, o[n])) {
|
|
226
|
+
if (r?.[o[n]]) continue;
|
|
227
|
+
const i = unwrap(e[o[n]]);
|
|
228
|
+
if (!isWrappable(i)) continue;
|
|
229
|
+
descendInto(i, t[o[n]], s, a);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function descendInto(e, t, n, r) {
|
|
235
|
+
const a = lookupTarget(e, n);
|
|
236
|
+
if (!a?.[STORE_DESC]) return;
|
|
237
|
+
const i = unwrap(t);
|
|
238
|
+
if (e === i || !isWrappable(i) || Array.isArray(e) !== Array.isArray(i) || r(e) != null && r(e) !== r(i)) return;
|
|
239
|
+
if (a[STORE_SHALLOW]) {
|
|
240
|
+
applyStateShallow(i, a);
|
|
241
|
+
} else if (a[STORE_OVERRIDE] || a[STORE_OPTIMISTIC_OVERRIDE]) {
|
|
242
|
+
applyStateSlow(i, a, r);
|
|
243
|
+
} else {
|
|
244
|
+
applyStateFast(i, a, r);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Dispatcher: every applyState call (including recursion) checks for the
|
|
249
|
+
// presence of override / optimistic-override slots once and routes to the
|
|
250
|
+
// appropriate body. The fast body never calls `getOverrideValue` and never
|
|
251
|
+
// branches on a `fastPath` boolean, so V8 sees a tighter, more inlinable
|
|
252
|
+
// shape for the overwhelmingly common case of plain stores.
|
|
253
|
+
function applyState(e, t, n) {
|
|
254
|
+
// Array items and root calls can pass a store proxy as `next`; normalize to
|
|
255
|
+
// its raw value or the swap would set a store's STORE_VALUE to its own proxy.
|
|
256
|
+
e = unwrap(e);
|
|
257
|
+
const r = t?.[$TARGET];
|
|
258
|
+
if (!r) return;
|
|
259
|
+
if (r[STORE_SHALLOW]) {
|
|
260
|
+
applyStateShallow(e, r);
|
|
261
|
+
} else if (r[STORE_OVERRIDE] || r[STORE_OPTIMISTIC_OVERRIDE]) {
|
|
262
|
+
applyStateSlow(e, r, n);
|
|
263
|
+
} else {
|
|
264
|
+
applyStateFast(e, r, n);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Shallow boundary diff: the target's own keys are reactive, its values are
|
|
269
|
+
// raw records replaced by reference — no recursion, no wrapping. Arrays merge
|
|
270
|
+
// positionally (below a shallow boundary the VALUE is the identity; keyed
|
|
271
|
+
// row identity belongs to the consumer, e.g. <For keyed>). Incoming
|
|
272
|
+
// wrappables are sticky-marked raw so they present raw through every store.
|
|
273
|
+
// One pass over a shallow target's node record: replace changed slots with
|
|
274
|
+
// raw next values, null out slots absent from next. Returns whether anything
|
|
275
|
+
// differed.
|
|
276
|
+
function shallowDiffNodes(e, t, n, r) {
|
|
277
|
+
let a = false;
|
|
278
|
+
for (const i in e) {
|
|
279
|
+
if (r && i === "length") continue;
|
|
280
|
+
if (i in t) {
|
|
281
|
+
const r = t[i];
|
|
282
|
+
if (r !== n(i)) {
|
|
283
|
+
a = true;
|
|
284
|
+
setSignal(e[i], r);
|
|
285
|
+
}
|
|
286
|
+
} else {
|
|
287
|
+
a = true;
|
|
288
|
+
setSignal(e[i], undefined);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return a;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function applyStateShallow(e, t, n) {
|
|
295
|
+
const r = t[STORE_VALUE];
|
|
296
|
+
const a = t[STORE_OVERRIDE];
|
|
297
|
+
const i = t[STORE_OPTIMISTIC_OVERRIDE];
|
|
298
|
+
if (e === r && !a && !i) return;
|
|
299
|
+
// Setter-staged writes fold into the diff: previous values resolve through
|
|
300
|
+
// the override layers (so a replaced slot compares against what readers
|
|
301
|
+
// saw), and the regular override clears with the swap — reconcile makes
|
|
302
|
+
// `next` the authoritative base, same as the deep slow path.
|
|
303
|
+
const prevAt = e => {
|
|
304
|
+
const t = getOverrideValue(r, a, e, i);
|
|
305
|
+
return t === $DELETED ? undefined : t;
|
|
306
|
+
};
|
|
307
|
+
t[STORE_OVERRIDE] = undefined;
|
|
308
|
+
const l = t[STORE_LOOKUP];
|
|
309
|
+
l !== undefined ? l.set(e, t[$PROXY]) : storeLookup.set(e, t);
|
|
310
|
+
t[STORE_VALUE] = e;
|
|
311
|
+
markRawIngest(e);
|
|
312
|
+
const s = t[STORE_NODE];
|
|
313
|
+
const o = s && s[$TRACK];
|
|
314
|
+
let f = false;
|
|
315
|
+
if (Array.isArray(r)) {
|
|
316
|
+
const n = a?.length ?? i?.length ?? r.length;
|
|
317
|
+
if (s) {
|
|
318
|
+
f = shallowDiffNodes(s, e, prevAt, true);
|
|
319
|
+
if (s.length && n !== e.length) setSignal(s.length, e.length);
|
|
320
|
+
}
|
|
321
|
+
if (!f && (o || t[STORE_HAS])) {
|
|
322
|
+
// Slots without nodes still feed $TRACK enumerators / `in` probes.
|
|
323
|
+
if (n !== e.length) f = true; else {
|
|
324
|
+
for (let t = 0, n = e.length; t < n; t++) {
|
|
325
|
+
if (prevAt(t) !== e[t]) {
|
|
326
|
+
f = true;
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
} else {
|
|
333
|
+
if (s) {
|
|
334
|
+
f = shallowDiffNodes(s, e, prevAt, false);
|
|
335
|
+
}
|
|
336
|
+
if (!f && (o || t[STORE_HAS])) f = true;
|
|
337
|
+
}
|
|
338
|
+
let u = t[STORE_HAS];
|
|
339
|
+
if (u) {
|
|
340
|
+
for (const t in u) {
|
|
341
|
+
setSignal(u[t], t in e);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
f && notifySelf(t);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function applyStateFast(e, t, n) {
|
|
348
|
+
const r = t[STORE_VALUE];
|
|
349
|
+
if (e === r) return;
|
|
350
|
+
const a = t[STORE_NODE];
|
|
351
|
+
// swap
|
|
352
|
+
{
|
|
353
|
+
const n = t[STORE_LOOKUP];
|
|
354
|
+
n !== undefined ? n.set(e, t[$PROXY]) : storeLookup.set(e, t);
|
|
355
|
+
}
|
|
356
|
+
t[STORE_VALUE] = e;
|
|
357
|
+
// merge
|
|
358
|
+
if (Array.isArray(r)) {
|
|
359
|
+
let i = false;
|
|
360
|
+
const l = r.length;
|
|
361
|
+
if (e.length && l && isWrappable(e[0]) && n(e[0]) != null) {
|
|
362
|
+
let s, o, f, u, p, c, S, E;
|
|
363
|
+
for (f = 0, u = Math.min(l, e.length); f < u && keyedMatch(c = r[f], e[f], n); f++) {
|
|
364
|
+
// keyedMatch established both sides wrappable unless they're the
|
|
365
|
+
// SAME reference — and an identical slot is a guaranteed no-op
|
|
366
|
+
// (the child's STORE_VALUE tracks the previous graph), so skip
|
|
367
|
+
// the recursion dispatch entirely. Raw-marked values are leaves:
|
|
368
|
+
// replace the slot node instead of recursing.
|
|
369
|
+
if (c !== e[f]) {
|
|
370
|
+
if (!recursablePair(c, e[f])) {
|
|
371
|
+
a?.[f] && setSignal(a[f], wrapValue(e[f], t));
|
|
372
|
+
} else applyStateChild(e[f], c, t, n);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
// Every position key-matched at equal length (the steady-state shape
|
|
376
|
+
// of a polling tick): membership, length, and order are unchanged —
|
|
377
|
+
// nothing below could do observable work, so skip the staging
|
|
378
|
+
// allocations and membership sync outright.
|
|
379
|
+
if (f === e.length && f === l) return;
|
|
380
|
+
const O = new Array(e.length), R = new Map;
|
|
381
|
+
for (u = l - 1, p = e.length - 1; u >= f && p >= f && keyedMatch(c = r[u], e[p], n); u--,
|
|
382
|
+
p--) {
|
|
383
|
+
O[p] = c;
|
|
384
|
+
}
|
|
385
|
+
if (f > p || f > u) {
|
|
386
|
+
for (o = f; o <= p; o++) {
|
|
387
|
+
i = true;
|
|
388
|
+
a?.[o] && setSignal(a[o], wrapValue(e[o], t));
|
|
389
|
+
}
|
|
390
|
+
for (;o < e.length; o++) {
|
|
391
|
+
i = true;
|
|
392
|
+
applyArrayItem(e[o], O[o], t, a?.[o], n);
|
|
393
|
+
}
|
|
394
|
+
syncArrayNodeMembership(t, e);
|
|
395
|
+
(i || l !== e.length) && notifySelf(t);
|
|
396
|
+
l !== e.length && a?.length && setSignal(a.length, e.length);
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
S = new Array(p + 1);
|
|
400
|
+
for (o = p; o >= f; o--) {
|
|
401
|
+
c = e[o];
|
|
402
|
+
E = itemKey(c, n);
|
|
403
|
+
s = R.get(E);
|
|
404
|
+
S[o] = s === undefined ? -1 : s;
|
|
405
|
+
R.set(E, o);
|
|
406
|
+
}
|
|
407
|
+
for (s = f; s <= u; s++) {
|
|
408
|
+
c = r[s];
|
|
409
|
+
E = itemKey(c, n);
|
|
410
|
+
o = R.get(E);
|
|
411
|
+
if (o !== undefined && o !== -1) {
|
|
412
|
+
O[o] = c;
|
|
413
|
+
o = S[o];
|
|
414
|
+
R.set(E, o);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
for (o = f; o < e.length; o++) {
|
|
418
|
+
if (o in O) {
|
|
419
|
+
applyArrayItem(e[o], O[o], t, a?.[o], n);
|
|
420
|
+
} else a?.[o] && setSignal(a[o], wrapValue(e[o], t));
|
|
421
|
+
}
|
|
422
|
+
if (f < e.length) i = true;
|
|
423
|
+
} else if (e.length) {
|
|
424
|
+
for (let l = 0, s = e.length; l < s; l++) {
|
|
425
|
+
const s = r[l];
|
|
426
|
+
if (recursablePair(s, e[l])) {
|
|
427
|
+
if (s !== e[l]) applyStateChild(e[l], s, t, n);
|
|
428
|
+
} else {
|
|
429
|
+
if (s !== e[l]) i = true;
|
|
430
|
+
a?.[l] && setSignal(a[l], wrapValue(e[l], t));
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
syncArrayNodeMembership(t, e);
|
|
435
|
+
if (l !== e.length) {
|
|
436
|
+
i = true;
|
|
437
|
+
a?.length && setSignal(a.length, e.length);
|
|
438
|
+
}
|
|
439
|
+
i && notifySelf(t);
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
// values
|
|
443
|
+
let i = t[STORE_NODE];
|
|
444
|
+
let l;
|
|
445
|
+
if (i) {
|
|
446
|
+
l = i[$TRACK];
|
|
447
|
+
// The per-key body is duplicated across both loops on purpose: this is
|
|
448
|
+
// the hottest object-diff site and a shared helper costs a per-key call
|
|
449
|
+
// in instruction counts (CodSpeed regressed ~7% on deep-tree reconciles
|
|
450
|
+
// with the extracted form).
|
|
451
|
+
if (l || symbolKeyedRecords.has(i)) {
|
|
452
|
+
const a = l ? getAllKeys(r, undefined, e) : nodeKeys(i);
|
|
453
|
+
for (let s = 0, o = a.length; s < o; s++) {
|
|
454
|
+
const o = a[s];
|
|
455
|
+
const f = i[o];
|
|
456
|
+
const u = unwrap(r[o]);
|
|
457
|
+
const p = unwrap(e[o]);
|
|
458
|
+
if (u === p) continue;
|
|
459
|
+
if (!u || !isWrappable(u) || !isWrappable(p) ||
|
|
460
|
+
// Raw-marked values are leaves replaced by reference — a "wrappable
|
|
461
|
+
// pair" is only recursable when both sides are actual store children.
|
|
462
|
+
rawValuesUsed && (isRawValue(u) || isRawValue(p)) || Array.isArray(u) !== Array.isArray(p) || n(u) != null && n(u) !== n(p)) {
|
|
463
|
+
l && setSignal(l, void 0);
|
|
464
|
+
f && setSignal(f, isWrappable(p) ? wrap(p, t) : p);
|
|
465
|
+
} else applyStateChild(p, u, t, n);
|
|
466
|
+
}
|
|
467
|
+
} else {
|
|
468
|
+
// Untracked, string-only node records (the overwhelmingly common case)
|
|
469
|
+
// iterate in place — nodeKeys() allocated a fresh key array per object
|
|
470
|
+
// per pass, which dominates allocation on large-graph reconciles.
|
|
471
|
+
for (const a in i) {
|
|
472
|
+
const s = i[a];
|
|
473
|
+
const o = unwrap(r[a]);
|
|
474
|
+
const f = unwrap(e[a]);
|
|
475
|
+
if (o === f) continue;
|
|
476
|
+
if (!o || !isWrappable(o) || !isWrappable(f) ||
|
|
477
|
+
// Raw-marked values are leaves replaced by reference — a "wrappable
|
|
478
|
+
// pair" is only recursable when both sides are actual store children.
|
|
479
|
+
rawValuesUsed && (isRawValue(o) || isRawValue(f)) || Array.isArray(o) !== Array.isArray(f) || n(o) != null && n(o) !== n(f)) {
|
|
480
|
+
l && setSignal(l, void 0);
|
|
481
|
+
s && setSignal(s, isWrappable(f) ? wrap(f, t) : f);
|
|
482
|
+
} else applyStateChild(f, o, t, n);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
if (!l && t[STORE_DESC]) applyDescendants(r, e, t, i, n);
|
|
487
|
+
// has
|
|
488
|
+
if (i = t[STORE_HAS]) {
|
|
489
|
+
const t = nodeKeys(i);
|
|
490
|
+
for (let n = 0, r = t.length; n < r; n++) {
|
|
491
|
+
const r = t[n];
|
|
492
|
+
setSignal(i[r], r in e);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function applyStateSlow(e, t, n) {
|
|
498
|
+
const r = t[STORE_VALUE];
|
|
499
|
+
const a = t[STORE_OVERRIDE];
|
|
500
|
+
const i = t[STORE_OPTIMISTIC_OVERRIDE];
|
|
501
|
+
let l = t[STORE_NODE];
|
|
502
|
+
// swap
|
|
503
|
+
{
|
|
504
|
+
const n = t[STORE_LOOKUP];
|
|
505
|
+
n !== undefined ? n.set(e, t[$PROXY]) : storeLookup.set(e, t);
|
|
506
|
+
}
|
|
507
|
+
t[STORE_VALUE] = e;
|
|
508
|
+
t[STORE_OVERRIDE] = undefined;
|
|
509
|
+
// merge
|
|
510
|
+
if (Array.isArray(r)) {
|
|
511
|
+
let s = false;
|
|
512
|
+
const o = getOverrideValue(r, a, "length", i);
|
|
513
|
+
if (e.length && o && isWrappable(e[0]) && n(e[0]) != null) {
|
|
514
|
+
let f, u, p, c, S, E, O, R;
|
|
515
|
+
for (p = 0, c = Math.min(o, e.length); p < c && keyedMatch(E = getOverrideValue(r, a, p, i), e[p], n); p++) {
|
|
516
|
+
if (E !== e[p] && isWrappable(E) && isWrappable(e[p])) {
|
|
517
|
+
if (!recursablePair(E, e[p])) {
|
|
518
|
+
l?.[p] && setSignal(l[p], wrapValue(e[p], t));
|
|
519
|
+
} else applyState(e[p], wrap(E, t), n);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
const d = new Array(e.length), y = new Map;
|
|
523
|
+
for (c = o - 1, S = e.length - 1; c >= p && S >= p && keyedMatch(E = getOverrideValue(r, a, c, i), e[S], n); c--,
|
|
524
|
+
S--) {
|
|
525
|
+
d[S] = E;
|
|
526
|
+
}
|
|
527
|
+
if (p > S || p > c) {
|
|
528
|
+
for (u = p; u <= S; u++) {
|
|
529
|
+
s = true;
|
|
530
|
+
l?.[u] && setSignal(l[u], wrapValue(e[u], t));
|
|
531
|
+
}
|
|
532
|
+
for (;u < e.length; u++) {
|
|
533
|
+
s = true;
|
|
534
|
+
applyArrayItem(e[u], d[u], t, l?.[u], n);
|
|
535
|
+
}
|
|
536
|
+
const r = e.length;
|
|
537
|
+
syncArrayNodeMembership(t, e);
|
|
538
|
+
(s || o !== r) && notifySelf(t);
|
|
539
|
+
o !== r && l?.length && setSignal(l.length, r);
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
O = new Array(S + 1);
|
|
543
|
+
for (u = S; u >= p; u--) {
|
|
544
|
+
E = e[u];
|
|
545
|
+
R = itemKey(E, n);
|
|
546
|
+
f = y.get(R);
|
|
547
|
+
O[u] = f === undefined ? -1 : f;
|
|
548
|
+
y.set(R, u);
|
|
549
|
+
}
|
|
550
|
+
for (f = p; f <= c; f++) {
|
|
551
|
+
E = getOverrideValue(r, a, f, i);
|
|
552
|
+
R = itemKey(E, n);
|
|
553
|
+
u = y.get(R);
|
|
554
|
+
if (u !== undefined && u !== -1) {
|
|
555
|
+
d[u] = E;
|
|
556
|
+
u = O[u];
|
|
557
|
+
y.set(R, u);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
for (u = p; u < e.length; u++) {
|
|
561
|
+
if (u in d) {
|
|
562
|
+
applyArrayItem(e[u], d[u], t, l?.[u], n);
|
|
563
|
+
} else l?.[u] && setSignal(l[u], wrapValue(e[u], t));
|
|
564
|
+
}
|
|
565
|
+
if (p < e.length) s = true;
|
|
566
|
+
} else if (e.length) {
|
|
567
|
+
for (let o = 0, f = e.length; o < f; o++) {
|
|
568
|
+
const f = getOverrideValue(r, a, o, i);
|
|
569
|
+
if (recursablePair(f, e[o])) {
|
|
570
|
+
if (f !== e[o]) applyState(e[o], wrap(f, t), n);
|
|
571
|
+
} else {
|
|
572
|
+
if (f !== e[o]) s = true;
|
|
573
|
+
l?.[o] && setSignal(l[o], wrapValue(e[o], t));
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
const f = e.length;
|
|
578
|
+
syncArrayNodeMembership(t, e);
|
|
579
|
+
if (o !== f) {
|
|
580
|
+
s = true;
|
|
581
|
+
l?.length && setSignal(l.length, f);
|
|
582
|
+
}
|
|
583
|
+
s && notifySelf(t);
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
// values
|
|
587
|
+
let s;
|
|
588
|
+
if (l) {
|
|
589
|
+
s = l[$TRACK];
|
|
590
|
+
const o = s ? getAllKeys(r, a, e) : nodeKeys(l);
|
|
591
|
+
for (let f = 0, u = o.length; f < u; f++) {
|
|
592
|
+
const u = o[f];
|
|
593
|
+
const p = l[u];
|
|
594
|
+
const c = unwrap(getOverrideValue(r, a, u, i));
|
|
595
|
+
let S = unwrap(e[u]);
|
|
596
|
+
if (c === S) continue;
|
|
597
|
+
if (!c || !isWrappable(c) || !isWrappable(S) || rawValuesUsed && (isRawValue(c) || isRawValue(S)) || Array.isArray(c) !== Array.isArray(S) || n(c) != null && n(c) !== n(S)) {
|
|
598
|
+
s && setSignal(s, void 0);
|
|
599
|
+
p && setSignal(p, isWrappable(S) ? wrap(S, t) : S);
|
|
600
|
+
} else applyState(S, wrap(c, t), n);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
if (!s && t[STORE_DESC]) applyDescendants(r, e, t, l, n, a, i);
|
|
604
|
+
// has
|
|
605
|
+
if (l = t[STORE_HAS]) {
|
|
606
|
+
const t = nodeKeys(l);
|
|
607
|
+
for (let n = 0, r = t.length; n < r; n++) {
|
|
608
|
+
const r = t[n];
|
|
609
|
+
setSignal(l[r], r in e);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// No-key reconcile: every item reports "no key", which routes array diffs to
|
|
615
|
+
// the positional branch and object descent to plain per-property merging.
|
|
616
|
+
const NOKEY = () => null;
|
|
617
|
+
|
|
618
|
+
// Identity-as-key: distinct objects never match, so every slot takes the diff's
|
|
619
|
+
// "not the same entity" branch and is replaced by reference rather than merged
|
|
620
|
+
// into. Slots holding the same raw on both sides still keep their proxy.
|
|
621
|
+
const IDENTITY = e => e;
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Shared body of `reconcile()` and the projection commit. `replace` is the
|
|
625
|
+
* only difference: a projection commit is a value swap, not a merge — its root
|
|
626
|
+
* proxy is a cell handed out by `createProjection` that can never change
|
|
627
|
+
* reference, so a derive returning a different entity is not the slot mistake
|
|
628
|
+
* `reconcile()` throws on. Nothing below the root survives that swap, which is
|
|
629
|
+
* the rule the keyed diff already applies at a nested slot on a key mismatch.
|
|
630
|
+
*
|
|
631
|
+
* @internal
|
|
632
|
+
*/ function reconcileState(e, t, n, r) {
|
|
633
|
+
if (t == null) throw new Error("");
|
|
634
|
+
// A projection commit whose derive returns a foreign store adopts it as the
|
|
635
|
+
// live backing (store-in-store chain — the same shape as a store-proxy
|
|
636
|
+
// seed): reads route through the inner store's own graph, so its updates
|
|
637
|
+
// flow with no re-derive, and a derive with no dependencies never recomputes
|
|
638
|
+
// (#2941). The diff below still runs against raw values so THIS store's
|
|
639
|
+
// existing subscribers see the swap; the live proxy is installed after.
|
|
640
|
+
// Shallow projections keep their raw-ingest contract and never chain.
|
|
641
|
+
let a;
|
|
642
|
+
const i = r ? t[$TARGET] : undefined;
|
|
643
|
+
if (i !== undefined) {
|
|
644
|
+
if (e?.[$TARGET] !== undefined && e[$TARGET] !== i && !i[STORE_SHALLOW]) {
|
|
645
|
+
if (i[STORE_VALUE] === e) return;
|
|
646
|
+
// already chained to this store
|
|
647
|
+
a = e;
|
|
648
|
+
}
|
|
649
|
+
// Re-diffing a previously chained backing goes through its raw — reads
|
|
650
|
+
// off the outgoing proxy would subscribe this computed to a store it is
|
|
651
|
+
// about to drop.
|
|
652
|
+
while (i[STORE_VALUE]?.[$TARGET] !== undefined) i[STORE_VALUE] = unwrap(i[STORE_VALUE]);
|
|
653
|
+
}
|
|
654
|
+
if (n === null) applyState(e, t, NOKEY); else {
|
|
655
|
+
let a = typeof n === "string" ? e => e[n] : n;
|
|
656
|
+
const i = a(t);
|
|
657
|
+
if (i !== undefined && a(e) !== i) {
|
|
658
|
+
if (!r) throw new Error("");
|
|
659
|
+
// Or the outgoing raw keeps resolving to this proxy and surfaces the
|
|
660
|
+
// incoming entity wherever it reappears in this family.
|
|
661
|
+
const n = t[$TARGET];
|
|
662
|
+
if (n && n[STORE_VALUE] !== unwrap(e)) n[STORE_LOOKUP]?.delete(n[STORE_VALUE]);
|
|
663
|
+
a = IDENTITY;
|
|
664
|
+
}
|
|
665
|
+
applyState(e, t, a);
|
|
666
|
+
}
|
|
667
|
+
if (a !== undefined) i[STORE_VALUE] = a;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Returns a draft-mutating function that smart-merges `value` into a store,
|
|
672
|
+
* preserving fine-grained reactivity: only changed leaves trigger updates.
|
|
673
|
+
*
|
|
674
|
+
* With a `key` (default `"id"`), array items whose key matches between old
|
|
675
|
+
* and new states keep their identity (updated in place, moves and removals
|
|
676
|
+
* update the corresponding signals) — the shape for keyed server payloads.
|
|
677
|
+
* Items without the key field fall back to positional matching.
|
|
678
|
+
*
|
|
679
|
+
* With `key: null`, matching is purely positional: index N of the new array
|
|
680
|
+
* merges into index N of the old, and object properties merge recursively —
|
|
681
|
+
* the classic pattern for fixed-shape data that churns in place (dashboards,
|
|
682
|
+
* monitors), where no keyed diff pass is needed or wanted.
|
|
683
|
+
*
|
|
684
|
+
* Merging into a slot that holds a *different* entity throws — the caller
|
|
685
|
+
* picked the slot, so a key mismatch there is a bug.
|
|
686
|
+
*
|
|
687
|
+
* @param value the next state to merge in
|
|
688
|
+
* @param key property name (string) or extractor function for stable
|
|
689
|
+
* identity (default `"id"`); pass `null` for positional merging
|
|
690
|
+
*
|
|
691
|
+
* @example
|
|
692
|
+
* ```ts
|
|
693
|
+
* const [todos, setTodos] = createStore<Todo[]>([]);
|
|
694
|
+
*
|
|
695
|
+
* async function refresh() {
|
|
696
|
+
* const fresh = await api.getTodos();
|
|
697
|
+
* setTodos(reconcile(fresh)); // diff-merge by `id`
|
|
698
|
+
* }
|
|
699
|
+
*
|
|
700
|
+
* // fixed-shape polling data — positional merge
|
|
701
|
+
* setStats(reconcile(nextStats, null));
|
|
702
|
+
* ```
|
|
703
|
+
*/ function reconcile(e, t = "id") {
|
|
704
|
+
return n => reconcileState(e, n, t, false);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
export { reconcile, reconcileState };
|