@solidjs/signals 2.0.0-rc.0 → 2.0.0-rc.10

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 (149) hide show
  1. package/README.md +1 -1
  2. package/dist/dev-shared.js +7138 -0
  3. package/dist/dev.attribution.js +3307 -0
  4. package/dist/dev.js +8076 -6692
  5. package/dist/observe/affects.js +129 -0
  6. package/dist/observe/attribution.js +7 -0
  7. package/dist/observe/boundaries.js +793 -0
  8. package/dist/observe/core/action.js +177 -0
  9. package/dist/observe/core/async.js +849 -0
  10. package/dist/observe/core/attribution-costs.js +66 -0
  11. package/dist/observe/core/attribution-feedback.js +280 -0
  12. package/dist/observe/core/attribution-hooks.js +83 -0
  13. package/dist/observe/core/attribution-queries.js +43 -0
  14. package/dist/observe/core/attribution.js +2904 -0
  15. package/dist/observe/core/constants.js +283 -0
  16. package/dist/{types-cjs/core/context.d.cts → observe/core/context.js} +37 -12
  17. package/dist/observe/core/core.js +1999 -0
  18. package/dist/observe/core/dev.js +343 -0
  19. package/dist/observe/core/effect.js +200 -0
  20. package/dist/observe/core/error-hooks.js +71 -0
  21. package/dist/observe/core/error.js +80 -0
  22. package/dist/observe/core/external.js +98 -0
  23. package/dist/observe/core/graph.js +155 -0
  24. package/dist/observe/core/heap.js +147 -0
  25. package/dist/observe/core/invariants.js +45 -0
  26. package/dist/observe/core/lanes.js +205 -0
  27. package/dist/observe/core/optimistic.js +627 -0
  28. package/dist/observe/core/owner.js +353 -0
  29. package/dist/observe/core/scheduler.js +1547 -0
  30. package/dist/observe/core/verdict.js +674 -0
  31. package/dist/observe/index.js +59 -0
  32. package/dist/observe/map.js +484 -0
  33. package/dist/observe/signals.js +731 -0
  34. package/dist/observe/store/index.js +44 -0
  35. package/dist/observe/store/next/optimistic.js +717 -0
  36. package/dist/observe/store/next/projection.js +266 -0
  37. package/dist/observe/store/next/reconcile.js +375 -0
  38. package/dist/observe/store/next/store.js +2480 -0
  39. package/dist/observe/store/next/target.js +51 -0
  40. package/dist/observe/store/store.js +331 -0
  41. package/dist/observe/store/storePath.js +103 -0
  42. package/dist/observe/store/utils.js +1070 -0
  43. package/dist/prod/affects.js +10 -9
  44. package/dist/prod/attribution.js +45 -0
  45. package/dist/prod/boundaries.js +388 -178
  46. package/dist/prod/core/action.js +65 -37
  47. package/dist/prod/core/async.js +477 -208
  48. package/dist/prod/core/attribution-hooks.js +3 -0
  49. package/dist/prod/core/constants.js +186 -1
  50. package/dist/prod/core/context.js +10 -16
  51. package/dist/prod/core/core.js +1556 -415
  52. package/dist/prod/core/dev.js +18 -1
  53. package/dist/prod/core/effect.js +119 -79
  54. package/dist/prod/core/error-hooks.js +71 -0
  55. package/dist/prod/core/error.js +13 -1
  56. package/dist/prod/core/external.js +4 -4
  57. package/dist/prod/core/graph.js +105 -54
  58. package/dist/prod/core/heap.js +72 -65
  59. package/dist/prod/core/invariants.js +3 -2
  60. package/dist/prod/core/lanes.js +117 -52
  61. package/dist/prod/core/optimistic.js +474 -103
  62. package/dist/prod/core/owner.js +131 -84
  63. package/dist/prod/core/scheduler.js +1013 -248
  64. package/dist/prod/core/verdict.js +418 -113
  65. package/dist/prod/index.js +25 -13
  66. package/dist/prod/map.js +314 -151
  67. package/dist/prod/signals.js +382 -32
  68. package/dist/prod/store/index.js +38 -0
  69. package/dist/prod/store/next/optimistic.js +710 -0
  70. package/dist/prod/store/next/projection.js +257 -0
  71. package/dist/prod/store/next/reconcile.js +373 -0
  72. package/dist/prod/store/next/store.js +2356 -0
  73. package/dist/prod/store/next/target.js +51 -0
  74. package/dist/prod/store/store.js +129 -881
  75. package/dist/prod/store/utils.js +982 -242
  76. package/dist/types/attribution.d.ts +19 -0
  77. package/dist/types/attribution.prod.d.ts +22 -0
  78. package/dist/types/boundaries.d.ts +78 -5
  79. package/dist/types/core/action.d.ts +12 -5
  80. package/dist/types/core/async.d.ts +3 -1
  81. package/dist/types/core/attribution-costs.d.ts +35 -0
  82. package/dist/types/core/attribution-feedback.d.ts +133 -0
  83. package/dist/types/core/attribution-hooks.d.ts +304 -0
  84. package/dist/types/core/attribution-queries.d.ts +24 -0
  85. package/dist/types/core/attribution.d.ts +892 -0
  86. package/dist/types/core/constants.d.ts +179 -0
  87. package/dist/types/core/core.d.ts +305 -23
  88. package/dist/types/core/dev.d.ts +421 -8
  89. package/dist/types/core/effect.d.ts +8 -1
  90. package/dist/types/core/error-hooks.d.ts +71 -0
  91. package/dist/types/core/error.d.ts +9 -0
  92. package/dist/types/core/graph.d.ts +23 -0
  93. package/dist/types/core/heap.d.ts +5 -3
  94. package/dist/types/core/index.d.ts +6 -3
  95. package/dist/types/core/invariants.d.ts +5 -1
  96. package/dist/types/core/lanes.d.ts +51 -19
  97. package/dist/types/core/owner.d.ts +15 -3
  98. package/dist/types/core/scheduler.d.ts +252 -3
  99. package/dist/types/core/types.d.ts +124 -41
  100. package/dist/types/index.d.ts +18 -5
  101. package/dist/types/map.d.ts +2 -0
  102. package/dist/types/signals.d.ts +164 -13
  103. package/dist/types/store/index.d.ts +16 -5
  104. package/dist/types/store/next/optimistic.d.ts +25 -0
  105. package/dist/types/store/next/projection.d.ts +27 -0
  106. package/dist/types/store/next/reconcile.d.ts +10 -0
  107. package/dist/types/store/next/store.d.ts +143 -0
  108. package/dist/types/store/next/target.d.ts +196 -0
  109. package/dist/types/store/store.d.ts +46 -112
  110. package/dist/types/store/utils.d.ts +188 -40
  111. package/package.json +32 -26
  112. package/dist/node.cjs +0 -7938
  113. package/dist/prod/store/optimistic.js +0 -217
  114. package/dist/prod/store/projection.js +0 -231
  115. package/dist/prod/store/reconcile.js +0 -707
  116. package/dist/types/store/optimistic.d.ts +0 -45
  117. package/dist/types/store/projection.d.ts +0 -70
  118. package/dist/types/store/reconcile.d.ts +0 -46
  119. package/dist/types-cjs/affects.d.cts +0 -47
  120. package/dist/types-cjs/boundaries.d.cts +0 -175
  121. package/dist/types-cjs/core/action.d.cts +0 -64
  122. package/dist/types-cjs/core/async.d.cts +0 -21
  123. package/dist/types-cjs/core/constants.d.cts +0 -71
  124. package/dist/types-cjs/core/core.d.cts +0 -148
  125. package/dist/types-cjs/core/dev.d.cts +0 -64
  126. package/dist/types-cjs/core/effect.d.cts +0 -30
  127. package/dist/types-cjs/core/error.d.cts +0 -47
  128. package/dist/types-cjs/core/external.d.cts +0 -15
  129. package/dist/types-cjs/core/graph.d.cts +0 -5
  130. package/dist/types-cjs/core/heap.d.cts +0 -22
  131. package/dist/types-cjs/core/index.d.cts +0 -13
  132. package/dist/types-cjs/core/invariants.d.cts +0 -59
  133. package/dist/types-cjs/core/lanes.d.cts +0 -56
  134. package/dist/types-cjs/core/optimistic.d.cts +0 -6
  135. package/dist/types-cjs/core/owner.d.cts +0 -124
  136. package/dist/types-cjs/core/scheduler.d.cts +0 -170
  137. package/dist/types-cjs/core/types.d.cts +0 -148
  138. package/dist/types-cjs/core/verdict.d.cts +0 -2
  139. package/dist/types-cjs/index.d.cts +0 -10
  140. package/dist/types-cjs/map.d.cts +0 -69
  141. package/dist/types-cjs/package.json +0 -3
  142. package/dist/types-cjs/signals.d.cts +0 -605
  143. package/dist/types-cjs/store/index.d.cts +0 -9
  144. package/dist/types-cjs/store/optimistic.d.cts +0 -45
  145. package/dist/types-cjs/store/projection.d.cts +0 -70
  146. package/dist/types-cjs/store/reconcile.d.cts +0 -46
  147. package/dist/types-cjs/store/store.d.cts +0 -213
  148. package/dist/types-cjs/store/storePath.d.cts +0 -58
  149. package/dist/types-cjs/store/utils.d.cts +0 -114
