@solidjs/signals 2.0.0-rc.7 → 2.0.0-rc.9

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 (134) hide show
  1. package/dist/dev-shared.js +6357 -0
  2. package/dist/dev.attribution.js +2485 -0
  3. package/dist/dev.js +2851 -7027
  4. package/dist/observe/affects.js +129 -0
  5. package/dist/observe/attribution.js +7 -0
  6. package/dist/observe/boundaries.js +641 -0
  7. package/dist/observe/core/action.js +177 -0
  8. package/dist/observe/core/async.js +846 -0
  9. package/dist/observe/core/attribution-costs.js +66 -0
  10. package/dist/observe/core/attribution-feedback.js +282 -0
  11. package/dist/observe/core/attribution-hooks.js +82 -0
  12. package/dist/observe/core/attribution-queries.js +28 -0
  13. package/dist/observe/core/attribution.js +2095 -0
  14. package/dist/observe/core/constants.js +248 -0
  15. package/dist/{types-cjs/core/context.d.cts → observe/core/context.js} +37 -12
  16. package/dist/observe/core/core.js +1789 -0
  17. package/dist/observe/core/dev.js +293 -0
  18. package/dist/observe/core/effect.js +195 -0
  19. package/dist/observe/core/error-hooks.js +71 -0
  20. package/dist/observe/core/error.js +80 -0
  21. package/dist/observe/core/external.js +98 -0
  22. package/dist/observe/core/graph.js +155 -0
  23. package/dist/observe/core/heap.js +147 -0
  24. package/dist/observe/core/invariants.js +45 -0
  25. package/dist/observe/core/lanes.js +205 -0
  26. package/dist/observe/core/optimistic.js +585 -0
  27. package/dist/observe/core/owner.js +322 -0
  28. package/dist/observe/core/scheduler.js +1428 -0
  29. package/dist/observe/core/verdict.js +669 -0
  30. package/dist/observe/index.js +59 -0
  31. package/dist/observe/map.js +484 -0
  32. package/dist/{types-cjs/signals.d.cts → observe/signals.js} +311 -317
  33. package/dist/observe/store/index.js +40 -0
  34. package/dist/observe/store/next/optimistic.js +660 -0
  35. package/dist/observe/store/next/projection.js +255 -0
  36. package/dist/observe/store/next/reconcile.js +375 -0
  37. package/dist/observe/store/next/store.js +2327 -0
  38. package/dist/observe/store/next/target.js +51 -0
  39. package/dist/observe/store/store.js +325 -0
  40. package/dist/observe/store/storePath.js +103 -0
  41. package/dist/observe/store/utils.js +1023 -0
  42. package/dist/prod/attribution.js +41 -0
  43. package/dist/prod/boundaries.js +128 -76
  44. package/dist/prod/core/action.js +65 -37
  45. package/dist/prod/core/async.js +324 -172
  46. package/dist/prod/core/constants.js +57 -1
  47. package/dist/prod/core/context.js +3 -3
  48. package/dist/prod/core/core.js +1003 -364
  49. package/dist/prod/core/dev.js +17 -1
  50. package/dist/prod/core/effect.js +76 -42
  51. package/dist/prod/core/error-hooks.js +71 -0
  52. package/dist/prod/core/external.js +4 -4
  53. package/dist/prod/core/graph.js +37 -37
  54. package/dist/prod/core/heap.js +61 -56
  55. package/dist/prod/core/lanes.js +107 -63
  56. package/dist/prod/core/optimistic.js +374 -93
  57. package/dist/prod/core/owner.js +62 -46
  58. package/dist/prod/core/scheduler.js +653 -286
  59. package/dist/prod/core/verdict.js +245 -127
  60. package/dist/prod/index.js +15 -5
  61. package/dist/prod/map.js +292 -141
  62. package/dist/prod/signals.js +29 -16
  63. package/dist/prod/store/next/optimistic.js +77 -55
  64. package/dist/prod/store/next/projection.js +31 -20
  65. package/dist/prod/store/next/reconcile.js +87 -84
  66. package/dist/prod/store/next/store.js +718 -401
  67. package/dist/prod/store/next/target.js +32 -10
  68. package/dist/prod/store/store.js +12 -14
  69. package/dist/prod/store/utils.js +950 -130
  70. package/dist/types/attribution.d.ts +20 -0
  71. package/dist/types/attribution.prod.d.ts +22 -0
  72. package/dist/types/boundaries.d.ts +10 -1
  73. package/dist/types/core/action.d.ts +12 -5
  74. package/dist/types/core/async.d.ts +1 -1
  75. package/dist/types/core/attribution-costs.d.ts +35 -0
  76. package/dist/types/core/attribution-feedback.d.ts +133 -0
  77. package/dist/types/core/attribution-hooks.d.ts +147 -9
  78. package/dist/types/core/attribution-queries.d.ts +10 -0
  79. package/dist/types/core/attribution.d.ts +216 -182
  80. package/dist/types/core/constants.d.ts +56 -0
  81. package/dist/types/core/core.d.ts +190 -2
  82. package/dist/types/core/dev.d.ts +246 -28
  83. package/dist/types/core/effect.d.ts +8 -1
  84. package/dist/types/core/error-hooks.d.ts +71 -0
  85. package/dist/types/core/index.d.ts +4 -1
  86. package/dist/types/core/invariants.d.ts +4 -0
  87. package/dist/types/core/lanes.d.ts +39 -5
  88. package/dist/types/core/scheduler.d.ts +153 -0
  89. package/dist/types/core/types.d.ts +32 -9
  90. package/dist/types/index.d.ts +10 -3
  91. package/dist/types/map.d.ts +2 -0
  92. package/dist/types/signals.d.ts +8 -0
  93. package/dist/types/store/index.d.ts +2 -0
  94. package/dist/types/store/next/optimistic.d.ts +1 -1
  95. package/dist/types/store/next/store.d.ts +11 -5
  96. package/dist/types/store/next/target.d.ts +37 -10
  97. package/dist/types/store/utils.d.ts +178 -0
  98. package/package.json +21 -17
  99. package/dist/node.cjs +0 -10541
  100. package/dist/node.dev.cjs +0 -13724
  101. package/dist/types-cjs/affects.d.cts +0 -47
  102. package/dist/types-cjs/boundaries.d.cts +0 -175
  103. package/dist/types-cjs/core/action.d.cts +0 -64
  104. package/dist/types-cjs/core/async.d.cts +0 -23
  105. package/dist/types-cjs/core/attribution-hooks.d.cts +0 -127
  106. package/dist/types-cjs/core/attribution.d.cts +0 -495
  107. package/dist/types-cjs/core/constants.d.cts +0 -159
  108. package/dist/types-cjs/core/core.d.cts +0 -185
  109. package/dist/types-cjs/core/dev.d.cts +0 -136
  110. package/dist/types-cjs/core/effect.d.cts +0 -30
  111. package/dist/types-cjs/core/error.d.cts +0 -56
  112. package/dist/types-cjs/core/external.d.cts +0 -15
  113. package/dist/types-cjs/core/graph.d.cts +0 -28
  114. package/dist/types-cjs/core/heap.d.cts +0 -24
  115. package/dist/types-cjs/core/index.d.cts +0 -13
  116. package/dist/types-cjs/core/invariants.d.cts +0 -59
  117. package/dist/types-cjs/core/lanes.d.cts +0 -54
  118. package/dist/types-cjs/core/optimistic.d.cts +0 -6
  119. package/dist/types-cjs/core/owner.d.cts +0 -124
  120. package/dist/types-cjs/core/scheduler.d.cts +0 -236
  121. package/dist/types-cjs/core/types.d.cts +0 -204
  122. package/dist/types-cjs/core/verdict.d.cts +0 -2
  123. package/dist/types-cjs/index.d.cts +0 -10
  124. package/dist/types-cjs/map.d.cts +0 -69
  125. package/dist/types-cjs/package.json +0 -3
  126. package/dist/types-cjs/store/index.d.cts +0 -18
  127. package/dist/types-cjs/store/next/optimistic.d.cts +0 -25
  128. package/dist/types-cjs/store/next/projection.d.cts +0 -8
  129. package/dist/types-cjs/store/next/reconcile.d.cts +0 -10
  130. package/dist/types-cjs/store/next/store.d.cts +0 -121
  131. package/dist/types-cjs/store/next/target.d.cts +0 -166
  132. package/dist/types-cjs/store/store.d.cts +0 -143
  133. package/dist/types-cjs/store/storePath.d.cts +0 -58
  134. package/dist/types-cjs/store/utils.d.cts +0 -74
