@solidjs/signals 2.0.0-rc.5 → 2.0.0-rc.6

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.
@@ -1,4 +1,4 @@
1
- import { unwrapOverride, $REFRESH, CONFIG_OWNED_WRITE, NOT_PENDING, CONFIG_OPTIMISTIC, CONFIG_HELD_TRUTH, STATUS_UNINITIALIZED, STATUS_ERROR, CONFIG_CHILDREN_FORBIDDEN, CONFIG_AUTHORITATIVE_READ } from "../../core/constants.js";
1
+ import { unwrapOverride, $REFRESH, NOT_PENDING, CONFIG_OWNED_WRITE, CONFIG_OPTIMISTIC, CONFIG_HELD_TRUTH, STATUS_UNINITIALIZED, STATUS_ERROR, CONFIG_CHILDREN_FORBIDDEN, CONFIG_AUTHORITATIVE_READ } from "../../core/constants.js";
2
2
 
3
3
  import { setSignal, isEqual, read, pendingCheckActive, latestReadActive, readNodeFast, READ_SLOW, signal, ext, setLatestReadActive, prepareComputed, context } from "../../core/core.js";
4
4
 
@@ -193,7 +193,7 @@ function getNode(e, t, n) {
193
193
  // Optimistic families: arm the override slot — setSignal routes armed
194
194
  // nodes through the core engine (lanes, ownership, reverts all native).
195
195
  if (e.fam?.opt) {
196
- ext(o).Oe = NOT_PENDING;
196
+ ext(o).Pe = NOT_PENDING;
197
197
  o.T |= CONFIG_OPTIMISTIC;
198
198
  }
199
199
  // A node born inside a live mark's identity scope inherits the mark
@@ -226,7 +226,7 @@ function getHasNode(e, t, n) {
226
226
  }, e.fam?.node ?? undefined);
227
227
  o.T |= CONFIG_OWNED_WRITE;
228
228
  if (e.fam?.opt) {
229
- ext(o).Oe = NOT_PENDING;
229
+ ext(o).Pe = NOT_PENDING;
230
230
  o.T |= CONFIG_OPTIMISTIC;
231
231
  }
232
232
  if (affectsScopesLive()) inheritAffectsMarks(o, e.v, t);
@@ -247,7 +247,7 @@ function getKeySetNode(e) {
247
247
  }, e.fam?.node ?? undefined);
248
248
  n.T |= CONFIG_OWNED_WRITE;
249
249
  if (e.fam?.opt) {
250
- ext(n).Oe = NOT_PENDING;
250
+ ext(n).Pe = NOT_PENDING;
251
251
  n.T |= CONFIG_OPTIMISTIC;
252
252
  }
253
253
  e.k = t;
@@ -267,7 +267,7 @@ function getDeepNode(e) {
267
267
  }, e.fam?.node ?? undefined);
268
268
  n.T |= CONFIG_OWNED_WRITE;
269
269
  if (e.fam?.opt) {
270
- ext(n).Oe = NOT_PENDING;
270
+ ext(n).Pe = NOT_PENDING;
271
271
  n.T |= CONFIG_OPTIMISTIC;
272
272
  }
273
273
  if (affectsScopesLive()) inheritAffectsMarks(n, e.v, $TRACK);
@@ -388,14 +388,14 @@ function ensurePB(e) {
388
388
  if (n !== null) {
389
389
  for (const e of Reflect.ownKeys(n)) {
390
390
  const r = n[e];
391
- if (hasActiveOverride(r)) t[e] = unwrapOverride(r.o?.Oe);
391
+ if (hasActiveOverride(r)) t[e] = unwrapOverride(r.o?.Pe);
392
392
  }
393
393
  }
394
394
  const r = e.h;
395
395
  if (r !== null) {
396
396
  for (const e of Reflect.ownKeys(r)) {
397
397
  const n = r[e];
398
- if (hasActiveOverride(n) && !unwrapOverride(n.o?.Oe)) delete t[e];
398
+ if (hasActiveOverride(n) && !unwrapOverride(n.o?.Pe)) delete t[e];
399
399
  }
400
400
  }
401
401
  }
