@solidjs/signals 2.0.0-beta.19 → 2.0.0-beta.21

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 (37) hide show
  1. package/README.md +5 -1
  2. package/dist/dev.js +753 -378
  3. package/dist/node.cjs +1947 -1607
  4. package/dist/prod/affects.js +17 -21
  5. package/dist/prod/core/action.js +66 -15
  6. package/dist/prod/core/async.js +57 -71
  7. package/dist/prod/core/constants.js +15 -1
  8. package/dist/prod/core/core.js +39 -39
  9. package/dist/prod/core/effect.js +14 -14
  10. package/dist/prod/core/graph.js +1 -1
  11. package/dist/prod/core/heap.js +11 -3
  12. package/dist/prod/core/lanes.js +23 -13
  13. package/dist/prod/core/optimistic.js +107 -99
  14. package/dist/prod/core/owner.js +23 -23
  15. package/dist/prod/core/scheduler.js +188 -181
  16. package/dist/prod/core/verdict.js +29 -15
  17. package/dist/prod/map.js +196 -167
  18. package/dist/prod/signals.js +1 -1
  19. package/dist/prod/store/optimistic.js +103 -68
  20. package/dist/prod/store/reconcile.js +80 -46
  21. package/dist/prod/store/store.js +204 -73
  22. package/dist/prod/store/utils.js +61 -42
  23. package/dist/types/core/action.d.ts +35 -6
  24. package/dist/types/core/constants.d.ts +12 -0
  25. package/dist/types/core/dev.d.ts +7 -0
  26. package/dist/types/core/lanes.d.ts +2 -0
  27. package/dist/types/core/scheduler.d.ts +2 -6
  28. package/dist/types/core/types.d.ts +9 -1
  29. package/dist/types/store/store.d.ts +8 -2
  30. package/dist/types-cjs/core/action.d.cts +35 -6
  31. package/dist/types-cjs/core/constants.d.cts +12 -0
  32. package/dist/types-cjs/core/dev.d.cts +7 -0
  33. package/dist/types-cjs/core/lanes.d.cts +2 -0
  34. package/dist/types-cjs/core/scheduler.d.cts +2 -6
  35. package/dist/types-cjs/core/types.d.cts +9 -1
  36. package/dist/types-cjs/store/store.d.cts +8 -2
  37. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { EFFECT_RENDER, EFFECT_USER, STATUS_PENDING, REACTIVE_DISPOSED, REACTIVE_REASK, CONFIG_IN_SNAPSHOT_SCOPE, REACTIVE_SNAPSHOT_STALE, REACTIVE_OPTIMISTIC_DIRTY, REACTIVE_ZOMBIE, NOT_PENDING, EFFECT_TRACKED, REACTIVE_MANUAL_WRITE, STATUS_UNINITIALIZED } from "./constants.js";
1
+ import { EFFECT_RENDER, EFFECT_USER, STATUS_PENDING, REACTIVE_DISPOSED, REACTIVE_ZOMBIE, NOT_PENDING, EFFECT_TRACKED, CONFIG_IN_SNAPSHOT_SCOPE, REACTIVE_SNAPSHOT_STALE, REACTIVE_OPTIMISTIC_DIRTY, REACTIVE_REASK, REACTIVE_MANUAL_WRITE, STATUS_UNINITIALIZED } from "./constants.js";
2
2
 
3
3
  import { currentOptimisticLane } from "./core.js";
4
4
 
@@ -55,7 +55,8 @@ function registerTransientStoreNode(e) {
55
55
  }
56
56
 
57
57
  function canUseSimpleSyncFlush(e) {
58
- return transitions.size === 0 && activeLanes.size === 0 && e.wt.length === 0 && e.Be.length === 0 && e.N.length === 0 && e.Lt.size === 0 && transientStoreNodes.size === 0;
58
+ const t = e.N;
59
+ return transitions.size === 0 && activeLanes.size === 0 && e.Qt.length === 0 && t.Be.length === 0 && t._.length === 0 && t.dn.size === 0 && transientStoreNodes.size === 0;
59
60
  }
60
61
 
