@solidjs/signals 2.0.0-beta.32 → 2.0.0-beta.34

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.
@@ -38,6 +38,22 @@ function clearPendingSources(e) {
38
38
  e.oe = undefined;
39
39
  }
40
40
 
41
+ /**
42
+ * A loading-window node hit an unready source (sync throw in recompute, or a
43
+ * NotReadyError-rejected flight): register for the source's settle — the
44
+ * settlePendingSource walk runs off `_pendingSources` + `_blocked` alone —
45
+ * with NO read-visible pending status, no downstream propagation, no
46
+ * transition, no lane registration. Commit #0 keeps serving.
47
+ */ function parkLoadingWindow(e, n) {
48
+ e.ue = true;
49
+ if (n.source) addPendingSource(e, n.source);
50
+ // A settled error is the node's answer ("the error stays the answer until
51
+ // this retry can actually run") — the park must not replace it: reads
52
+ // throw `_error` while STATUS_ERROR is set, and overwriting it here leaks
53
+ // a pending-class NotReadyError from a read-invisible park (#2989).
54
+ if (!(e.S & STATUS_ERROR)) setPendingError(e, n.source, n);
55
+ }
56
+
41
57
  function setPendingError(e, n, t) {
42
58
  if (!n) {
43
59
  e._ = null;
@@ -54,10 +70,10 @@ function setPendingError(e, n, t) {
54
70
  }
55
71
 
56
72
  function forEachDependent(e, n) {
57
- for (let t = e.o; t !== null; t = t.ue) n(t.le, t);
73
+ for (let t = e.o; t !== null; t = t.le) n(t.fe, t);
58
74
  // `?? null`: affects() marks route plain signals (no `_child` slot) through here.
59
- for (let t = e.u ?? null; t !== null; t = t.fe) {
60
- for (let e = t.o; e !== null; e = e.ue) n(e.le, e);
75
+ for (let t = e.u ?? null; t !== null; t = t.ae) {
76
+ for (let e = t.o; e !== null; e = e.le) n(e.fe, e);
61
77
  }
62
78
  }
63
79
 
@@ -72,7 +88,7 @@ function forEachDependent(e, n) {
72
88
  // callbacks handle their own release (settleAutodispose in handleAsync); this
73
89
  // covers derivatively-pending dependents, which have no callbacks of their own.
74
90
  function releaseIfSettledUnobserved(e) {
75
- e.ae && e.T & CONFIG_AUTO_DISPOSE && !e.o && !(e.se & REACTIVE_ZOMBIE) && !(e.S & STATUS_PENDING) && unobserved(e);
91
+ e.ce && e.T & CONFIG_AUTO_DISPOSE && !e.o && !(e.se & REACTIVE_ZOMBIE) && !(e.S & STATUS_PENDING) && unobserved(e);
76
92
  }
77
93
 
78
94
  // Error-path sweep: notifyStatus(STATUS_ERROR) clears dependents' pending
@@ -128,24 +144,30 @@ function settlePendingSource(e) {
128
144
  let t;
129
145
  const r = new Set;
130
146
  // Companion updates no-op without the verdict layer (null hook).
131
- const o = GlobalQueue.ce;
147
+ const o = GlobalQueue.Se;
132
148
  const settle = u => {
133
149
  if (r.has(u) || !removePendingSource(u, e)) return;
134
150
  r.add(u);
135
- u.Se = clock;
136
- const s = u.oe?.values().next().value;
137
- if (s) {
138
- setPendingError(u, s);
151
+ u.de = clock;
152
+ const i = u.oe?.values().next().value;
153
+ // STATUS_ERROR + pending sources only coexist via an errored loading
154
+ // window's park (notifyStatus(STATUS_ERROR) clears pending sources
155
+ // otherwise): the settled error stays the answer through the settle —
156
+ // nulling it here would have reads throw `null` until the re-enqueued
157
+ // retry lands, or lose it entirely if that retry parks again (#2989).
158
+ const l = u.S & STATUS_ERROR;
159
+ if (i) {
160
+ if (!l) setPendingError(u, i);
139
161
  o !== null && o(u);
140
162
  } else {
141
163
  u.S &= ~STATUS_PENDING;
142
- setPendingError(u);
164
+ if (!l) setPendingError(u);
143
165
  o !== null && o(u);
144
- if (u.de) {
166
+ if (u.ue) {
145
167
  enqueueSub(u);
146
168
  n = true;
147
169
  }
148
- u.de = false;
170
+ u.ue = false;
149
171
  // Fully settled with nobody watching: release candidate (#2934). Checked
150
172
  // again at release time — deferred so unobserved() can't unlink subs
151
173
  // lists this walk is still iterating.
@@ -176,6 +198,8 @@ function handleAsync(e, n, t) {
176
198
  }
177
199
  if (!o && !r) {
178
200
  e.Te = null;
201
+ // A sync landing is the first real answer for a loadingValue node.
202
+ e.Ee = false;
179
203
  return n;
180
204
  }
181
205
  e.Te = n;
@@ -192,21 +216,31 @@ function handleAsync(e, n, t) {
192
216
  // are the transaction's reveal machinery and always re-enter.
193
217
  const settleTransition = () => {
194
218
  const n = resolveTransition(e);
195
- if (n && e.S & STATUS_UNINITIALIZED && !currentTransition(n).Ee.has(e)) {
219
+ if (n && e.S & STATUS_UNINITIALIZED && !currentTransition(n).Ie.has(e)) {
196
220
  // Drop the stale stamp too: the plain settle write (setSignal) and the
197
221
  // stash-path restamp both re-enter the transaction through it.
198
- e.Ie = null;
222
+ e.Ne = null;
199
223
  return;
200
224
  }
201
225
  globalQueue.initTransition(n);
202
226
  };
203
227
  const handleError = t => {
204
228
  if (e.Te !== n) return;
205
- settleTransition();
206
229
  // NotReadyError from rejected promises should be treated as pending, not error
207
- const r = t instanceof NotReadyError;
230
+ let r = t instanceof NotReadyError;
231
+ if (r && e.Ee) {
232
+ // Loading window: the flight died waiting on an unready source. Keep
233
+ // serving commit #0 — same parking as recompute's catch for sync
234
+ // dependency throws. The dead flight is released so the clock-gated
235
+ // error-retry pull (updateIfNecessary) can also re-ask.
236
+ e.Te = null;
237
+ parkLoadingWindow(e, t);
238
+ e.de = clock;
239
+ return;
240
+ }
241
+ settleTransition();
208
242
  notifyStatus(e, r ? STATUS_PENDING : STATUS_ERROR, t);
209
- e.Se = clock;
243
+ e.de = clock;
210
244
  // A real error settles derivatively-pending dependents (notifyStatus
211
245
  // cleared their pending sources), so stranded lazy ones release here —
212
246
  // the error twin of settlePendingSource's release (#2934).
@@ -222,12 +256,12 @@ function handleAsync(e, n, t) {
222
256
  const u = !!(e.S & STATUS_UNINITIALIZED);
223
257
  trimStaleDeps(e);
224
258
  clearStatus(e);
225
- const s = resolveLane(e);
226
- if (s) s.Ne.delete(e);
259
+ const i = resolveLane(e);
260
+ if (i) i.Ae.delete(e);
227
261
  if (t) {
228
262
  t(r);
229
263
  if (u) clearStatus(e, true);
230
- } else if (e.Ae !== undefined) {
264
+ } else if (e._e !== undefined) {
231
265
  // Optimistic node — resting OR covered by an active override — holds
232
266
  // through the shared pending-node path, exactly like a plain async memo,
233
267
  // so the commit clears STATUS_UNINITIALIZED (#2806) and elevation to
@@ -246,22 +280,22 @@ function handleAsync(e, n, t) {
246
280
  // only notified when the hold is visible to them: under an active
247
281
  // override every reader sees the override (A17), so waking subs would
248
282
  // re-show an unchanged view — the revert is the notification point.
249
- GlobalQueue._e !== null && GlobalQueue._e(e, r);
283
+ GlobalQueue.Pe !== null && GlobalQueue.Pe(e, r);
250
284
  if (!hasActiveOverride(e)) insertSubs(e);
251
- e.Se = clock;
252
- } else if (s) {
285
+ e.de = clock;
286
+ } else if (i) {
253
287
  // Route through lane's effect queue for independent flushing
254
- const n = e.Pe;
255
- const t = e.be;
256
- const o = e.he;
288
+ const n = e.Re;
289
+ const t = e.Ue;
290
+ const o = e.be;
257
291
  try {
258
292
  if (!n && u || !o || !o(r, t)) {
259
- e.be = r;
260
- e.Se = clock;
293
+ e.Ue = r;
294
+ e.de = clock;
261
295
  // The latest() shadow write gives latest() effects independent lanes; the
262
296
  // _pendingSignal update is a no-op repeat of the clearStatus() call above
263
297
  // (computePendingState doesn't read _value).
264
- GlobalQueue._e !== null && GlobalQueue._e(e, r);
298
+ GlobalQueue.Pe !== null && GlobalQueue.Pe(e, r);
265
299
  insertSubs(e, true);
266
300
  }
267
301
  } catch (n) {
@@ -280,6 +314,13 @@ function handleAsync(e, n, t) {
280
314
  notifyStatus(e, STATUS_ERROR, n);
281
315
  }
282
316
  }
317
+ // First real answer landing: the window closes when the answer becomes
318
+ // OBSERVABLE. A direct commit is observable now; a transition-held write
319
+ // (`_pendingValue` set above or inside setSignal) is not — the verdict's
320
+ // held-value branch is window-gated, and commitPendingNode closes the
321
+ // window when the hold commits, so no one-frame isPending pulse can leak
322
+ // to live observers between the landing and its commit (#2990).
323
+ if (e.De === NOT_PENDING) e.Ee = false;
283
324
  settlePendingSource(e);
284
325
  schedule();
285
326
  flush();
@@ -300,9 +341,9 @@ function handleAsync(e, n, t) {
300
341
  return false;
301
342
  };
302
343
  if (o) {
303
- let t = false, r = false, o, s = true;
344
+ let t = false, r = false, o, i = true;
304
345
  n.then(e => {
305
- if (s) {
346
+ if (i) {
306
347
  u = e;
307
348
  t = true;
308
349
  } else {
@@ -310,7 +351,7 @@ function handleAsync(e, n, t) {
310
351
  settleAutodispose();
311
352
  }
312
353
  }, e => {
313
- if (s) {
354
+ if (i) {
314
355
  o = e;
315
356
  r = true;
316
357
  } else {
@@ -318,7 +359,7 @@ function handleAsync(e, n, t) {
318
359
  settleAutodispose();
319
360
  }
320
361
  });
321
- s = false;
362
+ i = false;
322
363
  if (r) {
323
364
  // Settle through the same status path an async rejection uses, then
324
365
  // unwind the in-progress synchronous read so the errored node isn't
@@ -326,15 +367,23 @@ function handleAsync(e, n, t) {
326
367
  handleError(o);
327
368
  throw o;
328
369
  } else if (!t) {
370
+ // Loading window: serve commit #0 instead of suspending. No transition
371
+ // is opened — first-flight work on a loadingValue node is loading-class
372
+ // (invisible to boundaries and transitions); the flight itself is
373
+ // already registered in _inFlight and lands through asyncWrite.
374
+ if (e.Ee) return e.Ue;
329
375
  globalQueue.initTransition(resolveTransition(e));
330
376
  throw new NotReadyError(context);
377
+ } else {
378
+ // Synchronously-resolved promise: the first real answer landed.
379
+ e.Ee = false;
331
380
  }
332
381
  }
333
382
  if (r) {
334
383
  const t = n[Symbol.asyncIterator]();
335
384
  let r = false;
336
385
  let o = false;
337
- let s = true;
386
+ let i = true;
338
387
  cleanup(() => {
339
388
  if (o) return;
340
389
  o = true;
@@ -350,7 +399,7 @@ function handleAsync(e, n, t) {
350
399
  if (!settleAutodispose()) iterate();
351
400
  };
352
401
  const iterate = () => {
353
- let l, i, f = false, a = false, c = true;
402
+ let l, s, f = false, a = false, c = true;
354
403
  // Protocol tolerance, matching `for await`: `await` unwraps whatever
355
404
  // next() returns — a thenable OR a bare IteratorResult. Real producers
356
405
  // use the bare form as a promise-free fast path when a value is already
@@ -367,7 +416,7 @@ function handleAsync(e, n, t) {
367
416
  // sync-thenable producer mid-stream — has no caller reading the
368
417
  // stash: it must write through the async path or the value is
369
418
  // silently dropped.
370
- if (c && s) {
419
+ if (c && i) {
371
420
  l = t;
372
421
  f = true;
373
422
  if (t.done) o = true;
@@ -389,7 +438,7 @@ function handleAsync(e, n, t) {
389
438
  }
390
439
  }, t => {
391
440
  if (c) {
392
- i = t;
441
+ s = t;
393
442
  a = true;
394
443
  } else if (e.Te === n) {
395
444
  o = true;
@@ -401,8 +450,8 @@ function handleAsync(e, n, t) {
401
450
  if (a) {
402
451
  // Match the promise branch, but only rethrow during the initial read.
403
452
  o = true;
404
- handleError(i);
405
- if (s) throw i;
453
+ handleError(s);
454
+ if (i) throw s;
406
455
  return true;
407
456
  }
408
457
  if (f && !l.done) {
@@ -414,28 +463,33 @@ function handleAsync(e, n, t) {
414
463
  };
415
464
  const l = iterate();
416
465
  // Later iterate() calls run from asyncWrite, where rethrowing would be unhandled.
417
- s = false;
466
+ i = false;
418
467
  if (!r && !l) {
468
+ // Loading window: serve commit #0 (see the promise branch above).
469
+ if (e.Ee) return e.Ue;
419
470
  globalQueue.initTransition(resolveTransition(e));
420
471
  throw new NotReadyError(context);
421
472
  }
473
+ // A sync first yield (or immediate empty completion) is the first real
474
+ // answer; async yields clear inside asyncWrite.
475
+ e.Ee = false;
422
476
  }
423
477
  return u;
424
478
  }
425
479
 
426
480
  function clearStatus(e, n = false) {
427
481
  if (e.oe) clearPendingSources(e);
428
- if (e.de) e.de = false;
482
+ if (e.ue) e.ue = false;
429
483
  // The pending window is over; its quiet classification dies with it.
430
484
  // (Unconditional: _reask is baked into the node literals, so this is a
431
485
  // plain store to an existing slot — no shape change.)
432
- e.Ue = false;
486
+ e.he = false;
433
487
  e.S = n ? 0 : e.S & STATUS_UNINITIALIZED;
434
488
  if (e._) setPendingError(e);
435
489
  // Update pending signal for isPending() reactivity (companions only exist
436
490
  // once the verdict layer created them, which installs the hooks).
437
- if (e.Re || e.pe) GlobalQueue.ce(e);
438
- if (e.u && GlobalQueue.Ge !== null) GlobalQueue.Ge(e);
491
+ if (e.ge || e.pe) GlobalQueue.Se(e);
492
+ if (e.u && GlobalQueue.Oe !== null) GlobalQueue.Oe(e);
439
493
  if (e.i) e.i();
440
494
  }
441
495
 
@@ -443,9 +497,9 @@ function notifyStatus(e, n, t, r, o) {
443
497
  // Wrap regular errors to track source node
444
498
  if (n === STATUS_ERROR && !(t instanceof StatusError) && !(t instanceof NotReadyError)) t = new StatusError(e, t);
445
499
  const u = n === STATUS_PENDING && t instanceof NotReadyError ? t.source : undefined;
446
- const s = u === e;
447
- const l = n === STATUS_PENDING && e.Ae !== undefined && !s;
448
- const i = l && hasActiveOverride(e);
500
+ const i = u === e;
501
+ const l = n === STATUS_PENDING && e._e !== undefined && !i;
502
+ const s = l && hasActiveOverride(e);
449
503
  if (!r) {
450
504
  if (n === STATUS_PENDING && u) {
451
505
  addPendingSource(e, u);
@@ -458,13 +512,13 @@ function notifyStatus(e, n, t, r, o) {
458
512
  e.S = n | (n !== STATUS_ERROR ? e.S & STATUS_UNINITIALIZED : 0);
459
513
  e._ = t;
460
514
  }
461
- GlobalQueue.ce !== null && GlobalQueue.ce(e);
462
- if (e.u && GlobalQueue.Ge !== null) GlobalQueue.Ge(e);
515
+ GlobalQueue.Se !== null && GlobalQueue.Se(e);
516
+ if (e.u && GlobalQueue.Oe !== null) GlobalQueue.Oe(e);
463
517
  }
464
518
  if (o && !r) {
465
519
  assignOrMergeLane(e, o);
466
520
  }
467
- const f = r || i;
521
+ const f = r || s;
468
522
  const a = r || l ? undefined : o;
469
523
  if (e.i) {
470
524
  if (r && n === STATUS_PENDING) {
@@ -478,22 +532,22 @@ function notifyStatus(e, n, t, r, o) {
478
532
  return;
479
533
  }
480
534
  forEachDependent(e, (e, r) => {
481
- e.Se = clock;
535
+ e.de = clock;
482
536
  if (n === STATUS_PENDING && u && !e.oe?.has(u) || n !== STATUS_PENDING && (e._ !== t || e.oe)) {
483
537
  // A pending-observer link is the subscription an `isPending` read created.
484
538
  // It exists so the observer re-runs when the source settles, but it must
485
539
  // not carry a real (non-NotReadyError) error — the synchronous `isPending`
486
540
  // read swallows those, and the async path must match. Re-run the observer
487
541
  // so `isPending` re-evaluates (to not-pending) instead of forwarding.
488
- if (r.Oe && n !== STATUS_PENDING && !(t instanceof NotReadyError)) {
542
+ if (r.Ge && n !== STATUS_PENDING && !(t instanceof NotReadyError)) {
489
543
  enqueueSub(e);
490
544
  schedule();
491
545
  return;
492
546
  }
493
- if (!f && !e.Ie) queuePendingNode(e);
547
+ if (!f && !e.Ne) queuePendingNode(e);
494
548
  notifyStatus(e, n, t, f, a);
495
549
  }
496
550
  });
497
551
  }
498
552
 
499
- export { addPendingSource, clearStatus, forEachDependent, handleAsync, isThenable, notifyStatus, releaseSettledDependents, setPendingError, settleErroredDependents, settlePendingSource };
553
+ export { addPendingSource, clearStatus, forEachDependent, handleAsync, isThenable, notifyStatus, parkLoadingWindow, releaseSettledDependents, setPendingError, settleErroredDependents, settlePendingSource };