@@ -575,7 +575,7 @@ function drainFolds() {
575
575
  !plainProto(t.ovl ? t.v : i) ? Reflect.ownKeys(o) : e;
576
576
  for (const e of n) {
577
577
  const t = o[e];
578
- if (t !== undefined && t.Pe !== NOT_PENDING) {
578
+ if (t !== undefined && t.Re !== NOT_PENDING) {
579
579
  r = true;
580
580
  break;
581
581
  }
@@ -749,15 +749,15 @@ function drainFolds() {
749
749
  // projection recompute can run before the prior fold commits) — the
750
750
  // node's OWN current value is the true old side, and setSignal's
751
751
  // internal equality already checks exactly that.
752
- const f = e.del !== null && e.del.has(o) ? undefined : t[o];
753
- setSignal(i, () => f);
752
+ const l = e.del !== null && e.del.has(o) ? undefined : t[o];
753
+ setSignal(i, () => l);
754
754
  }
755
755
  }
756
- const f = e.h;
757
- if (f !== null) {
758
- const n = o ?? Reflect.ownKeys(f);
756
+ const l = e.h;
757
+ if (l !== null) {
758
+ const n = o ?? Reflect.ownKeys(l);
759
759
  for (const r of n) {
760
- const n = f[r];
760
+ const n = l[r];
761
761
  if (n !== undefined) setSignal(n, r in t && !(e.del !== null && e.del.has(r)));
762
762
  }
763
763
  }
@@ -881,9 +881,9 @@ i = true) {
881
881
  if (i?.get !== o?.get || i?.set !== o?.set || i?.value !== o?.value) setSignal(e, () => FORCE);
882
882
  return;
883
883
  }
884
- const f = i?.value;
885
- const l = o?.value;
886
- if (!isEqual(f, l) && !targetsEqual(f, l)) setSignal(e, typeof l === "function" ? () => l : l);
884
+ const l = i?.value;
885
+ const f = o?.value;
886
+ if (!isEqual(l, f) && !targetsEqual(l, f)) setSignal(e, typeof f === "function" ? () => f : f);
887
887
  } else {
888
888
  const i = n[t];
889
889
  const o = r[t];
@@ -1030,7 +1030,7 @@ const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
1030
1030
  if (t === null) return false;
1031
1031
  for (const e of Reflect.ownKeys(t)) {
1032
1032
  const n = t[e];
1033
- if (n.Pe !== NOT_PENDING && n.Ae != null && n.Ae.sn !== true) return true;
1033
+ if (n.Re !== NOT_PENDING && n.Ae != null && n.Ae.sn !== true) return true;
1034
1034
  }
1035
1035
  return false;
1036
1036
  }
@@ -1044,28 +1044,39 @@ function readSource(e) {
1044
1044
  const t = heldMaskView(e);
1045
1045
  if (t !== null) return t;
1046
1046
  }
1047
- // Signal-parity visibility (core read(): owner-context reads serve
1048
- // _pendingValue, context-free reads serve committed — effects recompute
1049
- // BEFORE commitPendingNodes in the flush, so the pending view must be
1050
- // servable). Drafts (setter window OR projection write-override) and
1051
- // owner-context reads see the pending backing; context-free reads see
1052
- // committed. Node reads apply the same rule, so both homes agree.
1053
- if (e.pb !== null && (inDraft(e) || getWriteOverride() ||
1054
- // Owner-context readers see the pending backing — EXCEPT held truth
1055
- // on an optimistic family (#3164 fold): a live pb on an opt family
1056
- // outside the draft/write-override windows is a staged landing
1057
- // (tentative drafts never outlive their setter), and only the
1058
- // authoritative postures and latest() see it (the backing-level twin
1059
- // of core read()'s A17-for-held-truth arm; ordinary readers keep
1060
- // committed until the transaction's reveal).
1061
- inOwnerContext() && !heldTruthMasked(e) ||
1047
+ return pendingBackingVisible(e, false) ? e.pb : e.v;
1048
+ }
1049
+
1050
+ /** The single pb-vs-committed visibility decision (#3147), shared by per-key
1051
+ * backing reads (readSource) and deep()/snapshot composition (snapshotWalk)
1052
+ * so the two reader families can never disagree about a HELD landing.
1053
+ *
1054
+ * Signal-parity visibility (core read(): owner-context reads serve
1055
+ * _pendingValue, context-free reads serve committed effects recompute
1056
+ * BEFORE commitPendingNodes in the flush, so the pending view must be
1057
+ * servable). Drafts (setter window OR projection write-override) and
1058
+ * owner-context reads see the pending backing; context-free reads see
1059
+ * committed. Node reads apply the same rule, so all homes agree.
1060
+ *
1061
+ * `speculative` is deep()/snapshot's posture: an untrack/deep PEEK that sees
1062
+ * ordinary pending staging regardless of owner context (the documented
1063
+ * divergence from context-free per-key reads) — but never through a hold:
1064
+ * held truth stays masked exactly as it is for per-key readers. */ function pendingBackingVisible(e, t) {
1065
+ return e.pb !== null && (inDraft(e) || getWriteOverride() ||
1066
+ // Owner-context (and speculative-peek) readers see the pending
1067
+ // backing — EXCEPT held truth on an optimistic family (#3164 fold):
1068
+ // a live pb on an opt family outside the draft/write-override windows
1069
+ // is a staged landing (tentative drafts never outlive their setter),
1070
+ // and only the authoritative postures and latest() see it (the
1071
+ // backing-level twin of core read()'s A17-for-held-truth arm;
1072
+ // ordinary readers keep committed until the transaction's reveal).
1073
+ (t || inOwnerContext()) && !heldTruthMasked(e) ||
1062
1074
  // A projection's pending backing is authoritative-elect: serve it to
1063
1075
  // context-free readers too UNLESS a transition is holding the node
1064
1076
  // commits (downstream async hold — stale committed is the contract)
1065
1077
  // or the reader is a CHILDREN_FORBIDDEN scope, which never observes
1066
1078
  // its own unsettled write (#3082, signal parity per #3006).
1067
- e.fam !== null && !heldTruthMasked(e) && !foldHeld(e) && !inForbiddenScope())) return e.pb;
1068
- return e.v;
1079
+ e.fam !== null && !heldTruthMasked(e) && !foldHeld(e) && !inForbiddenScope());
1069
1080
  }
