@solidjs/signals 2.0.0-rc.6 → 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.
- package/dist/dev-shared.js +5243 -0
- package/dist/dev.attribution.js +2333 -0
- package/dist/dev.js +1758 -6621
- package/dist/observe/affects.js +127 -0
- package/dist/observe/attribution.js +1 -0
- package/dist/observe/boundaries.js +586 -0
- package/dist/observe/core/action.js +167 -0
- package/dist/observe/core/async.js +736 -0
- package/dist/observe/core/attribution-hooks.js +60 -0
- package/dist/observe/core/attribution.js +2318 -0
- package/dist/{types-cjs/core/constants.d.cts → observe/core/constants.js} +135 -71
- package/dist/{types-cjs/core/context.d.cts → observe/core/context.js} +37 -12
- package/dist/observe/core/core.js +1289 -0
- package/dist/observe/core/dev.js +232 -0
- package/dist/observe/core/effect.js +179 -0
- package/dist/observe/core/error.js +80 -0
- package/dist/observe/core/external.js +98 -0
- package/dist/observe/core/graph.js +155 -0
- package/dist/observe/core/heap.js +147 -0
- package/dist/observe/core/invariants.js +43 -0
- package/dist/observe/core/lanes.js +168 -0
- package/dist/observe/core/optimistic.js +438 -0
- package/dist/observe/core/owner.js +308 -0
- package/dist/observe/core/scheduler.js +1190 -0
- package/dist/observe/core/verdict.js +551 -0
- package/dist/observe/index.js +55 -0
- package/dist/observe/map.js +482 -0
- package/dist/{types-cjs/signals.d.cts → observe/signals.js} +305 -317
- package/dist/observe/store/index.js +38 -0
- package/dist/observe/store/next/optimistic.js +651 -0
- package/dist/observe/store/next/projection.js +242 -0
- package/dist/observe/store/next/reconcile.js +373 -0
- package/dist/observe/store/next/store.js +2213 -0
- package/dist/observe/store/next/target.js +51 -0
- package/dist/observe/store/store.js +323 -0
- package/dist/observe/store/storePath.js +103 -0
- package/dist/observe/store/utils.js +210 -0
- package/dist/prod/attribution.js +32 -0
- package/dist/prod/boundaries.js +3 -1
- package/dist/prod/core/action.js +52 -32
- package/dist/prod/core/async.js +177 -128
- package/dist/prod/core/attribution-hooks.js +3 -0
- package/dist/prod/core/constants.js +32 -1
- package/dist/prod/core/context.js +10 -16
- package/dist/prod/core/core.js +505 -286
- package/dist/prod/core/dev.js +2 -0
- package/dist/prod/core/effect.js +72 -43
- package/dist/prod/core/external.js +2 -2
- package/dist/prod/core/graph.js +40 -36
- package/dist/prod/core/heap.js +50 -51
- package/dist/prod/core/lanes.js +43 -22
- package/dist/prod/core/optimistic.js +224 -80
- package/dist/prod/core/owner.js +43 -39
- package/dist/prod/core/scheduler.js +347 -232
- package/dist/prod/core/verdict.js +71 -74
- package/dist/prod/index.js +9 -5
- package/dist/prod/map.js +311 -150
- package/dist/prod/signals.js +52 -38
- package/dist/prod/store/next/optimistic.js +127 -131
- package/dist/prod/store/next/projection.js +3 -3
- package/dist/prod/store/next/reconcile.js +149 -294
- package/dist/prod/store/next/store.js +694 -380
- package/dist/prod/store/next/target.js +32 -10
- package/dist/prod/store/store.js +12 -14
- package/dist/prod/store/utils.js +36 -27
- package/dist/types/attribution.d.ts +15 -0
- package/dist/types/attribution.prod.d.ts +13 -0
- package/dist/types/core/async.d.ts +1 -1
- package/dist/types/core/attribution-hooks.d.ts +183 -6
- package/dist/types/core/attribution.d.ts +435 -24
- package/dist/types/core/constants.d.ts +31 -0
- package/dist/types/core/core.d.ts +19 -3
- package/dist/types/core/dev.d.ts +165 -26
- package/dist/types/core/effect.d.ts +8 -1
- package/dist/types/core/heap.d.ts +5 -3
- package/dist/types/core/index.d.ts +2 -1
- package/dist/types/core/invariants.d.ts +1 -1
- package/dist/types/core/lanes.d.ts +17 -0
- package/dist/types/core/scheduler.d.ts +69 -4
- package/dist/types/core/types.d.ts +29 -9
- package/dist/types/index.d.ts +9 -2
- package/dist/types/map.d.ts +2 -0
- package/dist/types/signals.d.ts +10 -0
- package/dist/types/store/index.d.ts +4 -6
- package/dist/types/store/next/optimistic.d.ts +4 -2
- package/dist/types/store/next/reconcile.d.ts +5 -12
- package/dist/types/store/next/store.d.ts +8 -9
- package/dist/types/store/next/target.d.ts +56 -55
- package/dist/types/store/store.d.ts +14 -9
- package/dist/types/store/utils.d.ts +7 -0
- package/package.json +21 -16
- package/dist/node.cjs +0 -11121
- package/dist/prod/store/next/patch-hooks.js +0 -13
- package/dist/prod/store/next/patch.js +0 -614
- package/dist/types/store/next/patch-hooks.d.ts +0 -41
- package/dist/types/store/next/patch.d.ts +0 -91
- package/dist/types-cjs/affects.d.cts +0 -47
- package/dist/types-cjs/boundaries.d.cts +0 -175
- package/dist/types-cjs/core/action.d.cts +0 -64
- package/dist/types-cjs/core/async.d.cts +0 -23
- package/dist/types-cjs/core/attribution-hooks.d.cts +0 -63
- package/dist/types-cjs/core/attribution.d.cts +0 -239
- package/dist/types-cjs/core/core.d.cts +0 -176
- package/dist/types-cjs/core/dev.d.cts +0 -95
- package/dist/types-cjs/core/effect.d.cts +0 -30
- package/dist/types-cjs/core/error.d.cts +0 -56
- package/dist/types-cjs/core/external.d.cts +0 -15
- package/dist/types-cjs/core/graph.d.cts +0 -28
- package/dist/types-cjs/core/heap.d.cts +0 -22
- package/dist/types-cjs/core/index.d.cts +0 -13
- package/dist/types-cjs/core/invariants.d.cts +0 -59
- package/dist/types-cjs/core/lanes.d.cts +0 -44
- package/dist/types-cjs/core/optimistic.d.cts +0 -6
- package/dist/types-cjs/core/owner.d.cts +0 -124
- package/dist/types-cjs/core/scheduler.d.cts +0 -231
- package/dist/types-cjs/core/types.d.cts +0 -204
- package/dist/types-cjs/core/verdict.d.cts +0 -2
- package/dist/types-cjs/index.d.cts +0 -10
- package/dist/types-cjs/map.d.cts +0 -69
- package/dist/types-cjs/package.json +0 -3
- package/dist/types-cjs/store/index.d.cts +0 -21
- package/dist/types-cjs/store/next/optimistic.d.cts +0 -23
- package/dist/types-cjs/store/next/patch-hooks.d.cts +0 -41
- package/dist/types-cjs/store/next/patch.d.cts +0 -91
- package/dist/types-cjs/store/next/projection.d.cts +0 -8
- package/dist/types-cjs/store/next/reconcile.d.cts +0 -17
- package/dist/types-cjs/store/next/store.d.cts +0 -127
- package/dist/types-cjs/store/next/target.d.cts +0 -192
- package/dist/types-cjs/store/store.d.cts +0 -138
- package/dist/types-cjs/store/storePath.d.cts +0 -58
- package/dist/types-cjs/store/utils.d.cts +0 -74
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Computed } from "./types.js";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* "Why did this run" attribution — the engine behind
|
|
4
|
+
* `@solidjs/signals/attribution`.
|
|
4
5
|
*
|
|
5
6
|
* The runtime already knows the full dependency set of every scope; this
|
|
6
7
|
* module surfaces it. Every value commit stamps its node with a ChangeRecord
|
|
@@ -15,13 +16,56 @@ import type { Computed } from "./types.js";
|
|
|
15
16
|
*
|
|
16
17
|
* This module is the attribution ENGINE: all semantics live here, and it is
|
|
17
18
|
* decoupled from the core. `enable()` installs it into the core's narrow
|
|
18
|
-
*
|
|
19
|
-
* call those hooks with true facts. Disabled cost is one null check per
|
|
20
|
-
* site; prod builds fold the sites out entirely.
|
|
21
|
-
*
|
|
22
|
-
*
|
|
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.
|
|
23
26
|
*/
|
|
24
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 →"`), `at` the dispatch time on the `performance.now()`
|
|
34
|
+
* clock — the base every feedback-latency number is measured from.
|
|
35
|
+
* - `effect` — an effect callback (`name` = the effect's name; `run` = the
|
|
36
|
+
* compute run whose effect phase performed the write, when that run was
|
|
37
|
+
* recorded — so a write can be joined to the re-run that produced it).
|
|
38
|
+
* - `action` — a step of an `action()` generator (`name` = the generator's
|
|
39
|
+
* name, when it has one). Writes after an `await` (not a `yield`) run in a
|
|
40
|
+
* bare microtask and stamp `external` — the documented escape.
|
|
41
|
+
* - `async` — an async landing (`name` = the node whose flight landed).
|
|
42
|
+
* - `navigation` — a router's navigation, declared via `withOrigin` around
|
|
43
|
+
* the location write (`name` = the matched route pattern `/users/:id`;
|
|
44
|
+
* `to`/`from` the concrete paths; `params` what the pattern bound; `at`
|
|
45
|
+
* when it was requested). The router-agnostic seam: any router that wraps
|
|
46
|
+
* its write gets navigations named by route in every hold, re-run and
|
|
47
|
+
* verdict, with no per-router knowledge anywhere in the engine.
|
|
48
|
+
* - `external` — none of the above: timers, sockets, promise callbacks, setup.
|
|
49
|
+
*
|
|
50
|
+
* `interaction` on a non-interaction frame is the user event the frame runs
|
|
51
|
+
* under — an action started by a click, an effect whose run was caused by a
|
|
52
|
+
* click's write, a landing whose flight a click started, a navigation a link
|
|
53
|
+
* click performed. It is what lets every downstream cost be keyed by the
|
|
54
|
+
* interaction that paid for it.
|
|
55
|
+
*/
|
|
56
|
+
export interface ChangeOrigin {
|
|
57
|
+
kind: "interaction" | "effect" | "action" | "async" | "navigation" | "external";
|
|
58
|
+
name?: string;
|
|
59
|
+
target?: string;
|
|
60
|
+
at?: number;
|
|
61
|
+
interaction?: ChangeOrigin;
|
|
62
|
+
/** `effect` only: the `RerunEvent.run` of the compute run this callback belongs to. */
|
|
63
|
+
run?: number;
|
|
64
|
+
/** `navigation` only: concrete destination and departure paths, and the bound params. */
|
|
65
|
+
to?: string;
|
|
66
|
+
from?: string;
|
|
67
|
+
params?: Readonly<Record<string, string | undefined>>;
|
|
68
|
+
}
|
|
25
69
|
export interface ChangeRecord {
|
|
26
70
|
/** Global monotonic change sequence — orders causes across the app. */
|
|
27
71
|
seq: number;
|
|
@@ -34,10 +78,16 @@ export interface ChangeRecord {
|
|
|
34
78
|
stack?: string[];
|
|
35
79
|
/** For derived changes: the upstream changes that produced this one. */
|
|
36
80
|
causes?: ChangeRecord[];
|
|
81
|
+
/** Root changes only: who performed the write. */
|
|
82
|
+
origin?: ChangeOrigin;
|
|
83
|
+
/** Root changes only: when the write was stamped (`performance.now()` clock). */
|
|
84
|
+
at?: number;
|
|
37
85
|
}
|
|
38
86
|
export interface RerunEvent {
|
|
39
87
|
/** Global monotonic run sequence. */
|
|
40
88
|
run: number;
|
|
89
|
+
/** When the run started (`performance.now()` clock). */
|
|
90
|
+
at: number;
|
|
41
91
|
/** How many times this node has re-run since attribution was enabled. */
|
|
42
92
|
nodeRuns: number;
|
|
43
93
|
nodeKind: "effect" | "memo";
|
|
@@ -74,19 +124,21 @@ export interface RerunEvent {
|
|
|
74
124
|
changed: boolean;
|
|
75
125
|
/**
|
|
76
126
|
* Which posture this run executed under. "optimistic" = under an
|
|
77
|
-
* optimistic lane (overlay recompute); "
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
127
|
+
* optimistic lane (overlay recompute); "held" = a hold was open or owns
|
|
128
|
+
* the node (the run may be replayed/settled later); "plain" = an ordinary
|
|
129
|
+
* committed run. Overlay runs are real work (they count toward time
|
|
130
|
+
* budgets) but are never blamed as waste, and costs() reports their time
|
|
131
|
+
* separately as `overlayMs`.
|
|
82
132
|
*/
|
|
83
|
-
phase: "plain" | "
|
|
133
|
+
phase: "plain" | "held" | "optimistic";
|
|
84
134
|
/**
|
|
85
|
-
* The changed value was
|
|
86
|
-
*
|
|
87
|
-
*
|
|
135
|
+
* The changed value was parked in `_pendingValue` (held) rather than
|
|
136
|
+
* committed directly; its reveal happens on the hold's own schedule. Held
|
|
137
|
+
* runs are excluded from waste accounting.
|
|
88
138
|
*/
|
|
89
139
|
held: boolean;
|
|
140
|
+
/** The user interaction this run traces back to through its causes, if any. */
|
|
141
|
+
interaction?: ChangeOrigin;
|
|
90
142
|
}
|
|
91
143
|
export interface AttributionOptions {
|
|
92
144
|
/** Pretty-print each re-run to the console (default true). */
|
|
@@ -133,14 +185,11 @@ export interface AttributionOptions {
|
|
|
133
185
|
/**
|
|
134
186
|
* Written-fan-out warning: emit a diagnostic when a committed root
|
|
135
187
|
* invalidation (write, refresh, async landing) reaches a node with at
|
|
136
|
-
* least this many subscribers (default 250).
|
|
137
|
-
* HUGE_FAN_OUT graph-size warning,
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
* anything when the node actually changes. Once per node, re-warning only
|
|
142
|
-
* on 2x subscriber growth, so the two never spam the same node. `false`
|
|
143
|
-
* disables.
|
|
188
|
+
* least this many subscribers (default 250). The lower-bar, opt-in sibling
|
|
189
|
+
* of the always-on HUGE_FAN_OUT graph-size warning, which fires on the
|
|
190
|
+
* same kind of write from GRAPH_SIZE_WARN_AT (2000) up; this one hands
|
|
191
|
+
* over to it there, so a write never carries both. Once per node,
|
|
192
|
+
* re-warning only on 2x subscriber growth. `false` disables.
|
|
144
193
|
*/
|
|
145
194
|
wideWrites?: number | false;
|
|
146
195
|
/**
|
|
@@ -161,7 +210,56 @@ export interface AttributionOptions {
|
|
|
161
210
|
waterfalls?: {
|
|
162
211
|
minFlightMs: number;
|
|
163
212
|
} | false;
|
|
213
|
+
/**
|
|
214
|
+
* Silent-hold warning: emit a diagnostic when a user's writes were held
|
|
215
|
+
* behind async work for at least `infoMs` (default 100ms — RAIL's "feels
|
|
216
|
+
* instant" ceiling) and the screen never acknowledged the wait — no
|
|
217
|
+
* `isPending()`/`latest()` reader downstream of the held writes or their
|
|
218
|
+
* blockers, no optimistic value, no `affects()` mark, and no lane effect
|
|
219
|
+
* painted while held. Below `warnMs`
|
|
220
|
+
* (default 200ms — the INP "good" ceiling) the event is advisory
|
|
221
|
+
* (structured channel only); at or above it the console gets the finding.
|
|
222
|
+
* The engine measures to the commit, not the paint, so every number is a
|
|
223
|
+
* floor on what the user saw; the thresholds sit at the strict end of the
|
|
224
|
+
* band on purpose. Holds that staged no root write (initial loads, bare
|
|
225
|
+
* `refresh()`) are never judged: nothing the user did went unanswered.
|
|
226
|
+
* `false` disables hold tracking altogether (`longHolds` included).
|
|
227
|
+
*/
|
|
228
|
+
holds?: {
|
|
229
|
+
infoMs: number;
|
|
230
|
+
warnMs: number;
|
|
231
|
+
} | false;
|
|
232
|
+
/**
|
|
233
|
+
* Long-hold warning: emit a diagnostic when a hold's quiescent tail — the
|
|
234
|
+
* time from the LAST write to join it until it committed — reached
|
|
235
|
+
* `infoMs` (default 500ms), `warn` from `warnMs` (default 1000ms, where
|
|
236
|
+
* RAIL says the user loses the thread). Measured from the last join so a
|
|
237
|
+
* hold that keeps taking input (typing) is judged by each wait, not by its
|
|
238
|
+
* lifetime. A hold this long is past what a stale screen should carry,
|
|
239
|
+
* acknowledged or not: the honest UI is a fallback, which a `Loading`
|
|
240
|
+
* boundary gives only when it has not revealed yet or its `on` prop
|
|
241
|
+
* changed. Reported as LONG_HOLD when the hold was acknowledged; a silent
|
|
242
|
+
* long hold stays one SILENT_HOLD with the boundary repair appended.
|
|
243
|
+
* `false` disables.
|
|
244
|
+
*/
|
|
245
|
+
longHolds?: {
|
|
246
|
+
infoMs: number;
|
|
247
|
+
warnMs: number;
|
|
248
|
+
} | false;
|
|
164
249
|
}
|
|
250
|
+
/**
|
|
251
|
+
* The records the engine delivers, by `subscribe(type, …)` name. Every one is
|
|
252
|
+
* delivered synchronously at the moment it is complete — a re-run when its
|
|
253
|
+
* recompute ends, an interaction / hold / navigation when it settles — so a
|
|
254
|
+
* consumer never polls the ring buffers to learn that something finished.
|
|
255
|
+
*/
|
|
256
|
+
export interface AttributionRecords {
|
|
257
|
+
rerun: RerunEvent;
|
|
258
|
+
interaction: InteractionEvent;
|
|
259
|
+
hold: HoldEvent;
|
|
260
|
+
navigation: NavigationEvent;
|
|
261
|
+
}
|
|
262
|
+
export type AttributionRecordType = keyof AttributionRecords;
|
|
165
263
|
export interface ScopeCost {
|
|
166
264
|
name: string;
|
|
167
265
|
kind: "effect" | "memo";
|
|
@@ -185,11 +283,21 @@ export interface WriteCost {
|
|
|
185
283
|
/** Summed self-time of every downstream re-run it caused. */
|
|
186
284
|
downstreamMs: number;
|
|
187
285
|
}
|
|
286
|
+
/** `click on button#next "Next →"`, `effect "syncTitle"`, `action "save"`, `navigation to /users/:id`, … */
|
|
287
|
+
export declare function formatOrigin(origin: ChangeOrigin): string;
|
|
188
288
|
export declare function formatRerun(event: RerunEvent): string;
|
|
189
289
|
export interface Attribution {
|
|
190
290
|
enable(opts?: AttributionOptions): void;
|
|
191
291
|
disable(): void;
|
|
292
|
+
/**
|
|
293
|
+
* Deliver records as they complete — see `AttributionRecords`. The bare
|
|
294
|
+
* form is `subscribe("rerun", …)`. Records are the same objects the ring
|
|
295
|
+
* buffers hold (`history()`, `interactions()`, `holds()`, `navigations()`),
|
|
296
|
+
* delivered synchronously from the engine, so a listener must not write
|
|
297
|
+
* signals. All subscriptions are dropped by `disable()`.
|
|
298
|
+
*/
|
|
192
299
|
subscribe(listener: (event: RerunEvent) => void): () => void;
|
|
300
|
+
subscribe<K extends AttributionRecordType>(type: K, listener: (record: AttributionRecords[K]) => void): () => void;
|
|
193
301
|
history(): readonly RerunEvent[];
|
|
194
302
|
/** Re-run history for one node — pass a memo/effect accessor or raw node. */
|
|
195
303
|
why(target: unknown): RerunEvent[];
|
|
@@ -211,6 +319,48 @@ export interface Attribution {
|
|
|
211
319
|
* thresholded view of the same data.
|
|
212
320
|
*/
|
|
213
321
|
waterfalls(): readonly WaterfallRecord[];
|
|
322
|
+
/**
|
|
323
|
+
* Every settled transition hold that staged at least one root write since
|
|
324
|
+
* enable() (ring-buffered like history()). Facts, not verdicts: recorded
|
|
325
|
+
* regardless of duration or acknowledgment — the SILENT_HOLD diagnostic is
|
|
326
|
+
* the thresholded, unacknowledged subset.
|
|
327
|
+
*/
|
|
328
|
+
holds(): readonly HoldEvent[];
|
|
329
|
+
/**
|
|
330
|
+
* Every navigation a router declared via `withOrigin` since enable()
|
|
331
|
+
* (ring-buffered like history()), settled or not: what route, under which
|
|
332
|
+
* interaction, how many writes, and — once its writes are through — how
|
|
333
|
+
* long that took and how (`committed` in a plain drain, `held` behind
|
|
334
|
+
* route data with the HoldEvent attached, or `superseded` by a later
|
|
335
|
+
* navigation before it landed). Facts for any consumer that wants
|
|
336
|
+
* navigation spans named by route: no router integration needed.
|
|
337
|
+
*/
|
|
338
|
+
navigations(): readonly NavigationEvent[];
|
|
339
|
+
/**
|
|
340
|
+
* Every user interaction a runtime declared via `withInteraction` since
|
|
341
|
+
* enable() (ring-buffered like history()), settled or not: what was
|
|
342
|
+
* dispatched, when, what it wrote, the re-runs and creations it caused,
|
|
343
|
+
* the holds its writes waited in and the navigations it performed — and,
|
|
344
|
+
* once all of that is through, how long the person waited (`settledMs`)
|
|
345
|
+
* and how it ended (`idle` / `committed` / `held`). The per-dispatch
|
|
346
|
+
* record `feedback().interactions` folds by name.
|
|
347
|
+
*/
|
|
348
|
+
interactions(): readonly InteractionEvent[];
|
|
349
|
+
/**
|
|
350
|
+
* What the user waited on, folded from holds() and the interaction on each
|
|
351
|
+
* re-run: `sources` ranks async sources by the silent time writes spent
|
|
352
|
+
* held behind them (with which affordances answered, how often, and which
|
|
353
|
+
* interactions were held); `interactions` ranks user events by the total
|
|
354
|
+
* time they cost — re-run work caused (long-flush hazard) beside time held
|
|
355
|
+
* (silent-hold hazard). Facts at every duration; SILENT_HOLD is the
|
|
356
|
+
* thresholded verdict. Three more tables round out the picture:
|
|
357
|
+
* `navigations` ranks routes by the time spent held navigating to them
|
|
358
|
+
* (folded from navigations()), `flights` counts each async source's
|
|
359
|
+
* flights and how many were abandoned before landing (the re-ask storm),
|
|
360
|
+
* and `fallbacks` measures how long each loading boundary showed its
|
|
361
|
+
* fallback and how often that was a flash.
|
|
362
|
+
*/
|
|
363
|
+
feedback(): AttributionFeedbackTables;
|
|
214
364
|
/**
|
|
215
365
|
* Cooperative preload declaration: stamp a flight object (promise or async
|
|
216
366
|
* iterable) with its true kickoff time BEFORE the reactive graph sees it.
|
|
@@ -220,10 +370,11 @@ export interface Attribution {
|
|
|
220
370
|
* is then judged against the real start — work already in the air when its
|
|
221
371
|
* upstream landed is parallel, never a waterfall link. Callable while
|
|
222
372
|
* attribution is disabled (marks made at navigation time must survive a
|
|
223
|
-
* later enable()).
|
|
373
|
+
* later enable()).
|
|
224
374
|
*/
|
|
225
375
|
markFlight(flight: object, startedAt?: number): void;
|
|
226
376
|
format: typeof formatRerun;
|
|
377
|
+
formatOrigin: typeof formatOrigin;
|
|
227
378
|
}
|
|
228
379
|
/** One landed flight: its node name, wall duration, and upstream chain. */
|
|
229
380
|
export interface FlightLink {
|
|
@@ -236,4 +387,264 @@ export interface WaterfallRecord {
|
|
|
236
387
|
/** Summed wall time of the chain — the serialized cost. */
|
|
237
388
|
sequentialMs: number;
|
|
238
389
|
}
|
|
390
|
+
export interface HeldWrite {
|
|
391
|
+
name: string;
|
|
392
|
+
prev?: string;
|
|
393
|
+
value?: string;
|
|
394
|
+
origin?: ChangeOrigin;
|
|
395
|
+
}
|
|
396
|
+
export interface HoldEvent {
|
|
397
|
+
/**
|
|
398
|
+
* When the wait began (`performance.now()` clock): the interaction that
|
|
399
|
+
* performed the held writes when one is known (`interaction.at`) or the
|
|
400
|
+
* first flush that parked them, whichever is earlier. `at + holdMs` is the
|
|
401
|
+
* commit.
|
|
402
|
+
*/
|
|
403
|
+
at: number;
|
|
404
|
+
/** Wall time the user waited: `at` to the commit. */
|
|
405
|
+
holdMs: number;
|
|
406
|
+
/**
|
|
407
|
+
* The quiescent tail: from the LAST held write to join (the user's final
|
|
408
|
+
* input) to the commit. Equal to `holdMs` for a single write; shorter when
|
|
409
|
+
* the hold kept taking input. The LONG_HOLD measure.
|
|
410
|
+
*/
|
|
411
|
+
tailMs: number;
|
|
412
|
+
/** The user interaction whose writes were held, when the stamp is known. */
|
|
413
|
+
interaction?: ChangeOrigin;
|
|
414
|
+
/**
|
|
415
|
+
* The declared unit of work the held writes belong to — the `navigation`
|
|
416
|
+
* a router described via `withOrigin` — when one is known. What names the
|
|
417
|
+
* hold by route (`navigation to /users/:id`) rather than by signal; the
|
|
418
|
+
* same object as `navigations()[].origin`, so the two join by identity.
|
|
419
|
+
*/
|
|
420
|
+
origin?: ChangeOrigin;
|
|
421
|
+
/** Flushes that ended with the hold still open. */
|
|
422
|
+
flushes: number;
|
|
423
|
+
/** Root signal writes staged behind the hold (the user's unanswered input). */
|
|
424
|
+
heldWrites: HeldWrite[];
|
|
425
|
+
/** Async nodes the hold waited on (union across its parked flushes). */
|
|
426
|
+
blockers: string[];
|
|
427
|
+
/**
|
|
428
|
+
* Feedback the graph provably rendered for this hold — an `isPending()`
|
|
429
|
+
* reader, a `latest()` shadow, an optimistic value, an `affects()` mark —
|
|
430
|
+
* one entry per affordance, in the order found. Empty and
|
|
431
|
+
* `paintedDuringHold === 0` is the SILENT_HOLD signature.
|
|
432
|
+
*/
|
|
433
|
+
acknowledgements: Acknowledgement[];
|
|
434
|
+
/**
|
|
435
|
+
* Effect callbacks that ran inside the hold's parked flushes. Mainline
|
|
436
|
+
* effects are stashed while a hold is open, so these are lane effects —
|
|
437
|
+
* readers of optimistic values and of `isPending()`/`latest()` companions,
|
|
438
|
+
* i.e. the screen changing in response to the hold. An unrelated effect
|
|
439
|
+
* cannot land here: it waits with everything else.
|
|
440
|
+
*/
|
|
441
|
+
paintedDuringHold: number;
|
|
442
|
+
/** The hold was opened (or joined) by an `action()`. */
|
|
443
|
+
action: boolean;
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* One way the screen acknowledged a hold. `reader` is where it was painted —
|
|
447
|
+
* the owner path of the first effect the census found reading the
|
|
448
|
+
* affordance (`["<App>", "<Feed>", "effect"]`), so a consumer can say WHICH
|
|
449
|
+
* screen answered, not only that one did. Absent when the affordance was
|
|
450
|
+
* registered but the census found no reader through the graph (an optimistic
|
|
451
|
+
* store: its readers are proxy traps, not nodes). `feedback()` ranks
|
|
452
|
+
* acknowledgements by `kind:source` (`isPending:posts`).
|
|
453
|
+
*/
|
|
454
|
+
export interface Acknowledgement {
|
|
455
|
+
kind: "isPending" | "latest" | "optimistic" | "affects";
|
|
456
|
+
/** The node the affordance hangs on — the async source for `isPending`/`latest`, the optimistic/affected node otherwise. */
|
|
457
|
+
source: string;
|
|
458
|
+
reader?: string[];
|
|
459
|
+
}
|
|
460
|
+
/** A destination a navigation abandoned when a redirect sent it elsewhere. */
|
|
461
|
+
export interface NavigationHop {
|
|
462
|
+
name?: string;
|
|
463
|
+
to?: string;
|
|
464
|
+
params?: Readonly<Record<string, string | undefined>>;
|
|
465
|
+
/** When the redirect away from it was declared (`performance.now()` clock). */
|
|
466
|
+
at: number;
|
|
467
|
+
}
|
|
468
|
+
export interface NavigationEvent {
|
|
469
|
+
/** The matched route pattern the router gave — `/users/:id`. After a redirect, the final one. */
|
|
470
|
+
name?: string;
|
|
471
|
+
to?: string;
|
|
472
|
+
from?: string;
|
|
473
|
+
params?: Readonly<Record<string, string | undefined>>;
|
|
474
|
+
/** When the navigation was requested (`performance.now()` clock). */
|
|
475
|
+
at: number;
|
|
476
|
+
/** The user interaction it ran under, when known — a link click. */
|
|
477
|
+
interaction?: ChangeOrigin;
|
|
478
|
+
/** Root writes the frame performed, redirect hops included. */
|
|
479
|
+
writes: number;
|
|
480
|
+
/** Destinations abandoned along the way, in order — present only when a redirect occurred. */
|
|
481
|
+
redirects?: NavigationHop[];
|
|
482
|
+
/**
|
|
483
|
+
* Wall time from the request to settle: the end of the drain that committed
|
|
484
|
+
* its writes, or the commit of the hold they waited in. `undefined` while
|
|
485
|
+
* unsettled.
|
|
486
|
+
*/
|
|
487
|
+
settledMs?: number;
|
|
488
|
+
outcome?: "committed" | "held" | "superseded";
|
|
489
|
+
/** The hold its writes waited in, when hold tracking recorded one. */
|
|
490
|
+
hold?: HoldEvent;
|
|
491
|
+
/**
|
|
492
|
+
* The frame object its writes were stamped with — `ChangeRecord.origin` on
|
|
493
|
+
* each, `HoldEvent.origin` on the hold. Join key, by identity.
|
|
494
|
+
*/
|
|
495
|
+
origin: ChangeOrigin;
|
|
496
|
+
}
|
|
497
|
+
export interface InteractionEvent {
|
|
498
|
+
/** Event type — `click`, `keydown`, `input`… */
|
|
499
|
+
name: string;
|
|
500
|
+
/** The element hit, as the runtime described it — `button#next "Next →"`. */
|
|
501
|
+
target?: string;
|
|
502
|
+
/** Dispatch time (`performance.now()` clock). */
|
|
503
|
+
at: number;
|
|
504
|
+
/** Wall time of the handler itself, dispatch to return. */
|
|
505
|
+
handlerMs: number;
|
|
506
|
+
/** Root writes attributed to the frame: the handler's, and those of frames it opened (a navigation). */
|
|
507
|
+
writes: number;
|
|
508
|
+
/** Re-runs traced back to this interaction while the record was open. */
|
|
509
|
+
runs: number;
|
|
510
|
+
/** Computations created in those runs or in the frame's flushes (the "create 1,000 rows" work). */
|
|
511
|
+
created: number;
|
|
512
|
+
/** Summed self-time of `runs` and `created` (ms). Quantized per run; `settledMs` is the wall clock. */
|
|
513
|
+
runMs: number;
|
|
514
|
+
/** Holds its writes waited in, in settle order. */
|
|
515
|
+
holds: HoldEvent[];
|
|
516
|
+
/** Navigations performed under it, in open order. */
|
|
517
|
+
navigations: NavigationEvent[];
|
|
518
|
+
/**
|
|
519
|
+
* Dispatch to settle: the handler's return when it wrote nothing, the end of
|
|
520
|
+
* the drain that committed its writes, or the commit of the last hold they
|
|
521
|
+
* waited in — whichever came last. `undefined` while unsettled.
|
|
522
|
+
*/
|
|
523
|
+
settledMs?: number;
|
|
524
|
+
outcome?: "idle" | "committed" | "held";
|
|
525
|
+
/**
|
|
526
|
+
* The frame object every downstream fact carries — `ChangeOrigin.interaction`
|
|
527
|
+
* on writes and frames, `RerunEvent.interaction`, `HoldEvent.interaction`,
|
|
528
|
+
* `NavigationEvent.interaction`. Join key, by identity.
|
|
529
|
+
*/
|
|
530
|
+
origin: ChangeOrigin;
|
|
531
|
+
}
|
|
532
|
+
export interface FeedbackSource {
|
|
533
|
+
/** The async nodes the holds waited on; empty when an action alone kept them open. */
|
|
534
|
+
sources: string[];
|
|
535
|
+
holds: number;
|
|
536
|
+
/** Summed wait across the holds (ms). */
|
|
537
|
+
heldMs: number;
|
|
538
|
+
worstMs: number;
|
|
539
|
+
/** Holds with no acknowledgment at all — the SILENT_HOLD signature, at any duration. */
|
|
540
|
+
silent: number;
|
|
541
|
+
silentMs: number;
|
|
542
|
+
/** Holds whose only acknowledgment was a `latest()` shadow: the input showed, nothing said "loading". */
|
|
543
|
+
latestOnly: number;
|
|
544
|
+
/**
|
|
545
|
+
* Holds whose quiescent tail (last write to join → commit) reached
|
|
546
|
+
* `longHolds.infoMs`, acknowledged or not — the LONG_HOLD signature at the
|
|
547
|
+
* table level. The affordance is not the whole answer there: a fallback
|
|
548
|
+
* (`Loading` keyed with `on`), a preload, a cache, or a faster source is.
|
|
549
|
+
* `longMs` sums the tails.
|
|
550
|
+
*/
|
|
551
|
+
long: number;
|
|
552
|
+
longMs: number;
|
|
553
|
+
/** Which affordances answered, and in how many holds — ranked. */
|
|
554
|
+
acknowledgedBy: {
|
|
555
|
+
by: string;
|
|
556
|
+
holds: number;
|
|
557
|
+
}[];
|
|
558
|
+
/** Interactions whose writes were held here, ranked by holds. */
|
|
559
|
+
interactions: {
|
|
560
|
+
interaction: string;
|
|
561
|
+
holds: number;
|
|
562
|
+
}[];
|
|
563
|
+
/** Distinct root writes that were held. */
|
|
564
|
+
writes: string[];
|
|
565
|
+
/** Holds an action opened or joined. */
|
|
566
|
+
actions: number;
|
|
567
|
+
}
|
|
568
|
+
export interface FeedbackInteraction {
|
|
569
|
+
/** `click on button#next "Next →"` — type and target; repeated dispatches fold together. */
|
|
570
|
+
interaction: string;
|
|
571
|
+
/** Distinct dispatches seen (by dispatch time). */
|
|
572
|
+
dispatches: number;
|
|
573
|
+
/** Re-runs traced back to this interaction, and their summed self-time. */
|
|
574
|
+
runs: number;
|
|
575
|
+
selfMs: number;
|
|
576
|
+
/** The most re-run self-time a single dispatch caused — the long-flush hazard. */
|
|
577
|
+
worstDispatchMs: number;
|
|
578
|
+
/** Holds this interaction's writes waited in — the silent-hold hazard. */
|
|
579
|
+
holds: number;
|
|
580
|
+
heldMs: number;
|
|
581
|
+
silentMs: number;
|
|
582
|
+
worstHoldMs: number;
|
|
583
|
+
}
|
|
584
|
+
/** Per async source: how many flights it started, and how many it threw away. */
|
|
585
|
+
export interface FlightStats {
|
|
586
|
+
source: string;
|
|
587
|
+
/** Flights registered (a recompute that produced a new promise/iterable). */
|
|
588
|
+
flights: number;
|
|
589
|
+
/** Flights that landed (whether or not the value changed). */
|
|
590
|
+
landed: number;
|
|
591
|
+
/**
|
|
592
|
+
* Flights superseded by a newer one before landing — the search-as-you-type
|
|
593
|
+
* signature when large: every keystroke asked, most answers were discarded.
|
|
594
|
+
* A debounced/equality-gated derivation between input and fetch is the repair.
|
|
595
|
+
*/
|
|
596
|
+
abandoned: number;
|
|
597
|
+
/** Summed and worst wall time of landed flights (ms). */
|
|
598
|
+
landedMs: number;
|
|
599
|
+
worstMs: number;
|
|
600
|
+
}
|
|
601
|
+
/** Per loading boundary: how long, and how briefly, it showed its fallback. */
|
|
602
|
+
export interface FallbackStats {
|
|
603
|
+
/** The boundary's owner path (`<App> › <Feed>`), or `boundary` when unnamed. */
|
|
604
|
+
boundary: string;
|
|
605
|
+
/** Times the fallback was shown. */
|
|
606
|
+
shows: number;
|
|
607
|
+
/** Summed and worst fallback duration (ms) across completed shows. */
|
|
608
|
+
shownMs: number;
|
|
609
|
+
worstMs: number;
|
|
610
|
+
/**
|
|
611
|
+
* Shows shorter than the flash window (default 150ms): a spinner that
|
|
612
|
+
* appeared and vanished — the other end of the SILENT_HOLD spectrum, too
|
|
613
|
+
* much feedback for too little wait. A preload, a cache, or lifting the
|
|
614
|
+
* fetch above the boundary removes the flash.
|
|
615
|
+
*/
|
|
616
|
+
flashes: number;
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Per route: what navigating to it cost, folded from settled
|
|
620
|
+
* `NavigationEvent`s — the route-level view a router integration used to
|
|
621
|
+
* have to build itself, from the runtime's own facts.
|
|
622
|
+
*/
|
|
623
|
+
export interface FeedbackNavigation {
|
|
624
|
+
/** The route pattern (`/users/:id`), or the concrete `to` when the router gave no pattern. */
|
|
625
|
+
name: string;
|
|
626
|
+
navigations: number;
|
|
627
|
+
/** Summed and worst request-to-settle time (ms) across settled navigations. */
|
|
628
|
+
settledMs: number;
|
|
629
|
+
worstMs: number;
|
|
630
|
+
/** Navigations whose writes waited in a hold, and the time they waited. */
|
|
631
|
+
held: number;
|
|
632
|
+
heldMs: number;
|
|
633
|
+
/** Held navigations the screen acknowledged nothing for — the SILENT_HOLD signature. */
|
|
634
|
+
silent: number;
|
|
635
|
+
/** Navigations overwritten by a later one before they landed. */
|
|
636
|
+
superseded: number;
|
|
637
|
+
/** Navigations a redirect sent elsewhere on the way (keyed by where they ended up). */
|
|
638
|
+
redirected: number;
|
|
639
|
+
}
|
|
640
|
+
export interface AttributionFeedbackTables {
|
|
641
|
+
sources: FeedbackSource[];
|
|
642
|
+
interactions: FeedbackInteraction[];
|
|
643
|
+
/** Routes ranked by the time spent held navigating to them, then by total settle time. */
|
|
644
|
+
navigations: FeedbackNavigation[];
|
|
645
|
+
/** Async sources ranked by abandoned flights, then by flights. */
|
|
646
|
+
flights: FlightStats[];
|
|
647
|
+
/** Loading boundaries ranked by flashes, then by time shown. */
|
|
648
|
+
fallbacks: FallbackStats[];
|
|
649
|
+
}
|
|
239
650
|
export declare const attribution: Attribution;
|
|
@@ -105,6 +105,34 @@ export declare const CONFIG_FRESH_READ: number;
|
|
|
105
105
|
* (A17). Cleared at commit (the commit IS the reveal); subscribers masked
|
|
106
106
|
* during the hold are woken by finalizePureQueue's post-revert pass. */
|
|
107
107
|
export declare const CONFIG_HELD_TRUTH: number;
|
|
108
|
+
/** SLOT node (store leaf): created through `slotSignal` with `_host`/`_key`
|
|
109
|
+
* backrefs baked into the literal. The unobserved sweep dispatches these to
|
|
110
|
+
* the ONE shared hook (`setSlotUnobserved`) instead of a per-node closure
|
|
111
|
+
* held in a per-node extension — store mounts materialize one signal per
|
|
112
|
+
* touched leaf, so per-node allocations (options object, equals closure,
|
|
113
|
+
* unobserved closure, NodeExtension) were the measured create-floor bytes
|
|
114
|
+
* (warm dbmon profile: store node machinery ~36% + GC ~29%). */
|
|
115
|
+
export declare const CONFIG_SLOT_NODE: number;
|
|
116
|
+
/** Optimistic node whose own source arrived with a value DIFFERENT from its
|
|
117
|
+
* active override (A18 supersession, #3331). The override survives only as
|
|
118
|
+
* the displayed value — untracked reads and the applied frame keep it until
|
|
119
|
+
* the owning transaction commits — while the graph has already moved to the
|
|
120
|
+
* staged truth in `_pendingValue`: tracked readers see it and the corrected
|
|
121
|
+
* cascade is that transaction's held work. Set by the two own-source write
|
|
122
|
+
* paths (asyncWrite, transition-held recompute); cleared by a fresh optimistic
|
|
123
|
+
* write (a new override re-masks) and by the revert. */
|
|
124
|
+
export declare const CONFIG_OVERRIDE_SUPERSEDED: number;
|
|
125
|
+
/** In-flight async node whose inputs were PUBLISHED while it was pending: a
|
|
126
|
+
* batch or transaction committed with the node still `STATUS_PENDING` (an
|
|
127
|
+
* unobserved flight, #3305), so the inputs are on screen and the node's
|
|
128
|
+
* committed `_value` is stale against them. Governs read()'s reveal
|
|
129
|
+
* carve-out: a stale (render) reader in some OTHER transaction may show a
|
|
130
|
+
* foreign-held pending node's committed value — parallel transactions, no
|
|
131
|
+
* entanglement — only while that value is coherent with the visible frame,
|
|
132
|
+
* i.e. while the flight's inputs are themselves held (unpublished) and not
|
|
133
|
+
* lane-revealed. Set by `commitPendingNodes`; cleared when the node next
|
|
134
|
+
* enters pending fresh (a new flight from a settled state). */
|
|
135
|
+
export declare const CONFIG_INPUTS_PUBLISHED: number;
|
|
108
136
|
export declare const STATUS_NONE = 0;
|
|
109
137
|
export declare const STATUS_PENDING: number;
|
|
110
138
|
export declare const STATUS_ERROR: number;
|
|
@@ -113,6 +141,9 @@ export declare const EFFECT_PURE = 0;
|
|
|
113
141
|
export declare const EFFECT_RENDER = 1;
|
|
114
142
|
export declare const EFFECT_USER = 2;
|
|
115
143
|
export declare const EFFECT_TRACKED = 3;
|
|
144
|
+
/** OR-ed into the `type` a lane passes to its effect runners: lane runs
|
|
145
|
+
* apply ahead of their transaction and are exempt from ownership parking. */
|
|
146
|
+
export declare const LANE_RUN = 4;
|
|
116
147
|
export declare const NOT_PENDING: {};
|
|
117
148
|
export declare const NO_SNAPSHOT: {};
|
|
118
149
|
/**
|
|
@@ -54,6 +54,20 @@ export declare function setEffectStatusNotify(fn: NonNullable<typeof effectStatu
|
|
|
54
54
|
export declare function statusNotifierOf(el: any): ((this: any, status?: number, error?: any) => void) | undefined;
|
|
55
55
|
export declare function signal<T>(v: T, options?: NodeOptions<T>): Signal<T>;
|
|
56
56
|
export declare function signal<T>(v: T, options?: NodeOptions<T>, firewall?: Computed<any>): FirewallSignal<T>;
|
|
57
|
+
/** The shared slot-node unobserved handler — a live binding read directly by
|
|
58
|
+
* the sweep sites (no wrapper frame, no null check: a CONFIG_SLOT_NODE node
|
|
59
|
+
* existing implies the store module loaded and registered the hook). */
|
|
60
|
+
export declare let slotUnobservedHook: (node: Signal<any>) => void;
|
|
61
|
+
/** Install the shared slot-node unobserved handler (store module, once). */
|
|
62
|
+
export declare function setSlotUnobserved(fn: (node: Signal<any>) => void): void;
|
|
63
|
+
/** Release a firewall child the store no longer addresses (unobserved sweep
|
|
64
|
+
* dropped it from its target's cache): unlink it from the chain so the
|
|
65
|
+
* projection stops retaining it and its last value. The node keeps its own
|
|
66
|
+
* `_nextChild` so a walk that is mid-chain on it still terminates. Nodes in
|
|
67
|
+
* `_companionChildren` stay there — companions are permanent by contract
|
|
68
|
+
* and snap through that set, not the chain. */
|
|
69
|
+
export declare function unlinkFirewallChild(node: Signal<any>): void;
|
|
70
|
+
export declare function slotSignal<T>(v: T, equals: (a: T, b: T) => boolean, host: object, key: PropertyKey, acc: boolean, firewall?: Computed<unknown> | null): Signal<T>;
|
|
57
71
|
export declare function optimisticSignal<T>(v: T, options?: NodeOptions<T>): Signal<T>;
|
|
58
72
|
export declare function optimisticComputed<T>(fn: (prev?: T) => T | PromiseLike<T> | AsyncIterable<T>, options?: NodeOptions<T>): Computed<T>;
|
|
59
73
|
export declare function isEqual<T>(a: T, b: T): boolean;
|
|
@@ -115,9 +129,11 @@ export declare const READ_SLOW: unique symbol;
|
|
|
115
129
|
* installed at first until() call — apps that never use until() shake it.
|
|
116
130
|
*/
|
|
117
131
|
export declare function notifyAuthoritativeObservers(el: Signal<any> | Computed<any>): void;
|
|
118
|
-
/** Installs the
|
|
119
|
-
*
|
|
120
|
-
* optimistic engine
|
|
132
|
+
/** Installs the authoritative-reader wakeup hook. Idempotent; called by every
|
|
133
|
+
* creator of a CONFIG_AUTHORITATIVE_READ computation — until() and refresh() —
|
|
134
|
+
* before its first read (same late-binding contract as the optimistic engine;
|
|
135
|
+
* the gating bit is only ever set by such a read, so the `!` call sites are
|
|
136
|
+
* safe once every setter installs, #3303). */
|
|
121
137
|
export declare function installAuthoritativeRead(): void;
|
|
122
138
|
export declare function readNodeFast<T>(el: Signal<T>): T | typeof READ_SLOW;
|
|
123
139
|
export declare function read<T>(el: Signal<T> | Computed<T>): T;
|