@@ -0,0 +1,41 @@
1
+ const EMPTY = Object.freeze([]);
2
+
3
+ const noop = () => {};
4
+
5
+ const attribution = {
6
+ enable: noop,
7
+ disable: noop,
8
+ subscribe: () => noop,
9
+ history: () => EMPTY,
10
+ waterfalls: () => EMPTY,
11
+ holds: () => EMPTY,
12
+ navigations: () => EMPTY,
13
+ interactions: () => EMPTY,
14
+ markFlight: noop
15
+ };
16
+
17
+ // The named surface, inert: every fold empty, every query empty, every
18
+ // formatter blank. Typed against the real entry's exports so the two cannot
19
+ // drift.
20
+ const costs = () => ({
21
+ scopes: [],
22
+ writes: []
23
+ });
24
+
25
+ const feedback = () => ({
26
+ sources: [],
27
+ interactions: [],
28
+ navigations: [],
29
+ flights: [],
30
+ fallbacks: []
31
+ });
32
+
33
+ const why = () => [];
34
+
35
+ const subscriptions = () => [];
36
+
37
+ const formatRerun = () => "";
38
+
39
+ const formatOrigin = () => "";
40
+
41
+ export { attribution, costs, feedback, formatOrigin, formatRerun, subscriptions, why };
@@ -1,14 +1,14 @@
1
- import { runWithOwner, signal, untrack, read, computed, setSignal, ext, recompute } from "./core/core.js";
1
+ import { runWithOwner, signal, untrack, read, computed, spectate, setSignal, ext, recompute } from "./core/core.js";
2
2
 