1070
1081
 
1071
1082
  /** #3164 fold: HELD truth on an optimistic family — a pending backing
@@ -1110,7 +1121,7 @@ function isOwnAccessor(e, t) {
1110
1121
 
1111
1122
  /** Active optimistic override on an armed node (armed slot idles at
1112
1123
  * NOT_PENDING; undefined = unarmed plain node). */ function hasActiveOverride(e) {
1113
- return e.o?.Oe !== undefined && e.o?.Oe !== NOT_PENDING;
1124
+ return e.o?.Pe !== undefined && e.o?.Pe !== NOT_PENDING;
1114
1125
  }
1115
1126
 
1116
1127
  /** The reading computation is until()'s authoritative-view predicate — same
@@ -1145,14 +1156,14 @@ function isOwnAccessor(e, t) {
1145
1156
  // Authoritative-view reads (until()'s predicate) skip the override arm
1146
1157
  // only: staged pending values are authoritative, overrides are the
1147
1158
  // caller's optimism.
1148
- const n = !authoritativeServe() && hasActiveOverride(e) ? unwrapOverride(e.o?.Oe) : e.Pe !== NOT_PENDING && (latestReadActive ||
1159
+ const n = !authoritativeServe() && hasActiveOverride(e) ? unwrapOverride(e.o?.Pe) : e.Re !== NOT_PENDING && (latestReadActive ||
1149
1160
  // Owner-context pending visibility — except HELD truth (#3164,
1150
1161
  // see CONFIG_HELD_TRUTH: fold-staged or entangle-stolen
1151
1162
  // confirming truth), which only authoritative/latest readers
1152
1163
  // see (core read()'s A17-for-held-truth twin; ordinary readers
1153
1164
  // keep committed until the transaction's reveal — latest() is
1154
1165
  // exempted by the leading arm above).
1155
- (inOwnerContext() || authoritativeServe()) && !(e.T & CONFIG_HELD_TRUTH && !authoritativeServe())) ? e.Pe : t;
1166
+ (inOwnerContext() || authoritativeServe()) && !(e.T & CONFIG_HELD_TRUTH && !authoritativeServe())) ? e.Re : t;
1156
1167
  return n === FORCE ? t : n;
1157
1168
  }
1158
1169
 
@@ -1162,7 +1173,7 @@ function isOwnAccessor(e, t) {
1162
1173
  * node is linked only for adoption-swap notification, its value never
1163
1174
  * shadows the live chain. */ function serveDataKey(e, t, n, r, i) {
1164
1175
  const o = e.ch && r === e.v;
1165
- let f = n;
1176
+ let l = n;
1166
1177
  // §6: on optimistic arrays LENGTH IS A VIEW, not a node value — one home
1167
1178
  // (backing ± presence overrides) for both length and indices makes torn
1168
1179
  // iteration impossible (a length node's value rides different visibility
@@ -1190,7 +1201,7 @@ function isOwnAccessor(e, t) {
1190
1201
  // seeding rule, applied to the read side (#3108).
1191
1202
  if (e.fam?.opt && e.pb === null && !authoritativeServe()) {
1192
1203
  const n = e.n?.[t];
1193
- if (n !== undefined && hasActiveOverride(n)) f = unwrapOverride(n.o?.Oe);
1204
+ if (n !== undefined && hasActiveOverride(n)) l = unwrapOverride(n.o?.Pe);
1194
1205
  }
1195
1206
  } else {
1196
1207
  if (i !== undefined) {
@@ -1202,28 +1213,28 @@ function isOwnAccessor(e, t) {
1202
1213
  // parity): READ_SLOW = a global read window or non-plain node.
1203
1214
  let e = readNodeFast(i);
1204
1215
  if (e === READ_SLOW) e = read(i);
1205
- if (!o || hasActiveOverride(i)) f = e === FORCE ? n : e;
1216
+ if (!o || hasActiveOverride(i)) l = e === FORCE ? n : e;
1206
1217
  } else if (!o || hasActiveOverride(i)) {
1207
- f = nodeValue(i, n);
1218
+ l = nodeValue(i, n);
1208
1219
  }
1209
1220
  } else if (getObserver() !== null) {
1210
1221
  read(getNode(e, t, n));
1211
1222
  }
1212
1223
  }
1213
1224
  // Shallow stores serve data raw; store-proxy slots get boundary wrappers.
1214
- if (e.s) return serveShallow(e, t, f);
1225
+ if (e.s) return serveShallow(e, t, l);
1215
1226
  if (i !== undefined) {
1216
1227
  // Wrap cache (see getNode): only wrappables are ever cached, so a hit
1217
1228
  // skips isWrappable too — pointer-compare replaces both checks.
1218
- if (i.pxv === f && f !== undefined) return draftServe(e, i.px);
1219
- if (!isWrappable(f)) return f;
1220
- const n = wrapNext(f, e, t);
1229
+ if (i.pxv === l && l !== undefined) return draftServe(e, i.px);
1230
+ if (!isWrappable(l)) return l;
1231
+ const n = wrapNext(l, e, t);
1221
1232
  i.px = n;
1222
- i.pxv = f;
1233
+ i.pxv = l;
1223
1234
  return draftServe(e, n);
1224
1235
  }
1225
- if (!isWrappable(f)) return f;
1226
- return draftServe(e, wrapNext(f, e, t));
1236
+ if (!isWrappable(l)) return l;
1237
+ return draftServe(e, wrapNext(l, e, t));
1227
1238
  }
