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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -82,7 +82,7 @@ function releaseSubtree(e) {
82
82
  if (e.se & REACTIVE_SNAPSHOT_STALE) {
83
83
  e.se &= ~REACTIVE_SNAPSHOT_STALE;
84
84
  e.se |= REACTIVE_DIRTY;
85
- if (dirtyQueue.Fe > e.He) dirtyQueue.Fe = e.He;
85
+ if (dirtyQueue.He > e.Fe) dirtyQueue.He = e.Fe;
86
86
  insertIntoHeap(e, dirtyQueue);
87
87
  }
88
88
  }
@@ -95,7 +95,10 @@ function clearSnapshots() {
95
95
  if (snapshotSources) {
96
96
  for (const e of snapshotSources) {
97
97
  delete e.Ve;
98
- delete e[STORE_SNAPSHOT_PROPS];
98
+ // StoreNode targets share one pre-initialized hidden class (see
99
+ // createStoreProxy) — assign undefined instead of deleting, and only
100
+ // when present so signal-node sources don't grow the field.
101
+ if (e[STORE_SNAPSHOT_PROPS] !== undefined) e[STORE_SNAPSHOT_PROPS] = undefined;
99
102
  }
100
103
  snapshotSources = null;
101
104
  }
@@ -131,7 +134,7 @@ function recompute(e, t = false) {
131
134
  e.se = REACTIVE_RECOMPUTING_DEPS;
132
135
  e.ce = clock;
133
136
  let a = e.Ne === NOT_PENDING ? e.Pe : e.Ne;
134
- let r = e.He;
137
+ let r = e.Fe;
135
138
  let c = tracking;
136
139
  let _ = currentOptimisticLane;
137
140
  tracking = true;
@@ -252,7 +255,7 @@ function recompute(e, t = false) {
252
255
  // for commit on its own transition's schedule — invisibly (A17/A18).
253
256
  if (e.Ne === NOT_PENDING) queuePendingNode(e);
254
257
  e.Ne = a;
255
- } else if (e.He != r) {
258
+ } else if (e.Fe != r) {
256
259
  for (let t = e.o; t !== null; t = t.ue) {
257
260
  insertIntoHeapHeight(t.le, queueFor(t.le));
258
261
  }
@@ -301,7 +304,7 @@ function computed(e, t) {
301
304
  Qe: 0,
302
305
  ke: e,
303
306
  Pe: undefined,
304
- He: 0,
307
+ Fe: 0,
305
308
  u: null,
306
309
  lt: undefined,
307
310
  ut: null,
@@ -346,7 +349,7 @@ function computed(e, t) {
346
349
  Qe: 0,
347
350
  ke: e,
348
351
  Pe: undefined,
349
- He: 0,
352
+ Fe: 0,
350
353
  u: null,
351
354
  lt: undefined,
352
355
  ut: null,
@@ -397,7 +400,7 @@ function setupComputedNode(e, t) {
397
400
  context.me = e;
398
401
  }
399
402
  }
400
- if (n) e.He = n.He + 1;
403
+ if (n) e.Fe = n.Fe + 1;
401
404
  if (GlobalQueue.Tt !== null) GlobalQueue.Tt(e);
402
405
  !t?.lazy && recompute(e, true);
403
406
  if (snapshotCaptureActive && !t?.lazy) {
@@ -520,15 +523,15 @@ function read(e) {
520
523
  link(e, t, pendingCheckActive);
521
524
  if (l.ke) {
522
525
  const n = queueFor(e);
523
- if (l.He >= n.Fe) {
526
+ if (l.Fe >= n.He) {
524
527
  markNode(t);
525
528
  markHeap(n);
526
529
  updateIfNecessary(l);
527
530
  }
528
- const i = l.He;
531
+ const i = l.Fe;
529
532
  // parent check is shallow, might need to be recursive
530
- if (i >= t.He && e.Ge !== t) {
531
- t.He = i + 1;
533
+ if (i >= t.Fe && e.Ge !== t) {
534
+ t.Fe = i + 1;
532
535
  }
533
536
  }
534
537
  }
@@ -25,7 +25,16 @@
25
25
  class NotReadyError extends Error {
26
26
  source;
27
27
  constructor(r) {
28
+ // Control-flow throw: it happens on every read of a pending source, so in
29
+ // production skip V8's eager stack capture (proportional to stack depth —
30
+ // real cost under SSR) by zeroing the V8-specific stackTraceLimit around
31
+ // super(). Dev keeps the stack for debuggability; non-V8 engines (no
32
+ // stackTraceLimit) take the plain path.
33
+ const o = Error;
34
+ const t = o.stackTraceLimit;
35
+ if (t !== undefined) o.stackTraceLimit = 0;
28
36
  super();
37
+ if (t !== undefined) o.stackTraceLimit = t;
29
38
  this.source = r;
30
39
  }
31
40
  }
@@ -20,14 +20,14 @@ import { zombieQueue, dirtyQueue } from "./scheduler.js";
20
20
  return;
21
21
  }
22
22
  const E = queueFor(e);
23
- if (E.Fe > e.He) E.Fe = e.He;
23
+ if (E.He > e.Fe) E.He = e.Fe;
24
24
  insertIntoHeap(e, E);
25
25
  }
26
26
 
27
27
  function actualInsertIntoHeap(e, E) {
28
- const t = (e.Ge?.ft ? e.Ge.Nt?.He : e.Ge?.He) ?? -1;
29
- if (t >= e.He) e.He = t + 1;
30
- const n = e.He;
28
+ const t = (e.Ge?.ft ? e.Ge.Nt?.Fe : e.Ge?.Fe) ?? -1;
29
+ if (t >= e.Fe) e.Fe = t + 1;
30
+ const n = e.Fe;
31
31
  const I = E.eE[n];
32
32
  if (I === undefined) E.eE[n] = e; else {
33
33
  const E = I.ut;
@@ -66,7 +66,7 @@ function deleteFromHeap(e, E) {
66
66
  const t = e.se;
67
67
  if (!(t & (REACTIVE_IN_HEAP | REACTIVE_IN_HEAP_HEIGHT))) return;
68
68
  e.se = t & -25;
69
- const n = e.He;
69
+ const n = e.Fe;
70
70
  if (e.ut === e) E.eE[n] = undefined; else {
71
71
  const t = e.lt;
72
72
  const I = E.eE[n];
@@ -106,11 +106,11 @@ function markNode(e, E = REACTIVE_DIRTY) {
106
106
 
107
107
  function runHeap(e, E) {
108
108
  e.tE = false;
109
- for (e.Fe = 0; e.Fe <= e.EE; e.Fe++) {
110
- let t = e.eE[e.Fe];
109
+ for (e.He = 0; e.He <= e.EE; e.He++) {
110
+ let t = e.eE[e.He];
111
111
  while (t !== undefined) {
112
112
  if (t.se & REACTIVE_IN_HEAP) E(t); else adjustHeight(t, e);
113
- t = e.eE[e.Fe];
113
+ t = e.eE[e.He];
114
114
  }
115
115
  }
116
116
  e.EE = 0;
@@ -118,14 +118,14 @@ function runHeap(e, E) {
118
118
 
119
119
  function adjustHeight(e, E) {
120
120
  deleteFromHeap(e, E);
121
- let t = e.He;
121
+ let t = e.Fe;
122
122
  for (let E = e.Xe; E; E = E.et) {
123
123
  const e = E.tt;
124
124
  const n = e.nt || e;
125
- if (n.ke && n.He >= t) t = n.He + 1;
125
+ if (n.ke && n.Fe >= t) t = n.Fe + 1;
126
126
  }
127
- if (e.He !== t) {
128
- e.He = t;
127
+ if (e.Fe !== t) {
128
+ e.Fe = t;
129
129
  for (let E = e.o; E !== null; E = E.ue) {
130
130
  // Route each subscriber by its own zombie flag, mirroring the
131
131
  // post-recompute height-adjust path. Inserting into the running `heap`
@@ -19,14 +19,14 @@ const transitions = new Set;
19
19
  const dirtyQueue = {
20
20
  eE: new Array(2e3).fill(undefined),
21
21
  tE: false,
22
- Fe: 0,
22
+ He: 0,
23
23
  EE: 0
24
24
  };
25
25
 
26
26
  const zombieQueue = {
27
27
  eE: new Array(2e3).fill(undefined),
28
28
  tE: false,
29
- Fe: 0,
29
+ He: 0,
30
30
  EE: 0
31
31
  };
32
32
 
@@ -334,7 +334,7 @@ class GlobalQueue extends Queue {
334
334
  // A kept ambient batch may hold pending nodes (#2916): stay
335
335
  // scheduled so the outer drain loop commits them via the plain
336
336
  // flush path instead of leaving them until the next natural flush.
337
- scheduled = dirtyQueue.EE >= dirtyQueue.Fe || this.m.Qt.length > 0;
337
+ scheduled = dirtyQueue.EE >= dirtyQueue.He || this.m.Qt.length > 0;
338
338
  reassignPendingTransition(e.Qt);
339
339
  activeTransition = null;
340
340
  finalizePureQueue(null, true);
@@ -362,7 +362,7 @@ class GlobalQueue extends Queue {
362
362
  } else {
363
363
  if (canUseSimpleSyncFlush(this)) {
364
364
  commitPendingNodes();
365
- if (dirtyQueue.EE >= dirtyQueue.Fe) {
365
+ if (dirtyQueue.EE >= dirtyQueue.He) {
366
366
  runHeap(dirtyQueue, GlobalQueue.pe);
367
367
  commitPendingNodes();
368
368
  }
@@ -373,7 +373,7 @@ class GlobalQueue extends Queue {
373
373
  }
374
374
  clock++;
375
375
  // Check if finalization added items to the heap (from optimistic reversion)
376
- scheduled = dirtyQueue.EE >= dirtyQueue.Fe;
376
+ scheduled = dirtyQueue.EE >= dirtyQueue.He;
377
377
  // Run lane effects first (for ready lanes), then regular effects
378
378
  activeLanes.size && GlobalQueue.In(EFFECT_RENDER);
379
379
  this.run(EFFECT_RENDER);
@@ -527,7 +527,7 @@ function finalizePureQueue(e = null, t = false) {
527
527
  const i = !t;
528
528
  if (i) commitPendingNodes();
529
529
  if (!t && globalQueue.vt.length) checkBoundaryChildren(globalQueue);
530
- const n = dirtyQueue.EE >= dirtyQueue.Fe;
530
+ const n = dirtyQueue.EE >= dirtyQueue.He;
531
531
  if (n) runHeap(dirtyQueue, GlobalQueue.pe);
532
532
  if (i) {
533
533
  if (n) commitPendingNodes();
@@ -232,7 +232,7 @@ function getLatestValueComputed(e) {
232
232
  // until the flush (#2922). Mirror the tracked-read pull here: mark the
233
233
  // queued staleness through the graph, then bring the shadow up to date.
234
234
  const e = queueFor(t);
235
- if (t.He >= e.Fe && !(t.se & (REACTIVE_DISPOSED | REACTIVE_ZOMBIE))) {
235
+ if (t.Fe >= e.He && !(t.se & (REACTIVE_DISPOSED | REACTIVE_ZOMBIE))) {
236
236
  markHeap(e);
237
237
  prepareComputed(t, true);
238
238
  }
package/dist/prod/map.js CHANGED
@@ -118,22 +118,29 @@ function updateKeyedMap() {
118
118
  this.Mt = t.slice(0);
119
119
  this.jt = s;
120
120
  } else {
121
- let o, c, a, f, u, p, w, l, d, O = new Array(s), m = new Array(s);
122
- r = this.Ut ? new Array(s) : undefined;
123
- n = this.$t ? new Array(s) : undefined;
121
+ let o, c, a, f, u, p, w, l, d;
124
122
  // skip common prefix
125
123
  for (o = 0, c = Math.min(this.jt, s); o < c && (this.Mt[o] === t[o] || this.Ut && compare(this.Gt, this.Mt[o], t[o])); o++) {
126
124
  if (this.Ut) setSignal(this.Ut[o], t[o]);
127
125
  }
128
- // common suffix
126
+ // skip common suffix — counted only; retained entries land in one pass
127
+ // at commit instead of being staged and copied twice
129
128
  for (c = this.jt - 1, a = s - 1; c >= o && a >= o && (this.Mt[c] === t[a] || this.Ut && compare(this.Gt, this.Mt[c], t[a])); c--,
130
- a--) {
131
- O[a] = this.xt[c];
132
- m[a] = this.Ft[c];
133
- r && (r[a] = this.Ut[c]);
134
- n && (n[a] = this.$t[c]);
129
+ a--) ;
130
+ // no structural change (every position matched in place at equal
131
+ // length — the common post-reconcile shape): keep the same mapped
132
+ // array identity so downstream consumers don't re-run at all
133
+ if (o === s && this.jt === s) {
134
+ this.Mt = t.slice(0);
135
+ return;
135
136
  }
136
- // 0) prepare a map of all indices in newItems, scanning backwards so we encounter them in natural order
137
+ const O = s - this.jt;
138
+ const m = new Array(s);
139
+ const _ = new Array(s);
140
+ r = this.Ut ? new Array(s) : undefined;
141
+ n = this.$t ? new Array(s) : undefined;
142
+ // 0) prepare a map of all indices in the changed window of newItems,
143
+ // scanning backwards so we encounter them in natural order
137
144
  p = new Map;
138
145
  w = new Array(a + 1);
139
146
  for (e = a; e >= o; e--) {
@@ -143,14 +150,16 @@ function updateKeyedMap() {
143
150
  w[e] = i === undefined ? -1 : i;
144
151
  p.set(u, e);
145
152
  }
146
- // 1) step through all old items and see if they can be found in the new set; if so, save them in a temp array and mark them moved; if not, queue them for disposal at commit
153
+ // 1) step through the old changed window and see if items can be found
154
+ // in the new set; if so, stage them at their new positions; if not,
155
+ // queue them for disposal at commit
147
156
  for (i = o; i <= c; i++) {
148
157
  f = this.Mt[i];
149
158
  u = this.Gt ? this.Gt(f) : f;
150
159
  e = p.get(u);
151
160
  if (e !== undefined && e !== -1) {
152
- O[e] = this.xt[i];
153
- m[e] = this.Ft[i];
161
+ m[e] = this.xt[i];
162
+ _[e] = this.Ft[i];
154
163
  r && (r[e] = this.Ut[i]);
155
164
  n && (n[e] = this.$t[i]);
156
165
  e = w[e];
@@ -159,33 +168,48 @@ function updateKeyedMap() {
159
168
  }
160
169
  // 2) create new rows into the temp arrays; an abort disposes only these
161
170
  try {
162
- for (e = o; e < s; e++) {
163
- if (e in O) continue;
164
- (d ??= []).push(m[e] = createOwner());
165
- O[e] = runWithOwner(m[e], h);
171
+ for (e = o; e <= a; e++) {
172
+ if (_[e] !== undefined) continue;
173
+ (d ??= []).push(_[e] = createOwner());
174
+ m[e] = runWithOwner(_[e], h);
166
175
  }
167
176
  } catch (t) {
168
177
  if (d) for (i = 0; i < d.length; i++) d[i].dispose();
169
178
  throw t;
170
179
  }
171
- // 3) commit: land positions, then dispose exited rows
172
- for (e = o; e < s; e++) {
173
- this.xt[e] = O[e];
174
- this.Ft[e] = m[e];
180
+ // 3) commit: land the retained prefix and suffix plus the staged window
181
+ // into the fresh arrays, swap them in (new identity for downstream
182
+ // change propagation), then dispose exited rows
183
+ for (i = 0; i < o; i++) {
184
+ m[i] = this.xt[i];
185
+ _[i] = this.Ft[i];
186
+ r && (r[i] = this.Ut[i]);
187
+ n && (n[i] = this.$t[i]);
188
+ }
189
+ for (e = o; e <= a; e++) {
190
+ if (r) setSignal(r[e], t[e]);
191
+ if (n) setSignal(n[e], e);
192
+ }
193
+ for (e = a + 1; e < s; e++) {
194
+ m[e] = this.xt[e - O];
195
+ _[e] = this.Ft[e - O];
175
196
  if (r) {
176
- this.Ut[e] = r[e];
177
- setSignal(this.Ut[e], t[e]);
197
+ r[e] = this.Ut[e - O];
198
+ setSignal(r[e], t[e]);
178
199
  }
179
200
  if (n) {
180
- this.$t[e] = n[e];
181
- setSignal(this.$t[e], e);
201
+ n[e] = this.$t[e - O];
202
+ if (O !== 0) setSignal(n[e], e);
182
203
  }
183
204
  }
184
- if (l) for (i = 0; i < l.length; i++) l[i].dispose();
185
- // 4) in case the new set is shorter than the old, set the length of the mapped array
186
- this.xt = this.xt.slice(0, this.jt = s);
187
- // 5) save a copy of the mapped items for the next update
205
+ this.xt = m;
206
+ this.Ft = _;
207
+ r && (this.Ut = r);
208
+ n && (this.$t = n);
209
+ this.jt = s;
210
+ // save a copy of the mapped items for the next update
188
211
  this.Mt = t.slice(0);
212
+ if (l) for (i = 0; i < l.length; i++) l[i].dispose();
189
213
  }
190
214
  });
191
215
  return this.xt;
@@ -16,17 +16,17 @@ import { runProjectionComputed } from "./projection.js";
16
16
 
17
17
  import { storeSetter, $TARGET, STORE_OPTIMISTIC_OVERRIDE, STORE_NODE, STORE_OPTIMISTIC_OWNERS, getOverlayLayer, STORE_VALUE, $DELETED, isWrappable, wrap, visibleNodeValue, $TRACK, notifySelf, STORE_WRAP, createStoreProxy, storeTraps, STORE_LOOKUP, STORE_OPTIMISTIC, STORE_FIREWALL } from "./store.js";
18
18
 
19
- function createOptimisticStore(e, t, r) {
19
+ function createOptimisticStore(e, t, i) {
20
20
  // Register clear function with scheduler; store nodes marked
21
21
  // STORE_OPTIMISTIC take the engine's write path, so install it before any
22
22
  // node can be created.
23
23
  installOptimisticEngine();
24
24
  GlobalQueue.Dt ||= clearOptimisticStores;
25
- const i = typeof e === "function";
26
- const o = i ? t : e;
27
- const n = i ? e : undefined;
25
+ const r = typeof e === "function";
26
+ const o = r ? t : e;
27
+ const n = r ? e : undefined;
28
28
  // Create optimistic projection store
29
- const {store: c} = createOptimisticProjectionInternal(n, o, r);
29
+ const {store: c} = createOptimisticProjectionInternal(n, o, i);
30
30
  return [ c, e => storeSetter(c, e) ];
31
31
  }
32
32
 
@@ -35,8 +35,8 @@ function createOptimisticStore(e, t, r) {
35
35
  // scheduler's flush tail carries only a size-guarded hook call. The
36
36
  // completing transition scopes each clear to its own layer keys (#2899).
37
37
  function clearOptimisticStores(e, t) {
38
- for (const r of e) {
39
- const e = r[$TARGET];
38
+ for (const i of e) {
39
+ const e = i[$TARGET];
40
40
  if (e?.[STORE_OPTIMISTIC_OVERRIDE]) clearOptimisticOverride(e, t);
41
41
  }
42
42
  e.clear();
@@ -54,9 +54,9 @@ function clearOptimisticStores(e, t) {
54
54
  * fresh authoritative data) consumes everything — the correction supersedes
55
55
  * every tentative layer.
56
56
  */ function clearOptimisticOverride(e, t) {
57
- const r = e[STORE_OPTIMISTIC_OVERRIDE];
58
- if (!r) return;
59
- const i = e[STORE_NODE];
57
+ const i = e[STORE_OPTIMISTIC_OVERRIDE];
58
+ if (!i) return;
59
+ const r = e[STORE_NODE];
60
60
  const o = e[STORE_OPTIMISTIC_OWNERS];
61
61
  const n = t !== undefined;
62
62
  let c = false;
@@ -66,7 +66,7 @@ function clearOptimisticStores(e, t) {
66
66
  const O = projectionWriteActive;
67
67
  setProjectionWriteActive(true);
68
68
  try {
69
- for (const O of Reflect.ownKeys(r)) {
69
+ for (const O of Reflect.ownKeys(i)) {
70
70
  if (n) {
71
71
  let e = o?.[O] ?? null;
72
72
  // Resolve merge chains (entangled actions settle as one); path-compress
@@ -83,19 +83,19 @@ function clearOptimisticStores(e, t) {
83
83
  }
84
84
  }
85
85
  }
86
- delete r[O];
86
+ delete i[O];
87
87
  if (o) delete o[O];
88
88
  c = true;
89
- const T = i?.[O];
89
+ const T = r?.[O];
90
90
  if (T) {
91
91
  // Clear lane association so effects go to regular queue
92
92
  T.je = undefined;
93
93
  // Re-read from base — this key left the optimistic layer above, so the
94
94
  // overlay resolves to STORE_OVERRIDE or STORE_VALUE.
95
95
  const t = getOverlayLayer(e, O);
96
- const r = t ? t[O] : e[STORE_VALUE][O];
97
- const i = r === $DELETED ? undefined : r;
98
- const o = isWrappable(i) ? wrap(i, e) : i;
96
+ const i = t ? t[O] : e[STORE_VALUE][O];
97
+ const r = i === $DELETED ? undefined : i;
98
+ const o = isWrappable(r) ? wrap(r, e) : r;
99
99
  const n = visibleNodeValue(T);
100
100
  T.Ee = NOT_PENDING;
101
101
  T.sn = null;
@@ -108,12 +108,14 @@ function clearOptimisticStores(e, t) {
108
108
  }
109
109
  }
110
110
  if (!s) {
111
- delete e[STORE_OPTIMISTIC_OVERRIDE];
112
- delete e[STORE_OPTIMISTIC_OWNERS];
111
+ // Assignment, not delete: StoreNode targets share one pre-initialized
112
+ // hidden class (see createStoreProxy) and a delete would demote it.
113
+ e[STORE_OPTIMISTIC_OVERRIDE] = undefined;
114
+ e[STORE_OPTIMISTIC_OWNERS] = undefined;
113
115
  }
114
116
  // Notify $TRACK
115
- if (c && i?.[$TRACK]) {
116
- i[$TRACK].je = undefined;
117
+ if (c && r?.[$TRACK]) {
118
+ r[$TRACK].je = undefined;
117
119
  notifySelf(e);
118
120
  }
119
121
  } finally {
@@ -121,8 +123,8 @@ function clearOptimisticStores(e, t) {
121
123
  }
122
124
  }
123
125
 
124
- function createOptimisticProjectionInternal(e, t, r) {
125
- let i;
126
+ function createOptimisticProjectionInternal(e, t, i) {
127
+ let r;
126
128
  const o = new WeakMap;
127
129
  const wrapper = e => {
128
130
  e[STORE_WRAP] = wrapProjection;
@@ -131,7 +133,7 @@ function createOptimisticProjectionInternal(e, t, r) {
131
133
  // Mark as optimistic store
132
134
  Object.defineProperty(e, STORE_FIREWALL, {
133
135
  get() {
134
- return i;
136
+ return r;
135
137
  },
136
138
  configurable: true
137
139
  });
@@ -165,19 +167,19 @@ function createOptimisticProjectionInternal(e, t, r) {
165
167
  setProjectionWriteActive(t);
166
168
  }
167
169
  };
168
- i = computed(() => {
170
+ r = computed(() => {
169
171
  setProjectionWriteActive(true);
170
172
  try {
171
- runProjectionComputed(n, e, r?.key || "id", wrapCommit, clearProjectionOverride);
173
+ runProjectionComputed(n, e, i?.key || "id", wrapCommit, clearProjectionOverride);
172
174
  } finally {
173
175
  setProjectionWriteActive(false);
174
176
  }
175
177
  }, undefined);
176
- i.T &= ~CONFIG_AUTO_DISPOSE;
178
+ r.T &= ~CONFIG_AUTO_DISPOSE;
177
179
  }
178
180
  return {
179
181
  store: n,
180
- node: i
182
+ node: r
181
183
  };
182
184
  }
183
185