@solidjs/signals 2.0.0-rc.2 → 2.0.0-rc.3

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
@@ -236,7 +236,7 @@ function captureStack() {
236
236
  // remaining frames are the user code that performed the write.
237
237
  return raw
238
238
  .slice(1)
239
- .filter(line => !/solid-signals[/\\](src|dist)[/\\]/.test(line))
239
+ .filter(line => !/(?:^|[/\\])(?:packages[/\\])?signals[/\\](src|dist)[/\\]/.test(line))
240
240
  .slice(0, 3)
241
241
  .map(line => line.trim());
242
242
  }
@@ -4507,7 +4507,16 @@ function optimisticWrite(el, v) {
4507
4507
  const currentValue = hasOverride ? unwrapOverride(el._x?._overrideValue) : el._value;
4508
4508
  if (typeof v === "function") v = v(currentValue);
4509
4509
  const valueChanged =
4510
- !!(el._statusFlags & STATUS_UNINITIALIZED) || !el._equals || !el._equals(currentValue, v);
4510
+ !!(el._statusFlags & STATUS_UNINITIALIZED) ||
4511
+ // A dirty node's _value is stale (its queued recompute hasn't run — e.g.
4512
+ // a latest() shadow marked by the previous landing's companion snap), so
4513
+ // equality against it must not swallow the write. Without this, a sync
4514
+ // push returning the shadow to that stale value was dropped, the snap
4515
+ // recompute then committed the parent's old value, and the banner showed
4516
+ // the previous transition's target (#3041 follow-up).
4517
+ !!((el._flags ?? 0) & (REACTIVE_DIRTY | REACTIVE_CHECK)) ||
4518
+ !el._equals ||
4519
+ !el._equals(currentValue, v);
4511
4520
  if (!valueChanged) {
4512
4521
  // Same-value write with an active override still entangles the current
4513
4522
  // action's transition — the hold must outlast all overlapping actions.
@@ -4663,18 +4672,33 @@ function laneReadsCommitted(el, owner, c) {
4663
4672
  el._x?._overrideValue !== undefined ||
4664
4673
  !!el._x?._optimisticLane ||
4665
4674
  !!(owner._statusFlags & STATUS_PENDING)
4666
- )
4675
+ ) {
4676
+ // The committed view hides a staged in-flight value that will promote
4677
+ // silently (commitPendingNode never re-notifies). gatedRead records plain
4678
+ // signals for replay at commit; async memos are excluded from it by the
4679
+ // `_fn` check and reach here instead — a lane-assigned source whose async
4680
+ // already settled (laneAsyncSettled keeps _optimisticLane) served its
4681
+ // committed value to a reader that never re-ran after the landing, so a
4682
+ // pending-gated branch stayed one value behind permanently (#3041
4683
+ // follow-up). Record the reader under the same replay contract.
4684
+ if (el._pendingValue !== NOT_PENDING)
4685
+ (activeTransition ?? globalQueue._batch)._gatedSubs.add(c);
4667
4686
  return true;
4687
+ }
4668
4688
  if (owner === el && stale && c._x?._parentSource !== el) {
4669
- // The committed view can hide a same-tick ambient write (a lane member —
4670
- // even just an isPending companion flip — puts the reader "under a lane"
4671
- // against unrelated plain writes). With no transaction the write commits
4672
- // at THIS flush's end with no re-delivery, so record the reader for
4673
- // replay at commit the same contract gatedRead provides when a
4674
- // transaction is active (#2963). If a transaction forms mid-flush,
4675
- // initTransition carries the recording over to it.
4676
- if (el._pendingValue !== NOT_PENDING && activeTransition === null)
4677
- globalQueue._batch._gatedSubs.add(c);
4689
+ // The committed view can hide a staged write (a lane member — even just
4690
+ // an isPending companion flip — puts the reader "under a lane"). The
4691
+ // staged value commits with no re-delivery (commitPendingNode never
4692
+ // re-notifies), so record the reader for replay at commit — the same
4693
+ // contract gatedRead provides (#2963). gatedRead itself only covers
4694
+ // signal reads where the reading computed differs from the source; the
4695
+ // owner === el memo/self read lands here instead. With a transaction
4696
+ // active the staged value promotes silently at ITS landing, so record
4697
+ // into the transaction (#3041 follow-up: a pending-gated branch that
4698
+ // first read its async source during the landing flush stayed one value
4699
+ // behind permanently); with none, into the ambient batch.
4700
+ if (el._pendingValue !== NOT_PENDING)
4701
+ (activeTransition ?? globalQueue._batch)._gatedSubs.add(c);
4678
4702
  return true;
4679
4703
  }
4680
4704
  return false;
@@ -5030,6 +5054,13 @@ function snapCompanionsToState(owner) {
5030
5054
  }
5031
5055
  function getLatestValueComputed(el) {
5032
5056
  let lvc = el._x?._latestValueComputed;
5057
+ // A shadow disposed while unobserved (its gated reader unmounted at a
5058
+ // landing) is a corpse: sync writes into it equality-swallow against its
5059
+ // frozen _value, and a later read revives it via recompute — clearing
5060
+ // DISPOSED and re-deriving from the committed view, so the banner showed
5061
+ // the previous transition's target (#3041 follow-up). Treat it as absent;
5062
+ // recreation backfills from the in-flight write below.
5063
+ if (lvc && lvc._flags & REACTIVE_DISPOSED) lvc = undefined;
5033
5064
  if (!lvc) {
5034
5065
  const prevPending = latestReadActive;
5035
5066
  setLatestReadActive(false);
@@ -8463,10 +8494,29 @@ function descend(pv, nv, keyFn, fam, proj = false) {
8463
8494
  * primitives; the generic draft write-traps are reused from the legacy
8464
8495
  * module unchanged.
8465
8496
  */
8466
- function createWriteTraps(isActive, onDraftWrite) {
8467
- // Save/restore, never hard-reset: the draft can be driven from inside an
8468
- // enclosing authoritative-write scope (next-store optimistic derives), and
8469
- // a hard `false` would clobber it mid-derive.
8497
+ /**
8498
+ * Wrap a store proxy as a projection DRAFT: every operation carries the write
8499
+ * override (the derive is the author — its ops must not hit the §6c firewall
8500
+ * gate, even in a continuation after an `await`/`yield` where the sync write
8501
+ * scope has closed).
8502
+ *
8503
+ * FAKE TARGET, not the store proxy itself (#3060): after a proxy trap
8504
+ * returns, the engine runs spec invariant validation against the proxy's
8505
+ * TARGET — [[OwnPropertyKeys]] after ownKeys, [[GetOwnProperty]] after
8506
+ * set/getOwnPropertyDescriptor/defineProperty. With the store proxy as
8507
+ * target those checks re-enter the store's traps OUTSIDE the override
8508
+ * bracket (the trap's finally has already run), so `Object.keys(state)` in
8509
+ * a derive continuation fired the firewall gate and re-threw the
8510
+ * projection's own pending NotReadyError into the derive. A dummy of
8511
+ * matching kind (array/object, same trick as the store's own TargetShape)
8512
+ * keeps invariant validation away from the store entirely; the traps
8513
+ * forward to the closed-over inner proxy inside the bracket.
8514
+ *
8515
+ * Save/restore projectionWriteActive, never hard-reset: the draft can be
8516
+ * driven from inside an enclosing authoritative-write scope (next-store
8517
+ * optimistic derives), and a hard `false` would clobber it mid-derive.
8518
+ */
8519
+ function wrapDraft(inner, isActive, onDraftWrite) {
8470
8520
  const traps = {
8471
8521
  get(_, prop) {
8472
8522
  let value;
@@ -8474,13 +8524,15 @@ function createWriteTraps(isActive, onDraftWrite) {
8474
8524
  setWriteOverride(true);
8475
8525
  setProjectionWriteActive(true);
8476
8526
  try {
8477
- value = _[prop];
8527
+ value = inner[prop];
8478
8528
  } finally {
8479
8529
  setWriteOverride(false);
8480
8530
  setProjectionWriteActive(was);
8481
8531
  }
8482
8532
  if (prop === $TARGET) return value;
8483
- return typeof value === "object" && value !== null ? new Proxy(value, traps) : value;
8533
+ return typeof value === "object" && value !== null
8534
+ ? wrapDraft(value, isActive, onDraftWrite)
8535
+ : value;
8484
8536
  },
8485
8537
  has(_, prop) {
8486
8538
  let value;
@@ -8488,7 +8540,7 @@ function createWriteTraps(isActive, onDraftWrite) {
8488
8540
  setWriteOverride(true);
8489
8541
  setProjectionWriteActive(true);
8490
8542
  try {
8491
- value = prop in _;
8543
+ value = prop in inner;
8492
8544
  } finally {
8493
8545
  setWriteOverride(false);
8494
8546
  setProjectionWriteActive(was);
@@ -8501,7 +8553,7 @@ function createWriteTraps(isActive, onDraftWrite) {
8501
8553
  setWriteOverride(true);
8502
8554
  setProjectionWriteActive(true);
8503
8555
  try {
8504
- _[prop] = value;
8556
+ inner[prop] = value;
8505
8557
  onDraftWrite?.();
8506
8558
  } finally {
8507
8559
  setWriteOverride(false);
@@ -8515,7 +8567,49 @@ function createWriteTraps(isActive, onDraftWrite) {
8515
8567
  setWriteOverride(true);
8516
8568
  setProjectionWriteActive(true);
8517
8569
  try {
8518
- delete _[prop];
8570
+ delete inner[prop];
8571
+ onDraftWrite?.();
8572
+ } finally {
8573
+ setWriteOverride(false);
8574
+ setProjectionWriteActive(was);
8575
+ }
8576
+ return true;
8577
+ },
8578
+ ownKeys() {
8579
+ const was = projectionWriteActive;
8580
+ setWriteOverride(true);
8581
+ setProjectionWriteActive(true);
8582
+ try {
8583
+ return Reflect.ownKeys(inner);
8584
+ } finally {
8585
+ setWriteOverride(false);
8586
+ setProjectionWriteActive(was);
8587
+ }
8588
+ },
8589
+ getOwnPropertyDescriptor(_, prop) {
8590
+ let d;
8591
+ const was = projectionWriteActive;
8592
+ setWriteOverride(true);
8593
+ setProjectionWriteActive(true);
8594
+ try {
8595
+ d = Reflect.getOwnPropertyDescriptor(inner, prop);
8596
+ } finally {
8597
+ setWriteOverride(false);
8598
+ setProjectionWriteActive(was);
8599
+ }
8600
+ // The dummy target doesn't hold the key, so a non-configurable report
8601
+ // would violate the proxy invariant. Store descriptors are already
8602
+ // normalized configurable; enforce it for raw leaves too.
8603
+ if (d) d.configurable = true;
8604
+ return d;
8605
+ },
8606
+ defineProperty(_, prop, desc) {
8607
+ if (isActive && !isActive()) return true;
8608
+ const was = projectionWriteActive;
8609
+ setWriteOverride(true);
8610
+ setProjectionWriteActive(true);
8611
+ try {
8612
+ Reflect.defineProperty(inner, prop, desc);
8519
8613
  onDraftWrite?.();
8520
8614
  } finally {
8521
8615
  setWriteOverride(false);
@@ -8524,7 +8618,8 @@ function createWriteTraps(isActive, onDraftWrite) {
8524
8618
  return true;
8525
8619
  }
8526
8620
  };
8527
- return traps;
8621
+ // Matching-kind dummy so Array.isArray(draft) answers like the store.
8622
+ return new Proxy(Array.isArray(inner) ? [] : {}, traps);
8528
8623
  }
8529
8624
  function createProjectionNextInternal(fn, seed, options) {
8530
8625
  const fam = {
@@ -8578,9 +8673,10 @@ function runProjectionComputedNext(wrappedStore, fn, key, wrapCommit, onDraftWri
8578
8673
  const shadow = owner._loading
8579
8674
  ? JSON.parse(JSON.stringify(wrappedStore[$TARGET][STORE_VALUE]))
8580
8675
  : null;
8581
- const draft = new Proxy(
8676
+ const draft = wrapDraft(
8582
8677
  wrappedStore,
8583
- createWriteTraps(() => !settled || owner._x?._inFlight === result, onDraftWrite)
8678
+ () => !settled || owner._x?._inFlight === result,
8679
+ onDraftWrite
8584
8680
  );
8585
8681
  storeSetterNext(
8586
8682
  draft,