@solidjs/signals 2.0.0-rc.0 → 2.0.0-rc.2

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 (70) hide show
  1. package/README.md +1 -1
  2. package/dist/dev.js +4287 -2803
  3. package/dist/node.cjs +4563 -3756
  4. package/dist/prod/affects.js +16 -16
  5. package/dist/prod/boundaries.js +90 -90
  6. package/dist/prod/core/action.js +3 -3
  7. package/dist/prod/core/async.js +234 -178
  8. package/dist/prod/core/constants.js +39 -1
  9. package/dist/prod/core/core.js +362 -243
  10. package/dist/prod/core/effect.js +56 -56
  11. package/dist/prod/core/external.js +4 -4
  12. package/dist/prod/core/graph.js +65 -54
  13. package/dist/prod/core/heap.js +47 -39
  14. package/dist/prod/core/invariants.js +3 -2
  15. package/dist/prod/core/lanes.js +41 -34
  16. package/dist/prod/core/optimistic.js +78 -58
  17. package/dist/prod/core/owner.js +101 -100
  18. package/dist/prod/core/scheduler.js +249 -192
  19. package/dist/prod/core/verdict.js +185 -78
  20. package/dist/prod/index.js +7 -7
  21. package/dist/prod/map.js +108 -106
  22. package/dist/prod/signals.js +20 -1
  23. package/dist/prod/store/index.js +36 -0
  24. package/dist/prod/store/next/optimistic.js +385 -0
  25. package/dist/prod/store/next/projection.js +172 -0
  26. package/dist/prod/store/next/reconcile.js +331 -0
  27. package/dist/prod/store/next/store.js +1499 -0
  28. package/dist/prod/store/next/target.js +20 -0
  29. package/dist/prod/store/store.js +126 -882
  30. package/dist/prod/store/utils.js +59 -186
  31. package/dist/types/core/attribution-hooks.d.ts +52 -0
  32. package/dist/types/core/attribution.d.ts +186 -0
  33. package/dist/types/core/constants.d.ts +26 -0
  34. package/dist/types/core/core.d.ts +16 -1
  35. package/dist/types/core/dev.d.ts +20 -3
  36. package/dist/types/core/graph.d.ts +1 -0
  37. package/dist/types/core/lanes.d.ts +4 -16
  38. package/dist/types/core/scheduler.d.ts +12 -2
  39. package/dist/types/core/types.d.ts +85 -41
  40. package/dist/types/signals.d.ts +19 -0
  41. package/dist/types/store/index.d.ts +15 -5
  42. package/dist/types/store/next/optimistic.d.ts +20 -0
  43. package/dist/types/store/next/projection.d.ts +8 -0
  44. package/dist/types/store/next/reconcile.d.ts +3 -0
  45. package/dist/types/store/next/store.d.ts +77 -0
  46. package/dist/types/store/next/target.d.ts +117 -0
  47. package/dist/types/store/store.d.ts +26 -101
  48. package/dist/types/store/utils.d.ts +0 -40
  49. package/dist/types-cjs/core/attribution-hooks.d.cts +52 -0
  50. package/dist/types-cjs/core/attribution.d.cts +186 -0
  51. package/dist/types-cjs/core/constants.d.cts +26 -0
  52. package/dist/types-cjs/core/core.d.cts +16 -1
  53. package/dist/types-cjs/core/dev.d.cts +20 -3
  54. package/dist/types-cjs/core/graph.d.cts +1 -0
  55. package/dist/types-cjs/core/lanes.d.cts +4 -16
  56. package/dist/types-cjs/core/scheduler.d.cts +12 -2
  57. package/dist/types-cjs/core/types.d.cts +85 -41
  58. package/dist/types-cjs/signals.d.cts +19 -0
  59. package/dist/types-cjs/store/index.d.cts +15 -5
  60. package/dist/types-cjs/store/next/optimistic.d.cts +20 -0
  61. package/dist/types-cjs/store/next/projection.d.cts +8 -0
  62. package/dist/types-cjs/store/next/reconcile.d.cts +3 -0
  63. package/dist/types-cjs/store/next/store.d.cts +77 -0
  64. package/dist/types-cjs/store/next/target.d.cts +117 -0
  65. package/dist/types-cjs/store/store.d.cts +26 -101
  66. package/dist/types-cjs/store/utils.d.cts +0 -40
  67. package/package.json +2 -1
  68. package/dist/prod/store/optimistic.js +0 -217
  69. package/dist/prod/store/projection.js +0 -231
  70. package/dist/prod/store/reconcile.js +0 -707
