@solidjs/signals 2.0.0-beta.25 → 2.0.0-beta.27

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.
@@ -8,7 +8,7 @@ import "../core/verdict.js";
8
8
 
9
9
  import "../core/effect.js";
10
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, STORE_DESC, symbolKeyedRecords, $DELETED, isRawValue, getKeys, getStoreSymbols, STORE_WRAP, lookupTarget } from "./store.js";
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
12
 
13
13
  // Enumerate a node record's keys. Keeps the common string-key path on the
14
14
  // `Object.keys` fast path; only records currently holding a user symbol node
@@ -18,7 +18,7 @@ function nodeKeys(e) {
18
18
  const t = Object.keys(e);
19
19
  if (symbolKeyedRecords.has(e)) {
20
20
  const n = Object.getOwnPropertySymbols(e);
21
- for (let e = 0, a = n.length; e < a; e++) {
21
+ for (let e = 0, r = n.length; e < r; e++) {
22
22
  if (n[e] !== $TRACK) t.push(n[e]);
23
23
  }
24
24
  }
@@ -32,57 +32,57 @@ function unwrap(e) {
32
32
  return e[$TARGET]?.[STORE_VALUE] ?? e;
33
33
  }
34
34
 
35
- function getOverrideValue(e, t, n, a) {
36
- if (a && n in a) return a[n];
35
+ function getOverrideValue(e, t, n, r) {
36
+ if (r && n in r) return r[n];
37
37
  return t && n in t ? t[n] : e[n];
38
38
  }
39
39
 
40
40
  // Append `o`'s *enumerable* own symbol keys from a pre-fetched symbol list.
41
41
  function addEnumSymbols(e, t, n) {
42
- for (let a = 0, r = t.length; a < r; a++) {
43
- if (Object.prototype.propertyIsEnumerable.call(e, t[a])) n.add(t[a]);
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
44
  }
45
45
  }
46
46
 
47
47
  function getAllKeys(e, t, n) {
48
48
  // Symbols are merged explicitly below; keep the common string-key path on
49
49
  // Object.keys() and avoid reflecting the base symbols twice.
50
- const a = getKeys(e, t);
51
- const r = Object.keys(n);
50
+ const r = getKeys(e, t);
51
+ const a = Object.keys(n);
52
52
  // `value` can be a wrapped store (store-in-store) whose ownKeys trap tracks;
53
53
  // mirror `getKeys` and enumerate its symbols untracked in that case.
54
54
  const i = e[$TARGET] ? untrack(() => Object.getOwnPropertySymbols(e)) : Object.getOwnPropertySymbols(e);
55
- const s = Object.getOwnPropertySymbols(n);
55
+ const l = Object.getOwnPropertySymbols(n);
56
56
  // Symbol-free diff (the overwhelmingly common case) stays on the exact
57
57
  // pre-#2851 path, including the identical-key-sets fast path from #2756.
58
- if (i.length === 0 && s.length === 0) {
59
- if (a.length === r.length) {
58
+ if (i.length === 0 && l.length === 0) {
59
+ if (r.length === a.length) {
60
60
  let e = true;
61
- for (let t = 0; t < a.length; t++) {
62
- if (a[t] !== r[t]) {
61
+ for (let t = 0; t < r.length; t++) {
62
+ if (r[t] !== a[t]) {
63
63
  e = false;
64
64
  break;
65
65
  }
66
66
  }
67
- if (e) return a;
67
+ if (e) return r;
68
68
  }
69
- const e = new Set(a);
70
- for (let t = 0; t < r.length; t++) e.add(r[t]);
69
+ const e = new Set(r);
70
+ for (let t = 0; t < a.length; t++) e.add(a[t]);
71
71
  return Array.from(e);
72
72
  }
73
73
  // Symbol-aware diff (#2851): base symbols join the set, then override
74
74
  // adds/deletes are re-applied so a `$DELETED` symbol stays deleted, then
75
75
  // `next`'s keys (a key present in next is never deleted by the diff).
76
- const l = new Set(a);
77
- addEnumSymbols(e, i, l);
76
+ const s = new Set(r);
77
+ addEnumSymbols(e, i, s);
78
78
  if (t) {
79
79
  for (const e of Reflect.ownKeys(t)) {
80
- t[e] === $DELETED ? l.delete(e) : l.add(e);
80
+ t[e] === $DELETED ? s.delete(e) : s.add(e);
81
81
  }
82
82
  }
83
- for (let e = 0; e < r.length; e++) l.add(r[e]);
84
- addEnumSymbols(n, s, l);
85
- return Array.from(l);
83
+ for (let e = 0; e < a.length; e++) s.add(a[e]);
84
+ addEnumSymbols(n, l, s);
85
+ return Array.from(s);
86
86
  }
87
87
 
88
88
  // Array entries can be `null`/`undefined`/primitives, not just keyed objects.
@@ -100,6 +100,17 @@ function keyedMatch(e, t, n) {
100
100
  return e === t || isWrappable(e) && isWrappable(t) && n(e) === n(t);
101
101
  }
102
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
+
103
114
  // Array reconciliation updates the slots it visits, then swaps STORE_VALUE.
104
115
  // Previously tracked keys that are absent from `next` still need invalidating,
105
116
  // and `in` dependencies should follow the new value's membership. Use
@@ -114,8 +125,8 @@ function syncArrayNodeMembership(e, t) {
114
125
  if (n) {
115
126
  if (symbolKeyedRecords.has(n)) {
116
127
  const e = nodeKeys(n);
117
- for (let a = 0, r = e.length; a < r; a++) {
118
- e[a] in t || setSignal(n[e[a]], undefined);
128
+ for (let r = 0, a = e.length; r < a; r++) {
129
+ e[r] in t || setSignal(n[e[r]], undefined);
119
130
  }
120
131
  } else {
121
132
  for (const e in n) {
@@ -126,8 +137,8 @@ function syncArrayNodeMembership(e, t) {
126
137
  if (n = e[STORE_HAS]) {
127
138
  if (symbolKeyedRecords.has(n)) {
128
139
  const e = nodeKeys(n);
129
- for (let a = 0, r = e.length; a < r; a++) {
130
- setSignal(n[e[a]], e[a] in t);
140
+ for (let r = 0, a = e.length; r < a; r++) {
141
+ setSignal(n[e[r]], e[r] in t);
131
142
  }
132
143
  } else {
133
144
  for (const e in n) {
@@ -146,31 +157,31 @@ function syncArrayNodeMembership(e, t) {
146
157
  // and drops the wrap()/$PROXY/$TARGET round-trip per visited child.
147
158
  // Wrap-family stores (projections/optimistic) own child proxy creation and
148
159
  // keep the proxy-based recursion.
149
- function applyStateChild(e, t, n, a) {
160
+ function applyStateChild(e, t, n, r) {
150
161
  if (n[STORE_WRAP] !== undefined) {
151
- applyState(e, wrap(t, n), a);
162
+ applyState(e, wrap(t, n), r);
152
163
  return;
153
164
  }
154
- const r = t[$TARGET] ?? storeLookup.get(t);
155
- if (r === undefined) return;
165
+ const a = t[$TARGET] ?? storeLookup.get(t);
166
+ if (a === undefined) return;
156
167
  e = unwrap(e);
157
- if (r[STORE_SHALLOW]) {
158
- applyStateShallow(e, r);
159
- } else if (r[STORE_OVERRIDE] || r[STORE_OPTIMISTIC_OVERRIDE]) {
160
- applyStateSlow(e, r, a);
168
+ if (a[STORE_SHALLOW]) {
169
+ applyStateShallow(e, a);
170
+ } else if (a[STORE_OVERRIDE] || a[STORE_OPTIMISTIC_OVERRIDE]) {
171
+ applyStateSlow(e, a, r);
161
172
  } else {
162
- applyStateFast(e, r, a);
173
+ applyStateFast(e, a, r);
163
174
  }
164
175
  }
165
176
 
166
177
  // Reconcile a single array slot: recurse into a wrappable pair, otherwise replace
167
178
  // the node's value outright (covers object→primitive and primitive→object).
168
- function applyArrayItem(e, t, n, a, r) {
169
- if (isWrappable(e) && isWrappable(t) && !(rawValuesUsed && (isRawValue(t) || isRawValue(e)))) {
179
+ function applyArrayItem(e, t, n, r, a) {
180
+ if (recursablePair(t, e)) {
170
181
  const i = wrap(t, n);
171
- a && setSignal(a, i);
172
- applyState(e, i, r);
173
- } else a && setSignal(a, wrapValue(e, n));
182
+ r && setSignal(r, i);
183
+ applyState(e, i, a);
184
+ } else r && setSignal(r, wrapValue(e, n));
174
185
  }
175
186
 
176
187
  /**
@@ -184,17 +195,17 @@ function applyArrayItem(e, t, n, a, r) {
184
195
  * and stay pruned exactly as before; keys the main loop already visited
185
196
  * (node present) are skipped. Callers gate on the parent's own flag and on
186
197
  * `$TRACK` absence (an enumeration-tracked record already diffs every key).
187
- */ function applyDescendants(e, t, n, a, r, i, s) {
188
- const l = n[STORE_LOOKUP] || storeLookup;
198
+ */ function applyDescendants(e, t, n, r, a, i, l) {
199
+ const s = n[STORE_LOOKUP] || storeLookup;
189
200
  if (i) {
190
201
  const n = getKeys(e, i).concat(getStoreSymbols(e, i));
191
202
  for (let o = 0, f = n.length; o < f; o++) {
192
203
  const f = n[o];
193
- if (a?.[f]) continue;
204
+ if (r?.[f]) continue;
194
205
  // main loop already diffed this slot
195
- const p = unwrap(getOverrideValue(e, i, f, s));
196
- if (!isWrappable(p)) continue;
197
- descendInto(p, t[f], l, r);
206
+ const u = unwrap(getOverrideValue(e, i, f, l));
207
+ if (!isWrappable(u)) continue;
208
+ descendInto(u, t[f], s, a);
198
209
  }
199
210
  return;
200
211
  }
@@ -203,34 +214,34 @@ function applyArrayItem(e, t, n, a, r) {
203
214
  // bails (noded key, primitive value) stay inline — only genuine descent
204
215
  // candidates pay a call.
205
216
  for (const n in e) {
206
- if (a?.[n]) continue;
217
+ if (r?.[n]) continue;
207
218
  // main loop already diffed this slot
208
219
  const i = unwrap(e[n]);
209
220
  if (!isWrappable(i)) continue;
210
- descendInto(i, t[n], l, r);
221
+ descendInto(i, t[n], s, a);
211
222
  }
212
223
  const o = Object.getOwnPropertySymbols(e);
213
224
  for (let n = 0, i = o.length; n < i; n++) {
214
225
  if (Object.prototype.propertyIsEnumerable.call(e, o[n])) {
215
- if (a?.[o[n]]) continue;
226
+ if (r?.[o[n]]) continue;
216
227
  const i = unwrap(e[o[n]]);
217
228
  if (!isWrappable(i)) continue;
218
- descendInto(i, t[o[n]], l, r);
229
+ descendInto(i, t[o[n]], s, a);
219
230
  }
220
231
  }
221
232
  }
222
233
 
223
- function descendInto(e, t, n, a) {
224
- const r = lookupTarget(e, n);
225
- if (!r?.[STORE_DESC]) return;
234
+ function descendInto(e, t, n, r) {
235
+ const a = lookupTarget(e, n);
236
+ if (!a?.[STORE_DESC]) return;
226
237
  const i = unwrap(t);
227
- if (e === i || !isWrappable(i) || Array.isArray(e) !== Array.isArray(i) || a(e) != null && a(e) !== a(i)) return;
228
- if (r[STORE_SHALLOW]) {
229
- applyStateShallow(i, r);
230
- } else if (r[STORE_OVERRIDE] || r[STORE_OPTIMISTIC_OVERRIDE]) {
231
- applyStateSlow(i, r, a);
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);
232
243
  } else {
233
- applyStateFast(i, r, a);
244
+ applyStateFast(i, a, r);
234
245
  }
235
246
  }
236
247
 
@@ -243,14 +254,14 @@ function applyState(e, t, n) {
243
254
  // Array items and root calls can pass a store proxy as `next`; normalize to
244
255
  // its raw value or the swap would set a store's STORE_VALUE to its own proxy.
245
256
  e = unwrap(e);
246
- const a = t?.[$TARGET];
247
- if (!a) return;
248
- if (a[STORE_SHALLOW]) {
249
- applyStateShallow(e, a);
250
- } else if (a[STORE_OVERRIDE] || a[STORE_OPTIMISTIC_OVERRIDE]) {
251
- applyStateSlow(e, a, n);
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);
252
263
  } else {
253
- applyStateFast(e, a, n);
264
+ applyStateFast(e, r, n);
254
265
  }
255
266
  }
256
267
 
@@ -262,50 +273,50 @@ function applyState(e, t, n) {
262
273
  // One pass over a shallow target's node record: replace changed slots with
263
274
  // raw next values, null out slots absent from next. Returns whether anything
264
275
  // differed.
265
- function shallowDiffNodes(e, t, n, a) {
266
- let r = false;
276
+ function shallowDiffNodes(e, t, n, r) {
277
+ let a = false;
267
278
  for (const i in e) {
268
- if (a && i === "length") continue;
279
+ if (r && i === "length") continue;
269
280
  if (i in t) {
270
- const a = t[i];
271
- if (a !== n(i)) {
272
- r = true;
273
- setSignal(e[i], a);
281
+ const r = t[i];
282
+ if (r !== n(i)) {
283
+ a = true;
284
+ setSignal(e[i], r);
274
285
  }
275
286
  } else {
276
- r = true;
287
+ a = true;
277
288
  setSignal(e[i], undefined);
278
289
  }
279
290
  }
280
- return r;
291
+ return a;
281
292
  }
282
293
 
283
294
  function applyStateShallow(e, t, n) {
284
- const a = t[STORE_VALUE];
285
- const r = t[STORE_OVERRIDE];
295
+ const r = t[STORE_VALUE];
296
+ const a = t[STORE_OVERRIDE];
286
297
  const i = t[STORE_OPTIMISTIC_OVERRIDE];
287
- if (e === a && !r && !i) return;
298
+ if (e === r && !a && !i) return;
288
299
  // Setter-staged writes fold into the diff: previous values resolve through
289
300
  // the override layers (so a replaced slot compares against what readers
290
301
  // saw), and the regular override clears with the swap — reconcile makes
291
302
  // `next` the authoritative base, same as the deep slow path.
292
303
  const prevAt = e => {
293
- const t = getOverrideValue(a, r, e, i);
304
+ const t = getOverrideValue(r, a, e, i);
294
305
  return t === $DELETED ? undefined : t;
295
306
  };
296
307
  t[STORE_OVERRIDE] = undefined;
297
- const s = t[STORE_LOOKUP];
298
- s !== undefined ? s.set(e, t[$PROXY]) : storeLookup.set(e, t);
308
+ const l = t[STORE_LOOKUP];
309
+ l !== undefined ? l.set(e, t[$PROXY]) : storeLookup.set(e, t);
299
310
  t[STORE_VALUE] = e;
300
311
  markRawIngest(e);
301
- const l = t[STORE_NODE];
302
- const o = l && l[$TRACK];
312
+ const s = t[STORE_NODE];
313
+ const o = s && s[$TRACK];
303
314
  let f = false;
304
- if (Array.isArray(a)) {
305
- const n = r?.length ?? i?.length ?? a.length;
306
- if (l) {
307
- f = shallowDiffNodes(l, e, prevAt, true);
308
- if (l.length && n !== e.length) setSignal(l.length, e.length);
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);
309
320
  }
310
321
  if (!f && (o || t[STORE_HAS])) {
311
322
  // Slots without nodes still feed $TRACK enumerators / `in` probes.
@@ -319,24 +330,24 @@ function applyStateShallow(e, t, n) {
319
330
  }
320
331
  }
321
332
  } else {
322
- if (l) {
323
- f = shallowDiffNodes(l, e, prevAt, false);
333
+ if (s) {
334
+ f = shallowDiffNodes(s, e, prevAt, false);
324
335
  }
325
336
  if (!f && (o || t[STORE_HAS])) f = true;
326
337
  }
327
- let p = t[STORE_HAS];
328
- if (p) {
329
- for (const t in p) {
330
- setSignal(p[t], t in e);
338
+ let u = t[STORE_HAS];
339
+ if (u) {
340
+ for (const t in u) {
341
+ setSignal(u[t], t in e);
331
342
  }
332
343
  }
333
344
  f && notifySelf(t);
334
345
  }
335
346
 
336
347
  function applyStateFast(e, t, n) {
337
- const a = t[STORE_VALUE];
338
- if (e === a) return;
339
- const r = t[STORE_NODE];
348
+ const r = t[STORE_VALUE];
349
+ if (e === r) return;
350
+ const a = t[STORE_NODE];
340
351
  // swap
341
352
  {
342
353
  const n = t[STORE_LOOKUP];
@@ -344,20 +355,20 @@ function applyStateFast(e, t, n) {
344
355
  }
345
356
  t[STORE_VALUE] = e;
346
357
  // merge
347
- if (Array.isArray(a)) {
358
+ if (Array.isArray(r)) {
348
359
  let i = false;
349
- const s = a.length;
350
- if (e.length && s && isWrappable(e[0]) && n(e[0]) != null) {
351
- let l, o, f, p, u, c, S, R;
352
- for (f = 0, p = Math.min(s, e.length); f < p && keyedMatch(c = a[f], e[f], n); f++) {
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++) {
353
364
  // keyedMatch established both sides wrappable unless they're the
354
365
  // SAME reference — and an identical slot is a guaranteed no-op
355
366
  // (the child's STORE_VALUE tracks the previous graph), so skip
356
367
  // the recursion dispatch entirely. Raw-marked values are leaves:
357
368
  // replace the slot node instead of recursing.
358
369
  if (c !== e[f]) {
359
- if (rawValuesUsed && (isRawValue(c) || isRawValue(e[f]))) {
360
- r?.[f] && setSignal(r[f], wrapValue(e[f], t));
370
+ if (!recursablePair(c, e[f])) {
371
+ a?.[f] && setSignal(a[f], wrapValue(e[f], t));
361
372
  } else applyStateChild(e[f], c, t, n);
362
373
  }
363
374
  }
@@ -365,129 +376,129 @@ function applyStateFast(e, t, n) {
365
376
  // of a polling tick): membership, length, and order are unchanged —
366
377
  // nothing below could do observable work, so skip the staging
367
378
  // allocations and membership sync outright.
368
- if (f === e.length && f === s) return;
369
- const E = new Array(e.length), O = new Map;
370
- for (p = s - 1, u = e.length - 1; p >= f && u >= f && keyedMatch(c = a[p], e[u], n); p--,
371
- u--) {
372
- E[u] = c;
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;
373
384
  }
374
- if (f > u || f > p) {
375
- for (o = f; o <= u; o++) {
385
+ if (f > p || f > u) {
386
+ for (o = f; o <= p; o++) {
376
387
  i = true;
377
- r?.[o] && setSignal(r[o], wrapValue(e[o], t));
388
+ a?.[o] && setSignal(a[o], wrapValue(e[o], t));
378
389
  }
379
390
  for (;o < e.length; o++) {
380
391
  i = true;
381
- applyArrayItem(e[o], E[o], t, r?.[o], n);
392
+ applyArrayItem(e[o], O[o], t, a?.[o], n);
382
393
  }
383
394
  syncArrayNodeMembership(t, e);
384
- (i || s !== e.length) && notifySelf(t);
385
- s !== e.length && r?.length && setSignal(r.length, e.length);
395
+ (i || l !== e.length) && notifySelf(t);
396
+ l !== e.length && a?.length && setSignal(a.length, e.length);
386
397
  return;
387
398
  }
388
- S = new Array(u + 1);
389
- for (o = u; o >= f; o--) {
399
+ S = new Array(p + 1);
400
+ for (o = p; o >= f; o--) {
390
401
  c = e[o];
391
- R = itemKey(c, n);
392
- l = O.get(R);
393
- S[o] = l === undefined ? -1 : l;
394
- O.set(R, o);
402
+ E = itemKey(c, n);
403
+ s = R.get(E);
404
+ S[o] = s === undefined ? -1 : s;
405
+ R.set(E, o);
395
406
  }
396
- for (l = f; l <= p; l++) {
397
- c = a[l];
398
- R = itemKey(c, n);
399
- o = O.get(R);
407
+ for (s = f; s <= u; s++) {
408
+ c = r[s];
409
+ E = itemKey(c, n);
410
+ o = R.get(E);
400
411
  if (o !== undefined && o !== -1) {
401
- E[o] = c;
412
+ O[o] = c;
402
413
  o = S[o];
403
- O.set(R, o);
414
+ R.set(E, o);
404
415
  }
405
416
  }
406
417
  for (o = f; o < e.length; o++) {
407
- if (o in E) {
408
- applyArrayItem(e[o], E[o], t, r?.[o], n);
409
- } else r?.[o] && setSignal(r[o], wrapValue(e[o], t));
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));
410
421
  }
411
422
  if (f < e.length) i = true;
412
423
  } else if (e.length) {
413
- for (let s = 0, l = e.length; s < l; s++) {
414
- const l = a[s];
415
- if (isWrappable(l) && isWrappable(e[s]) && !(rawValuesUsed && (isRawValue(l) || isRawValue(e[s])))) {
416
- if (l !== e[s]) applyStateChild(e[s], l, t, n);
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);
417
428
  } else {
418
- if (l !== e[s]) i = true;
419
- r?.[s] && setSignal(r[s], wrapValue(e[s], t));
429
+ if (s !== e[l]) i = true;
430
+ a?.[l] && setSignal(a[l], wrapValue(e[l], t));
420
431
  }
421
432
  }
422
433
  }
423
434
  syncArrayNodeMembership(t, e);
424
- if (s !== e.length) {
435
+ if (l !== e.length) {
425
436
  i = true;
426
- r?.length && setSignal(r.length, e.length);
437
+ a?.length && setSignal(a.length, e.length);
427
438
  }
428
439
  i && notifySelf(t);
429
440
  return;
430
441
  }
431
442
  // values
432
443
  let i = t[STORE_NODE];
433
- let s;
444
+ let l;
434
445
  if (i) {
435
- s = i[$TRACK];
446
+ l = i[$TRACK];
436
447
  // The per-key body is duplicated across both loops on purpose: this is
437
448
  // the hottest object-diff site and a shared helper costs a per-key call
438
449
  // in instruction counts (CodSpeed regressed ~7% on deep-tree reconciles
439
450
  // with the extracted form).
440
- if (s || symbolKeyedRecords.has(i)) {
441
- const r = s ? getAllKeys(a, undefined, e) : nodeKeys(i);
442
- for (let l = 0, o = r.length; l < o; l++) {
443
- const o = r[l];
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];
444
455
  const f = i[o];
445
- const p = unwrap(a[o]);
446
- const u = unwrap(e[o]);
447
- if (p === u) continue;
448
- if (!p || !isWrappable(p) || !isWrappable(u) ||
456
+ const u = unwrap(r[o]);
457
+ const p = unwrap(e[o]);
458
+ if (u === p) continue;
459
+ if (!u || !isWrappable(u) || !isWrappable(p) ||
449
460
  // Raw-marked values are leaves replaced by reference — a "wrappable
450
461
  // pair" is only recursable when both sides are actual store children.
451
- rawValuesUsed && (isRawValue(p) || isRawValue(u)) || Array.isArray(p) !== Array.isArray(u) || n(p) != null && n(p) !== n(u)) {
452
- s && setSignal(s, void 0);
453
- f && setSignal(f, isWrappable(u) ? wrap(u, t) : u);
454
- } else applyStateChild(u, p, t, n);
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);
455
466
  }
456
467
  } else {
457
468
  // Untracked, string-only node records (the overwhelmingly common case)
458
469
  // iterate in place — nodeKeys() allocated a fresh key array per object
459
470
  // per pass, which dominates allocation on large-graph reconciles.
460
- for (const r in i) {
461
- const l = i[r];
462
- const o = unwrap(a[r]);
463
- const f = unwrap(e[r]);
471
+ for (const a in i) {
472
+ const s = i[a];
473
+ const o = unwrap(r[a]);
474
+ const f = unwrap(e[a]);
464
475
  if (o === f) continue;
465
476
  if (!o || !isWrappable(o) || !isWrappable(f) ||
466
477
  // Raw-marked values are leaves replaced by reference — a "wrappable
467
478
  // pair" is only recursable when both sides are actual store children.
468
479
  rawValuesUsed && (isRawValue(o) || isRawValue(f)) || Array.isArray(o) !== Array.isArray(f) || n(o) != null && n(o) !== n(f)) {
469
- s && setSignal(s, void 0);
470
- l && setSignal(l, isWrappable(f) ? wrap(f, t) : f);
480
+ l && setSignal(l, void 0);
481
+ s && setSignal(s, isWrappable(f) ? wrap(f, t) : f);
471
482
  } else applyStateChild(f, o, t, n);
472
483
  }
473
484
  }
474
485
  }
475
- if (!s && t[STORE_DESC]) applyDescendants(a, e, t, i, n);
486
+ if (!l && t[STORE_DESC]) applyDescendants(r, e, t, i, n);
476
487
  // has
477
488
  if (i = t[STORE_HAS]) {
478
489
  const t = nodeKeys(i);
479
- for (let n = 0, a = t.length; n < a; n++) {
480
- const a = t[n];
481
- setSignal(i[a], a in e);
490
+ for (let n = 0, r = t.length; n < r; n++) {
491
+ const r = t[n];
492
+ setSignal(i[r], r in e);
482
493
  }
483
494
  }
484
495
  }
485
496
 
486
497
  function applyStateSlow(e, t, n) {
487
- const a = t[STORE_VALUE];
488
- const r = t[STORE_OVERRIDE];
498
+ const r = t[STORE_VALUE];
499
+ const a = t[STORE_OVERRIDE];
489
500
  const i = t[STORE_OPTIMISTIC_OVERRIDE];
490
- let s = t[STORE_NODE];
501
+ let l = t[STORE_NODE];
491
502
  // swap
492
503
  {
493
504
  const n = t[STORE_LOOKUP];
@@ -496,106 +507,106 @@ function applyStateSlow(e, t, n) {
496
507
  t[STORE_VALUE] = e;
497
508
  t[STORE_OVERRIDE] = undefined;
498
509
  // merge
499
- if (Array.isArray(a)) {
500
- let l = false;
501
- const o = getOverrideValue(a, r, "length", i);
510
+ if (Array.isArray(r)) {
511
+ let s = false;
512
+ const o = getOverrideValue(r, a, "length", i);
502
513
  if (e.length && o && isWrappable(e[0]) && n(e[0]) != null) {
503
- let f, p, u, c, S, R, E, O;
504
- for (u = 0, c = Math.min(o, e.length); u < c && keyedMatch(R = getOverrideValue(a, r, u, i), e[u], n); u++) {
505
- if (isWrappable(R) && isWrappable(e[u]) && R !== e[u]) {
506
- if (rawValuesUsed && (isRawValue(R) || isRawValue(e[u]))) {
507
- s?.[u] && setSignal(s[u], wrapValue(e[u], t));
508
- } else applyState(e[u], wrap(R, t), n);
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);
509
520
  }
510
521
  }
511
- const y = new Array(e.length), d = new Map;
512
- for (c = o - 1, S = e.length - 1; c >= u && S >= u && keyedMatch(R = getOverrideValue(a, r, c, i), e[S], n); c--,
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--,
513
524
  S--) {
514
- y[S] = R;
525
+ d[S] = E;
515
526
  }
516
- if (u > S || u > c) {
517
- for (p = u; p <= S; p++) {
518
- l = true;
519
- s?.[p] && setSignal(s[p], wrapValue(e[p], t));
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));
520
531
  }
521
- for (;p < e.length; p++) {
522
- l = true;
523
- applyArrayItem(e[p], y[p], t, s?.[p], n);
532
+ for (;u < e.length; u++) {
533
+ s = true;
534
+ applyArrayItem(e[u], d[u], t, l?.[u], n);
524
535
  }
525
- const a = e.length;
536
+ const r = e.length;
526
537
  syncArrayNodeMembership(t, e);
527
- (l || o !== a) && notifySelf(t);
528
- o !== a && s?.length && setSignal(s.length, a);
538
+ (s || o !== r) && notifySelf(t);
539
+ o !== r && l?.length && setSignal(l.length, r);
529
540
  return;
530
541
  }
531
- E = new Array(S + 1);
532
- for (p = S; p >= u; p--) {
533
- R = e[p];
534
- O = itemKey(R, n);
535
- f = d.get(O);
536
- E[p] = f === undefined ? -1 : f;
537
- d.set(O, p);
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);
538
549
  }
539
- for (f = u; f <= c; f++) {
540
- R = getOverrideValue(a, r, f, i);
541
- O = itemKey(R, n);
542
- p = d.get(O);
543
- if (p !== undefined && p !== -1) {
544
- y[p] = R;
545
- p = E[p];
546
- d.set(O, p);
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);
547
558
  }
548
559
  }
549
- for (p = u; p < e.length; p++) {
550
- if (p in y) {
551
- applyArrayItem(e[p], y[p], t, s?.[p], n);
552
- } else s?.[p] && setSignal(s[p], wrapValue(e[p], t));
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));
553
564
  }
554
- if (u < e.length) l = true;
565
+ if (p < e.length) s = true;
555
566
  } else if (e.length) {
556
567
  for (let o = 0, f = e.length; o < f; o++) {
557
- const f = getOverrideValue(a, r, o, i);
558
- if (isWrappable(f) && isWrappable(e[o]) && !(rawValuesUsed && (isRawValue(f) || isRawValue(e[o])))) {
568
+ const f = getOverrideValue(r, a, o, i);
569
+ if (recursablePair(f, e[o])) {
559
570
  if (f !== e[o]) applyState(e[o], wrap(f, t), n);
560
571
  } else {
561
- if (f !== e[o]) l = true;
562
- s?.[o] && setSignal(s[o], wrapValue(e[o], t));
572
+ if (f !== e[o]) s = true;
573
+ l?.[o] && setSignal(l[o], wrapValue(e[o], t));
563
574
  }
564
575
  }
565
576
  }
566
577
  const f = e.length;
567
578
  syncArrayNodeMembership(t, e);
568
579
  if (o !== f) {
569
- l = true;
570
- s?.length && setSignal(s.length, f);
580
+ s = true;
581
+ l?.length && setSignal(l.length, f);
571
582
  }
572
- l && notifySelf(t);
583
+ s && notifySelf(t);
573
584
  return;
574
585
  }
575
586
  // values
576
- let l;
577
- if (s) {
578
- l = s[$TRACK];
579
- const o = l ? getAllKeys(a, r, e) : nodeKeys(s);
580
- for (let f = 0, p = o.length; f < p; f++) {
581
- const p = o[f];
582
- const u = s[p];
583
- const c = unwrap(getOverrideValue(a, r, p, i));
584
- let S = unwrap(e[p]);
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]);
585
596
  if (c === S) continue;
586
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)) {
587
- l && setSignal(l, void 0);
588
- u && setSignal(u, isWrappable(S) ? wrap(S, t) : S);
598
+ s && setSignal(s, void 0);
599
+ p && setSignal(p, isWrappable(S) ? wrap(S, t) : S);
589
600
  } else applyState(S, wrap(c, t), n);
590
601
  }
591
602
  }
592
- if (!l && t[STORE_DESC]) applyDescendants(a, e, t, s, n, r, i);
603
+ if (!s && t[STORE_DESC]) applyDescendants(r, e, t, l, n, a, i);
593
604
  // has
594
- if (s = t[STORE_HAS]) {
595
- const t = nodeKeys(s);
596
- for (let n = 0, a = t.length; n < a; n++) {
597
- const a = t[n];
598
- setSignal(s[a], a in e);
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);
599
610
  }
600
611
  }
601
612
  }
@@ -604,6 +615,58 @@ function applyStateSlow(e, t, n) {
604
615
  // the positional branch and object descent to plain per-property merging.
605
616
  const NOKEY = () => null;
606
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
+
607
670
  /**
608
671
  * Returns a draft-mutating function that smart-merges `value` into a store,
609
672
  * preserving fine-grained reactivity: only changed leaves trigger updates.
@@ -618,6 +681,9 @@ const NOKEY = () => null;
618
681
  * the classic pattern for fixed-shape data that churns in place (dashboards,
619
682
  * monitors), where no keyed diff pass is needed or wanted.
620
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
+ *
621
687
  * @param value the next state to merge in
622
688
  * @param key property name (string) or extractor function for stable
623
689
  * identity (default `"id"`); pass `null` for positional merging
@@ -635,17 +701,7 @@ const NOKEY = () => null;
635
701
  * setStats(reconcile(nextStats, null));
636
702
  * ```
637
703
  */ function reconcile(e, t = "id") {
638
- return n => {
639
- if (n == null) throw new Error("");
640
- if (t === null) {
641
- applyState(e, n, NOKEY);
642
- return;
643
- }
644
- const a = typeof t === "string" ? e => e[t] : t;
645
- const r = a(n);
646
- if (r !== undefined && a(e) !== r) throw new Error("");
647
- applyState(e, n, a);
648
- };
704
+ return n => reconcileState(e, n, t, false);
649
705
  }
650
706
 
651
- export { reconcile };
707
+ export { reconcile, reconcileState };