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

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.
Files changed (53) hide show
  1. package/dist/dev.js +1169 -186
  2. package/dist/node.cjs +2041 -1167
  3. package/dist/prod/boundaries.js +4 -1
  4. package/dist/prod/core/async.js +124 -95
  5. package/dist/prod/core/constants.js +55 -1
  6. package/dist/prod/core/core.js +297 -222
  7. package/dist/prod/core/effect.js +28 -28
  8. package/dist/prod/core/error.js +13 -1
  9. package/dist/prod/core/external.js +2 -2
  10. package/dist/prod/core/graph.js +27 -27
  11. package/dist/prod/core/heap.js +30 -30
  12. package/dist/prod/core/lanes.js +32 -32
  13. package/dist/prod/core/optimistic.js +54 -54
  14. package/dist/prod/core/owner.js +34 -34
  15. package/dist/prod/core/scheduler.js +318 -137
  16. package/dist/prod/core/verdict.js +112 -59
  17. package/dist/prod/index.js +3 -3
  18. package/dist/prod/map.js +106 -106
  19. package/dist/prod/signals.js +253 -25
  20. package/dist/prod/store/next/optimistic.js +262 -123
  21. package/dist/prod/store/next/patch.js +6 -6
  22. package/dist/prod/store/next/projection.js +23 -19
  23. package/dist/prod/store/next/store.js +129 -35
  24. package/dist/prod/store/store.js +2 -2
  25. package/dist/types/core/async.d.ts +2 -0
  26. package/dist/types/core/attribution.d.ts +9 -4
  27. package/dist/types/core/constants.d.ts +54 -0
  28. package/dist/types/core/core.d.ts +19 -20
  29. package/dist/types/core/error.d.ts +9 -0
  30. package/dist/types/core/index.d.ts +2 -2
  31. package/dist/types/core/scheduler.d.ts +34 -0
  32. package/dist/types/core/types.d.ts +12 -0
  33. package/dist/types/index.d.ts +3 -3
  34. package/dist/types/signals.d.ts +108 -0
  35. package/dist/types/store/next/optimistic.d.ts +13 -10
  36. package/dist/types/store/next/projection.d.ts +1 -1
  37. package/dist/types/store/next/store.d.ts +22 -0
  38. package/dist/types/store/next/target.d.ts +15 -0
  39. package/dist/types-cjs/core/async.d.cts +2 -0
  40. package/dist/types-cjs/core/attribution.d.cts +9 -4
  41. package/dist/types-cjs/core/constants.d.cts +54 -0
  42. package/dist/types-cjs/core/core.d.cts +19 -20
  43. package/dist/types-cjs/core/error.d.cts +9 -0
  44. package/dist/types-cjs/core/index.d.cts +2 -2
  45. package/dist/types-cjs/core/scheduler.d.cts +34 -0
  46. package/dist/types-cjs/core/types.d.cts +12 -0
  47. package/dist/types-cjs/index.d.cts +3 -3
  48. package/dist/types-cjs/signals.d.cts +108 -0
  49. package/dist/types-cjs/store/next/optimistic.d.cts +13 -10
  50. package/dist/types-cjs/store/next/projection.d.cts +1 -1
  51. package/dist/types-cjs/store/next/store.d.cts +22 -0
  52. package/dist/types-cjs/store/next/target.d.cts +15 -0
  53. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
- import { unwrapOverride, $REFRESH, CONFIG_OWNED_WRITE, NOT_PENDING, CONFIG_OPTIMISTIC, STATUS_UNINITIALIZED, STATUS_ERROR, CONFIG_CHILDREN_FORBIDDEN } from "../../core/constants.js";
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";
2
2
 
3
- import { setSignal, isEqual, read, pendingCheckActive, latestReadActive, readNodeFast, READ_SLOW, signal, ext, setLatestReadActive, prepareComputed } from "../../core/core.js";
3
+ import { setSignal, isEqual, read, pendingCheckActive, latestReadActive, readNodeFast, READ_SLOW, signal, ext, setLatestReadActive, prepareComputed, context } from "../../core/core.js";
4
4
 
5
5
  import { projectionWriteActive, setProjectionWriteActive, activeTransition, schedule, currentTransition, setStoreCommitHook } from "../../core/scheduler.js";
6
6
 
@@ -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).De = NOT_PENDING;
196
+ ext(o).Oe = 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).De = NOT_PENDING;
229
+ ext(o).Oe = 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).De = NOT_PENDING;
250
+ ext(n).Oe = 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).De = NOT_PENDING;
270
+ ext(n).Oe = NOT_PENDING;
271
271
  n.T |= CONFIG_OPTIMISTIC;
272
272
  }
273
273
  if (affectsScopesLive()) inheritAffectsMarks(n, e.v, $TRACK);
@@ -350,8 +350,22 @@ function cloneRaw(e, t) {
350
350
  }
351
351
 
