@solidjs/signals 2.0.0-beta.22 → 2.0.0-beta.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,7 +8,7 @@ import "../core/verdict.js";
8
8
 
9
9
  import "../core/effect.js";
10
10
 
11
- import { $TARGET, STORE_OVERRIDE, STORE_OPTIMISTIC_OVERRIDE, STORE_VALUE, STORE_NODE, storeLookup, STORE_LOOKUP, $PROXY, isWrappable, wrap, notifySelf, $TRACK, STORE_DESC, STORE_HAS, getKeys, $DELETED, symbolKeyedRecords, getStoreSymbols } from "./store.js";
11
+ import { $TARGET, STORE_OVERRIDE, STORE_OPTIMISTIC_OVERRIDE, STORE_VALUE, STORE_NODE, storeLookup, STORE_LOOKUP, $PROXY, isWrappable, wrap, notifySelf, $TRACK, STORE_DESC, STORE_HAS, symbolKeyedRecords, getKeys, $DELETED, getStoreSymbols } 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
@@ -155,9 +155,9 @@ function applyArrayItem(e, t, r, n, a) {
155
155
  if (!isWrappable(c)) continue;
156
156
  const u = (l.get(c) ?? storeLookup.get(c))?.[$TARGET];
157
157
  if (!u?.[STORE_DESC]) continue;
158
- const S = unwrap(t[f]);
159
- if (c === S || !isWrappable(S) || Array.isArray(c) !== Array.isArray(S) || a(c) != null && a(c) !== a(S)) continue;
160
- applyState(S, wrap(c, r), a);
158
+ const y = unwrap(t[f]);
159
+ if (c === y || !isWrappable(y) || Array.isArray(c) !== Array.isArray(y) || a(c) != null && a(c) !== a(y)) continue;
160
+ applyState(y, wrap(c, r), a);
161
161
  }
162
162
  }
163
163
 
