@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,20 +1,20 @@
1
- import { NOT_PENDING, REACTIVE_DISPOSED, REACTIVE_DIRTY, REACTIVE_CHECK, unwrapOverride, REACTIVE_ZOMBIE, STATUS_UNINITIALIZED, STATUS_PENDING, REACTIVE_MANUAL_WRITE, STATUS_ERROR, REACTIVE_RECOMPUTING_DEPS } from "./constants.js";
1
+ import { NOT_PENDING, REACTIVE_DISPOSED, REACTIVE_DIRTY, REACTIVE_CHECK, unwrapOverride, REACTIVE_ZOMBIE, STATUS_UNINITIALIZED, STATUS_PENDING, REACTIVE_OPTIMISTIC_DIRTY, REACTIVE_MANUAL_WRITE, CONFIG_HAS_COMPANIONS, CONFIG_CHILD_COMPANIONS, STATUS_ERROR, REACTIVE_RECOMPUTING_DEPS } from "./constants.js";
2
2
 
3
- import { setSignal, prepareComputed, read, context, stale, currentOptimisticLane, tracking, setLatestReadActive, setContextInternal, optimisticComputed, setPendingCheckActive, latestReadActive, optimisticSignal, pendingCheckActive } from "./core.js";
3
+ import { setSignal, prepareComputed, read, context, stale, currentOptimisticLane, tracking, ext, setLatestReadActive, setContextInternal, optimisticComputed, setPendingCheckActive, latestReadActive, optimisticSignal, pendingCheckActive } from "./core.js";
4
4
 
5
5
  import { NotReadyError } from "./error.js";
6
6
 
7
7
  import { link } from "./graph.js";
8
8
 
9
- import { insertIntoHeap, queueFor, markHeap } from "./heap.js";
9
+ import { insertIntoHeap, queueFor, markHeap, enqueueSub } from "./heap.js";
10
10
 
11
11
  import "./invariants.js";
12
12
 
13
- import { findLane, hasActiveOverride } from "./lanes.js";
13
+ import { findLane, hasActiveOverride, assignOrMergeLane } from "./lanes.js";
14
14
 
15
15
  import { installOptimisticEngine } from "./optimistic.js";
16
16
 
17
- import { GlobalQueue, clock, insertSubs, schedule, activeTransition, activeAffectsMarks } from "./scheduler.js";
17
+ import { GlobalQueue, insertSubs, schedule, activeTransition, currentTransition, activeAffectsMarks } from "./scheduler.js";
18
18
 
