@solidjs/signals 2.0.0-beta.20 → 2.0.0-beta.22

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.
@@ -22,23 +22,23 @@ function boundaryComputed(e, t) {
22
22
  const r = computed(e, {
23
23
  lazy: true
24
24
  });
25
- r.C = (e, t) => {
25
+ r.i = (e, t) => {
26
26
  // Use passed values if provided, otherwise read from node
27
- const n = e !== undefined ? e : r.i;
28
- const s = t !== undefined ? t : r.k;
27
+ const n = e !== undefined ? e : r.S;
28
+ const s = t !== undefined ? t : r._;
29
29
  // Notify both status dimensions like a render effect does; the queue chain
30
30
  // consumes this boundary's own type and forwards the remainder upward until
31
31
  // a boundary that handles it is found.
32
- r.i &= ~r.O;
33
- const i = r.v.notify(r, STATUS_PENDING | STATUS_ERROR, n, s);
32
+ r.S &= ~r.R;
33
+ const i = r.C.notify(r, STATUS_PENDING | STATUS_ERROR, n, s);
34
34
  // The queue is the only propagation channel: a foreign status must not stay
35
35
  // reader-visible on the tree, or reads re-throw it across the boundary and
36
36
  // link unrelated ambient contexts (the #2809 nested-boundary loop). Deps are
37
37
  // untouched, so the tree still recomputes when the foreign source settles.
38
- const o = n & ~r.O & (STATUS_PENDING | STATUS_ERROR);
38
+ const o = n & ~r.R & (STATUS_PENDING | STATUS_ERROR);
39
39
  if (o) {
40
- r.i &= ~o;
41
- if (r.k === s && !(r.i & (STATUS_PENDING | STATUS_ERROR))) r.k = undefined;
40
+ r.S &= ~o;
41
+ if (r._ === s && !(r.S & (STATUS_PENDING | STATUS_ERROR))) r._ = undefined;
42
42
  }
43
43
  // An ERROR the chain could not deliver to any boundary is uncaught. The
44
44
  // scrub above already removed it from reader-visible state, so without
@@ -49,16 +49,16 @@ function boundaryComputed(e, t) {
49
49
  throw s;
50
50
  }
51
51
  };
52
- r.O = t;
53
- r.U &= ~CONFIG_AUTO_DISPOSE;
52
+ r.R = t;
53
+ r.T &= ~CONFIG_AUTO_DISPOSE;
54
54
  recompute(r, true);
55
55
  return r;
56
56
  }
57
57
 
58
58
  function createBoundChildren(e, t, r, n) {
59
- const s = e.v;
60
- s.addChild(e.v = r);
61
- cleanup(() => s.removeChild(e.v));
59
+ const s = e.C;
60
+ s.addChild(e.C = r);
61
+ cleanup(() => s.removeChild(e.C));
62
62
  return runWithOwner(e, () => {
63
63
  const e = computed(t);
64
64
  return boundaryComputed(() => flatten(read(e)), n);
@@ -80,53 +80,53 @@ function isRevealController(e) {
80
80
  }
81
81
 
82
82
  function isSlotReady(e) {
83
- return isRevealController(e) ? e.B() : e.W.size === 0 && !e.L;
83
+ return isRevealController(e) ? e.O() : e.v.size === 0 && !e.N;
84
84
  }
85
85
 
86
86
  function isSlotMinimallyReady(e) {
87
- return isRevealController(e) ? e.q() : isSlotReady(e);
87
+ return isRevealController(e) ? e.U() : isSlotReady(e);
88
88
  }
89
89
 
90
90
  function setSlotState(e, t, r, n) {
91
- setSignal(e.V, r);
92
- setSignal(e.H, n);
91
+ setSignal(e.I, r);
92
+ setSignal(e.D, n);
93
93
  if (isRevealController(e)) {
94
- if (!r && e.J === t) e.J = undefined;
95
- return e.K(r, n);
94
+ if (!r && e.P === t) e.P = undefined;
95
+ return e.j(r, n);
96
96
  }
97
- if (!r && e.X === t && e.Y) e.X = undefined;
97
+ if (!r && e.B === t && e.W) e.B = undefined;
98
98
  }
99
99
 
100
100
  class RevealController {
101
- Z;
102
- ee;
103
- te=[];
104
- J;
105
- V=signal(false, {
101
+ L;
102
+ F;
103
+ q=[];
104
+ P;
105
+ I=signal(false, {
106
106
  ownedWrite: true,
107
- re: true
107
+ V: true
108
108
  });
109
- H=signal(false, {
109
+ D=signal(false, {
110
110
  ownedWrite: true,
111
- re: true
111
+ V: true
112
112
  });
113
- ne=true;
114
- se=true;
115
- ie=false;
113
+ H=true;
114
+ J=true;
115
+ K=false;
116
116
  constructor(e, t) {
117
- this.Z = e;
118
- this.ee = t;
117
+ this.L = e;
118
+ this.F = t;
119
119
  }
120
- oe(e) {
121
- for (let t = 0; t < this.te.length; t++) {
122
- const r = this.te[t];
123
- if ((isRevealController(r) ? r.J : r.X) !== this) continue;
120
+ X(e) {
121
+ for (let t = 0; t < this.q.length; t++) {
122
+ const r = this.q[t];
123
+ if ((isRevealController(r) ? r.P : r.B) !== this) continue;
124
124
  if (e(r) === false) return false;
125
125
  }
126
126
  return true;
127
127
  }
128
- B() {
129
- return this.oe(isSlotReady);
128
+ O() {
129
+ return this.X(isSlotReady);
130
130
  }
131
131
  /**
132
132
  * "Minimally ready" = this group has something visible to show under its own policy.
@@ -134,13 +134,13 @@ class RevealController {
134
134
  * - `together`: every direct slot is minimally ready.
135
135
  * - `sequential`: the first owned slot is minimally ready (frontier can advance).
136
136
  * - `natural`: any owned slot is minimally ready.
137
- */ q() {
138
- const e = untrack(this.Z);
139
- if (e === "together") return this.oe(isSlotMinimallyReady);
137
+ */ U() {
138
+ const e = untrack(this.L);
139
+ if (e === "together") return this.X(isSlotMinimallyReady);
140
140
  if (e === "natural") {
141
141
  let e = false;
142
142
  let t = false;
143
- this.oe(r => {
143
+ this.X(r => {
144
144
  e = true;
145
145
  if (isSlotMinimallyReady(r)) {
146
146
  t = true;
@@ -151,45 +151,45 @@ class RevealController {
151
151
  }
152
152
  // sequential: only the first owned slot matters.
153
153
  let t = true;
154
- this.oe(e => {
154
+ this.X(e => {
155
155
  t = isSlotMinimallyReady(e);
156
156
  return false;
157
157
  });
158
158
  return t;
159
159
  }
160
- le(e) {
161
- if (this.te.includes(e)) return;
162
- this.te.push(e);
163
- const t = untrack(this.Z);
164
- setSignal(e.V, true), setSignal(e.H, t === "sequential" ? !!untrack(this.ee) : false);
165
- untrack(() => this.K());
160
+ Y(e) {
161
+ if (this.q.includes(e)) return;
162
+ this.q.push(e);
163
+ const t = untrack(this.L);
164
+ setSignal(e.I, true), setSignal(e.D, t === "sequential" ? !!untrack(this.F) : false);
165
+ untrack(() => this.j());
166
166
  }
167
- ae(e) {
168
- const t = this.te.indexOf(e);
169
- if (t >= 0) this.te.splice(t, 1);
170
- untrack(() => this.K());
167
+ Z(e) {
168
+ const t = this.q.indexOf(e);
169
+ if (t >= 0) this.q.splice(t, 1);
170
+ untrack(() => this.j());
171
171
  }
172
- K(e, t) {
173
- if (this.ie) return;
174
- this.ie = true;
175
- const r = this.ne;
176
- const n = this.se;
172
+ j(e, t) {
173
+ if (this.K) return;
174
+ this.K = true;
175
+ const r = this.H;
176
+ const n = this.J;
177
177
  try {
178
- const r = e ?? read(this.V), n = untrack(this.Z), s = n === "sequential" && !!untrack(this.ee), i = t ?? s;
178
+ const r = e ?? read(this.I), n = untrack(this.L), s = n === "sequential" && !!untrack(this.F), i = t ?? s;
179
179
  if (r) {
180
180
  // Held by an outer group. Propagate the hold (and whatever collapsed policy
181
181
  // the outer asked for) down the whole subtree. Inner order is ignored while
182
182
  // held; it resumes once the outer releases us.
183
- this.oe(e => setSlotState(e, this, true, i));
183
+ this.X(e => setSlotState(e, this, true, i));
184
184
  } else if (n === "natural") {
185
185
  // Each child reveals based on its own readiness. A nested controller slot
186
186
  // is released to run its own order locally — we bypass setSlotState for it
187
187
  // so the parent backpointer survives for upward readiness notifications.
188
- this.oe(e => {
188
+ this.X(e => {
189
189
  if (isRevealController(e)) {
190
- setSignal(e.H, false);
191
- setSignal(e.V, false);
192
- e.K(false, false);
190
+ setSignal(e.D, false);
191
+ setSignal(e.I, false);
192
+ e.j(false, false);
193
193
  } else {
194
194
  setSlotState(e, this, !isSlotReady(e), false);
195
195
  }
@@ -200,11 +200,11 @@ class RevealController {
200
200
  // sequential's first slot being ready is minimally ready; natural having any
201
201
  // ready child is minimally ready. This lets `together` guarantee a single
202
202
  // cohesive reveal without waiting for every grandchild.
203
- const e = this.oe(isSlotMinimallyReady);
204
- this.oe(t => setSlotState(t, this, !e, false));
203
+ const e = this.X(isSlotMinimallyReady);
204
+ this.X(t => setSlotState(t, this, !e, false));
205
205
  } else {
206
206
  let e = false;
207
- this.oe(t => {
207
+ this.X(t => {
208
208
  if (e) return setSlotState(t, this, true, s);
209
209
  if (isSlotReady(t)) return setSlotState(t, this, false, false);
210
210
  e = true;
@@ -215,63 +215,63 @@ class RevealController {
215
215
  // advancing past this slot, and we bypass setSlotState so the parent
216
216
  // backpointer survives for upward readiness notifications.
217
217
  if (isRevealController(t)) {
218
- setSignal(t.H, false);
219
- setSignal(t.V, false);
220
- t.K(false, false);
218
+ setSignal(t.D, false);
219
+ setSignal(t.I, false);
220
+ t.j(false, false);
221
221
  } else {
222
222
  setSlotState(t, this, true, false);
223
223
  }
224
224
  });
225
225
  }
226
226
  } finally {
227
- this.ne = this.B();
228
- this.se = this.q();
229
- this.ie = false;
227
+ this.H = this.O();
228
+ this.J = this.U();
229
+ this.K = false;
230
230
  }
231
- if (this.J && (r !== this.ne || n !== this.se)) this.J.K();
231
+ if (this.P && (r !== this.H || n !== this.J)) this.P.j();
232
232
  }
233
233
  }
234
234
 
235
235
  class CollectionQueue extends Queue {
236
- ue;
237
- W=new Set;
238
- ce;
239
- L=true;
240
- V=signal(false, {
236
+ $;
237
+ v=new Set;
238
+ ee;
239
+ N=true;
240
+ I=signal(false, {
241
241
  ownedWrite: true,
242
- re: true
242
+ V: true
243
243
  });
244
- k;
245
- H=signal(false, {
244
+ _;
245
+ D=signal(false, {
246
246
  ownedWrite: true,
247
- re: true
247
+ V: true
248
248
  });
249
- X;
250
- Y=false;
251
- fe;
252
- he=ON_INIT;
249
+ B;
250
+ W=false;
251
+ te;
252
+ re=ON_INIT;
253
253
  constructor(e) {
254
254
  super();
255
- this.ue = e;
255
+ this.$ = e;
256
256
  }
257
257
  run(e) {
258
- if (!e || read(this.V) && (!_revealUsed || read(this.H))) return;
258
+ if (!e || read(this.I) && (!_revealUsed || read(this.D))) return;
259
259
  return super.run(e);
260
260
  }
261
261
  notify(e, t, r, n) {
262
- if (!(t & this.ue)) return super.notify(e, t, r, n);
263
- if (this.Y && this.fe) {
262
+ if (!(t & this.$)) return super.notify(e, t, r, n);
263
+ if (this.W && this.te) {
264
264
  const e = untrack(() => {
265
265
  try {
266
- return this.fe();
266
+ return this.te();
267
267
  } catch {
268
268
  return ON_INIT;
269
269
  }
270
270
  });
271
- if (e !== this.he) {
272
- this.he = e;
273
- this.Y = false;
274
- this.W.clear();
271
+ if (e !== this.re) {
272
+ this.re = e;
273
+ this.W = false;
274
+ this.v.clear();
275
275
  }
276
276
  }
277
277
  // Routing is dimension-independent: each boundary consumes only its own
@@ -284,43 +284,47 @@ class CollectionQueue extends Queue {
284
284
  // dimension, while a status already caught by an inner boundary arrives with
285
285
  // its dimension consumed from the mask and is correctly not re-routed
286
286
  // (the Loading > Errored > content composition escape, #2856).
287
- if (this.ue & STATUS_PENDING && this.Y) return super.notify(e, t, r, n);
288
- if (r & this.ue) {
289
- this.L = true;
290
- const t = n?.source || e.k?.source;
287
+ if (this.$ & STATUS_PENDING && this.W) return super.notify(e, t, r, n);
288
+ if (r & this.$) {
289
+ this.N = true;
290
+ const t = n?.source || e._?.source;
291
291
  if (t) {
292
- const e = this.W.size === 0;
293
- this.W.add(t);
294
- if (e) setSignal(this.V, true);
295
- if (this.ue & STATUS_ERROR) {
296
- setSignal(this.k, unwrapStatusError(t.k));
292
+ const e = this.v.size === 0;
293
+ this.v.add(t);
294
+ if (e) setSignal(this.I, true);
295
+ if (this.$ & STATUS_ERROR) {
296
+ setSignal(this._, unwrapStatusError(t._));
297
297
  }
298
298
  }
299
299
  }
300
- t &= ~this.ue;
300
+ t &= ~this.$;
301
301
  return t ? super.notify(e, t, r, n) : true;
302
302
  }
303
- Se() {
304
- for (const e of this.W) {
305
- if (e.u & REACTIVE_DISPOSED || !(e.i & this.ue) && !(this.ue & STATUS_ERROR && e.i & STATUS_PENDING)) this.W.delete(e);
303
+ ne() {
304
+ for (const e of this.v) {
305
+ // A source with a live affects() mark holds display state for the
306
+ // mark's lifetime (the visual channel): the marked node carries no
307
+ // status of its own, so the count is the liveness test. The release
308
+ // sweep (finalizePureQueue after mark release) re-runs this check.
309
+ if (e.se & REACTIVE_DISPOSED || !e.t && !(e.S & this.$) && !(this.$ & STATUS_ERROR && e.S & STATUS_PENDING)) this.v.delete(e);
306
310
  }
307
- if (!this.W.size) {
308
- if (this.ue & STATUS_PENDING && this.L && !this.Y && this.ce) {
309
- this.L = !!(this.ce.i & this.ue);
311
+ if (!this.v.size) {
312
+ if (this.$ & STATUS_PENDING && this.N && !this.W && this.ee) {
313
+ this.N = !!(this.ee.S & this.$);
310
314
  } else {
311
- this.L = false;
315
+ this.N = false;
312
316
  }
313
- if (!this.L) {
314
- setSignal(this.V, false);
315
- if (this.fe) {
317
+ if (!this.N) {
318
+ setSignal(this.I, false);
319
+ if (this.te) {
316
320
  try {
317
- this.he = untrack(() => this.fe());
321
+ this.re = untrack(() => this.te());
318
322
  } catch {
319
323
  /* value not yet committed — _prevOn stays stale, next notify will reset */}
320
324
  }
321
325
  }
322
326
  }
323
- if (_revealUsed) this.X?.K();
327
+ if (_revealUsed) this.B?.j();
324
328
  }
325
329
  }
326
330
 
@@ -328,12 +332,12 @@ function createCollectionBoundary(e, t, r, n) {
328
332
  const s = createOwner();
329
333
  if (_revealUsed) setContext(RevealControllerContext, null, s);
330
334
  const i = new CollectionQueue(e);
331
- if (e === STATUS_ERROR) i.k = signal(undefined, {
335
+ if (e === STATUS_ERROR) i._ = signal(undefined, {
332
336
  ownedWrite: true,
333
- re: true
337
+ V: true
334
338
  });
335
- if (n) i.fe = n;
336
- const o = i.ce = createBoundChildren(s, t, i, e);
339
+ if (n) i.te = n;
340
+ const o = i.ee = createBoundChildren(s, t, i, e);
337
341
  // Prime source tracking so reveal registration sees pending sources.
338
342
  untrack(() => {
339
343
  let t = false;
@@ -342,23 +346,23 @@ function createCollectionBoundary(e, t, r, n) {
342
346
  } catch (e) {
343
347
  if (e instanceof NotReadyError) t = true; else throw e;
344
348
  }
345
- i.L = t || !!(o.i & e) || o.k instanceof NotReadyError;
349
+ i.N = t || !!(o.S & e) || o._ instanceof NotReadyError;
346
350
  });
347
351
  const l = _revealUsed && e === STATUS_PENDING ? getContext(RevealControllerContext) : null;
348
352
  if (l) {
349
- i.X = l;
350
- l.le(i);
351
- cleanup(() => l.ae(i));
353
+ i.B = l;
354
+ l.Y(i);
355
+ cleanup(() => l.Z(i));
352
356
  }
353
357
  return accessor(computed(() => {
354
- if (!read(i.V)) {
358
+ if (!read(i.I)) {
355
359
  const e = read(o);
356
- if (!untrack(() => read(i.V))) return i.Y = true, e;
360
+ if (!untrack(() => read(i.I))) return i.W = true, e;
357
361
  }
358
362
  // Collapsed reveal slots suppress their own output entirely; the
359
363
  // renderer treats the hole as empty, so the cast never leaks to users
360
364
  // outside a `createRevealOrder` scope.
361
- if (_revealUsed && read(i.H)) return undefined;
365
+ if (_revealUsed && read(i.D)) return undefined;
362
366
  return r(i);
363
367
  },
364
368
  // Boundary structure, not a user source: its value is fallback-or-content and
@@ -366,7 +370,7 @@ function createCollectionBoundary(e, t, r, n) {
366
370
  // by snapshot capture. The tree no longer carries foreign status flags, so
367
371
  // capture can't rely on PENDING to skip this node the way it used to.
368
372
  {
369
- re: true
373
+ V: true
370
374
  }));
371
375
  }
372
376
 
@@ -419,8 +423,8 @@ function createCollectionBoundary(e, t, r, n) {
419
423
  * }
420
424
  * ```
421
425
  */ function createErrorBoundary(e, t) {
422
- return createCollectionBoundary(STATUS_ERROR, e, e => t(accessor(e.k), () => {
423
- for (const t of e.W) recompute(t);
426
+ return createCollectionBoundary(STATUS_ERROR, e, e => t(accessor(e._), () => {
427
+ for (const t of e.v) recompute(t);
424
428
  schedule();
425
429
  }));
426
430
  }
@@ -477,12 +481,12 @@ function createCollectionBoundary(e, t, r, n) {
477
481
  computed(() => {
478
482
  s();
479
483
  i();
480
- o.K();
484
+ o.j();
481
485
  });
482
486
  if (n) {
483
- o.J = n;
484
- n.le(o);
485
- cleanup(() => n.ae(o));
487
+ o.P = n;
488
+ n.Y(o);
489
+ cleanup(() => n.Z(o));
486
490
  }
487
491
  return t;
488
492
  });
@@ -4,14 +4,30 @@ import { isThenable } from "./async.js";
4
4
 
5
5
  import "./core.js";
6
6
 
7
- function restoreTransition(e, n) {
7
+ function restoreTransition(e, r) {
8
8
  globalQueue.initTransition(e);
9
- const t = n();
9
+ const t = r();
10
10
  flush();
11
11
  return t;
12
12
  }
13
13
 
14
14
  /**
15
+ * The primitive for mutations: imperative async workflows whose *writes span
16
+ * an async gap* — optimistic write, server round-trip, reconciling write —
17
+ * where intermediate state must not leak and failure must revert cleanly
18
+ * (pair with `createOptimistic` / `createOptimisticStore`).
19
+ *
20
+ * Navigation-shaped updates do not need an action. A plain setter call is
21
+ * enough: reads pull the async, and downstream async computeds hold their
22
+ * previous values per-node until the new ones are ready (`isPending` /
23
+ * `latest` expose the in-flight state). Reach for `action` only when writes
24
+ * happen *after* async work, not merely upstream of it.
25
+ *
26
+ * Framework-level actions (router form actions, server actions) are
27
+ * specializations of this primitive: they are actions in exactly this sense —
28
+ * the same transactional semantics — with form binding, serialization, and
29
+ * submission tracking layered on top. The shared name is deliberate.
30
+ *
15
31
  * Wraps a generator function so each invocation runs as a single transaction
16
32
  * (a "transition") that batches every signal/store write between yields. The
17
33
  * surrounding UI sees one atomic update per yielded step; nothing is committed
@@ -58,23 +74,23 @@ function restoreTransition(e, n) {
58
74
  * await addTodo("buy milk");
59
75
  * ```
60
76
  */ function action(e) {
61
- return (...n) => new Promise((t, r) => {
62
- const i = e(...n);
77
+ return (...r) => new Promise((t, n) => {
78
+ const i = e(...r);
63
79
  globalQueue.initTransition();
64
80
  let o = activeTransition;
65
- o.Te.push(i);
66
- const done = (e, n, s = false) => {
81
+ o.ie.push(i);
82
+ const done = (e, r, s = false) => {
67
83
  o = currentTransition(o);
68
- const u = o.Te.indexOf(i);
69
- if (u >= 0) o.Te.splice(u, 1);
84
+ const u = o.ie.indexOf(i);
85
+ if (u >= 0) o.ie.splice(u, 1);
70
86
  setActiveTransition(o);
71
87
  schedule();
72
- s ? r(n) : t(e);
88
+ s ? n(r) : t(e);
73
89
  };
74
- const step = (e, n) => {
90
+ const step = (e, r) => {
75
91
  let t;
76
92
  try {
77
- t = n ? i.throw(e) : i.next(e);
93
+ t = r ? i.throw(e) : i.next(e);
78
94
  } catch (e) {
79
95
  return done(undefined, e, true);
80
96
  }
@@ -86,7 +102,29 @@ function restoreTransition(e, n) {
86
102
  };
87
103
  const run = e => {
88
104
  if (e.done) return done(e.value);
89
- if (isThenable(e.value)) return void e.value.then(e => restoreTransition(o, () => step(e)), e => restoreTransition(o, () => step(e, true)));
105
+ // Thenable assimilation can itself throw synchronously (a `then`
106
+ // getter, or a `then()` method that throws — #2918). Match `await`
107
+ // semantics: the failure is thrown back into the generator at the
108
+ // yield point (catchable there); if uncaught, step()'s guard settles
109
+ // the action so its iterator never leaks in the transition. The
110
+ // settled flag implements A+ 2.3.3.3.4.1: a throw after the thenable
111
+ // already called a callback is ignored.
112
+ let r = false;
113
+ try {
114
+ if (isThenable(e.value)) return void e.value.then(e => {
115
+ if (r) return;
116
+ r = true;
117
+ restoreTransition(o, () => step(e));
118
+ }, e => {
119
+ if (r) return;
120
+ r = true;
121
+ restoreTransition(o, () => step(e, true));
122
+ });
123
+ } catch (e) {
124
+ if (r) return;
125
+ r = true;
126
+ return void restoreTransition(o, () => step(e, true));
127
+ }
90
128
  restoreTransition(o, () => step(e.value));
91
129
  };
92
130
  step();