@solidjs/signals 2.0.0-rc.7 → 2.0.0-rc.8

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 (117) hide show
  1. package/dist/dev-shared.js +5243 -0
  2. package/dist/dev.attribution.js +2333 -0
  3. package/dist/dev.js +1229 -6834
  4. package/dist/observe/affects.js +127 -0
  5. package/dist/observe/attribution.js +1 -0
  6. package/dist/observe/boundaries.js +586 -0
  7. package/dist/observe/core/action.js +167 -0
  8. package/dist/observe/core/async.js +736 -0
  9. package/dist/observe/core/attribution-hooks.js +60 -0
  10. package/dist/observe/core/attribution.js +2318 -0
  11. package/dist/{types-cjs/core/constants.d.cts → observe/core/constants.js} +129 -73
  12. package/dist/{types-cjs/core/context.d.cts → observe/core/context.js} +37 -12
  13. package/dist/observe/core/core.js +1289 -0
  14. package/dist/observe/core/dev.js +232 -0
  15. package/dist/observe/core/effect.js +179 -0
  16. package/dist/observe/core/error.js +80 -0
  17. package/dist/observe/core/external.js +98 -0
  18. package/dist/observe/core/graph.js +155 -0
  19. package/dist/observe/core/heap.js +147 -0
  20. package/dist/observe/core/invariants.js +43 -0
  21. package/dist/observe/core/lanes.js +168 -0
  22. package/dist/observe/core/optimistic.js +438 -0
  23. package/dist/observe/core/owner.js +308 -0
  24. package/dist/observe/core/scheduler.js +1190 -0
  25. package/dist/observe/core/verdict.js +551 -0
  26. package/dist/observe/index.js +55 -0
  27. package/dist/observe/map.js +482 -0
  28. package/dist/{types-cjs/signals.d.cts → observe/signals.js} +295 -317
  29. package/dist/observe/store/index.js +38 -0
  30. package/dist/observe/store/next/optimistic.js +651 -0
  31. package/dist/observe/store/next/projection.js +242 -0
  32. package/dist/observe/store/next/reconcile.js +373 -0
  33. package/dist/observe/store/next/store.js +2213 -0
  34. package/dist/observe/store/next/target.js +51 -0
  35. package/dist/observe/store/store.js +323 -0
  36. package/dist/observe/store/storePath.js +103 -0
  37. package/dist/observe/store/utils.js +210 -0
  38. package/dist/prod/attribution.js +32 -0
  39. package/dist/prod/core/action.js +52 -32
  40. package/dist/prod/core/async.js +171 -127
  41. package/dist/prod/core/constants.js +24 -1
  42. package/dist/prod/core/context.js +3 -3
  43. package/dist/prod/core/core.js +434 -291
  44. package/dist/prod/core/effect.js +54 -34
  45. package/dist/prod/core/external.js +2 -2
  46. package/dist/prod/core/graph.js +32 -32
  47. package/dist/prod/core/heap.js +49 -44
  48. package/dist/prod/core/lanes.js +45 -38
  49. package/dist/prod/core/optimistic.js +207 -73
  50. package/dist/prod/core/owner.js +43 -39
  51. package/dist/prod/core/scheduler.js +319 -190
  52. package/dist/prod/core/verdict.js +67 -67
  53. package/dist/prod/index.js +9 -3
  54. package/dist/prod/map.js +292 -141
  55. package/dist/prod/signals.js +7 -10
  56. package/dist/prod/store/next/optimistic.js +140 -125
  57. package/dist/prod/store/next/projection.js +3 -3
  58. package/dist/prod/store/next/reconcile.js +87 -84
  59. package/dist/prod/store/next/store.js +524 -280
  60. package/dist/prod/store/next/target.js +32 -10
  61. package/dist/prod/store/store.js +12 -14
  62. package/dist/prod/store/utils.js +36 -27
  63. package/dist/types/attribution.d.ts +15 -0
  64. package/dist/types/attribution.prod.d.ts +13 -0
  65. package/dist/types/core/async.d.ts +1 -1
  66. package/dist/types/core/attribution-hooks.d.ts +119 -6
  67. package/dist/types/core/attribution.d.ts +208 -53
  68. package/dist/types/core/constants.d.ts +23 -0
  69. package/dist/types/core/core.d.ts +7 -0
  70. package/dist/types/core/dev.d.ts +124 -26
  71. package/dist/types/core/effect.d.ts +8 -1
  72. package/dist/types/core/index.d.ts +2 -1
  73. package/dist/types/core/lanes.d.ts +8 -1
  74. package/dist/types/core/scheduler.d.ts +60 -0
  75. package/dist/types/core/types.d.ts +29 -9
  76. package/dist/types/index.d.ts +9 -2
  77. package/dist/types/map.d.ts +2 -0
  78. package/dist/types/store/index.d.ts +1 -0
  79. package/dist/types/store/next/store.d.ts +5 -0
  80. package/dist/types/store/next/target.d.ts +36 -9
  81. package/dist/types/store/utils.d.ts +7 -0
  82. package/package.json +21 -17
  83. package/dist/node.cjs +0 -10541
  84. package/dist/node.dev.cjs +0 -13724
  85. package/dist/types-cjs/affects.d.cts +0 -47
  86. package/dist/types-cjs/boundaries.d.cts +0 -175
  87. package/dist/types-cjs/core/action.d.cts +0 -64
  88. package/dist/types-cjs/core/async.d.cts +0 -23
  89. package/dist/types-cjs/core/attribution-hooks.d.cts +0 -127
  90. package/dist/types-cjs/core/attribution.d.cts +0 -495
  91. package/dist/types-cjs/core/core.d.cts +0 -185
  92. package/dist/types-cjs/core/dev.d.cts +0 -136
  93. package/dist/types-cjs/core/effect.d.cts +0 -30
  94. package/dist/types-cjs/core/error.d.cts +0 -56
  95. package/dist/types-cjs/core/external.d.cts +0 -15
  96. package/dist/types-cjs/core/graph.d.cts +0 -28
  97. package/dist/types-cjs/core/heap.d.cts +0 -24
  98. package/dist/types-cjs/core/index.d.cts +0 -13
  99. package/dist/types-cjs/core/invariants.d.cts +0 -59
  100. package/dist/types-cjs/core/lanes.d.cts +0 -54
  101. package/dist/types-cjs/core/optimistic.d.cts +0 -6
  102. package/dist/types-cjs/core/owner.d.cts +0 -124
  103. package/dist/types-cjs/core/scheduler.d.cts +0 -236
  104. package/dist/types-cjs/core/types.d.cts +0 -204
  105. package/dist/types-cjs/core/verdict.d.cts +0 -2
  106. package/dist/types-cjs/index.d.cts +0 -10
  107. package/dist/types-cjs/map.d.cts +0 -69
  108. package/dist/types-cjs/package.json +0 -3
  109. package/dist/types-cjs/store/index.d.cts +0 -18
  110. package/dist/types-cjs/store/next/optimistic.d.cts +0 -25
  111. package/dist/types-cjs/store/next/projection.d.cts +0 -8
  112. package/dist/types-cjs/store/next/reconcile.d.cts +0 -10
  113. package/dist/types-cjs/store/next/store.d.cts +0 -121
  114. package/dist/types-cjs/store/next/target.d.cts +0 -166
  115. package/dist/types-cjs/store/store.d.cts +0 -143
  116. package/dist/types-cjs/store/storePath.d.cts +0 -58
  117. package/dist/types-cjs/store/utils.d.cts +0 -74