1228
1239
 
1229
1240
  /** §6c store-wide status gate for reads that DON'T flow through a node:
@@ -1357,15 +1368,15 @@ const traps = {
1357
1368
  // that is own on NEITHER.
1358
1369
  const o = e.ovl && r === e.pb;
1359
1370
  if ((t === "constructor" || t === "__proto__" || t === "prototype") && !hasOwn.call(r, t) && !(o && hasOwn.call(e.v, t))) return undefined;
1360
- let f = r[t];
1361
- if (f === undefined ? !hasOwn.call(r, t) && !(o && hasOwn.call(e.v, t)) : false) {
1371
+ let l = r[t];
1372
+ if (l === undefined ? !hasOwn.call(r, t) && !(o && hasOwn.call(e.v, t)) : false) {
1362
1373
  // Inherited: prototype getters/methods run with the proxy receiver.
1363
- f = Reflect.get(r, t, n);
1364
- if (typeof f === "function") return f;
1374
+ l = Reflect.get(r, t, n);
1375
+ if (typeof l === "function") return l;
1365
1376
  // proto methods untracked
1366
1377
  // Reading a currently-absent own key subscribes to it (R12) — for any
1367
1378
  // target OUTSIDE its own draft scope, even mid-setter (#3037, above).
1368
- if (f === undefined && !inDraft(e)) {
1379
+ if (l === undefined && !inDraft(e)) {
1369
1380
  if (getObserver() !== null) read(getNode(e, t, undefined));
1370
1381
  const n = e.n?.[t];
1371
1382
  if (n) {
@@ -1373,19 +1384,19 @@ const traps = {
1373
1384
  if (e.s) return serveShallow(e, t, r);
1374
1385
  return isWrappable(r) ? draftServe(e, wrapNext(r, e, t)) : r;
1375
1386
  }
1376
- } else if (f === undefined && inDraft(e) && e.fam?.opt && e.pb === null &&
1387
+ } else if (l === undefined && inDraft(e) && e.fam?.opt && e.pb === null &&
1377
1388
  // AUTHORITATIVE drafts (landing folds) never seed from overrides —
1378
1389
  // the caller's optimism is not truth (has-trap twin below).
1379
1390
  !authoritativeServe()) {
1380
1391
  const n = e.n?.[t];
1381
- if (n !== undefined && hasActiveOverride(n)) f = unwrapOverride(n.o?.Oe);
1392
+ if (n !== undefined && hasActiveOverride(n)) l = unwrapOverride(n.o?.Pe);
1382
1393
  }
1383
- if (e.s) return serveShallow(e, t, f);
1384
- return isWrappable(f) ? draftServe(e, wrapNext(f, e, t)) : f;
1394
+ if (e.s) return serveShallow(e, t, l);
1395
+ return isWrappable(l) ? draftServe(e, wrapNext(l, e, t)) : l;
1385
1396
  }
1386
- if (typeof f === "function" && !hasOwn.call(r, t) && !(o && hasOwn.call(e.v, t))) return f;
1397
+ if (typeof l === "function" && !hasOwn.call(r, t) && !(o && hasOwn.call(e.v, t))) return l;
1387
1398
  // proto method
1388
- return serveDataKey(e, t, f, r, i);
1399
+ return serveDataKey(e, t, l, r, i);
1389
1400
  },
1390
1401
  has(e, t) {
1391
1402
  if (t === $TARGET || t === $PROXY || t === $TRACK) return true;
@@ -1404,11 +1415,11 @@ const traps = {
1404
1415
  if (hasActiveOverride(n)) r = !!i;
1405
1416
  } else if (!authoritativeServe()) {
1406
1417
  const n = e.h?.[t];
1407
- if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.Oe);
1418
+ if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.Pe);
1408
1419
  }
1409
1420
  } else if (e.fam?.opt && e.pb === null && !authoritativeServe()) {
1410
1421
  const n = e.h?.[t];
1411
- if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.Oe);
1422
+ if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.Pe);
1412
1423
  }
1413
1424
  return r;
1414
1425
  },
@@ -1439,7 +1450,7 @@ const traps = {
1439
1450
  const i = e.h[r];
1440
1451
  if (!hasActiveOverride(i)) continue;
1441
1452
  t ??= new Set(n);
1442
- if (unwrapOverride(i.o?.Oe)) t.add(r); else t.delete(r);
1453
+ if (unwrapOverride(i.o?.Pe)) t.add(r); else t.delete(r);
1443
1454
  }
1444
1455
  if (t !== null) return [ ...t ];
1445
1456
  }
@@ -1457,7 +1468,7 @@ const traps = {
1457
1468
  if (!authoritativeServe() && e.fam?.opt && !inDraft(e)) {
1458
1469
  const n = e.h?.[t];
1459
1470
  if (n !== undefined && hasActiveOverride(n)) {
1460
- if (!unwrapOverride(n.o?.Oe)) return undefined;
1471
+ if (!unwrapOverride(n.o?.Pe)) return undefined;
1461
1472
  // opt delete
1462
1473
  if (r === undefined) {
1463
1474
  const n = e.n?.[t];
@@ -1493,23 +1504,23 @@ const traps = {
1493
1504
  // passes through by reference (#2932; markRawOne skips proxies), while
1494
1505
  // deep stores unwrap to raw backings.
1495
1506
  const o = e.s ? n : unwrapValue(n);
1496
- const f = ensurePB(e);
1507
+ const l = ensurePB(e);
1497
1508
  pendingNotify.add(e);
1498
1509
  // Array length writes implicitly delete indices — the written-keys bound
1499
1510
  // can't see them, so poison to the full scan for this batch. Index
1500
1511
  // writes implicitly GROW length, so arrays always record it alongside.
1501
- const l = pcOf(e);
1502
- if (Array.isArray(f)) {
1503
- if (t === "length") l.wk = WK_ALL; else if (l.wk !== WK_ALL) {
1504
- const e = l.wk ??= new Set;
1512
+ const f = pcOf(e);
1513
+ if (Array.isArray(l)) {
1514
+ if (t === "length") f.wk = WK_ALL; else if (f.wk !== WK_ALL) {
1515
+ const e = f.wk ??= new Set;
1505
1516
  e.add(t);
1506
1517
  e.add("length");
1507
1518
  }
1508
- } else if (l.wk !== WK_ALL) (l.wk ??= new Set).add(t);
1519
+ } else if (f.wk !== WK_ALL) (f.wk ??= new Set).add(t);
1509
1520
  // Own data keys literally named "prototype"/"constructor" land as data —
1510
1521
  // defineProperty sidesteps a proto-chain setter named the same.
1511
1522
  if (UNSAFE_KEYS.has(t)) {
1512
- Object.defineProperty(f, t, {
1523
+ Object.defineProperty(l, t, {
1513
1524
  value: o,
1514
1525
  writable: true,
1515
1526
  enumerable: true,
@@ -1521,14 +1532,14 @@ const traps = {
1521
1532
  // Overlay first-write DEFINES the own key: assignment through the proto
1522
1533
  // chain would reject on a non-writable committed property (the clone
1523
1534
  // path normalized descriptors for exactly this — R51 parity).
1524
- if (e.ovl && !hasOwn.call(f, t)) {
1525
- Object.defineProperty(f, t, {
1535
+ if (e.ovl && !hasOwn.call(l, t)) {
1536
+ Object.defineProperty(l, t, {
1526
1537
  value: o,
1527
1538
  writable: true,
1528
1539
  enumerable: true,
1529
1540
  configurable: true
1530
1541
  });
1531
- } else f[t] = o;
1542
+ } else l[t] = o;
1532
1543
  if (e.del !== null) e.del.delete(t);
1533
1544
  // Shallow ingest: written records are sticky raw-marked (one entity is
1534
1545
  // never both deep-wrapped and raw — R41/#2932, shared invariant).
@@ -1558,8 +1569,8 @@ const traps = {
1558
1569
  };
1559
1570
  const o = ensurePB(e);
1560
1571
  pendingNotify.add(e);
1561
- const f = pcOf(e);
1562
- if (f.wk !== WK_ALL) (f.wk ??= new Set).add(t);
1572
+ const l = pcOf(e);
1573
+ if (l.wk !== WK_ALL) (l.wk ??= new Set).add(t);
1563
1574
  Object.defineProperty(o, t, n);
1564
1575
  if (e.del !== null) e.del.delete(t);
1565
1576
  if (i) notifyWrites(e);
@@ -1697,15 +1708,15 @@ function createStoreNext(e, t = false) {
1697
1708
  if (o === null || typeof o !== "object") continue;
1698
1709
  // Stored proxies (chained slots) resolve through their own target;
1699
1710
  // raw children through the family map, created on first visit.
1700
- let f = o[$TARGET] ?? r.get(o);
1701
- if (f === undefined) {
1711
+ let l = o[$TARGET] ?? r.get(o);
1712
+ if (l === undefined) {
1702
1713
  if (!isWrappable(o)) continue;
1703
1714
  wrapNext(o, e, n);
1704
- f = r.get(o);
1705
- if (f === undefined) continue;
1715
+ l = r.get(o);
1716
+ if (l === undefined) continue;
1706
1717
  // raw-marked: leaf by contract
1707
1718
  }
1708
- walkT(f);
1719
+ walkT(l);
1709
1720
  }
1710
1721
  };
1711
1722
  walkT(t);
@@ -1742,12 +1753,17 @@ function snapshotWalk(e, t, n) {
1742
1753
  if (e === undefined) break;
1743
1754
  if (e.fam !== null) n = e.fam;
1744
1755
  if (e.fam?.opt === true) (i ??= []).push(e);
1756
+ // The shared visibility decision (#3147): the speculative peek serves
1757
+ // pending staging, but a HELD landing is masked to committed exactly as
1758
+ // it is for per-key readers — the two families must answer alike while
1759
+ // a transaction holds store landings.
1760
+ const t = pendingBackingVisible(e, true);
1745
1761
  // Snapshot runs mid-flush (tracked memos execute before commit), so a
1746
1762
  // pending prototype overlay must present as a REAL merged container.
1747
- if (e.ovl) materializePB(e);
1748
- const t = e.pb ?? e.v;
1749
- if (t === r) break;
1750
- r = t;
1763
+ if (t && e.ovl) materializePB(e);
1764
+ const o = t ? e.pb : e.v;
1765
+ if (o === r) break;
1766
+ r = o;
1751
1767
  }
1752
1768
  if (!isWrappable(r)) return r;
1753
1769
  // Optimistic families: compose the visible view; a composed view is a fresh
@@ -1759,15 +1775,15 @@ function snapshotWalk(e, t, n) {
1759
1775
  const i = t.get(r);
1760
1776
  if (i !== undefined) return i;
1761
1777
  const o = Array.isArray(e);
1762
- const f = o ? [] : Object.create(Object.getPrototypeOf(e));
1763
- t.set(r, f);
1778
+ const l = o ? [] : Object.create(Object.getPrototypeOf(e));
1779
+ t.set(r, l);
1764
1780
  for (const r of Reflect.ownKeys(e)) {
1765
1781
  if (o && r === "length") continue;
1766
1782
  const i = e[r];
1767
- f[r] = i !== null && typeof i === "object" ? snapshotWalk(i, t, n) : i;
1783
+ l[r] = i !== null && typeof i === "object" ? snapshotWalk(i, t, n) : i;
1768
1784
  }
1769
- if (o) f.length = e.length;
1770
- return f;
1785
+ if (o) l.length = e.length;
1786
+ return l;
1771
1787
  }
1772
1788
  }
1773
1789
  const o = t.get(r);
@@ -1782,16 +1798,16 @@ function snapshotWalk(e, t, n) {
1782
1798
  t.set(r, i);
1783
1799
  for (const o of Reflect.ownKeys(r)) {
1784
1800
  if (e && o === "length") continue;
1785
- const f = Object.getOwnPropertyDescriptor(r, o);
1786
- if (typeof o === "symbol" && !f.enumerable) continue;
1787
- if (f.get || f.set) {
1788
- Object.defineProperty(i, o, f);
1801
+ const l = Object.getOwnPropertyDescriptor(r, o);
1802
+ if (typeof o === "symbol" && !l.enumerable) continue;
1803
+ if (l.get || l.set) {
1804
+ Object.defineProperty(i, o, l);
1789
1805
  continue;
1790
1806
  }
1791
- const l = f.value;
1792
- const u = l !== null && typeof l === "object" ? snapshotWalk(l, t, n) : l;
1793
- if (f.enumerable && f.writable && f.configurable) i[o] = u; else Object.defineProperty(i, o, {
1794
- ...f,
1807
+ const f = l.value;
1808
+ const u = f !== null && typeof f === "object" ? snapshotWalk(f, t, n) : f;
1809
+ if (l.enumerable && l.writable && l.configurable) i[o] = u; else Object.defineProperty(i, o, {
1810
+ ...l,
1795
1811
  value: u
1796
1812
  });
1797
1813
  }
@@ -1801,22 +1817,22 @@ function snapshotWalk(e, t, n) {
1801
1817
  // UNOWNED (shared/user) subtrees keep identity unless a descendant
1802
1818
  // substituted; copy-on-substitution preserves the documented CoW contract.
1803
1819
  t.set(r, r);
1804
- let f = null;
1820
+ let l = null;
1805
1821
  for (const e of Reflect.ownKeys(r)) {
1806
1822
  const i = Object.getOwnPropertyDescriptor(r, e);
1807
1823
  if (!i || i.get || i.set) continue;
1808
1824
  const o = i.value;
1809
1825
  if (o === null || typeof o !== "object") continue;
1810
- const l = snapshotWalk(o, t, n);
1811
- if (l !== o) {
1812
- if (f === null) {
1813
- f = Array.isArray(r) ? [ ...r ] : Object.create(Object.getPrototypeOf(r), Object.getOwnPropertyDescriptors(r));
1814
- t.set(r, f);
1826
+ const f = snapshotWalk(o, t, n);
1827
+ if (f !== o) {
1828
+ if (l === null) {
1829
+ l = Array.isArray(r) ? [ ...r ] : Object.create(Object.getPrototypeOf(r), Object.getOwnPropertyDescriptors(r));
1830
+ t.set(r, l);
1815
1831
  }
1816
- f[e] = l;
1832
+ l[e] = f;
1817
1833
  }
1818
1834
  }
1819
- return f ?? r;
1835
+ return l ?? r;
1820
1836
  }
1821
1837
 
1822
1838
  export { adoptPB, arrayStructureChanged, authoritativeRead, authoritativeServe, bumpDeep, createStoreNext, deepNext, getHasNode, getKeySetNode, getNode, hasAccessorFlag, hasActiveOverride, materializePB, membershipChanged, notifyFold, notifyFoldTail, notifyKeyDiff, notifyKeyValue, pcOf, runAuthoritative, snapshotNext, stagedTruthPB, storeHasFamily, storeHasOptimisticFamily, storeIsShallow, storeSetterNext, targetIsPlain, targetsEqual, unwrapValue, wrapNext };
@@ -36,6 +36,17 @@ export interface AttributionHooks {
36
36
  write(el: Signal<any> | Computed<any>, prev: unknown, value: unknown): void;
37
37
  /** refresh() invalidated this node (self-invalidation, no dep changed). */