3
3
  import { NotReadyError, unwrapStatusError } from "./core/error.js";
4
4
 
5
5
  import { createOwner, cleanup } from "./core/owner.js";
6
6
 
7
- import { Queue, haltReactivity, schedule } from "./core/scheduler.js";
7
+ import { Queue, transitions, wakeParked, haltReactivity, schedule } from "./core/scheduler.js";
8
8
 
9
9
  import { getContext, setContext, createContext } from "./core/context.js";
10
10
 
11
- import { STATUS_PENDING, STATUS_ERROR, REACTIVE_DISPOSED, CONFIG_AUTO_DISPOSE } from "./core/constants.js";
11
+ import { STATUS_PENDING, STATUS_ERROR, REACTIVE_ZOMBIE, REACTIVE_DISPOSED, CONFIG_AUTO_DISPOSE } from "./core/constants.js";
12
12
 
13
13
  import "./core/invariants.js";
14
14
 
@@ -16,6 +16,8 @@ import "./core/verdict.js";
16
16
 
17
17
  import "./core/effect.js";
18
18
 
19
+ import { reportClientError } from "./core/error-hooks.js";
20
+
19
21
  import { accessor } from "./signals.js";
20
22
 
21
23
  function boundaryComputed(e, t) {
@@ -80,11 +82,11 @@ function isRevealController(e) {
80
82
  }
81
83
 
82
84
  function isSlotReady(e) {
83
- return isRevealController(e) ? e.O() : e.v.size === 0 && !e.U;
85
+ return isRevealController(e) ? e.O() : e.I.size === 0 && !e.v;
84
86
  }
85
87
 
86
88
  function isSlotMinimallyReady(e) {
87
- return isRevealController(e) ? e.I() : isSlotReady(e);
89
+ return isRevealController(e) ? e.U() : isSlotReady(e);
88
90
  }
89
91
 
90
92
  function setSlotState(e, t, r, n) {
@@ -99,34 +101,34 @@ function setSlotState(e, t, r, n) {
99
101
 
100
102
  class RevealController {
101
103
  F;
102
- q;
103
- V=[];
104
+ V;
105
+ q=[];
104
106
  j;
105
107
  D=signal(false, {
106
108
  ownedWrite: true,
107
- H: true
109
+ Z: true
108
110
  });
109
111
  P=signal(false, {
110
112
  ownedWrite: true,
111
- H: true
113
+ Z: true
112
114
  });
115
+ H=true;
113
116
  J=true;
114
- K=true;
115
- X=false;
117
+ K=false;
116
118
  constructor(e, t) {
117
119
  this.F = e;
118
- this.q = t;
120
+ this.V = t;
119
121
  }
120
- Y(e) {
121
- for (let t = 0; t < this.V.length; t++) {
122
- const r = this.V[t];
122
+ X(e) {
123
+ for (let t = 0; t < this.q.length; t++) {
124
+ const r = this.q[t];
123
125
  if ((isRevealController(r) ? r.j : r.W) !== this) continue;
124
126
  if (e(r) === false) return false;
125
127
  }
126
128
  return true;
127
129
  }
128
130
  O() {
129
- return this.Y(isSlotReady);
131
+ return this.X(isSlotReady);
130
132
  }
131
133
  /**
132
134
  * "Minimally ready" = this group has something visible to show under its own policy.
@@ -134,13 +136,13 @@ class RevealController {
134
136
  * - `together`: every direct slot is minimally ready.
135
137
  * - `sequential`: the first owned slot is minimally ready (frontier can advance).
136
138
  * - `natural`: any owned slot is minimally ready.
137
- */ I() {
139
+ */ U() {
138
140
  const e = untrack(this.F);
139
- if (e === "together") return this.Y(isSlotMinimallyReady);
141
+ if (e === "together") return this.X(isSlotMinimallyReady);
140
142
  if (e === "natural") {
141
143
  let e = false;
142
144
  let t = false;
143
- this.Y(r => {
145
+ this.X(r => {
144
146
  e = true;
145
147
  if (isSlotMinimallyReady(r)) {
146
148
  t = true;
@@ -151,41 +153,41 @@ class RevealController {
151
153
  }
152
154
  // sequential: only the first owned slot matters.
153
155
  let t = true;
154
- this.Y(e => {
156
+ this.X(e => {
155
157
  t = isSlotMinimallyReady(e);
156
158
  return false;
157
159
  });
158
160
  return t;
159
161
  }
160
- Z(e) {
161
- if (this.V.includes(e)) return;
162
- this.V.push(e);
162
+ Y(e) {
163
+ if (this.q.includes(e)) return;
164
+ this.q.push(e);
163
165
  const t = untrack(this.F);
164
- setSignal(e.D, true), setSignal(e.P, t === "sequential" ? !!untrack(this.q) : false);
166
+ setSignal(e.D, true), setSignal(e.P, t === "sequential" ? !!untrack(this.V) : false);
165
167
  untrack(() => this.B());
166
168
  }
167
169
  $(e) {
168
- const t = this.V.indexOf(e);
169
- if (t >= 0) this.V.splice(t, 1);
170
+ const t = this.q.indexOf(e);
171
+ if (t >= 0) this.q.splice(t, 1);
170
172
  untrack(() => this.B());
171
173
  }
172
174
  B(e, t) {
173
- if (this.X) return;
174
- this.X = true;
175
- const r = this.J;
176
- const n = this.K;
175
+ if (this.K) return;
176
+ this.K = true;
177
+ const r = this.H;
178
+ const n = this.J;
177
179
  try {
178
- const r = e ?? read(this.D), n = untrack(this.F), s = n === "sequential" && !!untrack(this.q), i = t ?? s;
180
+ const r = e ?? read(this.D), n = untrack(this.F), s = n === "sequential" && !!untrack(this.V), i = t ?? s;
179
181
  if (r) {
180
182
  // Held by an outer group. Propagate the hold (and whatever collapsed policy
181
183
  // the outer asked for) down the whole subtree. Inner order is ignored while
182
184
  // held; it resumes once the outer releases us.
183
- this.Y(e => setSlotState(e, this, true, i));
185
+ this.X(e => setSlotState(e, this, true, i));
184
186
  } else if (n === "natural") {
185
187
  // Each child reveals based on its own readiness. A nested controller slot
186
188
  // is released to run its own order locally — we bypass setSlotState for it
187
189
  // so the parent backpointer survives for upward readiness notifications.
188
- this.Y(e => {
190
+ this.X(e => {
189
191
  if (isRevealController(e)) {
190
192
  setSignal(e.P, false);
191
193
  setSignal(e.D, false);
@@ -200,11 +202,11 @@ class RevealController {
200
202
  // sequential's first slot being ready is minimally ready; natural having any
201
203
  // ready child is minimally ready. This lets `together` guarantee a single
202
204
  // cohesive reveal without waiting for every grandchild.
203
- const e = this.Y(isSlotMinimallyReady);
204
- this.Y(t => setSlotState(t, this, !e, false));
205
+ const e = this.X(isSlotMinimallyReady);
206
+ this.X(t => setSlotState(t, this, !e, false));
205
207
  } else {
206
208
  let e = false;
207
- this.Y(t => {
209
+ this.X(t => {
208
210
  if (e) return setSlotState(t, this, true, s);
209
211
  if (isSlotReady(t)) return setSlotState(t, this, false, false);
210
212
  e = true;
@@ -224,32 +226,34 @@ class RevealController {
224
226
  });
225
227
  }
226
228
  } finally {
227
- this.J = this.O();
228
- this.K = this.I();
229
- this.X = false;
229
+ this.H = this.O();
230
+ this.J = this.U();
231
+ this.K = false;
230
232
  }
231
- if (this.j && (r !== this.J || n !== this.K)) this.j.B();
233
+ if (this.j && (r !== this.H || n !== this.J)) this.j.B();
232
234
  }
233
235
  }
234
236
 
235
237
  class CollectionQueue extends Queue {
236
238
  ee;
237
- v=new Set;
239
+ I=new Set;
238
240
  te;
239
- U=true;
241
+ v=true;
240
242
  D=signal(false, {
241
243
  ownedWrite: true,
242
- H: true
244
+ Z: true
243
245
  });
244
246
  _;
245
247
  P=signal(false, {
246
248
  ownedWrite: true,
247
- H: true
249
+ Z: true
248
250
  });
249
251
  W;
250
252
  L=false;
251
253
  re;
252
254
  ne=ON_INIT;
255
+ /** The boundary's owner — where a `caught` report locates itself, set before the children are built (a creation-time throw arrives before `_tree`). */
256
+ se;
253
257
  constructor(e) {
254
258
  super();
255
259
  this.ee = e;
@@ -258,20 +262,41 @@ class CollectionQueue extends Queue {
258
262
  if (!e || read(this.D) && (!_revealUsed || read(this.P))) return;
259
263
  return super.run(e);
260
264
  }
265
+ /** The `on` key, or ON_INIT when it throws. Evaluated mid-propagation,
266
+ * inside the pending node's own pass: read as a spectator so the key's
267
+ * sources never become that node's dependencies (#3528). */ ie() {
268
+ return spectate(() => {
269
+ try {
270
+ return this.re();
271
+ } catch {
272
+ return ON_INIT;
273
+ }
274
+ });
275
+ }
261
276
  notify(e, t, r, n) {
262
277
  if (!(t & this.ee)) return super.notify(e, t, r, n);
263
278
  if (this.L && this.re) {
264
- const e = untrack(() => {
265
- try {
266
- return this.re();
267
- } catch {
268
- return ON_INIT;
269
- }
270
- });
279
+ const e = this.ie();
271
280
  if (e !== this.ne) {
272
281
  this.ne = e;
273
282
  this.L = false;
274
- this.v.clear();
283
+ this.I.clear();
284
+ // Readers forwarded while this boundary showed content are behind the
285
+ // fallback now: they stop blocking (`reporterBlocksSource`), and the
286
+ // transactions they were holding must be re-judged for it (A33, #3375).
287
+ // What those readers still wait on is this boundary's to wait on now:
288
+ // they never re-notify (status propagation dedupes on the reader's
289
+ // `_pendingSources`), so the reset collects it from their registrations
290
+ // — the one place a forwarded reader is recorded (INV-3) — or a sibling
291
+ // reader's flight that lands first reveals them stale (A33, #3459).
292
+ for (const e of transitions) for (const [t, r] of e.oe) for (const e of r) if (this.le(e)) {
293
+ this.I.add(t);
294
+ e.o?.ae?.forEach(e => this.I.add(e));
295
+ }
296
+ if (this.I.size) {
297
+ setSignal(this.D, true);
298
+ }
299
+ wakeParked();
275
300
  }
276
301
  }
277
302
  // Routing is dimension-independent: each boundary consumes only its own
@@ -286,43 +311,69 @@ class CollectionQueue extends Queue {
286
311
  // (the Loading > Errored > content composition escape, #2856).
287
312
  if (this.ee & STATUS_PENDING && this.L) return super.notify(e, t, r, n);
288
313
  if (r & this.ee) {
289
- this.U = true;
314
+ this.v = true;
290
315
  const t = n?.source || e.o?._?.source;
291
316
  if (t) {
292
- const e = this.v.size === 0;
293
- this.v.add(t);
294
- if (e) {
317
+ const r = this.I.size === 0;
318
+ this.I.add(t);
319
+ // A collecting boundary waits on everything the effect is pending on,
320
+ // not only the source this notification carries. Status propagation
321
+ // dedupes on the effect's `_pendingSources`: a source it already
322
+ // carries (a flight that started before an `on` reset cleared the
323
+ // set) is never re-reported, and that source's later re-flight
324
+ // stays invisible — the boundary revealed when its one collected
325
+ // source settled while the effect was still pending (#3375).
326
+ if (this.ee & STATUS_PENDING) e.o?.ae?.forEach(e => this.I.add(e));
327
+ if (r) {
295
328
  setSignal(this.D, true);
296
329
  }
297
330
  if (this.ee & STATUS_ERROR) {
298
- setSignal(this._, unwrapStatusError(t.o?._));
331
+ const e = unwrapStatusError(t.o?._);
332
+ setSignal(this._, e);
333
+ // The client error hook: this boundary renders its fallback for
334
+ // it — the one road a rendered failure took that no global handler
335
+ // ever saw. `source` is the computation that threw (the status
336
+ // wrapper's, made at the first landing and kept downstream), so the
337
+ // hook hears where it broke as well as where it was met. Once per
338
+ // error object; a `reset()` re-collecting the same failure says
339
+ // nothing new.
340
+ reportClientError(e, this.se, t);
299
341
  }
300
342
  }
301
343
  }
302
344
  t &= ~this.ee;
303
345
  return t ? super.notify(e, t, r, n) : true;
304
346
  }
305
- se() {
306
- for (const e of this.v) {
347
+ /** Is `reporter` live and routed to this boundary — under it, with no
348
+ * collecting pending-type boundary in between (`reporterBlocksSource`'s test)? */ le(e) {
349
+ if (e.ue & (REACTIVE_ZOMBIE | REACTIVE_DISPOSED)) return false;
350
+ for (let t = e.C; t; t = t._parent) {
351
+ if (t === this) return true;
352
+ if (t.ee & STATUS_PENDING && !t.L) return false;
353
+ }
354
+ return false;
355
+ }
356
+ fe() {
357
+ for (const e of this.I) {
307
358
  // A source with a live affects() mark holds display state for the
308
359
  // mark's lifetime (the visual channel): the marked node carries no
309
360
  // status of its own, so the count is the liveness test. The release
310
361
  // sweep (finalizePureQueue after mark release) re-runs this check.
311
- if (e.ie & REACTIVE_DISPOSED || !e.o?.t && !(e.S & this.ee) && !(this.ee & STATUS_ERROR && e.S & STATUS_PENDING)) this.v.delete(e);
362
+ if (e.ue & REACTIVE_DISPOSED || !e.o?.t && !(e.S & this.ee) && !(this.ee & STATUS_ERROR && e.S & STATUS_PENDING)) this.I.delete(e);
312
363
  }
313
- if (!this.v.size) {
314
- if (this.ee & STATUS_PENDING && this.U && !this.L && this.te) {
315
- this.U = !!(this.te.S & this.ee);
364
+ if (!this.I.size) {
365
+ if (this.ee & STATUS_PENDING && this.v && !this.L && this.te) {
366
+ this.v = !!(this.te.S & this.ee);
316
367
  } else {
317
- this.U = false;
368
+ this.v = false;
318
369
  }
319
- if (!this.U) {
370
+ if (!this.v) {
320
371
  setSignal(this.D, false);
321
372
  if (this.re) {
322
- try {
323
- this.ne = untrack(() => this.re());
324
- } catch {
325
- /* value not yet committed _prevOn stays stale, next notify will reset */}
373
+ // A throw (value not yet committed) leaves _prevOn stale; the next
374
+ // notify then resets.
375
+ const e = this.ie();
376
+ if (e !== ON_INIT) this.ne = e;
326
377
  }
327
378
  }
328
379
  }
@@ -334,9 +385,10 @@ function createCollectionBoundary(e, t, r, n) {
334
385
  const s = createOwner();
335
386
  if (_revealUsed) setContext(RevealControllerContext, null, s);
336
387
  const i = new CollectionQueue(e);
388
+ i.se = s;
337
389
  if (e === STATUS_ERROR) i._ = signal(undefined, {
338
390
  ownedWrite: true,
339
- H: true
391
+ Z: true
340
392
  });
341
393
  if (n) i.re = n;
342
394
  const o = i.te = createBoundChildren(s, t, i, e);
@@ -348,12 +400,12 @@ function createCollectionBoundary(e, t, r, n) {
348
400
  } catch (e) {
349
401
  if (e instanceof NotReadyError) t = true; else throw e;
350
402
  }
351
- i.U = t || !!(o.S & e) || o.o?._ instanceof NotReadyError;
403
+ i.v = t || !!(o.S & e) || o.o?._ instanceof NotReadyError;
352
404
  });
353
405
  const l = _revealUsed && e === STATUS_PENDING ? getContext(RevealControllerContext) : null;
354
406
  if (l) {
355
407
  i.W = l;
356
- l.Z(i);
408
+ l.Y(i);
357
409
  cleanup(() => l.$(i));
358
410
  }
359
411
  return accessor(computed(() => {
@@ -372,7 +424,7 @@ function createCollectionBoundary(e, t, r, n) {
372
424
  // by snapshot capture. The tree no longer carries foreign status flags, so
373
425
  // capture can't rely on PENDING to skip this node the way it used to.
374
426
  {
375
- H: true
427
+ Z: true
376
428
  }));
377
429
  }
378
430
 
@@ -426,11 +478,11 @@ function createCollectionBoundary(e, t, r, n) {
426
478
  * ```
427
479
  */ function createErrorBoundary(e, t) {
428
480
  return createCollectionBoundary(STATUS_ERROR, e, e => t(accessor(e._), () => {
429
- for (const t of e.v) {
481
+ for (const t of e.I) {
430
482
  // Non-computed sources (patch-channel registrations under plain
431
483
  // owners) are not recomputable — their reset is the record's next
432
484
  // transition re-applying the patch (re-audit 2, P1-4).
433
- if (t.oe !== undefined) recompute(t);
485
+ if (t.ce !== undefined) recompute(t);
434
486
  }
435
487
  schedule();
436
488
  }));
@@ -492,7 +544,7 @@ function createCollectionBoundary(e, t, r, n) {
492
544
  });
493
545
  if (n) {
494
546
  o.j = n;
495
- n.Z(o);
547
+ n.Y(o);
496
548
  cleanup(() => n.$(o));
497
549
  }
498
550
  return t;
@@ -1,14 +1,25 @@
1
- import { globalQueue, activeTransition, currentTransition, schedule, flush } from "./scheduler.js";
1
+ import { setOrigin, globalQueue, activeTransition, currentTransition, schedule, actionStepDepth, flush, enterActionStep, exitActionStep } from "./scheduler.js";
2
2
 
3
3
  import { isThenable } from "./async.js";
4
4
 
5
5
  import "./core.js";
6
6
 
7
- function restoreTransition(e, r) {
8
- globalQueue.initTransition(e);
9
- const t = r();
10
- flush();
11
- return t;
7
+ /** Invocation order across all actions — the provenance every slice of an
8
+ * action runs under (scheduler `origin`): the flights and overrides its
9
+ * ambient windows issue are stamped with it, so a later action's override
10
+ * can tell this action's late answer from its own (#3331). */ let actionSeq = 0;
11
+
12
+ function restoreTransition(e, t, n) {
13
+ const r = setOrigin(e);
14
+ globalQueue.initTransition(t);
15
+ const i = n();
16
+ // A nested action resuming synchronously (its body yielded a non-thenable)
17
+ // runs this inside the OUTER action's slice: draining here would park the
18
+ // shared transaction and detach the outer body's remaining writes (the
19
+ // flush() rule, scheduler.ts). The outer step's own return drains.
20
+ if (actionStepDepth === 0) flush();
21
+ setOrigin(r);
22
+ return i;
12
23
  }
13
24
 
14
25
  /**
@@ -36,15 +47,22 @@ function restoreTransition(e, r) {
36
47
  * `yield` is the transaction-safe suspension point: the action waits for a
37
48
  * yielded promise and re-enters the transaction before running the code after
38
49
  * it. A plain `await` does NOT — the runtime has no hook into an async
39
- * generator's internal await continuations, so writes to fresh signals
40
- * between an `await` and the next `yield` escape the transaction and commit
41
- * immediately. `await` is still the ergonomic choice for typed results; just
42
- * put a bare `yield` before any writes that follow it:
50
+ * generator's internal await continuations, so code between an `await` and
51
+ * the next `yield` runs OUTSIDE the transaction: writes to fresh signals
52
+ * commit immediately, and anything that creates a reader there `until()`,
53
+ * `latest()`, a memo or effect, a mount is created mainline, where a read of
54
+ * this action's held state makes it born held (A29): staged with the
55
+ * transaction and replayed at its commit. For `until()` that commit is the
56
+ * settle its own promise holds open (#3482). `await` is still the ergonomic
57
+ * choice for typed results; just put a bare `yield` before any write or
58
+ * reader creation that follows it — including the expression of the next
59
+ * `yield`, which is evaluated before the step re-enters:
43
60
  *
44
61
  * ```ts
45
62
  * const saved = await api.createTodo(text); // typed result
46
- * yield; // re-enter the transaction before writing
63
+ * yield; // re-enter the transaction before writing or reading
47
64
  * setTodos(t => { ... });
65
+ * yield until(() => todos.some(t => t.id === saved.id));
48
66
  * ```
49
67
  *
50
68
  * (For the same reason, don't call `flush()` inside an action body — it
@@ -74,36 +92,46 @@ function restoreTransition(e, r) {
74
92
  * await addTodo("buy milk");
75
93
  * ```
76
94
  */ function action(e) {
77
- return (...r) => new Promise((t, n) => {
78
- const i = e(...r);
95
+ return (...t) => new Promise((n, r) => {
96
+ const i = e(...t);
97
+ const o = ++actionSeq;
98
+ // The first slice's window runs to the scheduled flush, which clears
99
+ // the provenance with the window — no restore here.
100
+ setOrigin(o);
79
101
  globalQueue.initTransition();
80
- let o = activeTransition;
81
- o.ue.push(i);
82
- const done = (e, r, u = false) => {
83
- o = currentTransition(o);
84
- const s = o.ue.indexOf(i);
85
- if (s >= 0) o.ue.splice(s, 1);
102
+ let s = activeTransition;
103
+ s.pe.push(i);
104
+ s.he = true;
105
+ const done = (e, t, o = false) => {
106
+ s = currentTransition(s);
107
+ const u = s.pe.indexOf(i);
108
+ if (u >= 0) s.pe.splice(u, 1);
86
109
  // Re-adopt through initTransition like every other resumption site:
87
110
  // a bare setActiveTransition leaves globalQueue._batch as a detached
88
111
  // ambient batch, and anything registered before the scheduled flush
89
112
  // (held writes on a merging transition, optimistic overrides,
90
113
  // affects() marks) lands there with nothing to ever finalize it.
91
- globalQueue.initTransition(o);
114
+ globalQueue.initTransition(s);
92
115
  schedule();
93
- u ? n(r) : t(e);
116
+ o ? r(t) : n(e);
94
117
  };
95
- const step = (e, r) => {
96
- let t;
118
+ const step = (e, t) => {
119
+ let n;
120
+ // The body is on the stack between these brackets: flush() is
121
+ // refused inside (FLUSH_IN_ACTION, scheduler.ts).
122
+ enterActionStep();
97
123
  try {
98
- t = r ? i.throw(e) : i.next(e);
124
+ n = t ? i.throw(e) : i.next(e);
99
125
  } catch (e) {
126
+ exitActionStep();
100
127
  return done(undefined, e, true);
101
128
  }
129
+ exitActionStep();
102
130
  // A rejected iterator result (async generators) means the error already
103
131
  // escaped the generator body — it is completed, and throwing back in
104
132
  // would just reject again forever. Settle instead.
105
- if (isThenable(t)) return void t.then(run, e => done(undefined, e, true));
106
- run(t);
133
+ if (isThenable(n)) return void n.then(run, e => done(undefined, e, true));
134
+ run(n);
107
135
  };
108
136
  const run = e => {
109
137
  if (e.done) return done(e.value);
@@ -114,23 +142,23 @@ function restoreTransition(e, r) {
114
142
  // the action so its iterator never leaks in the transition. The
115
143
  // settled flag implements A+ 2.3.3.3.4.1: a throw after the thenable
116
144
  // already called a callback is ignored.
117
- let r = false;
145
+ let t = false;
118
146
  try {
119
147
  if (isThenable(e.value)) return void e.value.then(e => {
120
- if (r) return;
121
- r = true;
122
- restoreTransition(o, () => step(e));
148
+ if (t) return;
149
+ t = true;
150
+ restoreTransition(o, s, () => step(e));
123
151
  }, e => {
124
- if (r) return;
125
- r = true;
126
- restoreTransition(o, () => step(e, true));
152
+ if (t) return;
153
+ t = true;
154
+ restoreTransition(o, s, () => step(e, true));
127
155
  });
128
156
  } catch (e) {
129
- if (r) return;
130
- r = true;
131
- return void restoreTransition(o, () => step(e, true));
157
+ if (t) return;
158
+ t = true;
159
+ return void restoreTransition(o, s, () => step(e, true));
132
160
  }
133
- restoreTransition(o, () => step(e.value));
161
+ restoreTransition(o, s, () => step(e.value));
134
162
  };
135
163
  step();
136
164
  });