352
352
  function ensurePB(e) {
353
- if (activeTransition !== null) foldBatches.set(e, activeTransition);
354
353
  let t = e.pb;
354
+ // Truth-staged backing hand-off (#3164 fold): a TENTATIVE draft opening on
355
+ // a target whose pending backing is truth-staged (a landing folded into a
356
+ // retaining transaction — it carries a foldBatches stamp) must not share
357
+ // the container. Tentative writes would pollute staged truth, and the
358
+ // tentative discard (notifyOptimisticWrites nulls pb) would destroy the
359
+ // landing. Park the staged backing and open a fresh draft seeded from the
360
+ // optimistic view below; the tentative discard restores it. The
361
+ // tentativePBs guard scopes this to draft OPEN: the draft's own backing
362
+ // (foldBatches-stamped by its first write when an action's transition is
363
+ // ambient) must not be parked by its own later writes.
364
+ if (t !== null && !tentativePBs.has(t) && e.fam?.opt === true && !projectionWriteActive && !getWriteOverride() && foldBatches.has(e)) {
365
+ stagedTruthPB.set(e, t);
366
+ t = e.pb = null;
367
+ }
368
+ if (activeTransition !== null) foldBatches.set(e, activeTransition);
355
369
  if (t === null) {
356
370
  // Prototype-chain overlay (#3044): plain-data non-array containers
357
371
  // outside projection/optimistic families open drafts in O(1) — own keys
@@ -369,18 +383,19 @@ function ensurePB(e) {
369
383
  // seed from committed truth — seeding overrides there would fold a lane
370
384
  // value into the committed home ("authority wins at reveal" would break).
371
385
  if (e.fam?.opt && !projectionWriteActive && !getWriteOverride()) {
386
+ tentativePBs.add(t);
372
387
  const n = e.n;
373
388
  if (n !== null) {
374
389
  for (const e of Reflect.ownKeys(n)) {
375
390
  const r = n[e];
376
- if (hasActiveOverride(r)) t[e] = unwrapOverride(r.o?.De);
391
+ if (hasActiveOverride(r)) t[e] = unwrapOverride(r.o?.Oe);
377
392
  }
378
393
  }
379
394
  const r = e.h;
380
395
  if (r !== null) {
381
396
  for (const e of Reflect.ownKeys(r)) {
382
397
  const n = r[e];
383
- if (hasActiveOverride(n) && !unwrapOverride(n.o?.De)) delete t[e];
398
+ if (hasActiveOverride(n) && !unwrapOverride(n.o?.Oe)) delete t[e];
384
399
  }
385
400
  }
386
401
  }
@@ -406,7 +421,7 @@ function ensurePB(e) {
406
421
  * foldHeld's node stamps). */ function heldMaskView(e) {
407
422
  const t = e.ht;
408
423
  if (t === null) return null;
409
- if (t !== PLAIN_HOLD && currentTransition(t)?.fn === true) return e.ht = e.hv = null;
424
+ if (t !== PLAIN_HOLD && currentTransition(t)?.sn === true) return e.ht = e.hv = null;
410
425
  return e.hv;
411
426
  }
412
427
 
@@ -491,6 +506,17 @@ function queueFold(e) {
491
506
  * Refreshed on every write; resolved through currentTransition at drain
492
507
  * (transitions merge — same rule as heldMaskView). */ const foldBatches = new WeakMap;
493
508
 
509
+ /** Parked truth-staged pending backings (#3164 fold): a tentative draft that
510
+ * opens while a folded landing's backing is live moves the staged container
511
+ * here (see ensurePB); the tentative discard in notifyOptimisticWrites
512
+ * restores it in place of the usual null. */ const stagedTruthPB = new WeakMap;
513
+
514
+ /** Backings opened by TENTATIVE drafts (optimistic user setters): ensurePB's
515
+ * truth-park must not fire against the draft's own container on its second
516
+ * and later writes (the first write stamps foldBatches whenever an action's
517
+ * transition is ambient). Entries die with their draft — tentative backings
518
+ * are consumed at setter exit. */ const tentativePBs = new WeakSet;
519
+
494
520
  /** Committed-time privatization for parent-chain slot updates (path copying). */ function privatizeCommitted(e) {
495
521
  if (ownedRaw.has(e.v)) return;
496
522
  const t = cloneRaw(e.v, e);
@@ -525,7 +551,7 @@ function drainFolds() {
525
551
  // keys; observed keys also hit the pending-node held check below).
526
552
  const e = foldBatches.get(t);
527
553
  if (e !== undefined) {
528
- if (currentTransition(e).fn === false) {
554
+ if (currentTransition(e).sn === false) {
529
555
  foldOlds.set(t, n);
530
556
  continue;
531
557
  }
@@ -781,7 +807,13 @@ function drainFolds() {
781
807
  // IMMEDIATE — landed truth shows to untracked readers even while a
782
808
  // downstream consumer's own async still holds the effect-level reveal
783
809
  // (spec-async "verdicts never inherit consumers' in-flight state").
784
- if (e.fam !== null && e.pb !== null && getWriteOverride()) {
810
+ // EXCEPT under an active transaction (#3164 fold): a landing riding a
811
+ // retaining transaction (the optimistic module's aroundWrite binds it)
812
+ // stages instead — ensurePB stamped foldBatches, so the backing commits
813
+ // with the transaction and the reveal is atomic at settle. The pinned
814
+ // immediate-commit contract is stated over the no-transaction microtask
815
+ // posture, which `activeTransition === null` is exactly.
816
+ if (e.fam !== null && e.pb !== null && getWriteOverride() && activeTransition === null) {
785
817
  // Landed truth (post-await write-override): immediately visible to every
786
818
  // reader — any staged held view is superseded.
787
819
  if (e.ht !== null) e.ht = e.hv = null;
@@ -976,7 +1008,7 @@ const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
976
1008
  * / onSettled callbacks) get COMMITTED visibility (#3006), same as core. */ function inOwnerContext() {
977
1009
  const e = getOwner();
978
1010
  if (e === null) return false;
979
- const t = e.Ct ? e.Ot : e;
1011
+ const t = e.Gt ? e.Dt : e;
980
1012
  return t != null && !(t.T & CONFIG_CHILDREN_FORBIDDEN);
981
1013
  }
982
1014
 
@@ -987,7 +1019,7 @@ const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
987
1019
  * committed to them regardless of staged writes. */ function inForbiddenScope() {
988
1020
  const e = getOwner();
989
1021
  if (e === null) return false;
990
- const t = e.Ct ? e.Ot : e;
1022
+ const t = e.Gt ? e.Dt : e;
991
1023
  return t != null && !!(t.T & CONFIG_CHILDREN_FORBIDDEN);
992
1024
  }
993
1025
 
@@ -998,7 +1030,7 @@ const UNSAFE_KEYS = new Set([ "__proto__", "prototype", "constructor" ]);
998
1030
  if (t === null) return false;
999
1031
  for (const e of Reflect.ownKeys(t)) {
1000
1032
  const n = t[e];
1001
- if (n.Pe !== NOT_PENDING && n._e != null && n._e.fn !== true) return true;
1033
+ if (n.Pe !== NOT_PENDING && n.Ae != null && n.Ae.sn !== true) return true;
1002
1034
  }
1003
1035
  return false;
1004
1036
  }
@@ -1018,16 +1050,37 @@ function readSource(e) {
1018
1050
  // servable). Drafts (setter window OR projection write-override) and
1019
1051
  // owner-context reads see the pending backing; context-free reads see
1020
1052
  // committed. Node reads apply the same rule, so both homes agree.
1021
- if (e.pb !== null && (inDraft(e) || getWriteOverride() || inOwnerContext() ||
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) ||
1022
1062
  // A projection's pending backing is authoritative-elect: serve it to
1023
1063
  // context-free readers too UNLESS a transition is holding the node
1024
1064
  // commits (downstream async hold — stale committed is the contract)
1025
1065
  // or the reader is a CHILDREN_FORBIDDEN scope, which never observes
1026
1066
  // its own unsettled write (#3082, signal parity per #3006).
1027
- e.fam !== null && !foldHeld(e) && !inForbiddenScope())) return e.pb;
1067
+ e.fam !== null && !heldTruthMasked(e) && !foldHeld(e) && !inForbiddenScope())) return e.pb;
1028
1068
  return e.v;
1029
1069
  }
1030
1070
 
1071
+ /** #3164 fold: HELD truth on an optimistic family — a pending backing
1072
+ * stamped by a live transition that retains optimism — is masked from
1073
+ * ordinary readers (they keep committed until the transaction's reveal);
1074
+ * the authoritative postures and latest() tunnel through. Un-stamped
1075
+ * backings and optimism-free transitions keep ordinary mid-batch/
1076
+ * speculation visibility. */ function heldTruthMasked(e) {
1077
+ if (e.fam?.opt !== true || latestReadActive || authoritativeServe()) return false;
1078
+ const t = foldBatches.get(e);
1079
+ // opt families are only created by createOptimisticStore, whose module
1080
+ // install populates optHooks — the assertion holds by construction.
1081
+ return t !== undefined && optHooks.retainsOptimism(t);
1082
+ }
1083
+
1031
1084
  const hasOwn = Object.prototype.hasOwnProperty;
1032
1085
 
1033
1086
  // Allocation-free own-accessor probe (replaces eager descriptor scans — the
@@ -1057,7 +1110,27 @@ function isOwnAccessor(e, t) {
1057
1110
 
1058
1111
  /** Active optimistic override on an armed node (armed slot idles at
1059
1112
  * NOT_PENDING; undefined = unarmed plain node). */ function hasActiveOverride(e) {
1060
- return e.o?.De !== undefined && e.o?.De !== NOT_PENDING;
1113
+ return e.o?.Oe !== undefined && e.o?.Oe !== NOT_PENDING;
1114
+ }
1115
+
1116
+ /** The reading computation is until()'s authoritative-view predicate — same
1117
+ * source of truth as core read()'s A17 carve-out (`context`, which persists
1118
+ * under untrack). optimisticView()'s composition gate consults exactly this:
1119
+ * write-side machinery (patch emission, tentative re-application) must keep
1120
+ * composing even when it runs inside an authoritative-write bracket. */ function authoritativeRead() {
1121
+ const e = context;
1122
+ return e !== null && (e.T & CONFIG_AUTHORITATIVE_READ) !== 0;
1123
+ }
1124
+
1125
+ /** Serve-side authoritative gate: until()'s predicate PLUS truth authors —
1126
+ * the projection derive's draft (wrapDraft trap brackets, runAuthoritative;
1127
+ * the same posture pair ensurePB classifies drafts by). A source computing
1128
+ * the next truth must never read its callers' tentative overlays: a derive
1129
+ * continuation's `store.push` computing its index from an action's
1130
+ * optimistic row landed truth in the wrong slot and corrupted committed
1131
+ * state (#3108). Trap-level overlay serves gate on this so values, length,
1132
+ * membership, and keys leave the authoritative view together. */ function authoritativeServe() {
1133
+ return projectionWriteActive || getWriteOverride() || authoritativeRead();
1061
1134
  }
1062
1135
 
1063
1136
  /** Context-aware node view for reads outside tracking: active override >
@@ -1069,7 +1142,17 @@ function isOwnAccessor(e, t) {
1069
1142
  * keys, which are served by the trap, not the node). */ function nodeValue(e, t) {
1070
1143
  // latest() sees the in-flight parked value like an owner-context reader
1071
1144
  // does (#3075) — signal/memo parity for store-node-backed keys.
1072
- const n = hasActiveOverride(e) ? unwrapOverride(e.o?.De) : e.Pe !== NOT_PENDING && (latestReadActive || inOwnerContext()) ? e.Pe : t;
1145
+ // Authoritative-view reads (until()'s predicate) skip the override arm
1146
+ // only: staged pending values are authoritative, overrides are the
1147
+ // caller's optimism.
1148
+ const n = !authoritativeServe() && hasActiveOverride(e) ? unwrapOverride(e.o?.Oe) : e.Pe !== NOT_PENDING && (latestReadActive ||
1149
+ // Owner-context pending visibility — except HELD truth (#3164,
1150
+ // see CONFIG_HELD_TRUTH: fold-staged or entangle-stolen
1151
+ // confirming truth), which only authoritative/latest readers
1152
+ // see (core read()'s A17-for-held-truth twin; ordinary readers
1153
+ // keep committed until the transaction's reveal — latest() is
1154
+ // exempted by the leading arm above).
1155
+ (inOwnerContext() || authoritativeServe()) && !(e.T & CONFIG_HELD_TRUTH && !authoritativeServe())) ? e.Pe : t;
1073
1156
  return n === FORCE ? t : n;
1074
1157
  }
1075
1158
 
@@ -1094,15 +1177,20 @@ function isOwnAccessor(e, t) {
1094
1177
  read(getNode(e, t, n));
1095
1178
  }
1096
1179
  }
1097
- return optHooks.optimisticView(e, r).length;
1180
+ // Truth authors read the backing's own length — an optimistic row from
1181
+ // the caller's transaction must not shift where the author's next write
1182
+ // lands (#3108).
1183
+ return (authoritativeServe() ? r : optHooks.optimisticView(e, r)).length;
1098
1184
  }
1099
1185
  if (inDraft(e)) {
1100
1186
  // Optimistic drafts before their first write have no pending backing yet;
1101
1187
  // reads must still see the live optimistic view (compose, not clobber —
1102
1188
  // #2951). Once ensurePB runs, the seeded clone carries the view.
1103
- if (e.fam?.opt && e.pb === null) {
1189
+ // AUTHORITATIVE drafts (projection derive) never overlay ensurePB's
1190
+ // seeding rule, applied to the read side (#3108).
1191
+ if (e.fam?.opt && e.pb === null && !authoritativeServe()) {
1104
1192
  const n = e.n?.[t];
1105
- if (n !== undefined && hasActiveOverride(n)) f = unwrapOverride(n.o?.De);
1193
+ if (n !== undefined && hasActiveOverride(n)) f = unwrapOverride(n.o?.Oe);
1106
1194
  }
1107
1195
  } else {
1108
1196
  if (i !== undefined) {
@@ -1285,9 +1373,12 @@ const traps = {
1285
1373
  if (e.s) return serveShallow(e, t, r);
1286
1374
  return isWrappable(r) ? draftServe(e, wrapNext(r, e, t)) : r;
1287
1375
  }
1288
- } else if (f === undefined && inDraft(e) && e.fam?.opt && e.pb === null) {
1376
+ } else if (f === undefined && inDraft(e) && e.fam?.opt && e.pb === null &&
1377
+ // AUTHORITATIVE drafts (landing folds) never seed from overrides —
1378
+ // the caller's optimism is not truth (has-trap twin below).
1379
+ !authoritativeServe()) {
1289
1380
  const n = e.n?.[t];
1290
- if (n !== undefined && hasActiveOverride(n)) f = unwrapOverride(n.o?.De);
1381
+ if (n !== undefined && hasActiveOverride(n)) f = unwrapOverride(n.o?.Oe);
1291
1382
  }
1292
1383
  if (e.s) return serveShallow(e, t, f);
1293
1384
  return isWrappable(f) ? draftServe(e, wrapNext(f, e, t)) : f;
@@ -1307,15 +1398,17 @@ const traps = {
1307
1398
  if (!inDraft(e)) {
1308
1399
  if (getObserver() !== null) {
1309
1400
  const n = getHasNode(e, t, r);
1310
- const i = read(n);
1401
+ // Authoritative-view readers get the right answer for free: core read()
1402
+ // skips the override arm for them, so nv is authoritative presence.
1403
+ const i = read(n);
1311
1404
  if (hasActiveOverride(n)) r = !!i;
1312
- } else {
1405
+ } else if (!authoritativeServe()) {
1313
1406
  const n = e.h?.[t];
1314
- if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.De);
1407
+ if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.Oe);
1315
1408
  }
1316
- } else if (e.fam?.opt && e.pb === null) {
1409
+ } else if (e.fam?.opt && e.pb === null && !authoritativeServe()) {
1317
1410
  const n = e.h?.[t];
1318
- if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.De);
1411
+ if (n !== undefined && hasActiveOverride(n)) r = !!unwrapOverride(n.o?.Oe);
1319
1412
  }
1320
1413
  return r;
1321
1414
  },
@@ -1338,14 +1431,15 @@ const traps = {
1338
1431
  // Optimistic membership overlay: presence-node overrides add/remove keys
1339
1432
  // (per-transaction lifecycle rides the nodes — §6, FINDING-2's fix).
1340
1433
  // Draft reads before the first write overlay too (pb, once created, is
1341
- // seeded with the view).
1342
- if (e.fam?.opt && e.h !== null && (!inDraft(e) || e.pb === null)) {
1434
+ // seeded with the view). Authoritative-view reads (until()'s predicate,
1435
+ // truth-author drafts) skip the overlay.
1436
+ if (!authoritativeServe() && e.fam?.opt && e.h !== null && (!inDraft(e) || e.pb === null)) {
1343
1437
  let t = null;
1344
1438
  for (const r of Reflect.ownKeys(e.h)) {
1345
1439
  const i = e.h[r];
1346
1440
  if (!hasActiveOverride(i)) continue;
1347
1441
  t ??= new Set(n);
1348
- if (unwrapOverride(i.o?.De)) t.add(r); else t.delete(r);
1442
+ if (unwrapOverride(i.o?.Oe)) t.add(r); else t.delete(r);
1349
1443
  }
1350
1444
  if (t !== null) return [ ...t ];
1351
1445
  }
@@ -1360,10 +1454,10 @@ const traps = {
1360
1454
  if (e.del !== null && e.del.has(t)) return undefined;
1361
1455
  if (r === undefined) r = Object.getOwnPropertyDescriptor(e.v, t);
1362
1456
  }
1363
- if (e.fam?.opt && !inDraft(e)) {
1457
+ if (!authoritativeServe() && e.fam?.opt && !inDraft(e)) {
1364
1458
  const n = e.h?.[t];
1365
1459
  if (n !== undefined && hasActiveOverride(n)) {
1366
- if (!unwrapOverride(n.o?.De)) return undefined;
1460
+ if (!unwrapOverride(n.o?.Oe)) return undefined;
1367
1461
  // opt delete
1368
1462
  if (r === undefined) {
1369
1463
  const n = e.n?.[t];
@@ -1725,4 +1819,4 @@ function snapshotWalk(e, t, n) {
1725
1819
  return f ?? r;
1726
1820
  }
1727
1821
 
1728
- export { adoptPB, bumpDeep, createStoreNext, deepNext, getHasNode, getKeySetNode, getNode, hasAccessorFlag, hasActiveOverride, materializePB, notifyFold, notifyFoldTail, notifyKeyDiff, notifyKeyValue, pcOf, runAuthoritative, snapshotNext, storeHasFamily, storeHasOptimisticFamily, storeIsShallow, storeSetterNext, targetIsPlain, targetsEqual, unwrapValue, wrapNext };
1822
+ 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 };
@@ -252,7 +252,7 @@ s) {
252
252
  // Callers guard on `pendingCheckActive`, which only flips inside
253
253
  // isPending() — the verdict layer is loaded and its hook installed.
254
254
  const o = e[STORE_NODE]?.[$AFFECTS];
255
- if (o?.o?.t) GlobalQueue.wt(o);
255
+ if (o?.o?.t) GlobalQueue.Wt(o);
256
256
  if (affectsScopes.size) {
257
257
  // Chained backings (§7b): a wrapper's STORE_VALUE can be another store's
258
258
  // proxy — marks cover by identity of the BASE raw, so resolve the chain
@@ -263,7 +263,7 @@ s) {
263
263
  let t = r;
264
264
  for (;;) {
265
265
  if (s.scope.has(t)) {
266
- GlobalQueue.wt(e);
266
+ GlobalQueue.Wt(e);
267
267
  break;
268
268
  }
269
269
  const o = t?.[$TARGET];
@@ -16,6 +16,8 @@ export declare function releaseSettledDependents(el: Computed<any>): void;
16
16
  export declare function settleErroredDependents(el: Computed<any>, error: any): void;
17
17
  export declare function settlePendingSource(el: Computed<any>): void;
18
18
  export declare function isThenable<T>(value: T | PromiseLike<T>): value is PromiseLike<T>;
19
+ /** Fire and clear a node's iterator-flight cancellation hook (#3122). */
20
+ export declare function releaseFlightTeardown(el: Computed<any>): void;
19
21
  export declare function handleAsync<T>(el: Computed<T>, result: T | PromiseLike<T> | AsyncIterable<T>, setter?: (value: T) => void): T;
20
22
  export declare function clearStatus(el: Computed<any>, clearUninitialized?: boolean): void;
21
23
  export declare function notifyStatus(el: Computed<any>, status: number, error: any, blockStatus?: boolean, lane?: OptimisticLane): void;
@@ -60,11 +60,16 @@ export interface RerunEvent {
60
60
  /** Wall time of this run including nested recomputes (ms). */
61
61
  totalMs: number;
62
62
  /**
63
- * Whether the run committed a changed value. A PLAIN memo run with
63
+ * Whether the run produced a changed value. A PLAIN memo run with
64
64
  * `changed: false` was pure waste — the equality cutoff stopped it from
65
- * notifying anyone; an effect run with `changed: false` computed without
66
- * firing its effect phase. Summed as `wastedMs` in costs() (plain,
67
- * non-held runs only see `phase`).
65
+ * notifying anyone. Effects run with `_equals: false` in core (their
66
+ * effect phase re-fires on every recompute), so the engine derives this
67
+ * fact itself: an effect run whose compute output is identical to the
68
+ * previous run's reports `changed: false` — the phase re-fired with the
69
+ * same input, pure waste. Side-effect-only computes (`undefined` output)
70
+ * are exempt: identity of `undefined` proves nothing about their work.
71
+ * Summed as `wastedMs` in costs() (plain, non-held runs only — see
72
+ * `phase`).
68
73
  */
69
74
  changed: boolean;
70
75
  /**
@@ -51,6 +51,60 @@ export declare const CONFIG_CHILD_COMPANIONS: number;
51
51
  * moved into the cold extension (§12), and an unconditional `_x` deref per
52
52
  * marked node measurably taxed the propagation hot path (diamond -22%). */
53
53
  export declare const CONFIG_FW_CHILDREN: number;
54
+ /** Authoritative-view reader (`until()`): while this node computes, reads
55
+ * dodge active optimistic OVERRIDES only — the predicate must observe
56
+ * arriving truth, never the caller's own tentative writes (which would
57
+ * trivially satisfy it). Everything else reads normally, INCLUDING
58
+ * transition-staged `_pendingValue`: staged data is authoritative (optimism
59
+ * lives only in override slots), and a hold that refused staged reads would
60
+ * deadlock on data the open transaction itself is holding (a refresh the
61
+ * action issued lands staged and cannot commit until the hold releases).
62
+ * read() checks the bit on the reading computation (`context`) directly — no
63
+ * ambient flag — so a shared computed the predicate pulls recomputes as
64
+ * itself (no bit) under the normal view, and its cache never forks. */
65
+ export declare const CONFIG_AUTHORITATIVE_READ: number;
66
+ /** Sticky mark: an authoritative-view reader read this node PAST an active
67
+ * override. The ack shape — an authoritative arrival EQUAL to the override —
68
+ * rides paths that are deliberately silent under A17 (every ordinary reader
69
+ * sees the override, so an equal landing changes nothing for them). A marked
70
+ * node notifies those readers on such paths anyway, so the landed truth is
71
+ * seen without re-firing ordinary subscribers. Never cleared — only nodes an
72
+ * until() predicate observed mid-override pay. */
73
+ export declare const CONFIG_AUTHORITATIVE_OBSERVED: number;
74
+ /** Promise-delivery effect (resolve()/until()): commits its computed value
75
+ * directly even when recomputing under its own held transition. These
76
+ * effects deliver applies on a microtask (#2930) instead of the stashed
77
+ * effect queues, so the value must ride the same immediate schedule — a
78
+ * staged value with an immediate apply delivers stale state (resolve) or
79
+ * deadlocks the hold (until). Safe because the node is a private leaf: no
80
+ * subscriber reads an effect's value, only its own apply does. */
81
+ export declare const CONFIG_DIRECT_COMMIT: number;
82
+ /** Fresh-pull reader (awaitable `refresh()`'s waiter effect): a read of a
83
+ * dirty source recomputes it inline even when the height gate defers to the
84
+ * flush. Closes the same-flush ordering race where a waiter created
85
+ * alongside a refresh() mark read the PRE-re-ask value as settled and
86
+ * delivered stale; with the pull, the waiter either parks on the re-ask's
87
+ * pending window (async — woken by the settle walk, which runs on every
88
+ * landing including equal-value ones) or serves its sync answer. resolve()
89
+ * deliberately keeps that race — its contract is "first settled value"
90
+ * (#2930), not "next quiescent state". */
91
+ export declare const CONFIG_FRESH_READ: number;
92
+ /** HELD truth (#3164): this node's staged `_pendingValue` is confirming
93
+ * truth riding a transaction that retains optimism, revealed only at that
94
+ * transaction's settle. Two arming sites, one meaning: the store fold
95
+ * (a landing staged into the retaining transaction) and until()'s
96
+ * flip-entanglement (a foreign carrier's staged write, stolen when it
97
+ * flipped the awaited predicate truthy). Until the reveal, ordinary
98
+ * readers — lane and speculative recomputes included — keep committed:
99
+ * the staging notified subscribers as a plain write, so without the mask
100
+ * a mid-hold recompute composes live optimism with the confirming truth,
101
+ * a frame no timeline contains (GabbeV's union tear). Authoritative
102
+ * readers (until()'s predicate) and latest() tunnel through — the
103
+ * exemption that keeps holds deadlock-free. Override-covered nodes never
104
+ * arm: the override is their display and its revert their notification
105
+ * (A17). Cleared at commit (the commit IS the reveal); subscribers masked
106
+ * during the hold are woken by finalizePureQueue's post-revert pass. */
107
+ export declare const CONFIG_HELD_TRUTH: number;
54
108
  export declare const STATUS_NONE = 0;
55
109
  export declare const STATUS_PENDING: number;
56
110
  export declare const STATUS_ERROR: number;
@@ -1,4 +1,3 @@
1
- import { type Refreshable } from "./constants.js";
2
1
  import { type OptimisticLane } from "./lanes.js";
3
2
  import type { Computed, FirewallSignal, NodeExtension, NodeOptions, Owner, Signal } from "./types.js";
4
3
  export declare const PRIMITIVE_IN_FORBIDDEN_SCOPE_MESSAGE = "[PRIMITIVE_IN_FORBIDDEN_SCOPE] Cannot create reactive primitives inside createTrackedEffect or owner-backed onSettled";
@@ -107,6 +106,19 @@ export declare const READ_SLOW: unique symbol;
107
106
  * snapshot / transition / lane / dev-strictRead state all take the full
108
107
  * resolution. Anything slow returns READ_SLOW; the caller then calls read().
109
108
  */
109
+ /**
110
+ * Wake only authoritative-view readers (until() predicates) subscribed to `el`.
111
+ * The A17-silent ack paths — an authoritative arrival equal to the active
112
+ * override — use this so the predicate re-evaluates without re-firing
113
+ * ordinary subscribers whose visible (override) value did not change.
114
+ * Pay-for-use: reached through GlobalQueue._notifyAuthoritativeObservers,
115
+ * installed at first until() call — apps that never use until() shake it.
116
+ */
117
+ export declare function notifyAuthoritativeObservers(el: Signal<any> | Computed<any>): void;
118
+ /** Installs the until() machinery hook. Idempotent; called by until() before
119
+ * any authoritative-view read happens (same late-binding contract as the
120
+ * optimistic engine). */
121
+ export declare function installAuthoritativeRead(): void;
110
122
  export declare function readNodeFast<T>(el: Signal<T>): T | typeof READ_SLOW;
111
123
  export declare function read<T>(el: Signal<T> | Computed<T>): T;
112
124
  /**
@@ -155,23 +167,10 @@ export declare function setMemo<T>(el: Computed<T>, v: T | ((prev: T) => T)): T;
155
167
  export declare function runWithOwner<T>(owner: Owner | null, fn: () => T): T;
156
168
  export declare function staleValues<T>(fn: () => T, set?: boolean): T;
157
169
  /**
158
- * Invalidates one reactive source, forcing it to re-execute even if its inputs
159
- * haven't changed.
160
- *
161
- * Pass either a Solid-created accessor or a projected store created from
162
- * `createStore(fn, ...)` / `createProjection(...)`. `refresh()` is a
163
- * write-like invalidation operation: it does not read the target's value, and
164
- * refreshing a plain signal accessor is a no-op.
165
- *
166
- * Use it to invalidate cached async values (e.g. force a re-fetch) without
167
- * tearing the consumer down.
168
- *
169
- * @example
170
- * ```ts
171
- * const user = createMemo(async () => fetch(`/users/${id()}`).then(r => r.json()));
172
- *
173
- * // Re-fetch on demand
174
- * <button onClick={() => refresh(user)}>Reload</button>
175
- * ```
170
+ * Core marking half of `refresh()` (the public wrapper lives in signals.ts
171
+ * it validates the target, marks through here, then builds the quiescence
172
+ * promise on the resolve()/until() effect machinery). Flags the node's next
173
+ * recompute as a quiet re-ask and schedules it; no-ops for non-derived or
174
+ * disposed targets and for same-tick manual writes.
176
175
  */
177
- export declare function refresh<T>(target: Refreshable<T>): void;
176
+ export declare function markRefresh(node: Computed<any>): void;
@@ -39,6 +39,15 @@ export declare class StatusError extends Error {
39
39
  }
40
40
  /** Return the user's error from an internal status wrapper. */
41
41
  export declare function unwrapStatusError(error: unknown): unknown;
42
+ /**
43
+ * Rejection value of `until(fn, { timeout })` when the predicate does not turn
44
+ * truthy within the window. Inside an `action()`, the rejection is thrown back
45
+ * in at the `yield` point — catchable there, or the action fails and its
46
+ * optimistic state reverts.
47
+ */
48
+ export declare class TimeoutError extends Error {
49
+ constructor(message?: string);
50
+ }
42
51
  export declare class NoOwnerError extends Error {
43
52
  constructor();
44
53
  }
@@ -1,5 +1,5 @@
1
- export { ContextNotFoundError, NoOwnerError, NotReadyError } from "./error.js";
2
- export { isEqual, untrack, runWithOwner, computed, signal, read, setSignal, setMemo, suppressComputedRecompute, optimisticSignal, optimisticComputed, refresh, staleValues, setSnapshotCapture, markSnapshotScope, releaseSnapshotScope, clearSnapshots } from "./core.js";
1
+ export { ContextNotFoundError, NoOwnerError, NotReadyError, TimeoutError } from "./error.js";
2
+ export { isEqual, untrack, runWithOwner, computed, signal, read, setSignal, setMemo, suppressComputedRecompute, optimisticSignal, optimisticComputed, installAuthoritativeRead, markRefresh, staleValues, setSnapshotCapture, markSnapshotScope, releaseSnapshotScope, clearSnapshots } from "./core.js";
3
3
  export { enableExternalSource, _resetExternalSourceConfig, type ExternalSourceFactory, type ExternalSource, type ExternalSourceConfig } from "./external.js";
4
4
  export { createOwner, createRoot, dispose, getNextChildId, getObserver, getOwner, isDisposed, cleanup, peekNextChildId } from "./owner.js";
5
5
  export { createContext, getContext, setContext, type Context, type ContextRecord } from "./context.js";
@@ -40,6 +40,24 @@ export interface Transition {
40
40
  _done: boolean | Transition;
41
41
  _gatedSubs: Set<Computed<any>>;
42
42
  }
43
+ /**
44
+ * Flip-entanglement (#3164 follow-up): `until()` is a declaration of
45
+ * relatedness — the predicate names the condition that confirms the awaiting
46
+ * transaction. When the predicate settles truthy, every live foreign
47
+ * transition whose staged write it read IS the confirming event by the
48
+ * user's own definition, so it merges into the awaiting transaction and
49
+ * reveals at the joint settle — the cross-primitive twin of the family fold
50
+ * (a landing on an optimism-carrying family joins the retaining
51
+ * transaction). Non-flipping updates never pass through here: falsy
52
+ * evaluations don't entangle, so unrelated traffic on the watched sources
53
+ * reveals freely on its own schedule.
54
+ *
55
+ * Runs inside the predicate's compute (pure phase) — the confirming
56
+ * transition's stamps are still live and its commit decision hasn't run, so
57
+ * the merge lands before any reveal. Only the tree-shaken graphs that call
58
+ * `until()` retain this.
59
+ */
60
+ export declare function entangleConfirmingTransitions(obs: Computed<any>, target: Transition): void;
43
61
  export declare function schedule(): void;
44
62
  /**
45
63
  * Permanently halts the reactive system. Called when a user error escapes
@@ -112,6 +130,10 @@ export declare class GlobalQueue extends Queue {
112
130
  static _laneReadsCommitted: ((el: OptimisticNode, owner: OptimisticNode, c: Computed<any>) => boolean) | null;
113
131
  static _recomputeLane: ((el: Computed<any>, own: boolean) => OptimisticLane | null | false) | null;
114
132
  static _laneAsyncPending: ((el: Computed<any>) => void) | null;
133
+ /** Authoritative-view reader wakeup (until()): installed at first until() call.
134
+ * Call sites are gated by CONFIG_AUTHORITATIVE_OBSERVED, which only until()'s
135
+ * carve-out read can set, so `!` invocations are safe once the gate holds. */
136
+ static _notifyAuthoritativeObservers: ((el: Signal<any> | Computed<any>) => void) | null;
115
137
  static _laneAsyncSettled: ((el: Computed<any>) => void) | null;
116
138
  static _trackOptimisticStore: ((store: any) => void) | null;
117
139
  flush(): void;
@@ -190,3 +212,15 @@ export declare function flush<T>(fn: () => T): T;
190
212
  export declare function currentTransition(transition: Transition): Transition;
191
213
  export declare function setActiveTransition(transition: Transition | null): void;
192
214
  export declare function runInTransition<T>(transition: Transition, fn: () => T): T;
215
+ /** Run `fn` with `transition` as BOTH the ambient transaction and the
216
+ * registration batch, restoring both after. runInTransition alone is not
217
+ * enough for code that WRITES on behalf of a transaction from inside someone
218
+ * else's window (optimistic replay re-arming a still-open action's edits
219
+ * during a landing commit, #3123): registrations route through the queue's
220
+ * batch pointer, and a bare activeTransition swap leaves them in the ambient
221
+ * batch — a plain batch "completes" at the next flush and reverts optimistic
222
+ * registrations that were supposed to live with the transaction.
223
+ * initTransition is the wrong tool here: it MERGES the currently ambient
224
+ * transaction into the target, entangling whatever the interrupted window
225
+ * belonged to. */
226
+ export declare function runAsTransitionBatch<T>(transition: Transition, fn: () => T): T;
@@ -27,6 +27,11 @@ export interface NodeOptions<T> {
27
27
  ownedWrite?: boolean;
28
28
  /** Exclude this signal from snapshot capture (internal — not part of public API) */
29
29
  _noSnapshot?: boolean;
30
+ /** Extra CONFIG_* bits OR'd into the node's config at creation (internal —
31
+ * not part of public API). Used by resolve()/until() for
32
+ * CONFIG_DIRECT_COMMIT / CONFIG_AUTHORITATIVE_READ, keeping the per-flag
33
+ * option arms out of the core creation path. */
34
+ _extraConfig?: number;
30
35
  unobserved?: () => void;
31
36
  lazy?: boolean;
32
37
  sync?: boolean;
@@ -73,6 +78,13 @@ export interface NodeExtension {
73
78
  */
74
79
  _affectsCount: number;
75
80
  _inFlight: PromiseLike<any> | AsyncIterable<any> | null;
81
+ /** Cancellation for the CURRENT iterator flight (#3122): closes the
82
+ * iterator (`it.return()`), idempotent. Fired at the sites that release
83
+ * `_inFlight` so a superseded stream stops at supersede time — its owner
84
+ * cleanup registration may ride the zombie-disposal channel, which a held
85
+ * transition defers until the SUPERSEDING flight settles. Null for plain
86
+ * promise flights (no cancellation hook exists). */
87
+ _flightTeardown: (() => void) | null;
76
88
  _error: unknown;
77
89
  _blocked: boolean | undefined;
78
90
  _pendingSources: Set<Computed<any>> | undefined;
@@ -1,9 +1,9 @@
1
- export { $REFRESH, ContextNotFoundError, NoOwnerError, NotReadyError, action, createContext, createOwner, createRoot, runWithOwner, flush, getNextChildId, peekNextChildId, getContext, setContext, getOwner, isDisposed, getObserver, isEqual, untrack, isPending, latest, refresh, SUPPORTS_PROXY, setSnapshotCapture, markSnapshotScope, releaseSnapshotScope, clearSnapshots, enforceLoadingBoundary, enableExternalSource, resetErrorHalt } from "./core/index.js";
1
+ export { $REFRESH, ContextNotFoundError, NoOwnerError, NotReadyError, TimeoutError, action, createContext, createOwner, createRoot, runWithOwner, flush, getNextChildId, peekNextChildId, getContext, setContext, getOwner, isDisposed, getObserver, isEqual, untrack, isPending, latest, SUPPORTS_PROXY, setSnapshotCapture, markSnapshotScope, releaseSnapshotScope, clearSnapshots, enforceLoadingBoundary, enableExternalSource, resetErrorHalt } from "./core/index.js";
2
2
  import { type Dev } from "./core/index.js";
3
3
  export declare const DEV: Dev | undefined;
4
4
  export type { Owner, Context, ContextRecord, IQueue, ExternalSourceFactory, ExternalSource, ExternalSourceConfig, Refreshable, Dev, DevHooks, DiagnosticCapture, DiagnosticCode, DiagnosticEvent, DiagnosticKind, Diagnostics, DiagnosticSeverity } from "./core/index.js";
5
- export { createSignal, createMemo, createEffect, createRenderEffect, createTrackedEffect, createReaction, createOptimistic, resolve, onSettled, onCleanup } from "./signals.js";
6
- export type { Accessor, SourceAccessor, Setter, Signal, ComputeFunction, EffectFunction, EffectBundle, EffectOptions, SignalOptions, MemoOptions, NoInfer } from "./signals.js";
5
+ export { createSignal, createMemo, createEffect, createRenderEffect, createTrackedEffect, createReaction, createOptimistic, refresh, resolve, until, onSettled, onCleanup } from "./signals.js";
6
+ export type { Truthy, UntilOptions, Accessor, SourceAccessor, Setter, Signal, ComputeFunction, EffectFunction, EffectBundle, EffectOptions, SignalOptions, MemoOptions, NoInfer } from "./signals.js";
7
7
  export { affects } from "./affects.js";
8
8
  export { mapArray, repeat, type Maybe } from "./map.js";
9
9
  export * from "./store/index.js";