38
38
  refreshed(el: Computed<any>): void;
39
+ /**
40
+ * A new async flight entered the system (`_inFlight` was just assigned
41
+ * during a recompute of `el`). Always fired inside the owning recompute —
42
+ * both call paths (core's recompute and the projection self-registration)
43
+ * run within one — so the engine can read the current frame stack to link
44
+ * the flight to the change that caused it (waterfall chaining). `flight`
45
+ * is the registered thenable/iterable itself: the engine keys a first-seen
46
+ * origin registry on its identity, so shared and preloader-marked promises
47
+ * carry their true start time instead of the moment the graph saw them.
48
+ */
49
+ flightStart(el: Computed<any>, flight: object): void;
39
50
  /** An async landing is about to apply its value (before any branch). */
40
51
  asyncStart(el: Computed<any>): void;
41
52
  /**
@@ -143,6 +143,24 @@ export interface AttributionOptions {
143
143
  * disables.
144
144
  */
145
145
  wideWrites?: number | false;
146
+ /**
147
+ * Async-waterfall warning: emit a diagnostic when an async flight that
148
+ * could only start after an upstream flight resolved (its recompute's
149
+ * cause chain reaches the upstream's async landing, and its origin
150
+ * post-dates that landing) forms a sequential chain of 2+ flights, each
151
+ * of which took at least `minFlightMs` (default 50ms). The duration gate
152
+ * is one safety valve for what the graph cannot see: a settled
153
+ * preload/cache hit resolves fast and never warns. In-flight preloads are
154
+ * absolved by origin: `markFlight()` stamps (and first-seen identity)
155
+ * prove work predated the upstream landing — parallel, not sequential.
156
+ * Chains of 2 emit at `info` severity, structured channel only (a
157
+ * dependent fetch is sometimes intrinsic, and unmarked external preloads
158
+ * are invisible); 3+ escalate to `warn` with console output. `false`
159
+ * disables.
160
+ */
161
+ waterfalls?: {
162
+ minFlightMs: number;
163
+ } | false;
146
164
  }
