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

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.
package/dist/dev.js CHANGED
@@ -916,6 +916,12 @@ class GlobalQueue extends Queue {
916
916
  }
917
917
  if (batch._affectsNodes.length) activeTransition._affectsNodes.push(...batch._affectsNodes);
918
918
  for (const store of batch._optimisticStores) activeTransition._optimisticStores.add(store);
919
+ // Gated readers recorded against the ambient batch move with it: their
920
+ // replay-at-commit now happens at the transaction's completion.
921
+ if (batch._gatedSubs.size) {
922
+ for (const sub of batch._gatedSubs) activeTransition._gatedSubs.add(sub);
923
+ batch._gatedSubs.clear();
924
+ }
919
925
  currentBatch = this._batch = activeTransition;
920
926
  }
921
927
  for (const lane of activeLanes) {
@@ -1003,13 +1009,19 @@ function finalizePureQueue(completingTransition = null, incomplete = false) {
1003
1009
  // which installed the engine's hooks.
1004
1010
  if (batch._optimisticNodes.length) GlobalQueue._resolveOptimistic(batch._optimisticNodes);
1005
1011
  // Replay entanglement: subs recorded by the read-time gate get rescheduled
1006
- // so they re-run with the now-committed values visible.
1007
- if (completingTransition && completingTransition._gatedSubs.size) {
1008
- for (const sub of completingTransition._gatedSubs) {
1012
+ // so they re-run with the now-committed values visible. The ambient batch
1013
+ // replays too laneReadsCommitted records readers whose committed-view
1014
+ // read hid a same-tick plain write that just committed above (#2963).
1015
+ if (batch._gatedSubs.size) {
1016
+ for (const sub of batch._gatedSubs) {
1009
1017
  if (sub._flags & REACTIVE_DISPOSED) continue;
1010
1018
  enqueueSub(sub);
1011
1019
  }
1012
- completingTransition._gatedSubs.clear();
1020
+ batch._gatedSubs.clear();
1021
+ // A completing transition keeps the outer flush loop alive by itself;
1022
+ // the ambient batch needs the re-arm or the replay sits in the heap
1023
+ // until the next unrelated write.
1024
+ schedule();
1013
1025
  }
1014
1026
  // Declared motion ends with the transaction: settle (or plain flush end
1015
1027
  // for ambient marks) releases each registration's refcount. A non-empty
@@ -2071,9 +2083,22 @@ function handleAsync(el, result, setter) {
2071
2083
  resolved = false,
2072
2084
  rejected = false,
2073
2085
  isSync = true;
2074
- it.next().then(
2086
+ // Protocol tolerance, matching `for await`: `await` unwraps whatever
2087
+ // next() returns — a thenable OR a bare IteratorResult. Real producers
2088
+ // use the bare form as a promise-free fast path when a value is already
2089
+ // buffered (seroval's deserialized streams do), so a bare result is
2090
+ // assimilated as an already-settled step instead of crashing on `.then`.
2091
+ const step = it.next();
2092
+ const settled = isThenable(step) ? step : { then: onSettle => void onSettle(step) };
2093
+ settled.then(
2075
2094
  r => {
2076
- if (isSync) {
2095
+ // The sync stash only serves the INITIAL drain (handleAsync's caller
2096
+ // consumes syncValue / throws NotReady from it). A sync-settled step
2097
+ // after an async gap — seroval buffering values between pulls, a
2098
+ // sync-thenable producer mid-stream — has no caller reading the
2099
+ // stash: it must write through the async path or the value is
2100
+ // silently dropped.
2101
+ if (isSync && initialRead) {
2077
2102
  syncResult = r;
2078
2103
  resolved = true;
2079
2104
  if (r.done) completed = true;
@@ -3503,12 +3528,25 @@ function gatedRead(el, owner, c) {
3503
3528
  * optimistic/lane-assigned signals, stale-mode reads, and pending owners.
3504
3529
  */
3505
3530
  function laneReadsCommitted(el, owner, c) {
3506
- return (
3531
+ if (
3507
3532
  el._overrideValue !== undefined ||
3508
3533
  !!el._optimisticLane ||
3509
- (owner === el && stale && c._parentSource !== el) ||
3510
3534
  !!(owner._statusFlags & STATUS_PENDING)
3511
- );
3535
+ )
3536
+ return true;
3537
+ if (owner === el && stale && c._parentSource !== el) {
3538
+ // The committed view can hide a same-tick ambient write (a lane member —
3539
+ // even just an isPending companion flip — puts the reader "under a lane"
3540
+ // against unrelated plain writes). With no transaction the write commits
3541
+ // at THIS flush's end with no re-delivery, so record the reader for
3542
+ // replay at commit — the same contract gatedRead provides when a
3543
+ // transaction is active (#2963). If a transaction forms mid-flush,
3544
+ // initTransition carries the recording over to it.
3545
+ if (el._pendingValue !== NOT_PENDING && activeTransition === null)
3546
+ globalQueue._batch._gatedSubs.add(c);
3547
+ return true;
3548
+ }
3549
+ return false;
3512
3550
  }
3513
3551
  /**
3514
3552
  * recompute()'s lane posture: resolve the node's own lane (own=true), or adopt