@@ -191,7 +191,7 @@ function applyStateFast(e, t, r) {
191
191
  let i = false;
192
192
  const o = n.length;
193
193
  if (e.length && o && isWrappable(e[0]) && r(e[0]) != null) {
194
- let l, s, p, f, c, u, S, y;
194
+ let l, s, p, f, c, u, y, S;
195
195
  for (p = 0, f = Math.min(o, e.length); p < f && keyedMatch(u = n[p], e[p], r); p++) {
196
196
  isWrappable(u) && isWrappable(e[p]) && applyState(e[p], wrap(u, t), r);
197
197
  }
@@ -214,22 +214,22 @@ function applyStateFast(e, t, r) {
214
214
  o !== e.length && a?.length && setSignal(a.length, e.length);
215
215
  return;
216
216
  }
217
- S = new Array(c + 1);
217
+ y = new Array(c + 1);
218
218
  for (s = c; s >= p; s--) {
219
219
  u = e[s];
220
- y = itemKey(u, r);
221
- l = O.get(y);
222
- S[s] = l === undefined ? -1 : l;
223
- O.set(y, s);
220
+ S = itemKey(u, r);
221
+ l = O.get(S);
222
+ y[s] = l === undefined ? -1 : l;
223
+ O.set(S, s);
224
224
  }
225
225
  for (l = p; l <= f; l++) {
226
226
  u = n[l];
227
- y = itemKey(u, r);
228
- s = O.get(y);
227
+ S = itemKey(u, r);
228
+ s = O.get(S);
229
229
  if (s !== undefined && s !== -1) {
230
230
  E[s] = u;
231
- s = S[s];
232
- O.set(y, s);
231
+ s = y[s];
232
+ O.set(S, s);
233
233
  }
234
234
  }
235
235
  for (s = p; s < e.length; s++) {
@@ -260,17 +260,18 @@ function applyStateFast(e, t, r) {
260
260
  let o;
261
261
  if (i) {
262
262
  o = i[$TRACK];
263
- const a = o ? getAllKeys(n, undefined, e) : nodeKeys(i);
264
- for (let l = 0, s = a.length; l < s; l++) {
265
- const s = a[l];
266
- const p = i[s];
267
- const f = unwrap(n[s]);
268
- let c = unwrap(e[s]);
269
- if (f === c) continue;
270
- if (!f || !isWrappable(f) || !isWrappable(c) || Array.isArray(f) !== Array.isArray(c) || r(f) != null && r(f) !== r(c)) {
271
- o && setSignal(o, void 0);
272
- p && setSignal(p, isWrappable(c) ? wrap(c, t) : c);
273
- } else applyState(c, wrap(f, t), r);
263
+ if (o || symbolKeyedRecords.has(i)) {
264
+ const a = o ? getAllKeys(n, undefined, e) : nodeKeys(i);
265
+ for (let l = 0, s = a.length; l < s; l++) {
266
+ diffNodeKey(a[l], i, n, e, t, o, r);
267
+ }
268
+ } else {
269
+ // Untracked, string-only node records (the overwhelmingly common case)
270
+ // iterate in place nodeKeys() allocated a fresh key array per object
271
+ // per pass, which dominates allocation on large-graph reconciles.
272
+ for (const a in i) {
273
+ diffNodeKey(a, i, n, e, t, o, r);
274
+ }
274
275
  }
275
276
  }
276
277
  if (!o && t[STORE_DESC]) applyDescendants(n, e, t, i, r);
@@ -284,6 +285,19 @@ function applyStateFast(e, t, r) {
284
285
  }
285
286
  }
286
287
 
288
+ // One node-key step of the fast object diff — shared by the array-iterating
289
+ // (tracked / symbol-keyed) and for-in (plain) loops in applyStateFast.
290
+ function diffNodeKey(e, t, r, n, a, i, o) {
291
+ const l = t[e];
292
+ const s = unwrap(r[e]);
293
+ let p = unwrap(n[e]);
294
+ if (s === p) return;
295
+ if (!s || !isWrappable(s) || !isWrappable(p) || Array.isArray(s) !== Array.isArray(p) || o(s) != null && o(s) !== o(p)) {
296
+ i && setSignal(i, void 0);
297
+ l && setSignal(l, isWrappable(p) ? wrap(p, a) : p);
298
+ } else applyState(p, wrap(s, a), o);
299
+ }
300
+
287
301
  function applyStateSlow(e, t, r) {
288
302
  const n = t[STORE_VALUE];
289
303
  const a = t[STORE_OVERRIDE];
@@ -298,23 +312,23 @@ function applyStateSlow(e, t, r) {
298
312
  let l = false;
299
313
  const s = getOverrideValue(n, a, "length", i);
300
314
  if (e.length && s && isWrappable(e[0]) && r(e[0]) != null) {
301
- let p, f, c, u, S, y, E, O;
302
- for (c = 0, u = Math.min(s, e.length); c < u && keyedMatch(y = getOverrideValue(n, a, c, i), e[c], r); c++) {
303
- isWrappable(y) && isWrappable(e[c]) && applyState(e[c], wrap(y, t), r);
315
+ let p, f, c, u, y, S, E, O;
316
+ for (c = 0, u = Math.min(s, e.length); c < u && keyedMatch(S = getOverrideValue(n, a, c, i), e[c], r); c++) {
317
+ isWrappable(S) && isWrappable(e[c]) && applyState(e[c], wrap(S, t), r);
304
318
  }
305
- const R = new Array(e.length), d = new Map;
306
- for (u = s - 1, S = e.length - 1; u >= c && S >= c && keyedMatch(y = getOverrideValue(n, a, u, i), e[S], r); u--,
307
- S--) {
308
- R[S] = y;
319
+ const d = new Array(e.length), R = new Map;
320
+ for (u = s - 1, y = e.length - 1; u >= c && y >= c && keyedMatch(S = getOverrideValue(n, a, u, i), e[y], r); u--,
321
+ y--) {
322
+ d[y] = S;
309
323
  }
310
- if (c > S || c > u) {
311
- for (f = c; f <= S; f++) {
324
+ if (c > y || c > u) {
325
+ for (f = c; f <= y; f++) {
312
326
  l = true;
313
327
  o?.[f] && setSignal(o[f], wrapValue(e[f], t));
314
328
  }
315
329
  for (;f < e.length; f++) {
316
330
  l = true;
317
- applyArrayItem(e[f], R[f], t, o?.[f], r);
331
+ applyArrayItem(e[f], d[f], t, o?.[f], r);
318
332
  }
319
333
  const n = e.length;
320
334
  syncArrayNodeMembership(t, e);
@@ -322,27 +336,27 @@ function applyStateSlow(e, t, r) {
322
336
  s !== n && o?.length && setSignal(o.length, n);
323
337
  return;
324
338
  }
325
- E = new Array(S + 1);
326
- for (f = S; f >= c; f--) {
327
- y = e[f];
328
- O = itemKey(y, r);
329
- p = d.get(O);
339
+ E = new Array(y + 1);
340
+ for (f = y; f >= c; f--) {
341
+ S = e[f];
342
+ O = itemKey(S, r);
343
+ p = R.get(O);
330
344
  E[f] = p === undefined ? -1 : p;
331
- d.set(O, f);
345
+ R.set(O, f);
332
346
  }
333
347
  for (p = c; p <= u; p++) {
334
- y = getOverrideValue(n, a, p, i);
335
- O = itemKey(y, r);
336
- f = d.get(O);
348
+ S = getOverrideValue(n, a, p, i);
349
+ O = itemKey(S, r);
350
+ f = R.get(O);
337
351
  if (f !== undefined && f !== -1) {
338
- R[f] = y;
352
+ d[f] = S;
339
353
  f = E[f];
340
- d.set(O, f);
354
+ R.set(O, f);
341
355
  }
342
356
  }
343
357
  for (f = c; f < e.length; f++) {
344
- if (f in R) {
345
- applyArrayItem(e[f], R[f], t, o?.[f], r);
358
+ if (f in d) {
359
+ applyArrayItem(e[f], d[f], t, o?.[f], r);
346
360
  } else o?.[f] && setSignal(o[f], wrapValue(e[f], t));
347
361
  }
348
362
  if (c < e.length) l = true;
@@ -373,12 +387,12 @@ function applyStateSlow(e, t, r) {
373
387
  const f = s[p];
374
388
  const c = o[f];
375
389
  const u = unwrap(getOverrideValue(n, a, f, i));
376
- let S = unwrap(e[f]);
377
- if (u === S) continue;
378
- if (!u || !isWrappable(u) || !isWrappable(S) || Array.isArray(u) !== Array.isArray(S) || r(u) != null && r(u) !== r(S)) {
390
+ let y = unwrap(e[f]);
391
+ if (u === y) continue;
392
+ if (!u || !isWrappable(u) || !isWrappable(y) || Array.isArray(u) !== Array.isArray(y) || r(u) != null && r(u) !== r(y)) {
379
393
  l && setSignal(l, void 0);
380
- c && setSignal(c, isWrappable(S) ? wrap(S, t) : S);
381
- } else applyState(S, wrap(u, t), r);
394
+ c && setSignal(c, isWrappable(y) ? wrap(y, t) : y);
395
+ } else applyState(y, wrap(u, t), r);
382
396
  }
383
397
  }
384
398
  if (!l && t[STORE_DESC]) applyDescendants(n, e, t, o, r, a, i);
@@ -392,17 +406,27 @@ function applyStateSlow(e, t, r) {
392
406
  }
393
407
  }
394
408
 
409
+ // No-key reconcile: every item reports "no key", which routes array diffs to
410
+ // the positional branch and object descent to plain per-property merging.
411
+ const NOKEY = () => null;
412
+
395
413
  /**
396
414
  * Returns a draft-mutating function that smart-merges `value` into a store,
397
- * preserving the identity of items whose `key` field matches between old and
398
- * new states. Useful when applying server payloads or full-replacement data
399
- * onto an existing store without losing fine-grained reactivity.
415
+ * preserving fine-grained reactivity: only changed leaves trigger updates.
416
+ *
417
+ * With a `key` (default `"id"`), array items whose key matches between old
418
+ * and new states keep their identity (updated in place, moves and removals
419
+ * update the corresponding signals) — the shape for keyed server payloads.
420
+ * Items without the key field fall back to positional matching.
400
421
  *
401
- * Items with the same key are updated in place (only changed properties
402
- * trigger updates). Items added or removed update the corresponding signals.
422
+ * With `key: null`, matching is purely positional: index N of the new array
423
+ * merges into index N of the old, and object properties merge recursively —
424
+ * the classic pattern for fixed-shape data that churns in place (dashboards,
425
+ * monitors), where no keyed diff pass is needed or wanted.
403
426
  *
404
427
  * @param value the next state to merge in
405
- * @param key property name (string) or extractor function for stable identity
428
+ * @param key property name (string) or extractor function for stable
429
+ * identity (default `"id"`); pass `null` for positional merging
406
430
  *
407
431
  * @example
408
432
  * ```ts
@@ -410,12 +434,19 @@ function applyStateSlow(e, t, r) {
410
434
  *
411
435
  * async function refresh() {
412
436
  * const fresh = await api.getTodos();
413
- * setTodos(reconcile(fresh, "id")); // diff-merge by `id`
437
+ * setTodos(reconcile(fresh)); // diff-merge by `id`
414
438
  * }
439
+ *
440
+ * // fixed-shape polling data — positional merge
441
+ * setStats(reconcile(nextStats, null));
415
442
  * ```
416
- */ function reconcile(e, t) {
443
+ */ function reconcile(e, t = "id") {
417
444
  return r => {
418
445
  if (r == null) throw new Error("");
446
+ if (t === null) {
447
+ applyState(e, r, NOKEY);
448
+ return;
449
+ }
419
450
  const n = typeof t === "string" ? e => e[t] : t;
420
451
  const a = n(r);
421
452
  if (a !== undefined && n(e) !== a) throw new Error("");