19
19
  /**
20
20
  * The isPending()/latest() verdict layer, moved out of core.ts. Importing this
@@ -28,19 +28,45 @@ installOptimisticEngine();
28
28
 
29
29
  let pendingProbe = null;
30
30
 
31
+ /**
32
+ * Probes whose verdict was suppressed by the fresh-read pairing rule while
33
+ * the held write's fate was still undecided (see recordFreshRead /
34
+ * wakeSuppressedProbes): held node → the wrapper computeds that probed it.
35
+ * Entries die with the hold — the commit/revert snap clears them.
36
+ */ const suppressedProbes = new Map;
37
+
31
38
  /**
32
39
  * Get or create the pending signal for a node (lazy).
33
40
  * Used by isPending() to track pending state reactively.
34
- */ function getPendingSignal(e) {
35
- if (!e.ge) {
41
+ */
42
+ /** #3038: register a companion-carrying firewall child on its firewall's
43
+ * companion set and arm the post-recompute snap (CONFIG_CHILD_COMPANIONS is
44
+ * the one-load gate at the call sites). The snap then iterates exactly the
45
+ * children someone asked verdicts of — O(companions) — never the full
46
+ * `_child` chain, which carries one node per materialized leaf (the
47
+ * O(all-leaves-ever-read)-per-update pathology). Entries are permanent like
48
+ * the companions themselves; a store with no leaf-level isPending()/latest()
49
+ * reads never allocates the set or pays the walk. */ function markFirewallChildCompanions(e) {
50
+ const t = e.lt;
51
+ if (!t) return;
52
+ t.T |= CONFIG_CHILD_COMPANIONS;
53
+ (ext(t).Nt ??= new Set).add(e);
54
+ }
55
+
56
+ function getPendingSignal(e) {
57
+ let t = e.o?.Ge;
58
+ if (!t) {
36
59
  // Start false, write true if pending - ensures reversion returns to false
37
- e.ge = optimisticSignal(false, {
60
+ t = optimisticSignal(false, {
38
61
  ownedWrite: true
39
62
  });
40
- e.ge.nn = e;
41
- if (computePendingState(e)) setSignal(e.ge, true);
63
+ ext(e).Ge = t;
64
+ e.T |= CONFIG_HAS_COMPANIONS;
65
+ markFirewallChildCompanions(e);
66
+ ext(t).It = e;
67
+ if (computePendingState(e)) setSignal(t, true);
42
68
  }
43
- return e.ge;
69
+ return t;
44
70
  }
45
71
 
46
72
  function collectPendingSources(e) {
@@ -70,7 +96,7 @@ function collectPendingSources(e) {
70
96
  * (`_pendingObserver`) are skipped so an `isPending` wrapper memo never
71
97
  * inherits the coverage it reports on.
72
98
  */ function markWalk(e, t) {
73
- if (e.t) return true;
99
+ if (e.o?.t) return true;
74
100
  // A real error outranks an inherited mark (A16/A24c): an errored node
75
101
  // answers probes with its error, not a coverage verdict, and coverage does
76
102
  // not flow through it — matching the rails' behavior, where propagation
@@ -86,10 +112,10 @@ function collectPendingSources(e) {
86
112
  // pass's dependency set — walking past it would read dropped deps and
87
113
  // latch a stale verdict on the companion.
88
114
  const i = e;
89
- const r = i.se & REACTIVE_RECOMPUTING_DEPS ? i.Ye : undefined;
115
+ const r = i.ie & REACTIVE_RECOMPUTING_DEPS ? i.Ye : undefined;
90
116
  if (r !== null) {
91
- for (let e = i.tt ?? null; e !== null; e = e.nt) {
92
- if (!e.Ge && markWalk(e.it, t)) return true;
117
+ for (let e = i.nt ?? null; e !== null; e = e.it) {
118
+ if (!e.me && markWalk(e.ut, t)) return true;
93
119
  if (e === r) break;
94
120
  }
95
121
  }
@@ -101,11 +127,11 @@ function collectPendingSources(e) {
101
127
  }
102
128
 
103
129
  function quietPending(e) {
104
- if (e.oe) {
105
- for (const t of e.oe) if (!t.he) return false;
130
+ if (e.o?.le) {
131
+ for (const t of e.o.le) if (!t.o?.pe) return false;
106
132
  return true;
107
133
  }
108
- return e.he;
134
+ return e.o?.pe ?? false;
109
135
  }
110
136
 
111
137
  // NOTE: a loadingValue node's open loading window (_loading) is verdict-quiet
@@ -121,47 +147,47 @@ function newQuestionInFlight(e) {
121
147
 
122
148
  function computePendingState(e) {
123
149
  const t = e;
124
- if (t.se & REACTIVE_DISPOSED) return false;
150
+ if (t.ie & REACTIVE_DISPOSED) return false;
125
151
  // Mark coverage is transitive by dep-graph reachability: a latest() shadow
126
152
  // reaches its owner (and a store leaf its firewall) through its own deps,
127
153
  // so the one walk covers direct marks, derivation, and companion chains.
128
154
  if (markCovered(e)) return true;
129
155
  const n = e.lt;
130
- if (e.nn) {
131
- const t = e.nn;
156
+ if (e.o?.It) {
157
+ const t = e.o?.It;
132
158
  const n = t.lt || t;
133
159
  return newQuestionInFlight(n);
134
160
  }
135
- if (n && e.De !== NOT_PENDING && !hasActiveOverride(e)) {
136
- return !!(n.se & REACTIVE_MANUAL_WRITE) || !n.Te && !(n.S & STATUS_PENDING) || !!(n.S & STATUS_PENDING) && quietPending(n);
161
+ if (n && e.Pe !== NOT_PENDING && !hasActiveOverride(e)) {
162
+ return !!(n.ie & REACTIVE_MANUAL_WRITE) || !n.o?.Ee && !(n.S & STATUS_PENDING) || !!(n.S & STATUS_PENDING) && quietPending(n);
137
163
  }
138
164
  // `!comp._loading`: a hold created while the loading window is still open is
139
165
  // the window's own landing in flight to its commit — verdict-quiet like the
140
166
  // rest of the window (the UNINITIALIZED check suppresses exactly this frame
141
167
  // for windowless first loads; born-committed nodes need their own gate, #2990).
142
- if (e.De !== NOT_PENDING && !(t.S & STATUS_UNINITIALIZED) && !t.Ee) {
143
- if (hasActiveOverride(e)) return !e.be || !e.be(e.De, unwrapOverride(e._e));
168
+ if (e.Pe !== NOT_PENDING && !(t.S & STATUS_UNINITIALIZED) && !t.Ie) {
169
+ if (hasActiveOverride(e)) return !e.Ue || !e.Ue(e.Pe, unwrapOverride(e.o?.De));
144
170
  return true;
145
171
  }
146
172
  return newQuestionInFlight(t);
147
173
  }
148
174
 
149
175
  function syncCompanions(e, t) {
150
- if (e.ge) updatePendingSignal(e);
151
- if (e.pe) setSignal(e.pe, t);
176
+ if (e.o?.Ge) updatePendingSignal(e);
177
+ if (e.o?.ge) setSignal(e.o?.ge, t);
152
178
  }
153
179
 
154
180
  function updatePendingSignal(e) {
155
- if (e.ge) {
156
- setSignal(e.ge, computePendingState(e));
181
+ if (e.o?.Ge) {
182
+ setSignal(e.o?.Ge, computePendingState(e));
157
183
  }
158
- if (e.pe) updatePendingSignal(e.pe);
184
+ if (e.o?.ge) updatePendingSignal(e.o?.ge);
159
185
  }
160
186
 
161
187
  function updateChildCompanions(e) {
162
- for (let t = e.u; t !== null; t = t.ae) {
163
- if (t.ge || t.pe) updatePendingSignal(t);
164
- }
188
+ const t = e.o?.Nt;
189
+ if (t === undefined) return;
190
+ for (const e of t) updatePendingSignal(e);
165
191
  }
166
192
 
167
193
  /**
@@ -178,31 +204,65 @@ function updateChildCompanions(e) {
178
204
  const visit = e => {
179
205
  if (i.has(e)) return;
180
206
  i.add(e);
181
- if (e.ge || e.pe) n(e);
182
- for (let t = e.o; t !== null; t = t.le) visit(t.fe);
183
- for (let t = e.u ?? null; t !== null; t = t.ae) {
207
+ if (e.o?.Ge || e.o?.ge) n(e);
208
+ for (let t = e.u; t !== null; t = t.fe) visit(t.ae);
209
+ for (let t = e.o?.l ?? null; t !== null; t = t.ce) {
184
210
  visit(t);
185
211
  }
186
212
  };
187
213
  visit(e);
188
214
  }
189
215
 
216
+ /**
217
+ * The correction half of the provisional fresh-read suppression (see
218
+ * collectPending): fired from the sanctioned async-registration site
219
+ * (GlobalQueue.notify) when a transaction gains an in-flight async blocker.
220
+ * Every probe that returned "not pending" purely because it read a held
221
+ * value belonging to that transaction re-runs — its re-probe now sees the
222
+ * live blocker through heldAwaitingAsync and lands the true verdict. The
223
+ * wake mirrors a companion write's own notification (optimistic-dirty on the
224
+ * companion's lane) so the corrected verdict commits and flushes immediately
225
+ * instead of being held with the transaction it reports on.
226
+ */ function wakeSuppressedProbes(e) {
227
+ if (suppressedProbes.size === 0) return;
228
+ let t = false;
229
+ for (const [n, i] of suppressedProbes) {
230
+ const r = n._e;
231
+ const s = r ? currentTransition(r) : null;
232
+ if (!s) {
233
+ suppressedProbes.delete(n);
234
+ continue;
235
+ }
236
+ if (s !== e) continue;
237
+ suppressedProbes.delete(n);
238
+ const o = n.o?.Ge?.o?.Be;
239
+ for (const e of i) {
240
+ if (e.ie & REACTIVE_DISPOSED) continue;
241
+ e.ie |= REACTIVE_OPTIMISTIC_DIRTY;
242
+ if (o) assignOrMergeLane(e, o); else if (e.o !== null) e.o.Be = undefined;
243
+ enqueueSub(e);
244
+ t = true;
245
+ }
246
+ }
247
+ if (t) schedule();
248
+ }
249
+
190
250
  function snapCompanionsToState(e) {
191
- const t = e.ge;
192
- if (t && (t._e === undefined || t._e === NOT_PENDING)) {
251
+ suppressedProbes.size !== 0 && suppressedProbes.delete(e);
252
+ const t = e.o?.Ge;
253
+ if (t && (t.o?.De === undefined || t.o?.De === NOT_PENDING)) {
193
254
  const n = computePendingState(e);
194
- if (t.Ue !== n || t.De !== NOT_PENDING) {
195
- t.Ue = n;
196
- t.De = NOT_PENDING;
197
- t.de = clock;
255
+ if (t.be !== n || t.Pe !== NOT_PENDING) {
256
+ t.be = n;
257
+ t.Pe = NOT_PENDING;
198
258
  insertSubs(t);
199
259
  schedule();
200
260
  }
201
261
  }
202
- const n = e.pe;
203
- if (n && !(n.se & REACTIVE_DISPOSED)) {
204
- if ((n._e === undefined || n._e === NOT_PENDING) && n.De === NOT_PENDING && !Object.is(n.Ue, e.Ue) && !(n.se & (REACTIVE_DIRTY | REACTIVE_CHECK))) {
205
- n.se |= REACTIVE_DIRTY;
262
+ const n = e.o?.ge;
263
+ if (n && !(n.ie & REACTIVE_DISPOSED)) {
264
+ if ((n.o?.De === undefined || n.o?.De === NOT_PENDING) && n.Pe === NOT_PENDING && !Object.is(n.be, e.be) && !(n.ie & (REACTIVE_DIRTY | REACTIVE_CHECK))) {
265
+ n.ie |= REACTIVE_DIRTY;
206
266
  insertIntoHeap(n, queueFor(n));
207
267
  insertSubs(n);
208
268
  schedule();
@@ -212,29 +272,38 @@ function snapCompanionsToState(e) {
212
272
  }
213
273
 
214
274
  function getLatestValueComputed(e) {
215
- if (!e.pe) {
216
- const t = latestReadActive;
275
+ let t = e.o?.ge;
276
+ if (!t) {
277
+ const n = latestReadActive;
217
278
  setLatestReadActive(false);
218
- const n = pendingCheckActive;
279
+ const i = pendingCheckActive;
219
280
  setPendingCheckActive(false);
220
- const i = context;
281
+ const r = context;
221
282
  setContextInternal(null);
222
283
  // Detach from owner so it isn't disposed with effects
223
- e.pe = optimisticComputed(() => read(e));
224
- e.pe.nn = e;
284
+ t = optimisticComputed(() => read(e));
285
+ ext(e).ge = t;
286
+ e.T |= CONFIG_HAS_COMPANIONS;
287
+ markFirewallChildCompanions(e);
288
+ ext(t).It = e;
225
289
  // Parent-child lane relationship
226
- setContextInternal(i);
227
- setPendingCheckActive(n);
228
- setLatestReadActive(t);
290
+ // Backfill an in-flight write (mirrors getPendingSignal): the companion is
291
+ // created lazily, possibly after the write was processed — syncCompanions
292
+ // only pushes into companions that already exist, so the first latest()
293
+ // read inside a held transition showed the committed value (#3041).
294
+ if (e.Pe !== NOT_PENDING && !hasActiveOverride(e)) setSignal(t, e.Pe);
295
+ setContextInternal(r);
296
+ setPendingCheckActive(i);
297
+ setLatestReadActive(n);
229
298
  }
230
- return e.pe;
299
+ return t;
231
300
  }
232
301
 
233
302
  /** The latest()-mode read path, installed as GlobalQueue._latestRead. */ function latestRead(e) {
234
303
  const t = getLatestValueComputed(e);
235
304
  const n = latestReadActive;
236
305
  setLatestReadActive(false);
237
- const i = e._e !== undefined && e._e !== NOT_PENDING ? unwrapOverride(e._e) : e.Ue;
306
+ const i = e.o?.De !== undefined && e.o?.De !== NOT_PENDING ? unwrapOverride(e.o?.De) : e.be;
238
307
  let r;
239
308
  try {
240
309
  // An untracked latest() read has no reading context, so read() never
@@ -243,7 +312,7 @@ function getLatestValueComputed(e) {
243
312
  // until the flush (#2922). Mirror the tracked-read pull here: mark the
244
313
  // queued staleness through the graph, then bring the shadow up to date.
245
314
  const e = queueFor(t);
246
- if (t.xe >= e.Ve && !(t.se & (REACTIVE_DISPOSED | REACTIVE_ZOMBIE))) {
315
+ if (t.Le >= e.xe && !(t.ie & (REACTIVE_DISPOSED | REACTIVE_ZOMBIE))) {
247
316
  markHeap(e);
248
317
  prepareComputed(t, true);
249
318
  }
@@ -255,8 +324,8 @@ function getLatestValueComputed(e) {
255
324
  setLatestReadActive(n);
256
325
  }
257
326
  if (t.S & STATUS_PENDING) return i;
258
- if (stale && currentOptimisticLane && t.Ke) {
259
- const e = findLane(t.Ke);
327
+ if (stale && currentOptimisticLane && t.o?.Be) {
328
+ const e = findLane(t.o?.Be);
260
329
  const n = findLane(currentOptimisticLane);
261
330
  if (e !== n && e.Ae.size > 0) {
262
331
  return i;
@@ -266,33 +335,54 @@ function getLatestValueComputed(e) {
266
335
  // speculative value in _pendingValue; a contextless read() only surfaces
267
336
  // _value. Overrides stay authoritative (A17), and stale readers keep the
268
337
  // other transition's committed view, matching read()'s own selection.
269
- if (t.De !== NOT_PENDING && !hasActiveOverride(t) && !(stale && t.Ne && activeTransition !== t.Ne)) return t.De;
338
+ if (t.Pe !== NOT_PENDING && !hasActiveOverride(t) && !(stale && t._e && activeTransition !== t._e)) return t.Pe;
270
339
  return r;
271
340
  }
272
341
 
273
342
  /** The isPending()-probe read path, installed as GlobalQueue._pendingCheck. */ function pendingCheckRead(e, t, n, i) {
274
343
  setPendingCheckActive(false);
275
- if (typeof e.ce === "function") prepareComputed(e, true);
344
+ if (typeof e.Se === "function") prepareComputed(e, true);
276
345
  const r = n.S;
277
346
  if (t && r & STATUS_PENDING && r & STATUS_UNINITIALIZED) {
278
347
  if (tracking && e !== t) link(e, t);
279
348
  setPendingCheckActive(true);
280
- throw n._;
349
+ throw n.o?._;
281
350
  }
282
351
  collectPendingSources(e);
283
352
  if (i) collectPendingSources(i);
284
353
  setPendingCheckActive(true);
285
354
  }
286
355
 
356
+ /**
357
+ * A held node whose transaction still has an async question in flight. The
358
+ * probe's fresh-read pairing rule (#2831 — "a reader that sees the fresh
359
+ * value must not also be told it is pending") only applies to LANDED answers
360
+ * awaiting reveal; while the answer is still computing, the fresh value the
361
+ * reader saw is an input, and pending remains the truth for every reader
362
+ * (#3028).
363
+ */ function heldAwaitingAsync(e) {
364
+ const t = e._e;
365
+ const n = t ? currentTransition(t) : null;
366
+ if (!n || n.an) return false;
367
+ for (const [e, t] of n.Ne) {
368
+ if (t.size && e.S & STATUS_PENDING && e.o?._?.source === e) return true;
369
+ }
370
+ return false;
371
+ }
372
+
287
373
  function recordFreshRead(e, t) {
288
- if (pendingProbe !== null && e.De !== NOT_PENDING && t === e.De) pendingProbe.freshReads.add(e);
374
+ if (pendingProbe !== null && e.Pe !== NOT_PENDING && t === e.Pe) {
375
+ if (heldAwaitingAsync(e)) return;
376
+ pendingProbe.freshReads.add(e);
377
+ }
289
378
  }
290
379
 
291
380
  function applyReask(e, t) {
292
381
  const n = !!(e.S & STATUS_PENDING);
293
- const i = t && !(n && !e.he);
294
- const r = n && e.he !== i;
295
- e.he = i;
382
+ const i = t && !(n && !e.o?.pe);
383
+ const r = n && (e.o?.pe ?? false) !== i;
384
+ // Allocation-free for the quiet case: false is the extension default.
385
+ if (i) ext(e).pe = true; else if (e.o !== null) e.o.pe = false;
296
386
  return r;
297
387
  }
298
388
 
@@ -313,17 +403,32 @@ function isPending(e) {
313
403
  const i = pendingProbe = {
314
404
  found: false,
315
405
  sources: new Set,
316
- freshReads: new Set
406
+ freshReads: new Set,
407
+ suppressed: []
317
408
  };
318
409
  const collectPending = () => {
319
410
  setPendingCheckActive(false);
320
411
  try {
321
412
  i.sources.forEach(e => {
322
- if (read(getPendingSignal(e)) && !i.freshReads.has(e)) i.found = true;
413
+ if (read(getPendingSignal(e))) {
414
+ if (!i.freshReads.has(e)) i.found = true; else i.suppressed.push(e);
415
+ }
323
416
  });
324
417
  } finally {
325
418
  setPendingCheckActive(true);
326
419
  }
420
+ // A "not pending" verdict that exists only because this reader saw the
421
+ // fresh held value is provisional: if the write turns out NOT to commit
422
+ // this flush (a downstream async pends and holds it), the suppression was
423
+ // wrong and the wrapper must re-ask (#3028). Remember who to wake — the
424
+ // async registration (GlobalQueue.notify) triggers wakeSuppressedProbes.
425
+ if (!i.found && i.suppressed.length && context && typeof context.Se === "function") {
426
+ for (const e of i.suppressed) {
427
+ let t = suppressedProbes.get(e);
428
+ if (!t) suppressedProbes.set(e, t = new Set);
429
+ t.add(context);
430
+ }
431
+ }
327
432
  };
328
433
  try {
329
434
  e();
@@ -346,24 +451,26 @@ function isPending(e) {
346
451
  // Hook installation (same late-binding pattern as GlobalQueue._update /
347
452
  // _propagateAffects): core call sites fire these behind the same guards the
348
453
  // direct calls used, so behavior is identical once this module loads.
349
- GlobalQueue.Pe = syncCompanions;
454
+ GlobalQueue.Oe = syncCompanions;
350
455
 
351
- GlobalQueue.Se = updatePendingSignal;
456
+ GlobalQueue.de = updatePendingSignal;
352
457
 
353
- GlobalQueue.Oe = updateChildCompanions;
458
+ GlobalQueue.ye = updateChildCompanions;
354
459
 
355
460
  GlobalQueue.un = snapCompanionsToState;
356
461
 
357
- GlobalQueue.At = latestRead;
462
+ GlobalQueue.Pt = latestRead;
463
+
464
+ GlobalQueue.Dt = pendingCheckRead;
358
465
 
359
- GlobalQueue.Ct = pendingCheckRead;
466
+ GlobalQueue.Ht = recordFreshRead;
360
467
 
361
- GlobalQueue.ht = recordFreshRead;
468
+ GlobalQueue.Je = applyReask;
362
469
 
363
- GlobalQueue.Be = applyReask;
470
+ GlobalQueue.p = repollDownstreamVerdicts;
364
471
 
365
- GlobalQueue.k = repollDownstreamVerdicts;
472
+ GlobalQueue.Bt = witnessAffects;
366
473
 
367
- GlobalQueue.Lt = witnessAffects;
474
+ GlobalQueue.wt = wakeSuppressedProbes;
368
475
 
369
476
  export { isPending, latest };
@@ -26,19 +26,19 @@ export { affects } from "./affects.js";
26
26
 
27
27
  export { mapArray, repeat } from "./map.js";
28
28
 
29
- export { $PROXY, $TARGET, $TRACK, createStore, isWrappable } from "./store/store.js";
29
+ export { createStore, deep, reconcile, snapshot } from "./store/index.js";
30
30
 
31
- export { createProjection } from "./store/projection.js";
31
+ export { createErrorBoundary, createLoadingBoundary, createRevealOrder, flatten } from "./boundaries.js";
32
32
 
33
- export { createOptimisticStore } from "./store/optimistic.js";
33
+ export { $PROXY, $TARGET, $TRACK, isWrappable } from "./store/store.js";
34
34
 
35
- export { reconcile } from "./store/reconcile.js";
35
+ export { createOptimisticStoreNext as createOptimisticStore } from "./store/next/optimistic.js";
36
36
 
37
- export { storePath } from "./store/storePath.js";
37
+ export { createProjectionNext as createProjection } from "./store/next/projection.js";
38
38
 
39
- export { deep, merge, omit, snapshot } from "./store/utils.js";
39
+ export { merge, omit } from "./store/utils.js";
40
40
 
41
- export { createErrorBoundary, createLoadingBoundary, createRevealOrder, flatten } from "./boundaries.js";
41
+ export { storePath } from "./store/storePath.js";
42
42
 
43
43
  const DEV = undefined;
44
44