@@ -1,11 +1,33 @@
1
1
  /**
2
- * Ownership (first cut, decision 2026-08-16d): one WeakSet of store-owned
3
- * backings serving both the production identity-skip guard and the false
4
- * no-mutation oracle.
2
+ * Ownership stamp (#3360): every backing the store ALLOCATES (CoW clones,
3
+ * privatized committed backings) carries its owning target under this
4
+ * enumerable symbol. One property write replaces the two weak-collection
5
+ * registrations (ownership set + raw→target map) a fresh object used to pay
6
+ * per draft — V8's identity-hash + ephemeron cost dominated the one-key
7
+ * write floor. Enumerable so a spread copy (the plain-data clone path) stays
8
+ * on the fast path and carries the stamp along.
9
+ *
10
+ * Owned backings are never user-reachable (`snapshot` copies them, the traps
11
+ * hide the key), so every raw key walk in the store must skip `$OWNER`, and
12
+ * ownership is answered by `isOwned` — a user object never carries it.
13
+ * Overlay drafts (`Object.create(v)` over an owned `v`) inherit the stamp.
5
14
  */
6
- const ownedRaw = new WeakSet;
15
+ const $OWNER = Symbol(0);
7
16
 
8
- /** raw → target. The only raw-keyed lookup; boundary mechanism (O8). */ const storeNextLookup = new WeakMap;
17
+ /** raw → target for UNOWNED backings (user-ingested, adopted); owned
18
+ * backings resolve through their `$OWNER` stamp. Boundary mechanism (O8). */ const storeNextLookup = new WeakMap;
19
+
20
+ /** A backing the store allocated and may mutate in place. */ function isOwned(e) {
21
+ return e[$OWNER] !== undefined;
22
+ }
23
+
24
+ /** raw → target within a family (`null` = plain stores / the global map).
25
+ * The stamp answers for backings owned by a target OF THAT FAMILY; anything
26
+ * else (user objects, adoptees, another family's backings the family
27
+ * re-registered for its own wrapper) resolves through the family's map. */ function lookupTarget(e, o) {
28
+ const t = e[$OWNER];
29
+ return t !== undefined && t.fam === o ? t : (o?.map ?? storeNextLookup).get(e);
30
+ }
9
31
 