147
165
  export interface ScopeCost {
148
166
  name: string;
@@ -186,6 +204,36 @@ export interface Attribution {
186
204
  scopes: ScopeCost[];
187
205
  writes: WriteCost[];
188
206
  };
207
+ /**
208
+ * Every graph-provable sequential flight chain observed since enable()
209
+ * (ring-buffered like history()). Facts, not verdicts: chains are recorded
210
+ * regardless of the duration gate — the ASYNC_WATERFALL diagnostic is the
211
+ * thresholded view of the same data.
212
+ */
213
+ waterfalls(): readonly WaterfallRecord[];
214
+ /**
215
+ * Cooperative preload declaration: stamp a flight object (promise or async
216
+ * iterable) with its true kickoff time BEFORE the reactive graph sees it.
217
+ * A route preloader or query cache calls this on the promise it hands out
218
+ * (on the WRAPPER it mints, with the original kickoff time — wrapping
219
+ * defeats identity tracking otherwise); any dependent that later awaits it
220
+ * is then judged against the real start — work already in the air when its
221
+ * upstream landed is parallel, never a waterfall link. Callable while
222
+ * attribution is disabled (marks made at navigation time must survive a
223
+ * later enable()). Dev-only, like the whole DEV surface.
224
+ */
225
+ markFlight(flight: object, startedAt?: number): void;
189
226
  format: typeof formatRerun;
190
227
  }
