@solidjs/signals 2.0.0-beta.9 → 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.
Files changed (91) hide show
  1. package/README.md +13 -9
  2. package/dist/dev.js +5971 -1426
  3. package/dist/node.cjs +7681 -3293
  4. package/dist/prod/affects.js +126 -0
  5. package/dist/prod/boundaries.js +572 -0
  6. package/dist/prod/core/action.js +139 -0
  7. package/dist/prod/core/async.js +553 -0
  8. package/dist/prod/core/constants.js +92 -0
  9. package/dist/prod/core/context.js +67 -0
  10. package/dist/prod/core/core.js +817 -0
  11. package/dist/prod/core/dev.js +3 -0
  12. package/dist/prod/core/effect.js +145 -0
  13. package/dist/prod/core/error.js +68 -0
  14. package/dist/prod/core/external.js +98 -0
  15. package/dist/prod/core/graph.js +104 -0
  16. package/dist/prod/core/heap.js +140 -0
  17. package/dist/prod/core/invariants.js +42 -0
  18. package/dist/prod/core/lanes.js +140 -0
  19. package/dist/prod/core/optimistic.js +245 -0
  20. package/dist/prod/core/owner.js +302 -0
  21. package/dist/prod/core/scheduler.js +749 -0
  22. package/dist/prod/core/verdict.js +369 -0
  23. package/dist/prod/index.js +45 -0
  24. package/dist/prod/map.js +317 -0
  25. package/dist/prod/signals.js +375 -0
  26. package/dist/prod/store/optimistic.js +217 -0
  27. package/dist/prod/store/projection.js +231 -0
  28. package/dist/prod/store/reconcile.js +707 -0
  29. package/dist/prod/store/store.js +1081 -0
  30. package/dist/prod/store/storePath.js +103 -0
  31. package/dist/prod/store/utils.js +328 -0
  32. package/dist/types/affects.d.ts +47 -0
  33. package/dist/types/boundaries.d.ts +60 -13
  34. package/dist/types/core/action.d.ts +35 -6
  35. package/dist/types/core/async.d.ts +16 -1
  36. package/dist/types/core/constants.d.ts +35 -6
  37. package/dist/types/core/context.d.ts +10 -2
  38. package/dist/types/core/core.d.ts +58 -63
  39. package/dist/types/core/dev.d.ts +17 -2
  40. package/dist/types/core/effect.d.ts +1 -2
  41. package/dist/types/core/error.d.ts +33 -0
  42. package/dist/types/core/external.d.ts +0 -11
  43. package/dist/types/core/graph.d.ts +2 -1
  44. package/dist/types/core/heap.d.ts +8 -0
  45. package/dist/types/core/index.d.ts +3 -2
  46. package/dist/types/core/invariants.d.ts +59 -0
  47. package/dist/types/core/lanes.d.ts +2 -0
  48. package/dist/types/core/optimistic.d.ts +6 -0
  49. package/dist/types/core/owner.d.ts +50 -3
  50. package/dist/types/core/scheduler.d.ts +82 -10
  51. package/dist/types/core/types.d.ts +66 -1
  52. package/dist/types/core/verdict.d.ts +2 -0
  53. package/dist/types/index.d.ts +3 -2
  54. package/dist/types/map.d.ts +21 -5
  55. package/dist/types/signals.d.ts +164 -12
  56. package/dist/types/store/index.d.ts +1 -1
  57. package/dist/types/store/optimistic.d.ts +3 -3
  58. package/dist/types/store/projection.d.ts +10 -6
  59. package/dist/types/store/reconcile.d.ts +31 -8
  60. package/dist/types/store/store.d.ts +101 -6
  61. package/dist/types-cjs/affects.d.cts +47 -0
  62. package/dist/types-cjs/boundaries.d.cts +60 -13
  63. package/dist/types-cjs/core/action.d.cts +35 -6
  64. package/dist/types-cjs/core/async.d.cts +16 -1
  65. package/dist/types-cjs/core/constants.d.cts +35 -6
  66. package/dist/types-cjs/core/context.d.cts +10 -2
  67. package/dist/types-cjs/core/core.d.cts +58 -63
  68. package/dist/types-cjs/core/dev.d.cts +17 -2
  69. package/dist/types-cjs/core/effect.d.cts +1 -2
  70. package/dist/types-cjs/core/error.d.cts +33 -0
  71. package/dist/types-cjs/core/external.d.cts +0 -11
  72. package/dist/types-cjs/core/graph.d.cts +2 -1
  73. package/dist/types-cjs/core/heap.d.cts +8 -0
  74. package/dist/types-cjs/core/index.d.cts +3 -2
  75. package/dist/types-cjs/core/invariants.d.cts +59 -0
  76. package/dist/types-cjs/core/lanes.d.cts +2 -0
  77. package/dist/types-cjs/core/optimistic.d.cts +6 -0
  78. package/dist/types-cjs/core/owner.d.cts +50 -3
  79. package/dist/types-cjs/core/scheduler.d.cts +82 -10
  80. package/dist/types-cjs/core/types.d.cts +66 -1
  81. package/dist/types-cjs/core/verdict.d.cts +2 -0
  82. package/dist/types-cjs/index.d.cts +3 -2
  83. package/dist/types-cjs/map.d.cts +21 -5
  84. package/dist/types-cjs/signals.d.cts +164 -12
  85. package/dist/types-cjs/store/index.d.cts +1 -1
  86. package/dist/types-cjs/store/optimistic.d.cts +3 -3
  87. package/dist/types-cjs/store/projection.d.cts +10 -6
  88. package/dist/types-cjs/store/reconcile.d.cts +31 -8
  89. package/dist/types-cjs/store/store.d.cts +101 -6
  90. package/package.json +11 -9
  91. package/dist/prod.js +0 -3627
@@ -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 };