61
62
  function sweepTransientStoreNodes() {
@@ -70,7 +71,7 @@ function sweepTransientStoreNodes() {
70
71
  // A live affects() mark keeps the node addressable: sweeping it would
71
72
  // detach the refcount from the slot (a fresh probe would upsert a new,
72
73
  // unmarked node for the same property).
73
- if (e.P) continue;
74
+ if (e.M) continue;
74
75
  transientStoreNodes.delete(e);
75
76
  e.ct?.();
76
77
  }
@@ -89,31 +90,57 @@ function setProjectionWriteActive(e) {
89
90
  projectionWriteActive = e;
90
91
  }
91
92
 
93
+ /**
94
+ * Ambient work IS a transaction: the global queue always carries one
95
+ * current-transaction-shaped batch (`globalQueue._batch`). With no transition
96
+ * active, registrations (pending commits, optimistic nodes, affects marks,
97
+ * optimistic stores) land in a plain ambient batch that the plain flush
98
+ * finalizes; when a transition initializes it adopts the ambient batch's
99
+ * contents and `_batch` becomes the transition itself, so later registrations
100
+ * land there directly — no per-field aliasing.
101
+ */ function createBatch() {
102
+ return {
103
+ Ie: clock,
104
+ yt: [],
105
+ Lt: new Map,
106
+ Be: [],
107
+ _: [],
108
+ dn: new Set,
109
+ Te: [],
110
+ Bt: {
111
+ Mt: [ [], [] ],
112
+ Qt: []
113
+ },
114
+ cn: false,
115
+ un: new Set
116
+ };
117
+ }
118
+
92
119
  function mergeTransitionState(e, t) {
93
- t.Ht = e;
120
+ t.cn = e;
94
121
  e.Te.push(...t.Te);
95
122
  for (const i of activeLanes) if (i.ve === t) i.ve = e;
96
123
  if (t.Be.length) {
97
- // Move (don't copy): the global queue may still alias the outgoing
98
- // array, and the adoption pass in initTransition would re-push its
124
+ // Move (don't copy): the global queue's batch may still be the outgoing
125
+ // transition, and the adoption pass in initTransition would re-push its
99
126
  // contents into the target — duplicating every entry.
100
127
  e.Be.push(...t.Be);
101
128
  t.Be.length = 0;
102
129
  }
103
- if (t.N.length) {
104
- // Move (don't copy): the global queue may still alias the outgoing
105
- // array, and the adoption pass in initTransition would re-push its
130
+ if (t._.length) {
131
+ // Move (don't copy): the global queue's batch may still be the outgoing
132
+ // transition, and the adoption pass in initTransition would re-push its
106
133
  // contents into the target — double-releasing every mark.
107
- e.N.push(...t.N);
108
- t.N.length = 0;
134
+ e._.push(...t._);
135
+ t._.length = 0;
109
136
  }
110
- for (const i of t.Lt) e.Lt.add(i);
111
- for (const [i, n] of t.qt) {
112
- let t = e.qt.get(i);
113
- if (!t) e.qt.set(i, t = new Set);
137
+ for (const i of t.dn) e.dn.add(i);
138
+ for (const [i, n] of t.Lt) {
139
+ let t = e.Lt.get(i);
140
+ if (!t) e.Lt.set(i, t = new Set);
114
141
  for (const e of n) t.add(e);
115
142
  }
116
- for (const i of t.Qt) e.Qt.add(i);
143
+ for (const i of t.un) e.un.add(i);
117
144
  }
118
145
 
119
146
  function schedule() {
@@ -123,7 +150,7 @@ function schedule() {
123
150
  }
124
151
  if (scheduled) return;
125
152
  scheduled = true;
126
- if (!syncDepth && !globalQueue.Bt && !projectionWriteActive) queueMicrotask(flush);
153
+ if (!syncDepth && !globalQueue.Ut && !projectionWriteActive) queueMicrotask(flush);
127
154
  }
128
155
 
129
156
  /**
@@ -153,32 +180,32 @@ function notifyHalted() {
153
180
  }
154
181
 
155
182
  class Queue {
156
- He=null;
157
- zt=[ [], [] ];
158
- wt=[];
183
+ Fe=null;
184
+ Mt=[ [], [] ];
185
+ Qt=[];
159
186
  created=clock;
160
187
  addChild(e) {
161
- this.wt.push(e);
162
- e.He = this;
188
+ this.Qt.push(e);
189
+ e.Fe = this;
163
190
  }
164
191
  removeChild(e) {
165
- const t = this.wt.indexOf(e);
192
+ const t = this.Qt.indexOf(e);
166
193
  if (t >= 0) {
167
- this.wt.splice(t, 1);
168
- e.He = null;
194
+ this.Qt.splice(t, 1);
195
+ e.Fe = null;
169
196
  }
170
197
  }
171
198
  notify(e, t, i, n) {
172
- if (this.He) return this.He.notify(e, t, i, n);
199
+ if (this.Fe) return this.Fe.notify(e, t, i, n);
173
200
  return false;
174
201
  }
175
202
  run(e) {
176
- if (this.zt[e - 1].length) {
177
- const t = this.zt[e - 1];
178
- this.zt[e - 1] = [];
203
+ if (this.Mt[e - 1].length) {
204
+ const t = this.Mt[e - 1];
205
+ this.Mt[e - 1] = [];
179
206
  runQueue(t, e);
180
207
  }
181
- for (let t = 0; t < this.wt.length; t++) this.wt[t].run?.(e);
208
+ for (let t = 0; t < this.Qt.length; t++) this.Qt[t].run?.(e);
182
209
  }
183
210
  enqueue(e, t) {
184
211
  if (e) {
@@ -187,50 +214,48 @@ class Queue {
187
214
  const i = findLane(currentOptimisticLane);
188
215
  i.tn[e - 1].push(t);
189
216
  } else {
190
- this.zt[e - 1].push(t);
217
+ this.Mt[e - 1].push(t);
191
218
  }
192
219
  }
193
220
  schedule();
194
221
  }
195
222
  stashQueues(e) {
196
- e.zt[0].push(...this.zt[0]);
197
- e.zt[1].push(...this.zt[1]);
198
- this.zt = [ [], [] ];
199
- for (let t = 0; t < this.wt.length; t++) {
200
- let i = this.wt[t];
201
- let n = e.wt[t];
223
+ e.Mt[0].push(...this.Mt[0]);
224
+ e.Mt[1].push(...this.Mt[1]);
225
+ this.Mt = [ [], [] ];
226
+ for (let t = 0; t < this.Qt.length; t++) {
227
+ let i = this.Qt[t];
228
+ let n = e.Qt[t];
202
229
  if (!n) {
203
230
  n = {
204
- zt: [ [], [] ],
205
- wt: []
231
+ Mt: [ [], [] ],
232
+ Qt: []
206
233
  };
207
- e.wt[t] = n;
234
+ e.Qt[t] = n;
208
235
  }
209
236
  i.stashQueues(n);
210
237
  }
211
238
  }
212
239
  restoreQueues(e) {
213
- this.zt[0].push(...e.zt[0]);
214
- this.zt[1].push(...e.zt[1]);
215
- for (let t = 0; t < e.wt.length; t++) {
216
- const i = e.wt[t];
217
- let n = this.wt[t];
240
+ this.Mt[0].push(...e.Mt[0]);
241
+ this.Mt[1].push(...e.Mt[1]);
242
+ for (let t = 0; t < e.Qt.length; t++) {
243
+ const i = e.Qt[t];
244
+ let n = this.Qt[t];
218
245
  if (n) n.restoreQueues(i);
219
246
  }
220
247
  }
221
248
  }
222
249
 
223
250
  class GlobalQueue extends Queue {
224
- Bt=false;
225
- xt=null;
226
- Yt=[];
227
- Be=[];
228
- N=[];
229
- Lt=new Set;
251
+ Ut=false;
252
+ // The current transaction-shaped batch: a plain ambient batch while no
253
+ // transition is active, the active transition itself after initTransition.
254
+ N=createBatch();
230
255
  static Ce;
231
256
  static Oe;
232
257
  static ut;
233
- static Kt=null;
258
+ static Vt=null;
234
259
  // Store-side hook: drops a keyless affects() mark's identity scope when the
235
260
  // carrier node's last registration releases (wired by store.ts, mirroring
236
261
  // _clearOptimisticStore).
@@ -256,15 +281,15 @@ class GlobalQueue extends Queue {
256
281
  // verdict.ts, and `pendingCheckActive`/`latestReadActive` only flip inside
257
282
  // isPending()/latest()).
258
283
  static _e=null;
259
- static _=null;
284
+ static P=null;
260
285
  static me=null;
261
286
  static R=null;
262
287
  static Gt=null;
263
288
  static Pt=null;
264
- static vt=null;
289
+ static kt=null;
265
290
  static tt=null;
266
291
  static nt=null;
267
- static Zt=null;
292
+ static wt=null;
268
293
  // Optimistic-engine hooks (wired by core/optimistic.ts via
269
294
  // installOptimisticEngine(), called from verdict.ts / createOptimistic /
270
295
  // createOptimisticStore — every module that can create optimistic state).
@@ -273,22 +298,22 @@ class GlobalQueue extends Queue {
273
298
  // entry, or a non-null `currentOptimisticLane`, so `!` invocations are safe
274
299
  // once the gate holds.
275
300
  static bt=null;
276
- static Ut=null;
277
- static yt=null;
278
- static Wt=null;
279
- static jt=null;
280
- static Mt=null;
281
- static gt=null;
301
+ static En=null;
302
+ static Tn=null;
303
+ static In=null;
304
+ static Nn=null;
305
+ static On=null;
282
306
  static Dt=null;
307
+ static gt=null;
283
308
  static ht=null;
284
- static kt=null;
309
+ static vt=null;
285
310
  static $e=null;
286
311
  static et=null;
287
312
  static Xe=null;
288
- static Vt=null;
313
+ static mn=null;
289
314
  flush() {
290
- if (this.Bt) return;
291
- this.Bt = true;
315
+ if (this.Ut) return;
316
+ this.Ut = true;
292
317
  try {
293
318
  if (false) ;
294
319
  runHeap(dirtyQueue, GlobalQueue.Ce);
@@ -297,38 +322,55 @@ class GlobalQueue extends Queue {
297
322
  if (!e) {
298
323
  const e = activeTransition;
299
324
  runHeap(zombieQueue, GlobalQueue.Ce);
300
- this.xt = null;
301
- this.Yt = [];
302
- this.Be = [];
303
- this.N = [];
304
- this.Lt = new Set;
325
+ // Detach: the stashed transition keeps its batch; ambient work that
326
+ // follows lands in a fresh one. If the batch is already a separate
327
+ // ambient one — action done() restored activeTransition without
328
+ // adopting the batch, and an ordinary write landed there before
329
+ // the scheduled flush (#2916) — keep it: replacing it would strand
330
+ // its queued pending nodes with held _pendingValues forever.
331
+ if (this.N === e) currentBatch = this.N = createBatch();
305
332
  // Run lane effects immediately (before stashing) - lanes with no pending async
306
333
  if (activeLanes.size) {
307
- GlobalQueue.Mt(EFFECT_RENDER);
308
- GlobalQueue.Mt(EFFECT_USER);
334
+ GlobalQueue.On(EFFECT_RENDER);
335
+ GlobalQueue.On(EFFECT_USER);
309
336
  }
310
- this.stashQueues(e.Jt);
337
+ this.stashQueues(e.Bt);
311
338
  clock++;
312
- scheduled = dirtyQueue.EE >= dirtyQueue.Le;
313
- reassignPendingTransition(e.Yt);
339
+ // A kept ambient batch may hold pending nodes (#2916): stay
340
+ // scheduled so the outer drain loop commits them via the plain
341
+ // flush path instead of leaving them until the next natural flush.
342
+ scheduled = dirtyQueue.EE >= dirtyQueue.Le || this.N.yt.length > 0;
343
+ reassignPendingTransition(e.yt);
314
344
  activeTransition = null;
315
345
  // The stash pass (committed-view rerun of plain optimistic signals)
316
346
  // wraps finalizePureQueue in the engine; a non-empty _optimisticNodes
317
347
  // means _optimisticWrite ran, which installed the hook.
318
- if (!e.Te.length && !e.qt.size && e.Be.length) {
319
- GlobalQueue.yt(e);
348
+ if (!e.Te.length && !e.Lt.size && e.Be.length) {
349
+ GlobalQueue.Tn(e);
320
350
  } else {
321
351
  finalizePureQueue(null, true);
322
352
  }
323
353
  return;
324
354
  }
325
- this.Yt !== activeTransition.Yt && this.Yt.push(...activeTransition.Yt);
326
- this.restoreQueues(activeTransition.Jt);
327
- transitions.delete(activeTransition);
328
355
  const t = activeTransition;
356
+ const i = this.N;
357
+ i !== t && i.yt.push(...t.yt);
358
+ this.restoreQueues(t.Bt);
359
+ transitions.delete(t);
329
360
  activeTransition = null;
330
- reassignPendingTransition(this.Yt);
361
+ reassignPendingTransition(i.yt);
331
362
  finalizePureQueue(t);
363
+ if (i === t) {
364
+ // Drop the dead Transition wrapper but keep its (drained) containers
365
+ // as the ambient batch — late registrations during finalization live
366
+ // there and must survive to the next flush.
367
+ const e = createBatch();
368
+ e.yt = i.yt;
369
+ e.Be = i.Be;
370
+ e._ = i._;
371
+ e.dn = i.dn;
372
+ currentBatch = this.N = e;
373
+ }
332
374
  } else {
333
375
  if (canUseSimpleSyncFlush(this)) {
334
376
  commitPendingNodes();
@@ -345,15 +387,15 @@ class GlobalQueue extends Queue {
345
387
  // Check if finalization added items to the heap (from optimistic reversion)
346
388
  scheduled = dirtyQueue.EE >= dirtyQueue.Le;
347
389
  // Run lane effects first (for ready lanes), then regular effects
348
- activeLanes.size && GlobalQueue.Mt(EFFECT_RENDER);
390
+ activeLanes.size && GlobalQueue.On(EFFECT_RENDER);
349
391
  this.run(EFFECT_RENDER);
350
- activeLanes.size && GlobalQueue.Mt(EFFECT_USER);
392
+ activeLanes.size && GlobalQueue.On(EFFECT_USER);
351
393
  this.run(EFFECT_USER);
352
394
  if (false) ;
353
395
  if (false && !scheduled && !activeTransition && transitions.size === 0 && activeLanes.size === 0) ;
354
396
  if (false) ;
355
397
  } finally {
356
- this.Bt = false;
398
+ this.Ut = false;
357
399
  }
358
400
  }
359
401
  notify(e, t, i, n) {
@@ -363,8 +405,8 @@ class GlobalQueue extends Queue {
363
405
  const t = n !== undefined ? n : e.k;
364
406
  if (activeTransition && t) {
365
407
  const i = t.source;
366
- let n = activeTransition.qt.get(i);
367
- if (!n) activeTransition.qt.set(i, n = new Set);
408
+ let n = activeTransition.Lt.get(i);
409
+ if (!n) activeTransition.Lt.set(i, n = new Set);
368
410
  const s = n.size;
369
411
  n.add(e);
370
412
  if (n.size !== s) schedule();
@@ -379,21 +421,7 @@ class GlobalQueue extends Queue {
379
421
  if (e && e === activeTransition) return;
380
422
  if (!e && activeTransition && activeTransition.Ie === clock) return;
381
423
  if (!activeTransition) {
382
- activeTransition = e ?? {
383
- Ie: clock,
384
- Yt: [],
385
- qt: new Map,
386
- Be: [],
387
- N: [],
388
- Lt: new Set,
389
- Te: [],
390
- Jt: {
391
- zt: [ [], [] ],
392
- wt: []
393
- },
394
- Ht: false,
395
- Qt: new Set
396
- };
424
+ activeTransition = e ?? createBatch();
397
425
  } else if (e) {
398
426
  const t = activeTransition;
399
427
  mergeTransitionState(e, t);
@@ -402,61 +430,37 @@ class GlobalQueue extends Queue {
402
430
  }
403
431
  transitions.add(activeTransition);
404
432
  activeTransition.Ie = clock;
405
- if (this.xt !== null) {
406
- this.xt.ve = activeTransition;
407
- activeTransition.Yt.push(this.xt);
408
- this.xt = null;
409
- }
410
- if (this.Yt !== activeTransition.Yt) {
411
- for (let e = 0; e < this.Yt.length; e++) {
412
- const t = this.Yt[e];
413
- t.ve = activeTransition;
414
- activeTransition.Yt.push(t);
433
+ const t = this.N;
434
+ if (t !== activeTransition) {
435
+ // Adopt the ambient batch into the transaction, then make the
436
+ // transaction the batch so later registrations land there directly.
437
+ // Pending and optimistic nodes are re-stamped as the transaction's;
438
+ // marks don't hijack the node's _transition — a mark on a plain signal
439
+ // must not entangle unrelated writes to it; the same rule holds one hop
440
+ // downstream: propagation never queues pended subscribers as pending
441
+ // nodes, see propagateAffectsMark, #2893.
442
+ for (let e = 0; e < t.yt.length; e++) {
443
+ const i = t.yt[e];
444
+ i.ve = activeTransition;
445
+ activeTransition.yt.push(i);
415
446
  }
416
- this.Yt = activeTransition.Yt;
417
- }
418
- if (this.Be !== activeTransition.Be) {
419
- for (let e = 0; e < this.Be.length; e++) {
420
- const t = this.Be[e];
421
- t.ve = activeTransition;
422
- activeTransition.Be.push(t);
447
+ for (let e = 0; e < t.Be.length; e++) {
448
+ const i = t.Be[e];
449
+ i.ve = activeTransition;
450
+ activeTransition.Be.push(i);
423
451
  }
424
- this.Be = activeTransition.Be;
425
- }
426
- if (this.N !== activeTransition.N) {
427
- // Adopt ambient marks into the transaction (marks don't hijack the
428
- // node's _transition — a mark on a plain signal must not entangle
429
- // unrelated writes to it; the same rule holds one hop downstream:
430
- // propagation never queues pended subscribers as pending nodes, see
431
- // propagateAffectsMark, #2893). After adoption the queue aliases the
432
- // transition's array, so later registrations land there directly.
433
- activeTransition.N.push(...this.N);
434
- this.N = activeTransition.N;
452
+ if (t._.length) activeTransition._.push(...t._);
453
+ for (const e of t.dn) activeTransition.dn.add(e);
454
+ currentBatch = this.N = activeTransition;
435
455
  }
436
456
  for (const e of activeLanes) {
437
457
  if (!e.ve) e.ve = activeTransition;
438
458
  }
439
- if (this.Lt !== activeTransition.Lt) {
440
- for (const e of this.Lt) activeTransition.Lt.add(e);
441
- this.Lt = activeTransition.Lt;
442
- }
443
459
  }
444
460
  }
445
461
 
446
462
  function queuePendingNode(e) {
447
- if (activeTransition) {
448
- globalQueue.Yt.push(e);
449
- return;
450
- }
451
- if (globalQueue.xt === null && globalQueue.Yt.length === 0) {
452
- globalQueue.xt = e;
453
- return;
454
- }
455
- if (globalQueue.xt !== null) {
456
- globalQueue.Yt.push(globalQueue.xt);
457
- globalQueue.xt = null;
458
- }
459
- globalQueue.Yt.push(e);
463
+ currentBatch.yt.push(e);
460
464
  }
461
465
 
462
466
  // Sticky: flips true on the first refresh() ever (the only setter of
@@ -474,23 +478,23 @@ function insertSubs(e, t = false) {
474
478
  const i = e.Je || currentOptimisticLane;
475
479
  const n = e.Ye !== undefined;
476
480
  const s = reaskArmed;
477
- for (let a = e.p; a !== null; a = a.de) {
481
+ for (let r = e.p; r !== null; r = r.de) {
478
482
  // A value-change notification is a new question for the subscriber: any
479
483
  // pending re-ask mark (refresh) it carried is superseded.
480
- if (s) a.Ee.u &= ~REACTIVE_REASK;
481
- if (n && a.Ee.U & CONFIG_IN_SNAPSHOT_SCOPE) {
482
- a.Ee.u |= REACTIVE_SNAPSHOT_STALE;
484
+ if (s) r.Ee.u &= ~REACTIVE_REASK;
485
+ if (n && r.Ee.U & CONFIG_IN_SNAPSHOT_SCOPE) {
486
+ r.Ee.u |= REACTIVE_SNAPSHOT_STALE;
483
487
  continue;
484
488
  }
485
489
  if (t && i) {
486
- a.Ee.u |= REACTIVE_OPTIMISTIC_DIRTY;
487
- assignOrMergeLane(a.Ee, i);
490
+ r.Ee.u |= REACTIVE_OPTIMISTIC_DIRTY;
491
+ assignOrMergeLane(r.Ee, i);
488
492
  } else if (t) {
489
- a.Ee.u |= REACTIVE_OPTIMISTIC_DIRTY;
493
+ r.Ee.u |= REACTIVE_OPTIMISTIC_DIRTY;
490
494
  // No source lane means reversion - clear subscriber's lane so effects go to regular queue
491
- a.Ee.Je = undefined;
495
+ r.Ee.Je = undefined;
492
496
  }
493
- enqueueSub(a.Ee);
497
+ enqueueSub(r.Ee);
494
498
  }
495
499
  }
496
500
 
@@ -517,11 +521,7 @@ function commitPendingNode(e) {
517
521
  }
518
522
 
519
523
  function commitPendingNodes() {
520
- if (globalQueue.xt !== null) {
521
- commitPendingNode(globalQueue.xt);
522
- globalQueue.xt = null;
523
- }
524
- const e = globalQueue.Yt;
524
+ const e = currentBatch.yt;
525
525
  for (let t = 0; t < e.length; t++) {
526
526
  commitPendingNode(e[t]);
527
527
  }
@@ -533,42 +533,42 @@ function finalizePureQueue(e = null, t = false) {
533
533
  // For completing transitions or no-transition, resolve pending and revert optimistic
534
534
  const i = !t;
535
535
  if (i) commitPendingNodes();
536
- if (!t && globalQueue.wt.length) checkBoundaryChildren(globalQueue);
536
+ if (!t && globalQueue.Qt.length) checkBoundaryChildren(globalQueue);
537
537
  const n = dirtyQueue.EE >= dirtyQueue.Le;
538
538
  if (n) runHeap(dirtyQueue, GlobalQueue.Ce);
539
539
  if (i) {
540
540
  if (n) commitPendingNodes();
541
+ // The settling batch: the completing transaction's, or the ambient one.
542
+ const t = e ?? globalQueue.N;
541
543
  // Optimistic reversion: a non-empty batch means _optimisticWrite ran,
542
544
  // which installed the engine's hooks.
543
- const t = e ? e.Be : globalQueue.Be;
544
- if (t.length) GlobalQueue.Ut(t);
545
+ if (t.Be.length) GlobalQueue.En(t.Be);
545
546
  // Replay entanglement: subs recorded by the read-time gate get rescheduled
546
547
  // so they re-run with the now-committed values visible.
547
- if (e && e.Qt.size) {
548
- for (const t of e.Qt) {
548
+ if (e && e.un.size) {
549
+ for (const t of e.un) {
549
550
  if (t.u & REACTIVE_DISPOSED) continue;
550
551
  enqueueSub(t);
551
552
  }
552
- e.Qt.clear();
553
+ e.un.clear();
553
554
  }
554
555
  // Declared motion ends with the transaction: settle (or plain flush end
555
556
  // for ambient marks) releases each registration's refcount. A non-empty
556
557
  // batch means registerAffectsMark ran, which installed the hook.
557
- const i = e ? e.N : globalQueue.N;
558
- if (i.length) GlobalQueue.h(i);
559
- const s = e ? e.Lt : globalQueue.Lt;
558
+ if (t._.length) GlobalQueue.h(t._);
560
559
  // A non-empty set means trackOptimisticStore ran, which installed the
561
560
  // hook; the hook iterates, clears, and schedules (keeping the loop out of
562
- // core lets esbuild shake it — rollup already folds the null guard).
563
- if (s.size) GlobalQueue.Kt(s);
561
+ // core lets esbuild shake it — rollup already folds the null guard). The
562
+ // completing transition scopes the clear to its own layer keys (#2899).
563
+ if (t.dn.size) GlobalQueue.Vt(t.dn, e);
564
564
  sweepTransientStoreNodes();
565
565
  // Lanes only enter activeLanes through the engine's getOrCreateLane.
566
- if (activeLanes.size) GlobalQueue.jt(e);
566
+ if (activeLanes.size) GlobalQueue.Nn(e);
567
567
  }
568
568
  }
569
569
 
570
570
  function checkBoundaryChildren(e) {
571
- for (const t of e.wt) {
571
+ for (const t of e.Qt) {
572
572
  t.Se?.();
573
573
  checkBoundaryChildren(t);
574
574
  }
@@ -598,6 +598,13 @@ function reassignPendingTransition(e) {
598
598
 
599
599
  const globalQueue = new GlobalQueue;
600
600
 
601
+ // Hot-path mirror of `globalQueue._batch`: `queuePendingNode` runs once per
602
+ // staged write and `commitPendingNodes` once per flush, and the extra
603
+ // property hop through `_batch` was a measured instruction-count regression
604
+ // (CodSpeed update1to1, PR #2905). The field stays authoritative for
605
+ // cross-module readers; every `_batch` assignment updates both.
606
+ let currentBatch = globalQueue.N;
607
+
601
608
  function flush(e) {
602
609
  if (e) {
603
610
  syncDepth++;
@@ -613,7 +620,7 @@ function flush(e) {
613
620
  }
614
621
  }
615
622
  }
616
- if (globalQueue.Bt) {
623
+ if (globalQueue.Ut) {
617
624
  return;
618
625
  }
619
626
  if (halted) return;
@@ -630,7 +637,7 @@ function runQueue(e, t) {
630
637
 
631
638
  function reporterBlocksSource(e, t) {
632
639
  if (e.u & (REACTIVE_ZOMBIE | REACTIVE_DISPOSED)) return false;
633
- if (e.m === t || e.M?.has(t)) return true;
640
+ if (e.m?.has(t)) return true;
634
641
  for (let i = e.S; i; i = i.st) {
635
642
  let e = i.ot;
636
643
  while (e) {
@@ -642,10 +649,10 @@ function reporterBlocksSource(e, t) {
642
649
  }
643
650
 
644
651
  function transitionComplete(e) {
645
- if (e.Ht) return true;
652
+ if (e.cn) return true;
646
653
  if (e.Te.length) return false;
647
654
  let t = true;
648
- for (const [i, n] of e.qt) {
655
+ for (const [i, n] of e.Lt) {
649
656
  let s = false;
650
657
  for (const e of n) {
651
658
  if (reporterBlocksSource(e, i)) {
@@ -654,7 +661,7 @@ function transitionComplete(e) {
654
661
  }
655
662
  n.delete(e);
656
663
  }
657
- if (!s) e.qt.delete(i); else if (i.i & STATUS_PENDING && i.k?.source === i) {
664
+ if (!s) e.Lt.delete(i); else if (i.i & STATUS_PENDING && i.k?.source === i) {
658
665
  t = false;
659
666
  break;
660
667
  }
@@ -662,13 +669,13 @@ function transitionComplete(e) {
662
669
  // Override blockage lives with the engine. Absent hook = "no optimistic
663
670
  // blockage", which is exact: only _optimisticWrite (engine) pushes to
664
671
  // _optimisticNodes, so without the engine the loop was vacuous anyway.
665
- if (t && e.Be.length && GlobalQueue.Wt(e)) t = false;
666
- t && (e.Ht = true);
672
+ if (t && e.Be.length && GlobalQueue.In(e)) t = false;
673
+ t && (e.cn = true);
667
674
  return t;
668
675
  }
669
676
 
670
677
  function currentTransition(e) {
671
- while (e.Ht && typeof e.Ht === "object") e = e.Ht;
678
+ while (e.cn && typeof e.cn === "object") e = e.cn;
672
679
  return e;
673
680
  }
674
681