10
32
  function devAssertNeverUserMutation(e) {
11
33
  return;
@@ -19,11 +41,11 @@ function setOptHooks(e) {
19
41
 
20
42
  /** Sticky descendants flag walk (§6d): reconcile's keyed pruning descends
21
43
  * only where subscriptions exist at/below. Nodes AND patches count. */ function markDescendants(e) {
22
- let t = e;
23
- while (t && !t.d) {
24
- t.d = true;
25
- t = t.u;
44
+ let o = e;
45
+ while (o && !o.d) {
46
+ o.d = true;
47
+ o = o.u;
26
48
  }
27
49
  }
28
50
 
29
- export { devAssertNeverUserMutation, markDescendants, optHooks, ownedRaw, setOptHooks, storeNextLookup };
51
+ export { $OWNER, devAssertNeverUserMutation, isOwned, lookupTarget, markDescendants, optHooks, setOptHooks, storeNextLookup };
@@ -8,7 +8,7 @@ import "../core/verdict.js";
8
8
 
9
9
  import "../core/effect.js";
10
10
 
11
- import { storeNextLookup } from "./next/target.js";
11
+ import { $OWNER, lookupTarget as lookupTarget$1 } from "./next/target.js";
12
12
 
13
13
  /**
14
14
  * Brand symbols used internally by the store proxy / projection plumbing.
@@ -24,13 +24,9 @@ $AFFECTS = Symbol(0);
24
24
  const STORE_VALUE = "v", STORE_NODE = "n", STORE_HAS = "h";
25
25
 
26
26
  function lookupTarget(e, t) {
27
- // Family maps (projections/optimistic) map raw -> target; the global next
28
- // lookup maps raw -> target too. Proxies resolve through $TARGET directly.
29
- if (t !== undefined) {
30
- const o = t.get(e);
31
- if (o !== undefined) return o[$TARGET] ?? o;
32
- }
33
- return storeNextLookup.get(e);
27
+ // Family registrations (projections/optimistic) first, then the global
28
+ // next lookup. Proxies resolve through $TARGET directly.
29
+ return (t ? lookupTarget$1(e, t) : undefined) ?? lookupTarget$1(e, null);
34
30
  }
35
31
 
36
32
  // Values marked raw never acquire a proxy identity: wrap() serves them as-is
@@ -202,9 +198,9 @@ s) {
202
198
  // scope must mark them like any property node.
203
199
  if (f.k) o.push(f.k);
204
200
  if (f.dk) o.push(f.dk);
205
- // Carry the effective lookup into untouched descendants (family maps for
206
- // projections/optimistic stores; the global next lookup otherwise).
207
- r = f.fam?.map ?? r ?? storeNextLookup;
201
+ // Carry the effective family into untouched descendants (projections/
202
+ // optimistic stores register children under their family).
203
+ r = f.fam ?? r;
208
204
  }
209
205
  // Overlays are gone (next has no layer): raw enumeration; the optimistic
210
206
  // view composition above already folded armed-node membership/values in.
@@ -214,6 +210,7 @@ s) {
214
210
  }
215
211
  const e = Object.getOwnPropertySymbols(n);
216
212
  for (let f = 0, c = e.length; f < c; f++) {
213
+ if (e[f] === $OWNER) continue;
217
214
  const c = Object.getOwnPropertyDescriptor(n, e[f]);
218
215
  if (!c || c.get) continue;
219
216
  walkAffectsScope(c.value, t, o, r, s);
@@ -221,6 +218,7 @@ s) {
221
218
  } else {
222
219
  const e = Reflect.ownKeys(n);
223
220
  for (let f = 0, c = e.length; f < c; f++) {
221
+ if (e[f] === $OWNER) continue;
224
222
  const c = Object.getOwnPropertyDescriptor(n, e[f]);
225
223
  if (!c || c.get) continue;
226
224
  walkAffectsScope(c.value, t, o, r, s);
@@ -252,7 +250,7 @@ s) {
252
250
  // Callers guard on `pendingCheckActive`, which only flips inside
253
251
  // isPending() — the verdict layer is loaded and its hook installed.
254
252
  const o = e[STORE_NODE]?.[$AFFECTS];
255
- if (o?.o?.t) GlobalQueue.zt(o);
253
+ if (o?.o?.t) GlobalQueue.ri(o);
256
254
  if (affectsScopes.size) {
257
255
  // Chained backings (§7b): a wrapper's STORE_VALUE can be another store's
258
256
  // proxy — marks cover by identity of the BASE raw, so resolve the chain
@@ -263,7 +261,7 @@ s) {
263
261
  let t = r;
264
262
  for (;;) {
265
263
  if (s.scope.has(t)) {
266
- GlobalQueue.zt(e);
264
+ GlobalQueue.ri(e);
267
265
  break;
268
266
  }
269
267
  const o = t?.[$TARGET];
@@ -302,7 +300,7 @@ s) {
302
300
  inherited: []
303
301
  });
304
302
  const r = [ t ];
305
- walkAffectsScope(e[$PROXY], o, r, e.fam?.map, new Set);
303
+ walkAffectsScope(e[$PROXY], o, r, e.fam, new Set);
306
304
  return r;
307
305
  }
308
306
  const o = e.n?.[t] ?? nextAffectsNodeResolver(e, t);
@@ -51,6 +51,15 @@ function resolveSource(e) {
51
51
 
52
52
  const $SOURCES = Symbol(0);
53
53
 
54
+ /** @internal The flattened sources behind a `merge()` PROXY, or undefined.
55
+ * Only the proxy form: its writes are no-ops, so the sources are the whole
56
+ * truth. merge()'s plain-object form also records `$SOURCES` (so nested
57
+ * merges flatten), but it is a real object callers may mutate afterwards
58
+ * (html's tagged templates assign props after spreading) — those own writes
59
+ * live on the object, not in the sources, so it must be read directly. */ function mergeSources(e) {
60
+ return e != null && e[$PROXY] === e ? e[$SOURCES] : undefined;
61
+ }
62
+
54
63
  /**
55
64
  * Merges multiple props-like objects into a single proxy that *preserves
56
65
  * reactivity*. Reads are forwarded to the right-most source that defines the
@@ -77,9 +86,9 @@ const $SOURCES = Symbol(0);
77
86
  for (let n = 0; n < e.length; n++) {
78
87
  const o = e[n];
79
88
  r = r || !!o && $PROXY in o;
80
- const s = !!o && o[$SOURCES];
81
- if (s) {
82
- for (let e = 0; e < s.length; e++) t.push(s[e]);
89
+ const u = !!o && o[$SOURCES];
90
+ if (u) {
91
+ for (let e = 0; e < u.length; e++) t.push(u[e]);
83
92
  } else t.push(typeof o === "function" ? (r = true, createMemo(o)) : o);
84
93
  }
85
94
  if (SUPPORTS_PROXY && r) {
@@ -109,21 +118,21 @@ const $SOURCES = Symbol(0);
109
118
  }
110
119
  const n = Object.create(null);
111
120
  let o = false;
112
- let s = t.length - 1;
113
- for (let e = s; e >= 0; e--) {
121
+ let u = t.length - 1;
122
+ for (let e = u; e >= 0; e--) {
114
123
  const r = t[e];
115
124
  if (!r) {
116
- e === s && s--;
125
+ e === u && u--;
117
126
  continue;
118
127
  }
119
- const u = Object.getOwnPropertyNames(r);
120
- for (let t = u.length - 1; t >= 0; t--) {
121
- const c = u[t];
122
- if (c === "__proto__" || c === "constructor") continue;
123
- if (!n[c]) {
124
- o = o || e !== s;
125
- const t = Object.getOwnPropertyDescriptor(r, c);
126
- n[c] = t.get ? {
128
+ const c = Object.getOwnPropertyNames(r);
129
+ for (let t = c.length - 1; t >= 0; t--) {
130
+ const s = c[t];
131
+ if (s === "__proto__" || s === "constructor") continue;
132
+ if (!n[s]) {
133
+ o = o || e !== u;
134
+ const t = Object.getOwnPropertyDescriptor(r, s);
135
+ n[s] = t.get ? {
127
136
  enumerable: true,
128
137
  configurable: true,
129
138
  get: t.get.bind(r)
@@ -131,15 +140,15 @@ const $SOURCES = Symbol(0);
131
140
  }
132
141
  }
133
142
  }
134
- if (!o) return t[s];
135
- const u = {};
136
- const c = Object.keys(n);
137
- for (let e = c.length - 1; e >= 0; e--) {
138
- const r = c[e], t = n[r];
139
- if (t.get) Object.defineProperty(u, r, t); else u[r] = t.value;
143
+ if (!o) return t[u];
144
+ const c = {};
145
+ const s = Object.keys(n);
146
+ for (let e = s.length - 1; e >= 0; e--) {
147
+ const r = s[e], t = n[r];
148
+ if (t.get) Object.defineProperty(c, r, t); else c[r] = t.value;
140
149
  }
141
- u[$SOURCES] = t;
142
- return u;
150
+ c[$SOURCES] = t;
151
+ return c;
143
152
  }
144
153
 
145
154
  /**
@@ -189,13 +198,13 @@ const $SOURCES = Symbol(0);
189
198
  const t = {};
190
199
  const n = Object.getOwnPropertyNames(e);
191
200
  const o = r.length > 4 && n.length > r.length ? new Set(r) : undefined;
192
- for (const s of n) {
193
- if (o ? !o.has(s) : !r.includes(s)) {
194
- const r = Object.getOwnPropertyDescriptor(e, s);
195
- !r.get && !r.set && r.enumerable && r.writable && r.configurable ? t[s] = r.value : Object.defineProperty(t, s, r);
201
+ for (const u of n) {
202
+ if (o ? !o.has(u) : !r.includes(u)) {
203
+ const r = Object.getOwnPropertyDescriptor(e, u);
204
+ !r.get && !r.set && r.enumerable && r.writable && r.configurable ? t[u] = r.value : Object.defineProperty(t, u, r);
196
205
  }
197
206
  }
198
207
  return t;
199
208
  }
200
209
 
201
- export { merge, omit };
210
+ export { merge, mergeSources, omit };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * `@solidjs/signals/attribution` — the "why did this run" engine.
3
+ *
4
+ * A separate entry on purpose: the core ships only the hook slot
5
+ * (`OBSERVE.attribution.install`) and the interaction frame
6
+ * (`OBSERVE.attribution.withInteraction`); the engine that turns hook facts
7
+ * into re-run explanations, cost tables, holds and feedback lives here, so an
8
+ * observe build carries it only when something imports this module. The dev
9
+ * and observe tiers resolve to this file; the prod tier resolves to
10
+ * `attribution.prod.ts`, an inert engine with the same surface, so app code
11
+ * can import it unconditionally.
12
+ */
13
+ export { attribution } from "./core/attribution.js";
14
+ export type { Acknowledgement, Attribution, AttributionFeedbackTables, AttributionOptions, AttributionRecords, AttributionRecordType, ChangeKind, ChangeOrigin, ChangeRecord, FallbackStats, FeedbackInteraction, FeedbackNavigation, FeedbackSource, FlightLink, FlightStats, HeldWrite, HoldEvent, InteractionEvent, NavigationEvent, NavigationHop, RerunEvent, ScopeCost, WaterfallRecord, WriteCost } from "./core/attribution.js";
15
+ export type { InteractionRef, NavigationRef, OriginRef } from "./core/attribution-hooks.js";
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `@solidjs/signals/attribution` as the prod tier resolves it.
3
+ *
4
+ * A prod build has no hook sites — `__OBSERVE__` folded every one out — so an
5
+ * engine installed there would never hear a fact. Rather than make apps guard
6
+ * the import per tier, prod resolves this inert twin: the same `Attribution`
7
+ * surface, every query empty, `enable()` a no-op. Type-checked against the
8
+ * real engine's interface so the two cannot drift.
9
+ */
10
+ import type { Attribution } from "./core/attribution.js";
11
+ export declare const attribution: Attribution;
12
+ export type { Acknowledgement, Attribution, AttributionFeedbackTables, AttributionOptions, AttributionRecords, AttributionRecordType, ChangeKind, ChangeOrigin, ChangeRecord, FallbackStats, FeedbackInteraction, FeedbackNavigation, FeedbackSource, FlightLink, FlightStats, HeldWrite, HoldEvent, InteractionEvent, NavigationEvent, NavigationHop, RerunEvent, ScopeCost, WaterfallRecord, WriteCost } from "./core/attribution.js";
13
+ export type { InteractionRef, NavigationRef, OriginRef } from "./core/attribution-hooks.js";
@@ -14,7 +14,7 @@ export declare function setPendingError(el: Computed<any>, source?: Computed<any
14
14
  export declare function forEachDependent(el: Computed<any>, fn: (node: Computed<any>, link: Link) => void): void;
15
15
  export declare function releaseSettledDependents(el: Computed<any>): void;
16
16
  export declare function settleErroredDependents(el: Computed<any>, error: any): void;
17
- export declare function settlePendingSource(el: Computed<any>): void;
17
+ export declare function settlePendingSource(el: Computed<any>, source?: Computed<any>): void;
18
18
  export declare function isThenable<T>(value: T | PromiseLike<T>): value is PromiseLike<T>;
19
19
  /** Fire and clear a node's iterator-flight cancellation hook (#3122). */
20
20
  export declare function releaseFlightTeardown(el: Computed<any>): void;
@@ -1,22 +1,47 @@
1
1
  import type { Transition } from "./scheduler.js";
2
2
  import type { Computed, Signal } from "./types.js";
3
3
  /**
4
- * Dev-only observability hook points for the reactive core.
4
+ * Observe-tier hook points for the reactive core.
5
5
  *
6
6
  * Core's obligation is to call these with true facts at the moments they
7
7
  * happen; ALL attribution semantics (stamps, cause chains, timings, warnings)
8
- * live in the engine that installs them (attribution.ts same pattern as the
9
- * GlobalQueue._* feature slots). `attrHooks` is null unless an engine is
10
- * installed, so the disabled cost is one null check per site, and prod builds
11
- * fold every site out behind __DEV__.
8
+ * live in the engine that installs them — `@solidjs/signals/attribution`, a
9
+ * separate entry so an observe build that never enables it never ships it
10
+ * (same pattern as the GlobalQueue._* feature slots). `attrHooks` is null
11
+ * unless an engine is installed, so the disabled cost is one null check per
12
+ * site, and prod builds fold every site out behind __OBSERVE__.
12
13
  *
13
14
  * IMPORTANT for implementers of call sites: a hook call must never sit inside
14
15
  * a `try` block — rollup's tryCatchDeoptimization retains functions referenced
15
- * inside `try` even behind a folded __DEV__ guard, which re-couples the dev
16
+ * inside `try` even behind a folded __OBSERVE__ guard, which re-couples the
16
17
  * engine into prod bundles (#2883 harness). Set a local flag inside the try
17
18
  * and call the hook after the catch.
18
19
  */
19
20
  export interface AttributionHooks {
21
+ /**
22
+ * `withInteraction` opened a user-interaction frame: root writes until the
23
+ * matching `interactionEnd` were performed by the handler of `ref`. Frames
24
+ * nest strictly (synchronous dispatch), so the engine keeps a stack.
25
+ */
26
+ interactionStart(ref: InteractionRef): void;
27
+ interactionEnd(): void;
28
+ /**
29
+ * `withOrigin` opened a declared-origin frame: root writes until the
30
+ * matching `originEnd` are the unit of work `ref` describes (a router's
31
+ * navigation). Nests inside an interaction frame — a link click that
32
+ * navigates — or stands alone (a redirect from an action, a programmatic
33
+ * `navigate()`). Frames nest strictly, so the engine keeps a stack.
34
+ */
35
+ originStart(ref: OriginRef): void;
36
+ originEnd(): void;
37
+ /**
38
+ * A `flush()` drain finished: every batch it processed either committed
39
+ * (its effects have run) or was parked in a held transition (`holdStart`
40
+ * fired for it). Fires once per drain, after the loop — not per batch, and
41
+ * not for a `flush()` call that found nothing to do. Gives the engine the
42
+ * "committed, screen updated" instant for writes no transition ever held.
43
+ */
44
+ flushEnd(): void;
20
45
  /**
21
46
  * A recompute is starting; `el._deps` still holds the previous run's links.
22
47
  * Always paired with `recomputeEnd` (recompute has no early returns).
@@ -123,5 +148,93 @@ export interface AttributionHooks {
123
148
  */
124
149
  boundaryFallback(boundary: object, tree: Computed<any> | undefined, shown: boolean): void;
125
150
  }
151
+ /** A user interaction, as a rendering runtime describes it to `withInteraction`. */
152
+ export interface InteractionRef {
153
+ /** Event type — `click`, `keydown`, `input`… */
154
+ type: string;
155
+ /** The element hit, e.g. `button#next "Next →"`. */
156
+ target?: string;
157
+ /** Dispatch time on the `performance.now()` clock; defaults to now. */
158
+ at?: number;
159
+ }
160
+ /**
161
+ * A navigation, as a router describes it to `withOrigin` around the location
162
+ * write it is about to perform. Match eagerly and describe before writing:
163
+ * the engine keys the work the write causes — the hold behind route data,
164
+ * the re-runs, the verdicts — to this record, and names it by the
165
+ * parametrized route so occurrences fold together.
166
+ *
167
+ * The engine keeps the object and reads `name`, `to` and `params` again when
168
+ * the navigation settles (and when a hold on it is judged), so a router whose
169
+ * match is not final at write time — a lazy route subtree that resolves inside
170
+ * the hold — may describe coarsely (`/admin/*`) and assign the exact pattern
171
+ * and params onto the same object once it knows them. `from` and `at` are
172
+ * read once, when the frame opens.
173
+ */
174
+ export interface NavigationRef {
175
+ kind: "navigation";
176
+ /** The matched route pattern — `/users/:id`. The name every consumer groups by. */
177
+ name?: string;
178
+ /** Concrete destination path. */
179
+ to?: string;
180
+ /** Concrete path being left. */
181
+ from?: string;
182
+ /** Route params the pattern bound — `{ id: "42" }` (optional params unbound: `undefined`). */
183
+ params?: Readonly<Record<string, string | undefined>>;
184
+ /**
185
+ * When the navigation was requested on the `performance.now()` clock;
186
+ * defaults to now. A router whose request predates the write (loaders
187
+ * awaited before the location moves) passes its own start here.
188
+ */
189
+ at?: number;
190
+ /**
191
+ * `>= 1`: this frame is the Nth redirect hop of the navigation still
192
+ * pending — a guard or loader sent it elsewhere before it landed — not a
193
+ * new navigation. The engine folds it onto that pending record: the record
194
+ * keeps the user's request time and interaction, its destination becomes
195
+ * this one, and the abandoned destination is kept in `redirects`. Without
196
+ * a pending navigation to fold onto it opens a navigation of its own.
197
+ */
198
+ redirect?: number;
199
+ }
200
+ /**
201
+ * What `withOrigin` accepts: a declared unit of work whose writes the engine
202
+ * should attribute as a whole. A discriminated union so kinds can be added
203
+ * (a form submission, a tab switch) without the seam changing shape; the
204
+ * engine knows `navigation` today.
205
+ */
206
+ export type OriginRef = NavigationRef;
126
207
  export declare let attrHooks: AttributionHooks | null;
127
208
  export declare function setAttributionHooks(hooks: AttributionHooks | null): void;
209
+ /**
210
+ * Run `fn` as the handler of a user interaction: every root write it performs
211
+ * (and every action step, effect or flight the write causes) is attributed to
212
+ * `ref` by whichever engine is installed. The web runtime wraps event
213
+ * dispatch in this; custom renderers and test harnesses call it themselves.
214
+ * With no engine installed it is `fn()` — the wiring, not the engine, so it
215
+ * lives in core and is reachable as `OBSERVE.attribution.withInteraction`.
216
+ *
217
+ * The `finally` is deliberate and safe under the try rule above: this
218
+ * function is referenced only from the `OBSERVE` object, which prod builds
219
+ * fold to `undefined`, so nothing retains it there.
220
+ */
221
+ export declare function withInteraction<T>(ref: InteractionRef, fn: () => T): T;
222
+ /**
223
+ * Run `fn` as a declared unit of work — a router's navigation: every root
224
+ * write it performs is attributed to `ref` (and, through it, to the enclosing
225
+ * interaction when there is one), so the hold those writes wait in, the
226
+ * re-runs they cause and the verdicts on them all carry the route's name
227
+ * instead of a bare signal's. Same contract as `withInteraction`: the wiring,
228
+ * not the engine; `fn()` with no engine installed. Reachable as
229
+ * `OBSERVE.attribution.withOrigin`.
230
+ *
231
+ * ```ts
232
+ * OBSERVE
233
+ * ? OBSERVE.attribution.withOrigin(
234
+ * { kind: "navigation", name: match.pattern, to, from, params: match.params },
235
+ * () => setLocation(to)
236
+ * )
237
+ * : setLocation(to);
238
+ * ```
239
+ */
240
+ export declare function withOrigin<T>(ref: OriginRef, fn: () => T): T;