@@ -0,0 +1,892 @@
1
+ import type { Computed, Signal } from "./types.js";
2
+ /**
3
+ * "Why did this run" attribution — the engine behind
4
+ * `@solidjs/signals/attribution`.
5
+ *
6
+ * The runtime already knows the full dependency set of every scope; this
7
+ * module surfaces it. Every value commit stamps its node with a ChangeRecord
8
+ * (a write, an async landing, a refresh() invalidation, or a derived change
9
+ * whose `causes` chain back to root writes). When a computation re-executes,
10
+ * the deps whose stamp is newer than the node's last run are its causes, so
11
+ * each re-run can be explained as a chain down to the originating write:
12
+ *
13
+ * [why-run] effect "docTitle" ran (run 4)
14
+ * ← memo "userLabel" changed (#6)
15
+ * ← signal "notifications" write (#5) 2 → 3
16
+ *
17
+ * This module is the attribution ENGINE: all semantics live here, and it is
18
+ * decoupled from the core. `enable()` installs it into the core's narrow
19
+ * observe-tier hook points (attribution-hooks.ts); core's only obligation is
20
+ * to call those hooks with true facts. Disabled cost is one null check per
21
+ * hook site; prod builds fold the sites out entirely. Nothing in the core
22
+ * imports this module — it is reachable only through the package's
23
+ * `./attribution` entry, so an observe build that never imports it never
24
+ * ships it. The same hook surface is the intended substrate for external
25
+ * consumers (devtools) — one mechanism, two front-ends.
26
+ */
27
+ export type ChangeKind = "write" | "derived" | "async" | "refresh";
28
+ /**
29
+ * Provenance of a root change: the imperative frame that performed it.
30
+ *
31
+ * - `interaction` — a user event handler (the web runtime marks dispatch via
32
+ * `withInteraction`). `name` is the event type, `target` the element hit
33
+ * (`button#next "Next →"` — the quoted text as `AttributionOptions.values`
34
+ * allows), `at` the dispatch time on the `performance.now()` clock — the
35
+ * base every feedback-latency number is measured from.
36
+ * - `effect` — an effect callback (`name` = the effect's name; `run` = the
37
+ * compute run whose effect phase performed the write, when that run was
38
+ * recorded — so a write can be joined to the re-run that produced it).
39
+ * - `action` — a step of an `action()` generator (`name` = the generator's
40
+ * name, when it has one). Writes after an `await` (not a `yield`) run in a
41
+ * bare microtask and stamp `external` — the documented escape.
42
+ * - `async` — an async landing (`name` = the node whose flight landed).
43
+ * - `navigation` — a router's navigation, declared via `withOrigin` around
44
+ * the location write (`name` = the matched route pattern `/users/:id`;
45
+ * `to`/`from` the concrete paths; `params` what the pattern bound; `at`
46
+ * when it was requested). The router-agnostic seam: any router that wraps
47
+ * its write gets navigations named by route in every hold, re-run and
48
+ * verdict, with no per-router knowledge anywhere in the engine.
49
+ * - `external` — none of the above: timers, sockets, promise callbacks, setup.
50
+ *
51
+ * `interaction` on a non-interaction frame is the user event the frame runs
52
+ * under — an action started by a click, an effect whose run was caused by a
53
+ * click's write, a landing whose flight a click started, a navigation a link
54
+ * click performed. It is what lets every downstream cost be keyed by the
55
+ * interaction that paid for it.
56
+ */
57
+ export interface ChangeOrigin {
58
+ kind: "interaction" | "effect" | "action" | "async" | "navigation" | "external";
59
+ name?: string;
60
+ target?: string;
61
+ /**
62
+ * When the frame opened (`performance.now()` clock). Always set for
63
+ * `interaction` and `navigation`; set on an `effect` frame only while an
64
+ * `effect` record listener exists (the callback's timed start).
65
+ */
66
+ at?: number;
67
+ interaction?: ChangeOrigin;
68
+ /** `effect` only: the `RerunEvent.run` of the compute run this callback belongs to. */
69
+ run?: number;
70
+ /** `navigation` only: concrete destination and departure paths, and the bound params. */
71
+ to?: string;
72
+ from?: string;
73
+ params?: Readonly<Record<string, string | undefined>>;
74
+ }
75
+ export interface ChangeRecord {
76
+ /** Global monotonic change sequence — orders causes across the app. */
77
+ seq: number;
78
+ kind: ChangeKind;
79
+ name: string;
80
+ /**
81
+ * Identity of the node that changed — the signal written, the memo whose
82
+ * value changed — in the same id space as `RerunEvent.nodeId`, so a
83
+ * derived cause joins the run that produced it and repeated writes to
84
+ * one signal join each other where `name` alone would merge every
85
+ * unnamed `signal`. Stamped on every record the engine makes; optional
86
+ * for a record built elsewhere (a `HeldWrite`, a deserialized artifact).
87
+ */
88
+ nodeId?: number;
89
+ /**
90
+ * Short previews of the value transition (writes only) — present under
91
+ * `AttributionOptions.values: "full"`, never carried otherwise.
92
+ */
93
+ prev?: string;
94
+ value?: string;
95
+ /** First user frames of the triggering write's stack (opt-in). */
96
+ stack?: string[];
97
+ /** For derived changes: the upstream changes that produced this one. */
98
+ causes?: ChangeRecord[];
99
+ /** Root changes only: who performed the write. */
100
+ origin?: ChangeOrigin;
101
+ /** Root changes only: when the write was stamped (`performance.now()` clock). */
102
+ at?: number;
103
+ }
104
+ export interface RerunEvent {
105
+ /** Global monotonic run sequence. */
106
+ run: number;
107
+ /** When the run started (`performance.now()` clock). */
108
+ at: number;
109
+ /** How many times this node has re-run since attribution was enabled. */
110
+ nodeRuns: number;
111
+ nodeKind: "effect" | "memo";
112
+ nodeName: string;
113
+ /**
114
+ * Identity of the scope that ran, stable for the node's lifetime within
115
+ * the process: every run of one memo/effect carries the same `nodeId`, so
116
+ * runs join to a scope after the record has left the process (where
117
+ * `nodeName` alone would merge every unnamed `effect`). The engine's own
118
+ * per-node id, also what `ChangeOrigin.run` and the cycle/relay checks
119
+ * key on; not meaningful across processes or sessions. In-process
120
+ * consumers get the live node as the `live` argument beside the record
121
+ * (`OBSERVE.records.subscribe("rerun", (event, node) => …)`).
122
+ */
123
+ nodeId: number;
124
+ /**
125
+ * The deps that changed since this node's previous run. Empty means the
126
+ * re-run was not triggered by a tracked value change (creation-adjacent
127
+ * pull, error retry, or a cause this prototype does not stamp yet).
128
+ */
129
+ causes: ChangeRecord[];
130
+ /** Dependency count after this run. */
131
+ depCount: number;
132
+ /** Names of deps this run subscribed to that the previous run did not. */
133
+ depsAdded: string[];
134
+ /** Names of deps the previous run had that this run dropped. */
135
+ depsRemoved: string[];
136
+ /** Wall time of this run excluding nested recomputes (ms). */
137
+ selfMs: number;
138
+ /** Wall time of this run including nested recomputes (ms). */
139
+ totalMs: number;
140
+ /**
141
+ * Whether the run produced a changed value. A PLAIN memo run with
142
+ * `changed: false` was pure waste — the equality cutoff stopped it from
143
+ * notifying anyone. Effects run with `_equals: false` in core (their
144
+ * effect phase re-fires on every recompute), so the engine derives this
145
+ * fact itself: an effect run whose compute output is identical to the
146
+ * previous run's reports `changed: false` — the phase re-fired with the
147
+ * same input, pure waste. Side-effect-only computes (`undefined` output)
148
+ * are exempt: identity of `undefined` proves nothing about their work.
149
+ * Summed as `wastedMs` in costs() (plain, non-held runs only — see
150
+ * `phase`).
151
+ */
152
+ changed: boolean;
153
+ /**
154
+ * Which posture this run executed under. "optimistic" = under an
155
+ * optimistic lane (overlay recompute); "held" = a hold was open or owns
156
+ * the node (the run may be replayed/settled later); "plain" = an ordinary
157
+ * committed run. Overlay runs are real work (they count toward time
158
+ * budgets) but are never blamed as waste, and costs() reports their time
159
+ * separately as `overlayMs`.
160
+ */
161
+ phase: "plain" | "held" | "optimistic";
162
+ /**
163
+ * The changed value was parked in `_pendingValue` (held) rather than
164
+ * committed directly; its reveal happens on the hold's own schedule. Held
165
+ * runs are excluded from waste accounting.
166
+ */
167
+ held: boolean;
168
+ /** The user interaction this run traces back to through its causes, if any. */
169
+ interaction?: ChangeOrigin;
170
+ }
171
+ /**
172
+ * A computation's creation run — the first run, the one with no causes to
173
+ * explain (a `RerunEvent` is every run after it). The same measurements as a
174
+ * re-run, less the causal fields a first run cannot have, and `interaction`
175
+ * inherited from whatever built the node: the enclosing recompute (a
176
+ * parent's fn creating children) or the handler/effect frame at the top of
177
+ * the stack. Creation time is already charged to the interaction record's
178
+ * `created`; this is the per-node face of that sum.
179
+ */
180
+ export interface CreateEvent {
181
+ /** When the run started (`performance.now()` clock). */
182
+ at: number;
183
+ nodeKind: "effect" | "memo";
184
+ nodeName: string;
185
+ /** Same id space as `RerunEvent.nodeId` — the node's later re-runs join here. */
186
+ nodeId: number;
187
+ /** Dependency count after this run. */
188
+ depCount: number;
189
+ /** Wall time of this run excluding nested recomputes (ms) — children created inside report their own. */
190
+ selfMs: number;
191
+ /** Wall time of this run including nested recomputes (ms). */
192
+ totalMs: number;
193
+ /** Posture the run executed under — see `RerunEvent.phase`. */
194
+ phase: "plain" | "held" | "optimistic";
195
+ /** The value was parked in `_pendingValue` rather than committed — see `RerunEvent.held`. */
196
+ held: boolean;
197
+ /** The interaction whose handler or flush built this node, if any. */
198
+ interaction?: ChangeOrigin;
199
+ }
200
+ /**
201
+ * One run of an effect's imperative half — the callback that touches the DOM
202
+ * or the outside world — timed from entry to exit, cleanup included, whether
203
+ * or not it threw. The compute half is the `RerunEvent`/`CreateEvent` with
204
+ * the same `nodeId` that preceded it in the flush; `run` joins the two for a
205
+ * re-run and is absent for a creation's first callback.
206
+ */
207
+ export interface EffectRunEvent {
208
+ /** When the callback started (`performance.now()` clock). */
209
+ at: number;
210
+ /** Wall time of the callback, nested runs included (ms). */
211
+ durationMs: number;
212
+ nodeId: number;
213
+ nodeName: string;
214
+ /** `RerunEvent.run` of the compute run whose effect phase this is; absent for a creation. */
215
+ run?: number;
216
+ /** The interaction the preceding compute run traced to, if any. */
217
+ interaction?: ChangeOrigin;
218
+ }
219
+ /**
220
+ * One `flush()` drain: from the scheduler picking up scheduled work until
221
+ * every batch it processed has committed (effects ran) or been parked in a
222
+ * held transition. The unit React's "Scheduler" track paints; the engine's
223
+ * `flushEnd` settles interactions and navigations on the same instant.
224
+ * Counts cover the runs the engine recorded inside the drain (excluded
225
+ * scopes not counted).
226
+ */
227
+ export interface FlushEvent {
228
+ /** When the drain started (`performance.now()` clock). */
229
+ at: number;
230
+ /** Wall time of the drain (ms). */
231
+ durationMs: number;
232
+ /** Re-runs recorded during the drain. */
233
+ runs: number;
234
+ /** Creation runs during the drain. */
235
+ created: number;
236
+ /** A transition was judged incomplete during the drain — some of its writes stayed staged. */
237
+ held: boolean;
238
+ /**
239
+ * The interaction every recorded run of the drain traced to, when there
240
+ * was exactly one; absent when none did, or when runs for several
241
+ * interactions shared the drain.
242
+ */
243
+ interaction?: ChangeOrigin;
244
+ }
245
+ /**
246
+ * One async flight — a promise or async iterable an async computation
247
+ * registered — from its origin (the earliest the engine knows: a
248
+ * `markFlight` preload mark, or first sight at registration) to the moment
249
+ * it landed or was superseded by the node's next flight (`abandoned` — the
250
+ * answer will be discarded). A flight whose node is disposed mid-air
251
+ * produces no record.
252
+ */
253
+ export interface FlightEvent {
254
+ nodeId: number;
255
+ nodeName: string;
256
+ /** Owner-chain labels of the async node, root first, when the node is owned. */
257
+ ownerPath?: string[];
258
+ /** When the flight started (`performance.now()` clock). */
259
+ at: number;
260
+ /** Wall time in the air (ms). */
261
+ durationMs: number;
262
+ outcome: "landed" | "abandoned";
263
+ /** The interaction whose write started the flight, if any. */
264
+ interaction?: ChangeOrigin;
265
+ }
266
+ /**
267
+ * One showing of a loading boundary's fallback, delivered when it stops
268
+ * showing. `ownerPath` names the boundary by its subtree's owner chain
269
+ * (`["<App>", "<Feed>"]`); absent when the subtree never reported a path.
270
+ * The fold in `feedback().fallbacks` is this record summed per boundary,
271
+ * with the flash verdict.
272
+ */
273
+ export interface FallbackEvent {
274
+ ownerPath?: string[];
275
+ /** When the fallback appeared (`performance.now()` clock). */
276
+ at: number;
277
+ /** How long it stayed (ms). */
278
+ shownMs: number;
279
+ /** The interaction whose work the boundary was waiting on, if the engine could tell. */
280
+ interaction?: ChangeOrigin;
281
+ }
282
+ /**
283
+ * The live graph's size at a navigation's settle — the moment an app has
284
+ * finished moving between two screens, so a count that climbs visit after
285
+ * visit is a root or a subscription the previous screen left behind.
286
+ * Delivered on `OBSERVE.records.subscribe("graph", …)` per settled
287
+ * navigation; a walk of the owner tree from the registered top-level roots,
288
+ * made only when something listens or `graphGrowth` is on.
289
+ */
290
+ export interface GraphEvent extends GraphSize {
291
+ /** When the navigation settled (`performance.now()` clock). */
292
+ at: number;
293
+ /** The route pattern the navigation matched (`name`), else its `to`. */
294
+ route?: string;
295
+ /** The navigation this count belongs to. */
296
+ navigation: NavigationEvent;
297
+ }
298
+ /**
299
+ * The live reactive graph's size, as `graphSize()` counts it: the owner tree
300
+ * from the registered top-level roots, then every computation and signal
301
+ * reachable from it through dependencies and subscriptions — which is how
302
+ * an ownerless effect (created with no owner, kept alive by its sources)
303
+ * is found — and the edges between them.
304
+ */
305
+ export interface GraphSize {
306
+ /** Top-level roots alive (`render()`'s, module-scope `createRoot()`s, panels). */
307
+ roots: number;
308
+ /** Owners in the tree: roots, component owners, owned computations. */
309
+ owners: number;
310
+ /** Computations (memos, effects, boundaries), owned or reached through a subscription. */
311
+ computations: number;
312
+ /** Signals reached through a computation's dependencies. */
313
+ signals: number;
314
+ /** Dependency links — each computation's sources, counted once. */
315
+ edges: number;
316
+ }
317
+ /**
318
+ * What user data the engine's records carry — see `AttributionOptions.values`.
319
+ * Ordered: `"full"` carries the most, `"none"` the least.
320
+ */
321
+ export type AttributionValues = "full" | "labels" | "none";
322
+ export interface AttributionOptions {
323
+ /** Pretty-print each re-run to the console (default true). */
324
+ log?: boolean;
325
+ /**
326
+ * What user data records carry. Default per build tier: `"full"` in dev
327
+ * builds, `"none"` in observe builds — a production observability
328
+ * artifact carries no user data unless a holder asks for it. Records name things —
329
+ * owner paths, `name` options, store paths, route patterns — and are
330
+ * otherwise numbers, kinds and outcomes; this option governs the fields
331
+ * that quote application data: the value previews on a root write
332
+ * (`ChangeRecord.prev`/`value`, and the `HeldWrite.prev`/`value` copied
333
+ * from them), the text of the element an interaction hit
334
+ * (`ChangeOrigin.target` / `InteractionEvent.target` — the `"Next →"` in
335
+ * `button#next "Next →"`), and every sentence built from those (the
336
+ * console log, `formatRerun`, `formatOrigin`, the SILENT_HOLD / LONG_HOLD
337
+ * / STACKED_HOLDS verdicts naming the interaction, OPTIMISTIC_REVERTED's
338
+ * shown and settled values). Applied where the record is BUILT, so
339
+ * nothing downstream — a ring buffer, a fold, a listener, an exporter —
340
+ * ever holds what the level excludes.
341
+ *
342
+ * - `"full"` — previews of the written values (strings quoted and cut at
343
+ * 40 characters, numbers and booleans verbatim, everything else a type
344
+ * tag such as `Array(12)`) and the element's text: what makes dev
345
+ * output readable.
346
+ * - `"labels"` — no value previews; element text kept only on a `button`
347
+ * or an `a` (the control's caption — what the person pressed is the
348
+ * point of an interaction record) and dropped for anything else (the
349
+ * text of a `div` or a `td` is content).
350
+ * - `"none"` — no value previews, no element text: names, numbers, kinds
351
+ * and outcomes only. What an observe-tier holder that carries records
352
+ * out of the process (an APM adapter) should pass.
353
+ *
354
+ * Across holds the LEAST permissive level wins — the one key that
355
+ * combines the other way from the rest, where a hold can only ask for
356
+ * more: a holder that must not see user data is not overruled by one
357
+ * that wants it. A holder that names no level asks for the tier's
358
+ * default, so in an observe build it tightens to `"none"` beside anyone;
359
+ * a single holder passing `"full"` there gets `"full"`, and an explicit
360
+ * `"full"` never loosens what another holder demanded. Governs what is
361
+ * built from the moment the level is in effect; records already in a
362
+ * ring buffer keep what they carried. A
363
+ * navigation's concrete paths and params (`ChangeOrigin.to`/`from`/
364
+ * `params`) are the router's description and are not governed here.
365
+ */
366
+ values?: AttributionValues;
367
+ /**
368
+ * Run the cost checks — the thresholded findings over the engine's own
369
+ * accounting: `hotRuns`, `hotTime`, `wideDeps`, `unstableMemos`,
370
+ * `fanOut`, `wastedRecompute` (default true). `false` turns all six off at once, whatever
371
+ * their individual settings, so a consumer that wants records only (an
372
+ * exporter, a profiler track) pays for none of their per-node bookkeeping.
373
+ * Hold, long-hold and waterfall tracking are records with verdicts on top,
374
+ * not checks, and are unaffected; disable those through their own options.
375
+ */
376
+ checks?: boolean;
377
+ /** Capture the user stack frame of each write — slow (default false). */
378
+ stacks?: boolean;
379
+ /** Ring-buffer size for each `history(type)` buffer (default 200). */
380
+ historyLimit?: number;
381
+ /**
382
+ * Hot-scope warning: emit a diagnostic when one scope re-runs `count`
383
+ * times within `windowMs` (default 120 runs / 1000ms — deliberately above
384
+ * animation-frame cadence, so a legitimate rAF-driven scope at 60/s does
385
+ * not cry wolf). `false` disables.
386
+ */
387
+ hotRuns?: {
388
+ count: number;
389
+ windowMs: number;
390
+ } | false;
391
+ /**
392
+ * Wide-scope warning: emit a diagnostic when a scope's dependency count
393
+ * reaches this (default 30) — the coarse-read / helper-leak signature.
394
+ * Re-warns only if the count then grows by another 50%. `false` disables.
395
+ */
396
+ wideDeps?: number | false;
397
+ /**
398
+ * Time-budget warning: emit a diagnostic when one scope's summed self-time
399
+ * inside `windowMs` exceeds `budgetMs` (default 8ms / 1000ms — half a frame
400
+ * spent in one scope). Unlike `hotRuns` this catches the few-but-expensive
401
+ * scope that run counts miss. `false` disables.
402
+ */
403
+ hotTime?: {
404
+ budgetMs: number;
405
+ windowMs: number;
406
+ } | false;
407
+ /**
408
+ * Unstable-output warning: emit a diagnostic when a memo commits a
409
+ * referentially-new but shallowly-equivalent plain object/array on this
410
+ * many consecutive runs (default 4). Such a memo's equality gate never
411
+ * closes — every subscriber re-runs on every upstream change — which makes
412
+ * it a fan-out amplifier that is otherwise only findable by profiling.
413
+ * `false` disables.
414
+ */
415
+ unstableMemos?: number | false;
416
+ /**
417
+ * Wasted-recompute warning: emit WASTED_RECOMPUTE when a scope re-ran at
418
+ * least `minRuns` times within `windowMs` and `ratio` or more of those
419
+ * runs produced an unchanged value while costing `budgetMs` or more of
420
+ * compute in all (default 5 runs / 80% / 2ms / 1000ms). The equality gate
421
+ * closed every time: the scope's inputs changed without changing its
422
+ * result, so the runs were pure cost — the profiler-shaped fact
423
+ * `costs().wastedMs` sums, as a finding. Held and overlay runs are not
424
+ * counted (they may be replayed). Once per window per scope. `false`
425
+ * disables.
426
+ */
427
+ wastedRecompute?: {
428
+ minRuns: number;
429
+ ratio: number;
430
+ budgetMs: number;
431
+ windowMs: number;
432
+ } | false;
433
+ /**
434
+ * HUGE_FAN_OUT threshold while the engine is enabled: emit the finding
435
+ * when a committed root invalidation (write, refresh, async landing)
436
+ * reaches a node with at least this many subscribers (default 250). The
437
+ * same code the always-on core check emits from GRAPH_SIZE_WARN_AT (2000)
438
+ * up, with `data.write` naming the invalidation; the engine hands over to
439
+ * the core there, so one change never carries two findings. Once per
440
+ * node, re-warning once the count has grown by another 500. `false`
441
+ * leaves only the always-on threshold.
442
+ */
443
+ fanOut?: number | false;
444
+ /**
445
+ * Async-waterfall warning: emit a diagnostic when an async flight that
446
+ * could only start after an upstream flight resolved (its recompute's
447
+ * cause chain reaches the upstream's async landing, and its origin
448
+ * post-dates that landing) forms a sequential chain of 2+ flights, each
449
+ * of which took at least `minFlightMs` (default 50ms). The duration gate
450
+ * is one safety valve for what the graph cannot see: a settled
451
+ * preload/cache hit resolves fast and never warns. In-flight preloads are
452
+ * absolved by origin: `markFlight()` stamps (and first-seen identity)
453
+ * prove work predated the upstream landing — parallel, not sequential.
454
+ * Chains of 2 emit at `info` severity, structured channel only (a
455
+ * dependent fetch is sometimes intrinsic, and unmarked external preloads
456
+ * are invisible); 3+ escalate to `warn` with console output. `false`
457
+ * disables.
458
+ */
459
+ waterfalls?: {
460
+ minFlightMs: number;
461
+ } | false;
462
+ /**
463
+ * Silent-hold warning: emit a diagnostic when a user's writes were held
464
+ * behind async work for at least `infoMs` (default 100ms — RAIL's "feels
465
+ * instant" ceiling) and the screen never acknowledged the wait — no
466
+ * `isPending()`/`latest()` reader downstream of the held writes or their
467
+ * blockers, no optimistic value, no `affects()` mark, and no lane effect
468
+ * painted while held. Below `warnMs`
469
+ * (default 200ms — the INP "good" ceiling) the event is advisory
470
+ * (structured channel only); at or above it the console gets the finding.
471
+ * The engine measures to the commit, not the paint, so every number is a
472
+ * floor on what the user saw; the thresholds sit at the strict end of the
473
+ * band on purpose. Holds that staged no root write (initial loads, bare
474
+ * `refresh()`) are never judged: nothing the user did went unanswered.
475
+ * `false` disables hold tracking altogether (`longHolds` included).
476
+ */
477
+ holds?: {
478
+ infoMs: number;
479
+ warnMs: number;
480
+ } | false;
481
+ /**
482
+ * Long-hold warning: emit a diagnostic when a hold's quiescent tail — the
483
+ * time from the LAST write to join it until it committed — reached
484
+ * `infoMs` (default 500ms), `warn` from `warnMs` (default 1000ms, where
485
+ * RAIL says the user loses the thread). Measured from the last join so a
486
+ * hold that keeps taking input (typing) is judged by each wait, not by its
487
+ * lifetime. A hold this long is past what a stale screen should carry,
488
+ * acknowledged or not: the honest UI is a fallback, which a `Loading`
489
+ * boundary gives only when it has not revealed yet or its `on` prop
490
+ * changed. Reported as LONG_HOLD when the hold was acknowledged; a silent
491
+ * long hold stays one SILENT_HOLD with the boundary repair appended.
492
+ * `false` disables.
493
+ */
494
+ longHolds?: {
495
+ infoMs: number;
496
+ warnMs: number;
497
+ } | false;
498
+ /**
499
+ * Graph-growth warning: emit GRAPH_GROWTH when the live owner count at the
500
+ * settle of the same route has climbed on `visits` consecutive visits
501
+ * (default 3) to `ratio` or more of the first (default 1.25) — a root or a
502
+ * subscription each visit leaves behind, the leak class a heap snapshot
503
+ * finds. The count is a walk at settle, never per node. `false` disables.
504
+ */
505
+ graphGrowth?: {
506
+ visits: number;
507
+ ratio: number;
508
+ } | false;
509
+ /**
510
+ * Abandoned-flights warning: emit ABANDONED_FLIGHTS when one async source
511
+ * abandons `count` flights within `windowMs` — each superseded by the
512
+ * next before it landed (default 3 / 1000ms: the request-per-keystroke
513
+ * signature, every input asking again and the answers discarded). Once
514
+ * per window per source. `false` disables.
515
+ */
516
+ abandonedFlights?: {
517
+ count: number;
518
+ windowMs: number;
519
+ } | false;
520
+ /**
521
+ * Fallback-flash finding: emit FALLBACK_FLASH (`info`) when a `Loading`
522
+ * boundary's fallback shows for less than `FALLBACK_FLASH_MS` (150ms) — a
523
+ * spinner that appeared and vanished, too much feedback for too little
524
+ * wait (default true). `false` disables.
525
+ */
526
+ fallbackFlashes?: boolean;
527
+ /**
528
+ * Stacked-holds warning: emit STACKED_HOLDS when `count` or more
529
+ * interactions are waiting in one hold when it commits (default 3) — the
530
+ * person kept clicking or typing while the first answer was in the air,
531
+ * and every one of them waited on the same source. `false` disables.
532
+ */
533
+ stackedHolds?: {
534
+ count: number;
535
+ } | false;
536
+ /**
537
+ * Optimistic-revert finding: emit OPTIMISTIC_REVERTED (`info`) when an
538
+ * optimistic value the screen showed is replaced by a different one —
539
+ * reverted at settle, or superseded by the truth (default true). The
540
+ * runtime's own optimistic nodes — `isPending`/`latest` companions and
541
+ * derived overrides — are never judged: they are the acknowledgement
542
+ * machinery, not a guess the person saw. `false` disables.
543
+ */
544
+ optimisticReverts?: boolean;
545
+ }
546
+ /** A fallback shown for less than this is a flash: feedback for a wait too short to need it. */
547
+ export declare const FALLBACK_FLASH_MS = 150;
548
+ /**
549
+ * The ring buffers `attribution.history(type)` reads, by type. Four of the
550
+ * five are the channel's records kept since the window opened; `waterfall`
551
+ * is a fact the engine keeps but never emits — a graph-provable sequential
552
+ * flight chain (`WaterfallRecord`), of which the ASYNC_WATERFALL finding is
553
+ * the thresholded view.
554
+ */
555
+ export interface HistoryRecords {
556
+ rerun: RerunEvent;
557
+ waterfall: WaterfallRecord;
558
+ hold: HoldEvent;
559
+ navigation: NavigationEvent;
560
+ interaction: InteractionEvent;
561
+ }
562
+ export type HistoryType = keyof HistoryRecords;
563
+ /** @internal The engine's clock: `performance.now()` where it exists. */
564
+ export declare const now: () => number;
565
+ /**
566
+ * @internal The fold tables' seam. The engine emits records; the tables that
567
+ * fold them — `costs()` (scopes and writes), `feedback()` (sources,
568
+ * interactions, navigations, flights, fallbacks) — live in their own modules
569
+ * and register here when imported, so a consumer that only subscribes to
570
+ * records never ships them. Each hook fires at the moment the engine has the
571
+ * fact; `reset` on `enable()`/`disable()`. With nothing registered every
572
+ * site is one empty loop.
573
+ */
574
+ export interface FoldHooks {
575
+ rerun?(el: Computed<any>, event: RerunEvent): void;
576
+ hold?(event: HoldEvent): void;
577
+ navigation?(event: NavigationEvent): void;
578
+ /** A flight started; `abandoned` when it superseded one still in the air. */
579
+ flightStart?(el: Computed<any>, abandoned: boolean): void;
580
+ flightLanded?(el: Computed<any>, ms: number): void;
581
+ fallback?(boundary: object, tree: Computed<any> | undefined, shown: boolean): void;
582
+ reset?(): void;
583
+ }
584
+ /** @internal */
585
+ export declare function registerFold(hooks: FoldHooks): void;
586
+ /** @internal Root cause names of a cause chain — the writes/landings/refreshes the chain bottoms out in. */
587
+ export declare function rootsOf(causes: ChangeRecord[], out: Set<string>): void;
588
+ export declare function nodeName(node: Signal<any> | Computed<any>): string;
589
+ /** `click on button#next "Next →"`, `effect "syncTitle"`, `action "save"`, `navigation to /users/:id`, … */
590
+ export declare function formatOrigin(origin: ChangeOrigin): string;
591
+ export declare function formatRerun(event: RerunEvent): string;
592
+ /**
593
+ * The engine: turn it on and read its ring buffers. Its records are
594
+ * delivered on the core's channel — `OBSERVE.records.subscribe("rerun" |
595
+ * "hold" | "interaction" | "navigation" | "create" | "effect" | "flush" |
596
+ * "flight" | "fallback" | "graph", (event, live) => …)` — the same place
597
+ * the runtimes' records arrive, so a consumer has one subscribe. The folds
598
+ * over those records — `costs()`, `feedback()` — and the point queries —
599
+ * `why()`, `subscriptions()` — and the formatters — `formatRerun()`,
600
+ * `formatOrigin()` — are named exports of `@solidjs/signals/attribution`
601
+ * rather than methods here, so a consumer that only wants records (a
602
+ * production adapter) does not ship the tables a console or an agent reads;
603
+ * importing a fold is what turns its accounting on.
604
+ */
605
+ export interface Attribution {
606
+ /**
607
+ * Install the engine, or take one more hold on it, and return the release
608
+ * for that hold. The engine is shared by every consumer in the page — a
609
+ * profiler track, an APM adapter, a diagnostics capture — so each
610
+ * `enable()` is a hold: the first installs the hooks and resets
611
+ * everything; one taken while already enabled opens a fresh window over
612
+ * the ring buffers and folds (`history(type)`, `costs()`, `feedback()` …
613
+ * read from here on) without disturbing the live tracking state or
614
+ * anyone's subscriptions, so a capture begun beside a running consumer
615
+ * still measures only its own scenario.
616
+ *
617
+ * Options combine across holds by the most demanding value per key: a
618
+ * hold's `opts` say what it wants (the defaults fill what it leaves
619
+ * unsaid), and the engine does whatever any holder asked for — the log
620
+ * prints while any holder wants it, a check runs while any holder wants
621
+ * it and at the most sensitive threshold requested, `historyLimit` is the
622
+ * largest. A hold can add to what another asked for, never take it away,
623
+ * so the result does not depend on the order holds were taken; releasing
624
+ * a hold withdraws its requests — a track enabled with `log: false` beside
625
+ * a console session never silences it, and a capture with tight
626
+ * thresholds beside a records-only adapter runs the checks for its own
627
+ * duration. One key runs the other way: `values` combines to the LEAST
628
+ * permissive level any holder asked for, so an adapter that must not see
629
+ * user data (`values: "none"`) is honoured beside a console session that
630
+ * wants everything. The release is idempotent; the last release uninstalls the
631
+ * hooks and clears every ring buffer. A consumer that re-`enable()`s to
632
+ * reopen its window must release both holds (or `disable()`).
633
+ *
634
+ * Subscriptions are not the engine's: its records arrive on
635
+ * `OBSERVE.records`, whose listeners outlive any hold — subscribe before
636
+ * or after `enable()`, and unsubscribe with the function `subscribe`
637
+ * returned.
638
+ */
639
+ enable(opts?: AttributionOptions): () => void;
640
+ /**
641
+ * Tear the engine down whatever holds are outstanding: drops every hold,
642
+ * uninstalls the hooks and clears every ring buffer. The console's and a
643
+ * test harness's reset — a consumer sharing the page with others releases
644
+ * its own hold with the function `enable()` returned instead. Idempotent;
645
+ * a `disable()` with nothing enabled is a no-op reset. Listeners on
646
+ * `OBSERVE.records` are untouched: they are the channel's.
647
+ */
648
+ disable(): void;
649
+ /**
650
+ * The ring buffer of `type` since `enable()` — the last `historyLimit`
651
+ * records (default 200), oldest first; the same objects the channel
652
+ * delivered. Facts, not verdicts: each is recorded regardless of the
653
+ * thresholds the findings apply to it.
654
+ *
655
+ * - `"rerun"` — every re-run recorded (see `RerunEvent`). Kept only while
656
+ * a record had an audience — a `rerun` listener, an imported fold
657
+ * (`costs`/`feedback`), or the console log; a records-only consumer
658
+ * that wants none of those pays for none, and reads an empty buffer.
659
+ * - `"waterfall"` — every graph-provable sequential flight chain, warned
660
+ * or not: the ASYNC_WATERFALL finding is the duration-gated view.
661
+ * - `"hold"` — every settled transition hold that staged at least one root
662
+ * write, acknowledged or not: SILENT_HOLD / LONG_HOLD are the
663
+ * thresholded verdicts (`HoldEvent.silent` / `.long` carry them).
664
+ * - `"navigation"` — every navigation a router declared via `withOrigin`,
665
+ * settled or not: what route, under which interaction, how many writes,
666
+ * and — once its writes are through — how long that took and how
667
+ * (`committed`, `held` with the `HoldEvent` attached, `superseded`).
668
+ * - `"interaction"` — every user interaction a runtime declared via
669
+ * `withInteraction`, settled or not: what was dispatched, when, what it
670
+ * wrote, the re-runs and creations it caused, the holds its writes
671
+ * waited in and the navigations it performed — and, once through, how
672
+ * long the person waited (`settledMs`) and how it ended. The
673
+ * per-dispatch record `feedback().interactions` folds by name.
674
+ */
675
+ history<K extends HistoryType>(type: K): readonly HistoryRecords[K][];
676
+ /**
677
+ * Cooperative preload declaration: stamp a flight object (promise or async
678
+ * iterable) with its true kickoff time BEFORE the reactive graph sees it.
679
+ * A route preloader or query cache calls this on the promise it hands out
680
+ * (on the WRAPPER it mints, with the original kickoff time — wrapping
681
+ * defeats identity tracking otherwise); any dependent that later awaits it
682
+ * is then judged against the real start — work already in the air when its
683
+ * upstream landed is parallel, never a waterfall link. Callable while
684
+ * attribution is disabled (marks made at navigation time must survive a
685
+ * later enable()).
686
+ */
687
+ markFlight(flight: object, startedAt?: number): void;
688
+ }
689
+ /** @internal The id the engine's records name `node` by, if it has one yet — read without assigning. */
690
+ export declare function nodeIdOf(node: object): number | undefined;
691
+ /** One landed flight: its node name, wall duration, and upstream chain. */
692
+ export interface FlightLink {
693
+ name: string;
694
+ ms: number;
695
+ }
696
+ export interface WaterfallRecord {
697
+ /** Sequential flights, oldest first, ending at the flight that landed. */
698
+ chain: FlightLink[];
699
+ /** Summed wall time of the chain — the serialized cost. */
700
+ sequentialMs: number;
701
+ }
702
+ export interface HeldWrite {
703
+ name: string;
704
+ prev?: string;
705
+ value?: string;
706
+ origin?: ChangeOrigin;
707
+ }
708
+ export interface HoldEvent {
709
+ /**
710
+ * When the wait began (`performance.now()` clock): the interaction that
711
+ * performed the held writes when one is known (`interaction.at`) or the
712
+ * first flush that parked them, whichever is earlier. `at + holdMs` is the
713
+ * commit.
714
+ */
715
+ at: number;
716
+ /** Wall time the user waited: `at` to the commit. */
717
+ holdMs: number;
718
+ /**
719
+ * The quiescent tail: from the LAST held write to join (the user's final
720
+ * input) to the commit. Equal to `holdMs` for a single write; shorter when
721
+ * the hold kept taking input. The LONG_HOLD measure.
722
+ */
723
+ tailMs: number;
724
+ /** The user interaction whose writes were held, when the stamp is known. */
725
+ interaction?: ChangeOrigin;
726
+ /**
727
+ * The declared unit of work the held writes belong to — the `navigation`
728
+ * a router described via `withOrigin` — when one is known. What names the
729
+ * hold by route (`navigation to /users/:id`) rather than by signal; the
730
+ * same object as `NavigationEvent.origin`, so the two join by identity.
731
+ */
732
+ origin?: ChangeOrigin;
733
+ /** Flushes that ended with the hold still open. */
734
+ flushes: number;
735
+ /** Root signal writes staged behind the hold (the user's unanswered input). */
736
+ heldWrites: HeldWrite[];
737
+ /** Async nodes the hold waited on (union across its parked flushes). */
738
+ blockers: string[];
739
+ /**
740
+ * Feedback the graph provably rendered for this hold — an `isPending()`
741
+ * reader, a `latest()` shadow, an optimistic value, an `affects()` mark —
742
+ * one entry per affordance, in the order found. Empty and
743
+ * `paintedDuringHold === 0` is the SILENT_HOLD signature.
744
+ */
745
+ acknowledgements: Acknowledgement[];
746
+ /**
747
+ * Effect callbacks that ran inside the hold's parked flushes. Mainline
748
+ * effects are stashed while a hold is open, so these are lane effects —
749
+ * readers of optimistic values and of `isPending()`/`latest()` companions,
750
+ * i.e. the screen changing in response to the hold. An unrelated effect
751
+ * cannot land here: it waits with everything else.
752
+ */
753
+ paintedDuringHold: number;
754
+ /** The hold was opened (or joined) by an `action()`. */
755
+ action: boolean;
756
+ /**
757
+ * The engine's silent-hold verdict, stamped at settle: no affordance
758
+ * acknowledged the wait (`acknowledgements` empty) and nothing painted
759
+ * while it was open (`paintedDuringHold === 0`). Duration-free — the
760
+ * SILENT_HOLD finding is this above `holds.infoMs` — so a consumer can
761
+ * flag every silent wait or apply its own floor, and a record that left
762
+ * the process still carries the verdict.
763
+ */
764
+ silent: boolean;
765
+ /**
766
+ * The engine's long-hold verdict, stamped at settle: the quiescent tail
767
+ * (`tailMs`) reached `longHolds.infoMs` under the options in effect;
768
+ * `false` when long-hold tracking is off. The LONG_HOLD finding is this
769
+ * verdict, tiered by `warnMs`.
770
+ */
771
+ long: boolean;
772
+ }
773
+ /**
774
+ * One way the screen acknowledged a hold. `reader` is where it was painted —
775
+ * the owner path of the first effect the census found reading the
776
+ * affordance (`["<App>", "<Feed>", "effect"]`), so a consumer can say WHICH
777
+ * screen answered, not only that one did. Absent when the affordance was
778
+ * registered but the census found no reader through the graph (an optimistic
779
+ * store: its readers are proxy traps, not nodes). `feedback()` ranks
780
+ * acknowledgements by `kind:source` (`isPending:posts`).
781
+ */
782
+ export interface Acknowledgement {
783
+ kind: "isPending" | "latest" | "optimistic" | "affects";
784
+ /** The node the affordance hangs on — the async source for `isPending`/`latest`, the optimistic/affected node otherwise. */
785
+ source: string;
786
+ reader?: string[];
787
+ }
788
+ /**
789
+ * The live graph's size — a walk, not a counter: nothing is charged at node
790
+ * creation, disposal, write or re-run; the engine asks at a navigation's
791
+ * settle. Two passes. The owner tree from the registered top-level roots
792
+ * gives `owners` and seeds the computations. Then each computation's
793
+ * dependency links give `edges` and the `signals` and computations they
794
+ * reach, and each newly met source's subscriber list gives the computations
795
+ * that read it — including one created with no owner, which no chain holds
796
+ * and only its sources keep alive: the subscription leak a heap snapshot
797
+ * finds. Measured at 5–10 ns per link; a 50k-owner graph walks in under a
798
+ * millisecond. Dormant nodes are spliced out of their chain and are not
799
+ * counted unless a subscription still reaches them.
800
+ */
801
+ export declare function graphSize(): GraphSize;
802
+ /** A destination a navigation abandoned when a redirect sent it elsewhere. */
803
+ export interface NavigationHop {
804
+ name?: string;
805
+ to?: string;
806
+ params?: Readonly<Record<string, string | undefined>>;
807
+ /** When the redirect away from it was declared (`performance.now()` clock). */
808
+ at: number;
809
+ }
810
+ export interface NavigationEvent {
811
+ /** The matched route pattern the router gave — `/users/:id`. After a redirect, the final one. */
812
+ name?: string;
813
+ to?: string;
814
+ from?: string;
815
+ params?: Readonly<Record<string, string | undefined>>;
816
+ /** When the navigation was requested (`performance.now()` clock). */
817
+ at: number;
818
+ /** The user interaction it ran under, when known — a link click. */
819
+ interaction?: ChangeOrigin;
820
+ /** Root writes the frame performed, redirect hops included. */
821
+ writes: number;
822
+ /** Destinations abandoned along the way, in order — present only when a redirect occurred. */
823
+ redirects?: NavigationHop[];
824
+ /**
825
+ * Wall time from the request to settle: the end of the drain that committed
826
+ * its writes, or the commit of the hold they waited in. `undefined` while
827
+ * unsettled.
828
+ */
829
+ settledMs?: number;
830
+ outcome?: "committed" | "held" | "superseded";
831
+ /** The hold its writes waited in, when hold tracking recorded one. */
832
+ hold?: HoldEvent;
833
+ /**
834
+ * The frame object its writes were stamped with — `ChangeRecord.origin` on
835
+ * each, `HoldEvent.origin` on the hold. Join key, by identity.
836
+ */
837
+ origin: ChangeOrigin;
838
+ }
839
+ export interface InteractionEvent {
840
+ /** Event type — `click`, `keydown`, `input`… */
841
+ name: string;
842
+ /** The element hit, as the runtime described it — `button#next "Next →"`. */
843
+ target?: string;
844
+ /**
845
+ * When the interaction began (`performance.now()` clock): the browser event's
846
+ * own timestamp when the runtime supplied it, else the moment the handler
847
+ * frame opened. Joins `PerformanceEventTiming.startTime` for the same event.
848
+ */
849
+ at: number;
850
+ /**
851
+ * Browser event creation to handler entry (ms) — the queueing the browser's
852
+ * INP counts as input delay. Present only when `at` predates the frame.
853
+ */
854
+ inputDelayMs?: number;
855
+ /** Wall time of the handler itself, entry to return. */
856
+ handlerMs: number;
857
+ /** Root writes attributed to the frame: the handler's, and those of frames it opened (a navigation). */
858
+ writes: number;
859
+ /** Re-runs traced back to this interaction while the record was open. */
860
+ runs: number;
861
+ /** Computations created in those runs or in the frame's flushes (the "create 1,000 rows" work). */
862
+ created: number;
863
+ /** Summed self-time of `runs` and `created` (ms). Quantized per run; `settledMs` is the wall clock. */
864
+ runMs: number;
865
+ /** Holds its writes waited in, in settle order. */
866
+ holds: HoldEvent[];
867
+ /** Navigations performed under it, in open order. */
868
+ navigations: NavigationEvent[];
869
+ /**
870
+ * Dispatch to settle: the handler's return when it wrote nothing, the end of
871
+ * the drain that committed its writes, or the commit of the last hold they
872
+ * waited in — whichever came last. `undefined` while unsettled.
873
+ */
874
+ settledMs?: number;
875
+ outcome?: "idle" | "committed" | "held";
876
+ /**
877
+ * The handler returned a thenable (`async () => { await save(); … }`) and
878
+ * the record waited for it: handler return → the promise settling, in
879
+ * milliseconds, capped at `ASYNC_HANDLER_CAP_MS`. The continuation runs
880
+ * with no frame on the stack, so writes it makes are not attributed to
881
+ * this interaction — only its duration is. Absent when the handler
882
+ * returned synchronously.
883
+ */
884
+ continuationMs?: number;
885
+ /**
886
+ * The frame object every downstream fact carries — `ChangeOrigin.interaction`
887
+ * on writes and frames, `RerunEvent.interaction`, `HoldEvent.interaction`,
888
+ * `NavigationEvent.interaction`. Join key, by identity.
889
+ */
890
+ origin: ChangeOrigin;
891
+ }
892
+ export declare const attribution: Attribution;