228
+ /** One landed flight: its node name, wall duration, and upstream chain. */
229
+ export interface FlightLink {
230
+ name: string;
231
+ ms: number;
232
+ }
233
+ export interface WaterfallRecord {
234
+ /** Sequential flights, oldest first, ending at the flight that landed. */
235
+ chain: FlightLink[];
236
+ /** Summed wall time of the chain — the serialized cost. */
237
+ sequentialMs: number;
238
+ }
191
239
  export declare const attribution: Attribution;
@@ -6,8 +6,14 @@ export interface DevHooks {
6
6
  onUpdate?: () => void;
7
7
  onStoreNodeUpdate?: (state: any, property: PropertyKey, value: any, prev: any) => void;
8
8
  }
9
- export type DiagnosticSeverity = "warn" | "error";
10
- export type DiagnosticCode = "STRICT_READ_UNTRACKED" | "PENDING_ASYNC_UNTRACKED_READ" | "PENDING_ASYNC_FORBIDDEN_SCOPE" | "REACTIVE_WRITE_IN_OWNED_SCOPE" | "ACTION_CALLED_IN_OWNED_SCOPE" | "RUN_WITH_DISPOSED_OWNER" | "NO_OWNER_CLEANUP" | "CLEANUP_IN_FORBIDDEN_SCOPE" | "SETTLED_CLEANUP_UNOWNED" | "FLUSH_IN_EFFECT_CALLBACK" | "PRIMITIVE_IN_FORBIDDEN_SCOPE" | "NO_OWNER_EFFECT" | "NO_OWNER_BOUNDARY" | "ASYNC_OUTSIDE_LOADING_BOUNDARY" | "INVALID_REFRESH_TARGET" | "INVALID_AFFECTS_TARGET" | "MISSING_EFFECT_FN" | "SYNC_NODE_RECEIVED_ASYNC" | "REACTIVITY_HALTED" | "INVARIANT_VIOLATION" | "HUGE_FAN_OUT" | "HUGE_FAN_IN" | "HOT_SCOPE_RERUNS" | "HOT_SCOPE_TIME" | "WIDE_SCOPE_DEPS" | "UNSTABLE_MEMO_OUTPUT" | "WIDE_WRITE";
9
+ /**
10
+ * `info` is the advisory tier: a structural fact worth surfacing that is not
11
+ * presumptively a bug (e.g. a 2-deep sequential fetch chain, which may be an
12
+ * intrinsic data dependency). Budget/assertion consumers should treat only
13
+ * `warn`/`error` as failures unless they opt in to `info`.
14
+ */
15
+ export type DiagnosticSeverity = "info" | "warn" | "error";
16
+ export type DiagnosticCode = "STRICT_READ_UNTRACKED" | "PENDING_ASYNC_UNTRACKED_READ" | "PENDING_ASYNC_FORBIDDEN_SCOPE" | "REACTIVE_WRITE_IN_OWNED_SCOPE" | "ACTION_CALLED_IN_OWNED_SCOPE" | "RUN_WITH_DISPOSED_OWNER" | "NO_OWNER_CLEANUP" | "CLEANUP_IN_FORBIDDEN_SCOPE" | "SETTLED_CLEANUP_UNOWNED" | "SETTLE_WALK_UNINITIALIZED_SOURCE" | "FLUSH_IN_EFFECT_CALLBACK" | "PRIMITIVE_IN_FORBIDDEN_SCOPE" | "NO_OWNER_EFFECT" | "NO_OWNER_BOUNDARY" | "ASYNC_OUTSIDE_LOADING_BOUNDARY" | "INVALID_REFRESH_TARGET" | "INVALID_AFFECTS_TARGET" | "MISSING_EFFECT_FN" | "SYNC_NODE_RECEIVED_ASYNC" | "REACTIVITY_HALTED" | "INVARIANT_VIOLATION" | "HUGE_FAN_OUT" | "HUGE_FAN_IN" | "HOT_SCOPE_RERUNS" | "HOT_SCOPE_TIME" | "WIDE_SCOPE_DEPS" | "UNSTABLE_MEMO_OUTPUT" | "WIDE_WRITE" | "ASYNC_WATERFALL" | "HOT_SCOPE_FANOUT";
11
17
  export type DiagnosticKind = "strict-read" | "async" | "write" | "lifecycle" | "owner" | "error" | "perf" | "graph";