@@ -1,4 +1,4 @@
1
- import { EFFECT_RENDER, EFFECT_USER, STATUS_PENDING, REACTIVE_IN_HEAP_HEIGHT, 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";
1
+ import { EFFECT_RENDER, EFFECT_USER, STATUS_PENDING, REACTIVE_IN_HEAP_HEIGHT, REACTIVE_DISPOSED, REACTIVE_ZOMBIE, NOT_PENDING, CONFIG_HAS_COMPANIONS, EFFECT_TRACKED, CONFIG_HAS_LANE, CONFIG_HAS_SNAPSHOT, REACTIVE_RECOMPUTING_DEPS, REACTIVE_MISSED_WAKE, 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
 
@@ -19,23 +19,23 @@ const transitions = new Set;
19
19
  const dirtyQueue = {
20
20
  eE: new Array(2e3).fill(undefined),
21
21
  tE: false,
22
- Ve: 0,
22
+ xe: 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
- Ve: 0,
29
+ xe: 0,
30
30
  EE: 0
31
31
  };
32
32
 
33
33
  /** runHeap callback that discards a queued zombie recompute instead of running
34
34
  * it: unlink pure recompute entries; strip just the recompute bit from dirtied
35
35
  * height-adjust entries so their height work still happens. */ function cancelZombieRecompute(e) {
36
- if (e.se & REACTIVE_IN_HEAP_HEIGHT) e.se &= -12; else {
36
+ if (e.ie & REACTIVE_IN_HEAP_HEIGHT) e.ie &= -12; else {
37
37
  deleteFromHeap(e, zombieQueue);
38
- e.se &= -4;
38
+ e.ie &= -4;
39
39
  }
40
40
  }
41
41
 
@@ -59,30 +59,26 @@ let projectionWriteActive = false;
59
59
  // releasing the slot in the parent store's node map.
60
60
  const transientStoreNodes = new Set;
61
61
 
62
- function registerTransientStoreNode(e) {
63
- transientStoreNodes.add(e);
64
- }
65
-
66
62
  function canUseSimpleSyncFlush(e) {
67
- const t = e.m;
68
- return transitions.size === 0 && activeLanes.size === 0 && e.vt.length === 0 && t.Me.length === 0 && t.A.length === 0 && t.cn.size === 0 && transientStoreNodes.size === 0;
63
+ const t = e.k;
64
+ return transitions.size === 0 && activeLanes.size === 0 && e.Qt.length === 0 && t.Ke.length === 0 && t.m.length === 0 && t.cn.size === 0 && transientStoreNodes.size === 0;
69
65
  }
70
66
 
71
67
  function sweepTransientStoreNodes() {
72
68
  if (transientStoreNodes.size === 0) return;
73
69
  for (const e of transientStoreNodes) {
74
- if (e.o !== null) {
70
+ if (e.u !== null) {
75
71
  transientStoreNodes.delete(e);
76
72
  continue;
77
73
  }
78
- if (e.De !== NOT_PENDING) continue;
79
- if (e._e !== undefined && e._e !== NOT_PENDING) continue;
74
+ if (e.Pe !== NOT_PENDING) continue;
75
+ if (e.o?.De !== undefined && e.o?.De !== NOT_PENDING) continue;
80
76
  // A live affects() mark keeps the node addressable: sweeping it would
81
77
  // detach the refcount from the slot (a fresh probe would upsert a new,
82
78
  // unmarked node for the same property).
83
- if (e.t) continue;
79
+ if (e.o?.t) continue;
84
80
  transientStoreNodes.delete(e);
85
- e.ut?.();
81
+ e.o?.ft?.();
86
82
  }
87
83
  }
88
84
 
@@ -109,44 +105,44 @@ function setProjectionWriteActive(e) {
109
105
  * land there directly — no per-field aliasing.
110
106
  */ function createBatch() {
111
107
  return {
112
- de: clock,
113
- Qt: [],
114
- Ie: new Map,
115
- Me: [],
116
- A: [],
108
+ Te: clock,
109
+ yt: [],
110
+ Ne: new Map,
111
+ Ke: [],
112
+ m: [],
117
113
  cn: new Set,
118
- ie: [],
119
- yt: {
120
- bt: [ [], [] ],
121
- vt: []
114
+ oe: [],
115
+ bt: {
116
+ Lt: [ [], [] ],
117
+ Qt: []
122
118
  },
123
- fn: false,
119
+ an: false,
124
120
  ln: new Set
125
121
  };
126
122
  }
127
123
 
128
124
  function mergeTransitionState(e, t) {
129
- t.fn = e;
130
- e.ie.push(...t.ie);
131
- for (const i of activeLanes) if (i.Ne === t) i.Ne = e;
132
- if (t.Me.length) {
125
+ t.an = e;
126
+ e.oe.push(...t.oe);
127
+ for (const i of activeLanes) if (i._e === t) i._e = e;
128
+ if (t.Ke.length) {
133
129
  // Move (don't copy): the global queue's batch may still be the outgoing
134
130
  // transition, and the adoption pass in initTransition would re-push its
135
131
  // contents into the target — duplicating every entry.
136
- e.Me.push(...t.Me);
137
- t.Me.length = 0;
132
+ e.Ke.push(...t.Ke);
133
+ t.Ke.length = 0;
138
134
  }
139
- if (t.A.length) {
135
+ if (t.m.length) {
140
136
  // Move (don't copy): the global queue's batch may still be the outgoing
141
137
  // transition, and the adoption pass in initTransition would re-push its
142
138
  // contents into the target — double-releasing every mark.
143
- e.A.push(...t.A);
144
- t.A.length = 0;
139
+ e.m.push(...t.m);
140
+ t.m.length = 0;
145
141
  }
146
142
  for (const i of t.cn) e.cn.add(i);
147
- for (const [i, n] of t.Ie) {
148
- let t = e.Ie.get(i);
149
- if (!t) e.Ie.set(i, t = new Set);
143
+ for (const [i, n] of t.Ne) {
144
+ let t = e.Ne.get(i);
145
+ if (!t) e.Ne.set(i, t = new Set);
150
146
  for (const e of n) t.add(e);
151
147
  }
152
148
  for (const i of t.ln) e.ln.add(i);
@@ -159,7 +155,7 @@ function schedule() {
159
155
  }
160
156
  if (scheduled) return;
161
157
  scheduled = true;
162
- if (!syncDepth && !globalQueue.gt && !projectionWriteActive) queueMicrotask(flush);
158
+ if (!syncDepth && !globalQueue.sn && !projectionWriteActive) queueMicrotask(flush);
163
159
  }
164
160
 
165
161
  /**
@@ -193,30 +189,30 @@ function notifyHalted() {
193
189
  let queueRunToken = 0;
194
190
 
195
191
  class Queue {
196
- ke=null;
197
- bt=[ [], [] ];
198
- vt=[];
199
- kt=0;
192
+ ve=null;
193
+ Lt=[ [], [] ];
194
+ Qt=[];
195
+ Mt=0;
200
196
  created=clock;
201
197
  addChild(e) {
202
- this.vt.push(e);
203
- e.ke = this;
198
+ this.Qt.push(e);
199
+ e.ve = this;
204
200
  }
205
201
  removeChild(e) {
206
- const t = this.vt.indexOf(e);
202
+ const t = this.Qt.indexOf(e);
207
203
  if (t >= 0) {
208
- this.vt.splice(t, 1);
209
- e.ke = null;
204
+ this.Qt.splice(t, 1);
205
+ e.ve = null;
210
206
  }
211
207
  }
212
208
  notify(e, t, i, n) {
213
- if (this.ke) return this.ke.notify(e, t, i, n);
209
+ if (this.ve) return this.ve.notify(e, t, i, n);
214
210
  return false;
215
211
  }
216
212
  run(e) {
217
- if (this.bt[e - 1].length) {
218
- const t = this.bt[e - 1];
219
- this.bt[e - 1] = [];
213
+ if (this.Lt[e - 1].length) {
214
+ const t = this.Lt[e - 1];
215
+ this.Lt[e - 1] = [];
220
216
  runQueue(t, e);
221
217
  }
222
218
  // Effects run here can dispose owners, and disposal removes queues from
@@ -226,12 +222,12 @@ class Queue {
226
222
  // can be recovered by rescanning from the front and every child still runs
227
223
  // exactly once. Children appended mid-pass carry a stale stamp and run,
228
224
  // matching the previous live-array behaviour.
229
- const t = this.vt;
225
+ const t = this.Qt;
230
226
  const i = ++queueRunToken;
231
227
  for (let n = 0; n < t.length; ) {
232
228
  const s = t[n];
233
- if (s.kt !== i) {
234
- s.kt = i;
229
+ if (s.Mt !== i) {
230
+ s.Mt = i;
235
231
  s.run?.(e);
236
232
  if (t[n] !== s) {
237
233
  n = 0;
@@ -246,81 +242,84 @@ class Queue {
246
242
  // Route to lane's effect queue if we're in an optimistic recomputation
247
243
  if (currentOptimisticLane) {
248
244
  const i = findLane(currentOptimisticLane);
249
- i.rn[e - 1].push(t);
245
+ i.en[e - 1].push(t);
250
246
  } else {
251
- this.bt[e - 1].push(t);
247
+ this.Lt[e - 1].push(t);
252
248
  }
253
249
  }
254
250
  schedule();
255
251
  }
256
252
  stashQueues(e) {
257
- e.bt[0].push(...this.bt[0]);
258
- e.bt[1].push(...this.bt[1]);
259
- this.bt = [ [], [] ];
260
- for (let t = 0; t < this.vt.length; t++) {
261
- let i = this.vt[t];
262
- let n = e.vt[t];
253
+ e.Lt[0].push(...this.Lt[0]);
254
+ e.Lt[1].push(...this.Lt[1]);
255
+ this.Lt = [ [], [] ];
256
+ for (let t = 0; t < this.Qt.length; t++) {
257
+ let i = this.Qt[t];
258
+ let n = e.Qt[t];
263
259
  if (!n) {
264
260
  n = {
265
- bt: [ [], [] ],
266
- vt: []
261
+ Lt: [ [], [] ],
262
+ Qt: []
267
263
  };
268
- e.vt[t] = n;
264
+ e.Qt[t] = n;
269
265
  }
270
266
  i.stashQueues(n);
271
267
  }
272
268
  }
273
269
  restoreQueues(e) {
274
- this.bt[0].push(...e.bt[0]);
275
- this.bt[1].push(...e.bt[1]);
276
- for (let t = 0; t < e.vt.length; t++) {
277
- const i = e.vt[t];
278
- let n = this.vt[t];
270
+ this.Lt[0].push(...e.Lt[0]);
271
+ this.Lt[1].push(...e.Lt[1]);
272
+ for (let t = 0; t < e.Qt.length; t++) {
273
+ const i = e.Qt[t];
274
+ let n = this.Qt[t];
279
275
  if (n) n.restoreQueues(i);
280
276
  }
281
277
  }
282
278
  }
283
279
 
284
280
  class GlobalQueue extends Queue {
285
- gt=false;
281
+ sn=false;
286
282
  // The current transaction-shaped batch: a plain ambient batch while no
287
283
  // transition is active, the active transition itself after initTransition.
288
- m=createBatch();
284
+ k=createBatch();
289
285
  static Ce;
290
- static me;
291
- static et;
292
- static Dt=null;
286
+ static Fe;
287
+ static tt;
288
+ static Vt=null;
293
289
  // Store-side hook: drops a keyless affects() mark's identity scope when the
294
290
  // carrier node's last registration releases (wired by store.ts, mirroring
295
291
  // _clearOptimisticStore).
296
- static p=null;
292
+ static G=null;
297
293
  // affects()-side hooks (wired by affects.ts, mirroring _update): the mark
298
294
  // engine — count/register/release — lives with the feature. Every call site
299
295
  // is gated by state only that module creates, so `!` invocations are safe
300
296
  // once the gate holds.
301
- static G=null;
302
297
  static M=null;
303
298
  static h=null;
299
+ static j=null;
304
300
  // External-source bridge (wired by enableExternalSource(); null while no
305
301
  // config is active — including after _resetExternalSourceConfig()).
306
- static It=null;
307
- static St=null;
302
+ static Rt=null;
303
+ static Gt=null;
308
304
  // Verdict-layer hooks (wired by verdict.ts when isPending()/latest() are
309
305
  // imported; null in apps that never use them). Call sites either guard for
310
306
  // null or sit behind state only the verdict layer can create (`!` is safe
311
307
  // there: `_pendingSignal`/`_latestValueComputed` are only ever assigned by
312
308
  // verdict.ts, and `pendingCheckActive`/`latestReadActive` only flip inside
313
309
  // isPending()/latest()).
314
- static Pe=null;
315
- static Se=null;
316
310
  static Oe=null;
311
+ static de=null;
312
+ static ye=null;
317
313
  static un=null;
318
- static At=null;
319
- static Ct=null;
320
- static ht=null;
321
- static Be=null;
322
- static k=null;
323
- static Lt=null;
314
+ static Pt=null;
315
+ static Dt=null;
316
+ static Ht=null;
317
+ static Je=null;
318
+ static p=null;
319
+ static Bt=null;
320
+ // Re-asks probes whose verdict was provisionally suppressed by a fresh read
321
+ // of a held value, once the transaction gains an async blocker (#3028).
322
+ static wt=null;
324
323
  // Optimistic-engine hooks (wired by core/optimistic.ts via
325
324
  // installOptimisticEngine(), called from verdict.ts / createOptimistic /
326
325
  // createOptimisticStore — every module that can create optimistic state).
@@ -328,21 +327,37 @@ class GlobalQueue extends Queue {
328
327
  // `_overrideValue` slot, a lane in `activeLanes`, an `_optimisticNodes`
329
328
  // entry, or a non-null `currentOptimisticLane`, so `!` invocations are safe
330
329
  // once the gate holds.
331
- static Gt=null;
330
+ static vt=null;
331
+ static fn=null;
332
+ static dn=null;
332
333
  static En=null;
333
334
  static Tn=null;
334
- static dn=null;
335
- static In=null;
336
- static Rt=null;
337
- static Ot=null;
338
- static Pt=null;
335
+ static Ft=null;
336
+ static ht=null;
337
+ static gt=null;
339
338
  static je=null;
340
339
  static $e=null;
341
340
  static ze=null;
342
341
  static Nn=null;
343
342
  flush() {
344
- if (this.gt) return;
345
- this.gt = true;
343
+ if (this.sn) return;
344
+ // Fast drain: nothing in flight but plain pending commits — no dirty
345
+ // computeds, no queued effects, no child queues, no transitions/lanes/
346
+ // optimistic state. Commit and go; anything a commit hook schedules
347
+ // (companion snaps, store folds notifying subs) re-arms `scheduled`
348
+ // below and the outer drain loop takes the full spine next round.
349
+ if (activeTransition === null && dirtyQueue.EE < dirtyQueue.xe && this.Lt[0].length === 0 && this.Lt[1].length === 0 && this.Qt.length === 0 && canUseSimpleSyncFlush(this)) {
350
+ this.sn = true;
351
+ try {
352
+ commitPendingNodes();
353
+ } finally {
354
+ this.sn = false;
355
+ }
356
+ clock++;
357
+ scheduled = dirtyQueue.EE >= dirtyQueue.xe || this.Lt[0].length !== 0 || this.Lt[1].length !== 0 || this.k.yt.length !== 0;
358
+ return;
359
+ }
360
+ this.sn = true;
346
361
  try {
347
362
  if (false) ;
348
363
  runHeap(dirtyQueue, GlobalQueue.Ce);
@@ -358,53 +373,53 @@ class GlobalQueue extends Queue {
358
373
  // zombies here. Height-adjust entries still process normally: a
359
374
  // dirtied one keeps its height flag and falls through to runHeap's
360
375
  // adjustHeight path on the next pass of the bucket.
361
- runHeap(zombieQueue, this.m === e ? cancelZombieRecompute : GlobalQueue.Ce);
376
+ runHeap(zombieQueue, this.k === e ? cancelZombieRecompute : GlobalQueue.Ce);
362
377
  // Detach: the stashed transition keeps its batch; ambient work that
363
378
  // follows lands in a fresh one. If the batch is already a separate
364
379
  // ambient one — action done() restored activeTransition without
365
380
  // adopting the batch, and an ordinary write landed there before
366
381
  // the scheduled flush (#2916) — keep it: replacing it would strand
367
382
  // its queued pending nodes with held _pendingValues forever.
368
- if (this.m === e) currentBatch = this.m = createBatch();
383
+ if (this.k === e) currentBatch = this.k = createBatch();
369
384
  // Run lane effects immediately (before stashing) - lanes with no pending async
370
385
  if (activeLanes.size) {
371
- GlobalQueue.In(EFFECT_RENDER);
372
- GlobalQueue.In(EFFECT_USER);
386
+ GlobalQueue.Tn(EFFECT_RENDER);
387
+ GlobalQueue.Tn(EFFECT_USER);
373
388
  }
374
- this.stashQueues(e.yt);
389
+ this.stashQueues(e.bt);
375
390
  clock++;
376
391
  // A kept ambient batch may hold pending nodes (#2916): stay
377
392
  // scheduled so the outer drain loop commits them via the plain
378
393
  // flush path instead of leaving them until the next natural flush.
379
- scheduled = dirtyQueue.EE >= dirtyQueue.Ve || this.m.Qt.length > 0;
380
- reassignPendingTransition(e.Qt);
394
+ scheduled = dirtyQueue.EE >= dirtyQueue.xe || this.k.yt.length > 0;
395
+ reassignPendingTransition(e.yt);
381
396
  activeTransition = null;
382
397
  finalizePureQueue(null, true);
383
398
  return;
384
399
  }
385
400
  const t = activeTransition;
386
- const i = this.m;
387
- i !== t && i.Qt.push(...t.Qt);
388
- this.restoreQueues(t.yt);
401
+ const i = this.k;
402
+ i !== t && i.yt.push(...t.yt);
403
+ this.restoreQueues(t.bt);
389
404
  transitions.delete(t);
390
405
  activeTransition = null;
391
- reassignPendingTransition(i.Qt);
406
+ reassignPendingTransition(i.yt);
392
407
  finalizePureQueue(t);
393
408
  if (i === t) {
394
409
  // Drop the dead Transition wrapper but keep its (drained) containers
395
410
  // as the ambient batch — late registrations during finalization live
396
411
  // there and must survive to the next flush.
397
412
  const e = createBatch();
398
- e.Qt = i.Qt;
399
- e.Me = i.Me;
400
- e.A = i.A;
413
+ e.yt = i.yt;
414
+ e.Ke = i.Ke;
415
+ e.m = i.m;
401
416
  e.cn = i.cn;
402
- currentBatch = this.m = e;
417
+ currentBatch = this.k = e;
403
418
  }
404
419
  } else {
405
420
  if (canUseSimpleSyncFlush(this)) {
406
421
  commitPendingNodes();
407
- if (dirtyQueue.EE >= dirtyQueue.Ve) {
422
+ if (dirtyQueue.EE >= dirtyQueue.xe) {
408
423
  runHeap(dirtyQueue, GlobalQueue.Ce);
409
424
  commitPendingNodes();
410
425
  }
@@ -415,36 +430,39 @@ class GlobalQueue extends Queue {
415
430
  }
416
431
  clock++;
417
432
  // Check if finalization added items to the heap (from optimistic reversion)
418
- scheduled = dirtyQueue.EE >= dirtyQueue.Ve;
433
+ scheduled = dirtyQueue.EE >= dirtyQueue.xe;
419
434
  // Run lane effects first (for ready lanes), then regular effects
420
- activeLanes.size && GlobalQueue.In(EFFECT_RENDER);
435
+ activeLanes.size && GlobalQueue.Tn(EFFECT_RENDER);
421
436
  this.run(EFFECT_RENDER);
422
- activeLanes.size && GlobalQueue.In(EFFECT_USER);
437
+ activeLanes.size && GlobalQueue.Tn(EFFECT_USER);
423
438
  this.run(EFFECT_USER);
424
439
  if (false) ;
425
440
  if (false && !scheduled && !activeTransition && transitions.size === 0 && activeLanes.size === 0) ;
426
441
  if (false) ;
427
442
  } finally {
428
- this.gt = false;
443
+ this.sn = false;
429
444
  }
430
445
  }
431
446
  notify(e, t, i, n) {
432
447
  // Only track async if the boundary is propagating STATUS_PENDING (not caught by boundary)
433
448
  if (t & STATUS_PENDING) {
434
449
  if (i & STATUS_PENDING) {
435
- const t = n !== undefined ? n : e._;
450
+ const t = n !== undefined ? n : e.o?._;
436
451
  // A visibility-only mark notification (the affects() boundary
437
452
  // channel) updates display state on its way up but must be invisible
438
453
  // to completion accounting BY CONSTRUCTION: it never registers a
439
454
  // reporter and never counts toward the loading-boundary diagnostic.
440
- if (t?.l) return true;
455
+ if (t?.i) return true;
441
456
  if (activeTransition && t) {
442
457
  const i = t.source;
443
- let n = activeTransition.Ie.get(i);
444
- if (!n) activeTransition.Ie.set(i, n = new Set);
458
+ let n = activeTransition.Ne.get(i);
459
+ if (!n) activeTransition.Ne.set(i, n = new Set);
445
460
  const s = n.size;
446
461
  n.add(e);
447
- if (n.size !== s) schedule();
462
+ if (n.size !== s) {
463
+ schedule();
464
+ GlobalQueue.wt?.(activeTransition);
465
+ }
448
466
  }
449
467
  }
450
468
  return true;
@@ -454,7 +472,7 @@ class GlobalQueue extends Queue {
454
472
  initTransition(e) {
455
473
  if (e) e = currentTransition(e);
456
474
  if (e && e === activeTransition) return;
457
- if (!e && activeTransition && activeTransition.de === clock) return;
475
+ if (!e && activeTransition && activeTransition.Te === clock) return;
458
476
  if (!activeTransition) {
459
477
  activeTransition = e ?? createBatch();
460
478
  } else if (e) {
@@ -464,8 +482,8 @@ class GlobalQueue extends Queue {
464
482
  activeTransition = e;
465
483
  }
466
484
  transitions.add(activeTransition);
467
- activeTransition.de = clock;
468
- const t = this.m;
485
+ activeTransition.Te = clock;
486
+ const t = this.k;
469
487
  if (t !== activeTransition) {
470
488
  // Adopt the ambient batch into the transaction, then make the
471
489
  // transaction the batch so later registrations land there directly.
@@ -474,17 +492,17 @@ class GlobalQueue extends Queue {
474
492
  // must not entangle unrelated writes to it; the same rule holds one hop
475
493
  // downstream: propagation never queues pended subscribers as pending
476
494
  // nodes, see propagateAffectsMark, #2893.
477
- for (let e = 0; e < t.Qt.length; e++) {
478
- const i = t.Qt[e];
479
- i.Ne = activeTransition;
480
- activeTransition.Qt.push(i);
495
+ for (let e = 0; e < t.yt.length; e++) {
496
+ const i = t.yt[e];
497
+ i._e = activeTransition;
498
+ activeTransition.yt.push(i);
481
499
  }
482
- for (let e = 0; e < t.Me.length; e++) {
483
- const i = t.Me[e];
484
- i.Ne = activeTransition;
485
- activeTransition.Me.push(i);
500
+ for (let e = 0; e < t.Ke.length; e++) {
501
+ const i = t.Ke[e];
502
+ i._e = activeTransition;
503
+ activeTransition.Ke.push(i);
486
504
  }
487
- if (t.A.length) activeTransition.A.push(...t.A);
505
+ if (t.m.length) activeTransition.m.push(...t.m);
488
506
  for (const e of t.cn) activeTransition.cn.add(e);
489
507
  // Gated readers recorded against the ambient batch move with it: their
490
508
  // replay-at-commit now happens at the transaction's completion.
@@ -492,16 +510,16 @@ class GlobalQueue extends Queue {
492
510
  for (const e of t.ln) activeTransition.ln.add(e);
493
511
  t.ln.clear();
494
512
  }
495
- currentBatch = this.m = activeTransition;
513
+ currentBatch = this.k = activeTransition;
496
514
  }
497
515
  for (const e of activeLanes) {
498
- if (!e.Ne) e.Ne = activeTransition;
516
+ if (!e._e) e._e = activeTransition;
499
517
  }
500
518
  }
501
519
  }
502
520
 
503
521
  function queuePendingNode(e) {
504
- currentBatch.Qt.push(e);
522
+ currentBatch.yt.push(e);
505
523
  }
506
524
 
507
525
  // Sticky: flips true on the first refresh() ever (the only setter of
@@ -509,68 +527,107 @@ function queuePendingNode(e) {
509
527
  // clear entirely in apps that never refresh.
510
528
  let reaskArmed = false;
511
529
 
530
+ /** §12d: bumped by every recompute and every new subscriber edge. A node's
531
+ * staged-rewrite skip is sound only while NOTHING recomputed or linked since
532
+ * its last notify — a mid-batch pull can clean a marked subscriber, and a
533
+ * skipped re-write would leave it stale. */ let notifyEpoch = 0;
534
+
535
+ function bumpNotifyEpoch() {
536
+ notifyEpoch++;
537
+ }
538
+
512
539
  function armReaskClear() {
513
540
  reaskArmed = true;
514
541
  }
515
542
 
516
543
  function insertSubs(e, t = false) {
544
+ // §12d: stamp before walking — setSignal's staged-rewrite fast path skips
545
+ // the next walk for this node while the epoch holds (marking is idempotent).
546
+ e._t = notifyEpoch;
517
547
  // Get source lane: prefer node's own lane over current context
518
548
  // This is important for isPending signals which need their own lane to flush immediately
519
- const i = e.Ke || currentOptimisticLane;
520
- const n = e.Le !== undefined;
521
- const s = reaskArmed;
522
- for (let r = e.o; r !== null; r = r.le) {
549
+ // Presence bits gate the optional-slot probes (see constants.ts): one
550
+ // masked read of the always-present _config instead of missing-property
551
+ // lookups in the hottest notify loop. Bits are sticky — the field read
552
+ // stays authoritative when a bit is set.
553
+ const i = e.T;
554
+ const n = (i & CONFIG_HAS_LANE ? e.o?.Be : undefined) || currentOptimisticLane;
555
+ const s = (i & CONFIG_HAS_SNAPSHOT) !== 0 && e.o?.Qe !== undefined;
556
+ const o = reaskArmed;
557
+ for (let i = e.u; i !== null; i = i.fe) {
558
+ const e = i.ae;
523
559
  // A value-change notification is a new question for the subscriber: any
524
560
  // pending re-ask mark (refresh) it carried is superseded.
525
- if (s) r.fe.se &= ~REACTIVE_REASK;
526
- if (n && r.fe.T & CONFIG_IN_SNAPSHOT_SCOPE) {
527
- r.fe.se |= REACTIVE_SNAPSHOT_STALE;
561
+ if (o) e.ie &= ~REACTIVE_REASK;
562
+ // Missed-wake latch (#3037): this write is landing while the subscriber
563
+ // is mid-recompute (a nested pull committing beneath its reads), and the
564
+ // heap refuses RECOMPUTING nodes. A gen-current link means the pass
565
+ // already validated this dep — the value it read is now stale — so latch
566
+ // for recompute's tail to reschedule. Untouched links need no latch (the
567
+ // pass either re-reads them fresh or trims them), and neither does the
568
+ // tail link: it is the read IN FLIGHT — read() links before it pulls, so
569
+ // this very commit is what that read returns.
570
+ if (e.ie & REACTIVE_RECOMPUTING_DEPS && i.ll === e.Ze && i !== e.Ye) e.ie |= REACTIVE_MISSED_WAKE;
571
+ if (s && e.T & CONFIG_IN_SNAPSHOT_SCOPE) {
572
+ e.ie |= REACTIVE_SNAPSHOT_STALE;
528
573
  continue;
529
574
  }
530
- if (t && i) {
531
- r.fe.se |= REACTIVE_OPTIMISTIC_DIRTY;
532
- assignOrMergeLane(r.fe, i);
575
+ if (t && n) {
576
+ e.ie |= REACTIVE_OPTIMISTIC_DIRTY;
577
+ assignOrMergeLane(e, n);
533
578
  } else if (t) {
534
- r.fe.se |= REACTIVE_OPTIMISTIC_DIRTY;
579
+ e.ie |= REACTIVE_OPTIMISTIC_DIRTY;
535
580
  // No source lane means reversion - clear subscriber's lane so effects go to regular queue
536
- r.fe.Ke = undefined;
581
+ if (e.o) e.o.Be = undefined;
537
582
  }
538
- enqueueSub(r.fe);
583
+ enqueueSub(e);
539
584
  }
540
585
  }
541
586
 
542
587
  function commitPendingNode(e) {
543
588
  const t = e;
544
- if (!t.ce) {
545
- if (e.De !== NOT_PENDING) {
546
- e.Ue = e.De;
547
- e.De = NOT_PENDING;
589
+ if (!t.Se) {
590
+ if (e.Pe !== NOT_PENDING) {
591
+ e.be = e.Pe;
592
+ e.Pe = NOT_PENDING;
548
593
  }
549
- if (e.ge || e.pe) GlobalQueue.un(e);
594
+ if (e.T & CONFIG_HAS_COMPANIONS) GlobalQueue.un(e);
550
595
  return;
551
596
  }
552
- if (e.De !== NOT_PENDING) {
553
- e.Ue = e.De;
554
- e.De = NOT_PENDING;
597
+ if (e.Pe !== NOT_PENDING) {
598
+ e.be = e.Pe;
599
+ e.Pe = NOT_PENDING;
555
600
  // Set _modified for effects, but not for tracked effects (they handle their own scheduling)
556
- if (e.Re && e.Re !== EFFECT_TRACKED) e.Je = true;
601
+ if (e.Re && e.Re !== EFFECT_TRACKED) e.Xe = true;
557
602
  }
558
603
  // The committed hold is the first observable answer for a loading-window
559
604
  // node — the window closes here, not at compute time (#2990). Unconditional
560
605
  // store to an always-present computed slot.
561
- t.Ee = false;
562
- t.se &= ~REACTIVE_MANUAL_WRITE;
606
+ t.Ie = false;
607
+ t.ie &= ~REACTIVE_MANUAL_WRITE;
563
608
  if (!(t.S & STATUS_PENDING)) t.S &= ~STATUS_UNINITIALIZED;
564
- if (t.We !== null || t.Qe !== null) GlobalQueue.me(t, false, true);
565
- if (e.ge || e.pe) GlobalQueue.un(e);
609
+ if (t.o != null && (t.o.qe !== null || t.o.We !== null)) GlobalQueue.Fe(t, false, true);
610
+ if (e.T & CONFIG_HAS_COMPANIONS) GlobalQueue.un(e);
611
+ }
612
+
613
+ // Store commit hook (INTERNALS-STORE-STATE.md §3): installed by the store
614
+ // module at init (same treeshakeable pattern as _resolveOptimistic /
615
+ // _clearOptimisticStores). Folds committed store-node values into their
616
+ // backing objects at the same moment pending values commit — the single
617
+ // mutation point of the owned-raw model.
618
+ let storeCommitHook = null;
619
+
620
+ function setStoreCommitHook(e) {
621
+ storeCommitHook = e;
566
622
  }
567
623
 
568
624
  function commitPendingNodes() {
569
- const e = currentBatch.Qt;
625
+ const e = currentBatch.yt;
570
626
  for (let t = 0; t < e.length; t++) {
571
627
  commitPendingNode(e[t]);
572
628
  }
573
629
  e.length = 0;
630
+ storeCommitHook?.();
574
631
  }
575
632
 
576
633
  function finalizePureQueue(e = null, t = false) {
@@ -578,23 +635,23 @@ function finalizePureQueue(e = null, t = false) {
578
635
  // For completing transitions or no-transition, resolve pending and revert optimistic
579
636
  const i = !t;
580
637
  if (i) commitPendingNodes();
581
- if (!t && globalQueue.vt.length) checkBoundaryChildren(globalQueue);
582
- const n = dirtyQueue.EE >= dirtyQueue.Ve;
638
+ if (!t && globalQueue.Qt.length) checkBoundaryChildren(globalQueue);
639
+ const n = dirtyQueue.EE >= dirtyQueue.xe;
583
640
  if (n) runHeap(dirtyQueue, GlobalQueue.Ce);
584
641
  if (i) {
585
642
  if (n) commitPendingNodes();
586
643
  // The settling batch: the completing transaction's, or the ambient one.
587
- const t = e ?? globalQueue.m;
644
+ const t = e ?? globalQueue.k;
588
645
  // Optimistic reversion: a non-empty batch means _optimisticWrite ran,
589
646
  // which installed the engine's hooks.
590
- if (t.Me.length) GlobalQueue.En(t.Me);
647
+ if (t.Ke.length) GlobalQueue.fn(t.Ke);
591
648
  // Replay entanglement: subs recorded by the read-time gate get rescheduled
592
649
  // so they re-run with the now-committed values visible. The ambient batch
593
650
  // replays too — laneReadsCommitted records readers whose committed-view
594
651
  // read hid a same-tick plain write that just committed above (#2963).
595
652
  if (t.ln.size) {
596
653
  for (const e of t.ln) {
597
- if (e.se & REACTIVE_DISPOSED) continue;
654
+ if (e.ie & REACTIVE_DISPOSED) continue;
598
655
  enqueueSub(e);
599
656
  }
600
657
  t.ln.clear();
@@ -609,24 +666,24 @@ function finalizePureQueue(e = null, t = false) {
609
666
  // held boundary display state through the visual channel, and their
610
667
  // release is the display-state update point — re-run the boundary sweep
611
668
  // (the earlier sweep above ran while the marks were still live).
612
- if (t.A.length) {
613
- GlobalQueue.G(t.A);
614
- if (globalQueue.vt.length) checkBoundaryChildren(globalQueue);
669
+ if (t.m.length) {
670
+ GlobalQueue.M(t.m);
671
+ if (globalQueue.Qt.length) checkBoundaryChildren(globalQueue);
615
672
  }
616
673
  // A non-empty set means trackOptimisticStore ran, which installed the
617
674
  // hook; the hook iterates, clears, and schedules (keeping the loop out of
618
675
  // core lets esbuild shake it — rollup already folds the null guard). The
619
676
  // completing transition scopes the clear to its own layer keys (#2899).
620
- if (t.cn.size) GlobalQueue.Dt(t.cn, e);
677
+ if (t.cn.size) GlobalQueue.Vt(t.cn, e);
621
678
  sweepTransientStoreNodes();
622
679
  // Lanes only enter activeLanes through the engine's getOrCreateLane.
623
- if (activeLanes.size) GlobalQueue.dn(e);
680
+ if (activeLanes.size) GlobalQueue.En(e);
624
681
  }
625
682
  }
626
683
 
627
684
  function checkBoundaryChildren(e) {
628
- for (const t of e.vt) {
629
- t.ne?.();
685
+ for (const t of e.Qt) {
686
+ t.se?.();
630
687
  checkBoundaryChildren(t);
631
688
  }
632
689
  }
@@ -649,7 +706,7 @@ function checkBoundaryChildren(e) {
649
706
 
650
707
  function reassignPendingTransition(e) {
651
708
  for (let t = 0; t < e.length; t++) {
652
- e[t].Ne = activeTransition;
709
+ e[t]._e = activeTransition;
653
710
  }
654
711
  }
655
712
 
@@ -660,7 +717,7 @@ const globalQueue = new GlobalQueue;
660
717
  // property hop through `_batch` was a measured instruction-count regression
661
718
  // (CodSpeed update1to1, PR #2905). The field stays authoritative for
662
719
  // cross-module readers; every `_batch` assignment updates both.
663
- let currentBatch = globalQueue.m;
720
+ let currentBatch = globalQueue.k;
664
721
 
665
722
  function flush(e) {
666
723
  if (e) {
@@ -677,7 +734,7 @@ function flush(e) {
677
734
  }
678
735
  }
679
736
  }
680
- if (globalQueue.gt) {
737
+ if (globalQueue.sn) {
681
738
  return;
682
739
  }
683
740
  if (halted) return;
@@ -693,23 +750,23 @@ function runQueue(e, t) {
693
750
  }
694
751
 
695
752
  function reporterBlocksSource(e, t) {
696
- if (e.se & (REACTIVE_ZOMBIE | REACTIVE_DISPOSED)) return false;
697
- if (e.oe?.has(t)) return true;
698
- for (let i = e.tt; i; i = i.nt) {
699
- let e = i.it;
753
+ if (e.ie & (REACTIVE_ZOMBIE | REACTIVE_DISPOSED)) return false;
754
+ if (e.o?.le?.has(t)) return true;
755
+ for (let i = e.nt; i; i = i.it) {
756
+ let e = i.ut;
700
757
  while (e) {
701
758
  if (e === t || e.lt === t) return true;
702
- e = e.nn;
759
+ e = e.o?.It;
703
760
  }
704
761
  }
705
- return !!(e.S & STATUS_PENDING && e._ instanceof NotReadyError && e._.source === t);
762
+ return !!(e.S & STATUS_PENDING && e.o?._ instanceof NotReadyError && e.o?._.source === t);
706
763
  }
707
764
 
708
765
  function transitionComplete(e) {
709
- if (e.fn) return true;
710
- if (e.ie.length) return false;
766
+ if (e.an) return true;
767
+ if (e.oe.length) return false;
711
768
  let t = true;
712
- for (const [i, n] of e.Ie) {
769
+ for (const [i, n] of e.Ne) {
713
770
  let s = false;
714
771
  for (const e of n) {
715
772
  if (reporterBlocksSource(e, i)) {
@@ -718,7 +775,7 @@ function transitionComplete(e) {
718
775
  }
719
776
  n.delete(e);
720
777
  }
721
- if (!s) e.Ie.delete(i); else if (i.S & STATUS_PENDING && i._?.source === i) {
778
+ if (!s) e.Ne.delete(i); else if (i.S & STATUS_PENDING && i.o?._?.source === i) {
722
779
  t = false;
723
780
  break;
724
781
  }
@@ -726,13 +783,13 @@ function transitionComplete(e) {
726
783
  // Override blockage lives with the engine (absent hook = "no optimistic
727
784
  // blockage"); the hook's loops over _optimisticNodes/_optimisticStores are
728
785
  // no-ops when the transition holds neither, so no pre-check is needed.
729
- if (t && GlobalQueue.Tn?.(e)) t = false;
730
- t && (e.fn = true);
786
+ if (t && GlobalQueue.dn?.(e)) t = false;
787
+ t && (e.an = true);
731
788
  return t;
732
789
  }
733
790
 
734
791
  function currentTransition(e) {
735
- while (e.fn && typeof e.fn === "object") e = e.fn;
792
+ while (e.an && typeof e.an === "object") e = e.an;
736
793
  return e;
737
794
  }
738
795
 
@@ -746,4 +803,4 @@ function runInTransition(e, t) {
746
803
  }
747
804
  }
748
805
 
749
- export { GlobalQueue, Queue, activeAffectsMarks, activeLanes, activeTransition, armReaskClear, assignOrMergeLane, clock, currentTransition, dirtyQueue, enforceLoadingBoundary, finalizePureQueue, findLane, flush, globalQueue, haltReactivity, insertSubs, projectionWriteActive, queuePendingNode, registerTransientStoreNode, resetErrorHalt, runInTransition, schedule, setProjectionWriteActive, shiftAffectsMarks, zombieQueue };
806
+ export { GlobalQueue, Queue, activeAffectsMarks, activeLanes, activeTransition, armReaskClear, assignOrMergeLane, bumpNotifyEpoch, clock, currentTransition, dirtyQueue, enforceLoadingBoundary, finalizePureQueue, findLane, flush, globalQueue, haltReactivity, insertSubs, notifyEpoch, projectionWriteActive, queuePendingNode, reaskArmed, resetErrorHalt, runInTransition, schedule, setProjectionWriteActive, setStoreCommitHook, shiftAffectsMarks, storeCommitHook, zombieQueue };