12
18
  /** First warning when a node's live edge count reaches this size. */
13
19
  export declare const GRAPH_SIZE_WARN_AT = 2000;
@@ -209,6 +209,11 @@ export declare const globalQueue: GlobalQueue;
209
209
  */
210
210
  export declare function flush(): void;
211
211
  export declare function flush<T>(fn: () => T): T;
212
+ /** A fresh, unentered transaction (#3146): the optimistic store's truth
213
+ * flight DECLARES an owned transaction instead of relying on whatever the
214
+ * ambient adoption machinery stamped on its firewall. Activate it with
215
+ * initTransition; it is a plain batch until then. */
216
+ export declare function createTransition(): Transition;
212
217
  export declare function currentTransition(transition: Transition): Transition;
213
218
  export declare function setActiveTransition(transition: Transition | null): void;
214
219
  export declare function runInTransition<T>(transition: Transition, fn: () => T): T;
@@ -365,17 +365,6 @@ export declare function createMemo<T>(compute: ComputeFunction<undefined | NoInf
365
365
  * @description https://docs.solidjs.com/reference/basic-reactivity/create-effect
366
366
  */
367
367
  export declare function createEffect<T>(compute: ComputeFunction<undefined | NoInfer<T>, T>, effectFn: EffectFunction<NoInfer<T>, T> | EffectBundle<NoInfer<T>, T>, options?: EffectOptions): void;
368
- /**
369
- * @deprecated `createEffect(compute)` (single argument) is no longer supported.
370
- * Pass a separate effect function as the second argument:
371
- * `createEffect(compute, effect)`. See [MISSING_EFFECT_FN].
372
- *
373
- * - For a side effect that reacts to changes, split the work:
374
- * `createEffect(() => signal(), value => doWork(value))`.
375
- * - For a derived value, use `createMemo(() => signal())`.
376
- * - For a one-shot side effect at construction time, just call the function.
377
- */
378
- export declare function createEffect<T>(compute: ComputeFunction<undefined | NoInfer<T>, T>): never;
379
368
  /**
380
369
  * Creates a reactive computation that runs during the render phase as DOM elements
381
370
  * are created and updated but not necessarily connected.