@solidjs/signals 2.0.0-rc.7 → 2.0.0-rc.9
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 +6357 -0
- package/dist/dev.attribution.js +2485 -0
- package/dist/dev.js +2851 -7027
- package/dist/observe/affects.js +129 -0
- package/dist/observe/attribution.js +7 -0
- package/dist/observe/boundaries.js +641 -0
- package/dist/observe/core/action.js +177 -0
- package/dist/observe/core/async.js +846 -0
- package/dist/observe/core/attribution-costs.js +66 -0
- package/dist/observe/core/attribution-feedback.js +282 -0
- package/dist/observe/core/attribution-hooks.js +82 -0
- package/dist/observe/core/attribution-queries.js +28 -0
- package/dist/observe/core/attribution.js +2095 -0
- package/dist/observe/core/constants.js +248 -0
- package/dist/{types-cjs/core/context.d.cts → observe/core/context.js} +37 -12
- package/dist/observe/core/core.js +1789 -0
- package/dist/observe/core/dev.js +293 -0
- package/dist/observe/core/effect.js +195 -0
- package/dist/observe/core/error-hooks.js +71 -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 +45 -0
- package/dist/observe/core/lanes.js +205 -0
- package/dist/observe/core/optimistic.js +585 -0
- package/dist/observe/core/owner.js +322 -0
- package/dist/observe/core/scheduler.js +1428 -0
- package/dist/observe/core/verdict.js +669 -0
- package/dist/observe/index.js +59 -0
- package/dist/observe/map.js +484 -0
- package/dist/{types-cjs/signals.d.cts → observe/signals.js} +311 -317
- package/dist/observe/store/index.js +40 -0
- package/dist/observe/store/next/optimistic.js +660 -0
- package/dist/observe/store/next/projection.js +255 -0
- package/dist/observe/store/next/reconcile.js +375 -0
- package/dist/observe/store/next/store.js +2327 -0
- package/dist/observe/store/next/target.js +51 -0
- package/dist/observe/store/store.js +325 -0
- package/dist/observe/store/storePath.js +103 -0
- package/dist/observe/store/utils.js +1023 -0
- package/dist/prod/attribution.js +41 -0
- package/dist/prod/boundaries.js +128 -76
- package/dist/prod/core/action.js +65 -37
- package/dist/prod/core/async.js +324 -172
- package/dist/prod/core/constants.js +57 -1
- package/dist/prod/core/context.js +3 -3
- package/dist/prod/core/core.js +1003 -364
- package/dist/prod/core/dev.js +17 -1
- package/dist/prod/core/effect.js +76 -42
- package/dist/prod/core/error-hooks.js +71 -0
- package/dist/prod/core/external.js +4 -4
- package/dist/prod/core/graph.js +37 -37
- package/dist/prod/core/heap.js +61 -56
- package/dist/prod/core/lanes.js +107 -63
- package/dist/prod/core/optimistic.js +374 -93
- package/dist/prod/core/owner.js +62 -46
- package/dist/prod/core/scheduler.js +653 -286
- package/dist/prod/core/verdict.js +245 -127
- package/dist/prod/index.js +15 -5
- package/dist/prod/map.js +292 -141
- package/dist/prod/signals.js +29 -16
- package/dist/prod/store/next/optimistic.js +77 -55
- package/dist/prod/store/next/projection.js +31 -20
- package/dist/prod/store/next/reconcile.js +87 -84
- package/dist/prod/store/next/store.js +718 -401
- package/dist/prod/store/next/target.js +32 -10
- package/dist/prod/store/store.js +12 -14
- package/dist/prod/store/utils.js +950 -130
- package/dist/types/attribution.d.ts +20 -0
- package/dist/types/attribution.prod.d.ts +22 -0
- package/dist/types/boundaries.d.ts +10 -1
- package/dist/types/core/action.d.ts +12 -5
- package/dist/types/core/async.d.ts +1 -1
- package/dist/types/core/attribution-costs.d.ts +35 -0
- package/dist/types/core/attribution-feedback.d.ts +133 -0
- package/dist/types/core/attribution-hooks.d.ts +147 -9
- package/dist/types/core/attribution-queries.d.ts +10 -0
- package/dist/types/core/attribution.d.ts +216 -182
- package/dist/types/core/constants.d.ts +56 -0
- package/dist/types/core/core.d.ts +190 -2
- package/dist/types/core/dev.d.ts +246 -28
- package/dist/types/core/effect.d.ts +8 -1
- package/dist/types/core/error-hooks.d.ts +71 -0
- package/dist/types/core/index.d.ts +4 -1
- package/dist/types/core/invariants.d.ts +4 -0
- package/dist/types/core/lanes.d.ts +39 -5
- package/dist/types/core/scheduler.d.ts +153 -0
- package/dist/types/core/types.d.ts +32 -9
- package/dist/types/index.d.ts +10 -3
- package/dist/types/map.d.ts +2 -0
- package/dist/types/signals.d.ts +8 -0
- package/dist/types/store/index.d.ts +2 -0
- package/dist/types/store/next/optimistic.d.ts +1 -1
- package/dist/types/store/next/store.d.ts +11 -5
- package/dist/types/store/next/target.d.ts +37 -10
- package/dist/types/store/utils.d.ts +178 -0
- package/package.json +21 -17
- package/dist/node.cjs +0 -10541
- package/dist/node.dev.cjs +0 -13724
- 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 -127
- package/dist/types-cjs/core/attribution.d.cts +0 -495
- package/dist/types-cjs/core/constants.d.cts +0 -159
- package/dist/types-cjs/core/core.d.cts +0 -185
- package/dist/types-cjs/core/dev.d.cts +0 -136
- 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 -24
- 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 -54
- 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 -236
- 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 -18
- package/dist/types-cjs/store/next/optimistic.d.cts +0 -25
- package/dist/types-cjs/store/next/projection.d.cts +0 -8
- package/dist/types-cjs/store/next/reconcile.d.cts +0 -10
- package/dist/types-cjs/store/next/store.d.cts +0 -121
- package/dist/types-cjs/store/next/target.d.cts +0 -166
- package/dist/types-cjs/store/store.d.cts +0 -143
- package/dist/types-cjs/store/storePath.d.cts +0 -58
- package/dist/types-cjs/store/utils.d.cts +0 -74
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { registerFold, rootsOf } from "./attribution.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `costs()` — the cost tables: scopes ranked by the self-time they burn,
|
|
5
|
+
* root writes ranked by the downstream re-run time they cause. Folded from
|
|
6
|
+
* every `RerunEvent` as the engine records it.
|
|
7
|
+
*
|
|
8
|
+
* Its own module on purpose: the tables register with the engine's fold seam
|
|
9
|
+
* when this module is evaluated, so an observe-tier consumer that only
|
|
10
|
+
* subscribes to records (an APM adapter) never ships the accumulation or the
|
|
11
|
+
* tables; a consumer that imports `costs` gets both. Reset with the engine on
|
|
12
|
+
* `enable()`/`disable()`.
|
|
13
|
+
*/ const scopeCosts = new Map;
|
|
14
|
+
|
|
15
|
+
const writeCosts = new Map;
|
|
16
|
+
|
|
17
|
+
function recordCosts(s, o) {
|
|
18
|
+
let t = scopeCosts.get(s);
|
|
19
|
+
if (t === undefined) {
|
|
20
|
+
t = {
|
|
21
|
+
name: o.nodeName,
|
|
22
|
+
kind: o.nodeKind,
|
|
23
|
+
runs: 0,
|
|
24
|
+
selfMs: 0,
|
|
25
|
+
wastedMs: 0,
|
|
26
|
+
overlayMs: 0
|
|
27
|
+
};
|
|
28
|
+
scopeCosts.set(s, t);
|
|
29
|
+
}
|
|
30
|
+
t.runs++;
|
|
31
|
+
t.selfMs += o.selfMs;
|
|
32
|
+
if (o.phase !== "plain") t.overlayMs += o.selfMs; else if (!o.changed && !o.held) t.wastedMs += o.selfMs;
|
|
33
|
+
const e = new Set;
|
|
34
|
+
rootsOf(o.causes, e);
|
|
35
|
+
for (const s of e) {
|
|
36
|
+
let t = writeCosts.get(s);
|
|
37
|
+
if (t === undefined) writeCosts.set(s, t = {
|
|
38
|
+
name: s,
|
|
39
|
+
runs: 0,
|
|
40
|
+
downstreamMs: 0
|
|
41
|
+
});
|
|
42
|
+
t.runs++;
|
|
43
|
+
t.downstreamMs += o.selfMs;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
registerFold({
|
|
48
|
+
rerun: recordCosts,
|
|
49
|
+
reset() {
|
|
50
|
+
scopeCosts.clear();
|
|
51
|
+
writeCosts.clear();
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Aggregated cost tables since `enable()`: `scopes` ranked by self-time
|
|
57
|
+
* (with `wastedMs` = time spent on unchanged-value runs), `writes` ranked by
|
|
58
|
+
* total downstream re-run time each root write caused.
|
|
59
|
+
*/ function costs() {
|
|
60
|
+
return {
|
|
61
|
+
scopes: [ ...scopeCosts.values() ].sort((s, o) => o.selfMs - s.selfMs),
|
|
62
|
+
writes: [ ...writeCosts.values() ].sort((s, o) => o.downstreamMs - s.downstreamMs)
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export { costs };
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { registerFold, now, isSilentHold, isLongHold, formatOrigin, nodeName } from "./attribution.js";
|
|
2
|
+
|
|
3
|
+
import { ownerPath } from "./dev.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `feedback()` — what the user waited on, as ranked tables. Folded from the
|
|
7
|
+
* records the engine already keeps (holds, the interaction on each re-run,
|
|
8
|
+
* navigations) and two censuses the engine reports as they happen (flights
|
|
9
|
+
* started/landed/abandoned per async source; fallback shows per boundary).
|
|
10
|
+
*
|
|
11
|
+
* Its own module on purpose: the tables register with the engine's fold seam
|
|
12
|
+
* when this module is evaluated, so an observe-tier consumer that only
|
|
13
|
+
* subscribes to records never ships them. Reset with the engine on
|
|
14
|
+
* `enable()`/`disable()`.
|
|
15
|
+
*/ const feedbackSources = new Map;
|
|
16
|
+
|
|
17
|
+
const feedbackInteractions = new Map;
|
|
18
|
+
|
|
19
|
+
const feedbackNavigations = new Map;
|
|
20
|
+
|
|
21
|
+
const flightStats = new Map;
|
|
22
|
+
|
|
23
|
+
const fallbackStats = new Map;
|
|
24
|
+
|
|
25
|
+
const FALLBACK_FLASH_MS = 150;
|
|
26
|
+
|
|
27
|
+
function flightBucket(e) {
|
|
28
|
+
let n = flightStats.get(e);
|
|
29
|
+
if (n === undefined) {
|
|
30
|
+
n = {
|
|
31
|
+
source: nodeName(e),
|
|
32
|
+
flights: 0,
|
|
33
|
+
landed: 0,
|
|
34
|
+
abandoned: 0,
|
|
35
|
+
landedMs: 0,
|
|
36
|
+
worstMs: 0
|
|
37
|
+
};
|
|
38
|
+
flightStats.set(e, n);
|
|
39
|
+
}
|
|
40
|
+
return n;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function trackFallback(e, n, t) {
|
|
44
|
+
let i = fallbackStats.get(e);
|
|
45
|
+
if (i === undefined) {
|
|
46
|
+
i = {
|
|
47
|
+
row: {
|
|
48
|
+
boundary: "boundary",
|
|
49
|
+
shows: 0,
|
|
50
|
+
shownMs: 0,
|
|
51
|
+
worstMs: 0,
|
|
52
|
+
flashes: 0
|
|
53
|
+
},
|
|
54
|
+
shownAt: null
|
|
55
|
+
};
|
|
56
|
+
fallbackStats.set(e, i);
|
|
57
|
+
}
|
|
58
|
+
// The first show can fire before the subtree exists; name on first sight.
|
|
59
|
+
if (i.row.boundary === "boundary" && n !== undefined) {
|
|
60
|
+
const e = ownerPath(n);
|
|
61
|
+
if (e !== undefined) i.row.boundary = e.join(" › ");
|
|
62
|
+
}
|
|
63
|
+
if (t) {
|
|
64
|
+
if (i.shownAt === null) {
|
|
65
|
+
i.shownAt = now();
|
|
66
|
+
i.row.shows++;
|
|
67
|
+
}
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (i.shownAt === null) return;
|
|
71
|
+
const s = now() - i.shownAt;
|
|
72
|
+
i.shownAt = null;
|
|
73
|
+
i.row.shownMs += s;
|
|
74
|
+
if (s > i.row.worstMs) i.row.worstMs = s;
|
|
75
|
+
if (s < FALLBACK_FLASH_MS) i.row.flashes++;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function interactionBucket(e) {
|
|
79
|
+
const n = formatOrigin(e);
|
|
80
|
+
let t = feedbackInteractions.get(n);
|
|
81
|
+
if (t === undefined) {
|
|
82
|
+
t = {
|
|
83
|
+
row: {
|
|
84
|
+
interaction: n,
|
|
85
|
+
dispatches: 0,
|
|
86
|
+
runs: 0,
|
|
87
|
+
selfMs: 0,
|
|
88
|
+
worstDispatchMs: 0,
|
|
89
|
+
holds: 0,
|
|
90
|
+
heldMs: 0,
|
|
91
|
+
silentMs: 0,
|
|
92
|
+
worstHoldMs: 0
|
|
93
|
+
},
|
|
94
|
+
dispatches: new Map
|
|
95
|
+
};
|
|
96
|
+
feedbackInteractions.set(n, t);
|
|
97
|
+
}
|
|
98
|
+
const i = e.at ?? 0;
|
|
99
|
+
if (!t.dispatches.has(i)) {
|
|
100
|
+
t.dispatches.set(i, 0);
|
|
101
|
+
t.row.dispatches++;
|
|
102
|
+
}
|
|
103
|
+
return t;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function recordFeedbackRun(e) {
|
|
107
|
+
if (e.interaction === undefined) return;
|
|
108
|
+
const n = interactionBucket(e.interaction);
|
|
109
|
+
n.row.runs++;
|
|
110
|
+
n.row.selfMs += e.selfMs;
|
|
111
|
+
const t = e.interaction.at ?? 0;
|
|
112
|
+
const i = n.dispatches.get(t) + e.selfMs;
|
|
113
|
+
n.dispatches.set(t, i);
|
|
114
|
+
if (i > n.row.worstDispatchMs) n.row.worstDispatchMs = i;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function recordFeedbackHold(e) {
|
|
118
|
+
const n = [ ...e.blockers ].sort();
|
|
119
|
+
const t = n.join("\0");
|
|
120
|
+
let i = feedbackSources.get(t);
|
|
121
|
+
if (i === undefined) {
|
|
122
|
+
i = {
|
|
123
|
+
row: {
|
|
124
|
+
sources: n,
|
|
125
|
+
holds: 0,
|
|
126
|
+
heldMs: 0,
|
|
127
|
+
worstMs: 0,
|
|
128
|
+
silent: 0,
|
|
129
|
+
silentMs: 0,
|
|
130
|
+
latestOnly: 0,
|
|
131
|
+
long: 0,
|
|
132
|
+
longMs: 0,
|
|
133
|
+
acknowledgedBy: [],
|
|
134
|
+
interactions: [],
|
|
135
|
+
writes: [],
|
|
136
|
+
actions: 0
|
|
137
|
+
},
|
|
138
|
+
acks: new Map,
|
|
139
|
+
interactions: new Map,
|
|
140
|
+
writes: new Set
|
|
141
|
+
};
|
|
142
|
+
feedbackSources.set(t, i);
|
|
143
|
+
}
|
|
144
|
+
const s = i.row;
|
|
145
|
+
const o = isSilentHold(e);
|
|
146
|
+
s.holds++;
|
|
147
|
+
s.heldMs += e.holdMs;
|
|
148
|
+
if (e.holdMs > s.worstMs) s.worstMs = e.holdMs;
|
|
149
|
+
if (o) {
|
|
150
|
+
s.silent++;
|
|
151
|
+
s.silentMs += e.holdMs;
|
|
152
|
+
} else if (e.acknowledgements.length > 0 && e.acknowledgements.every(e => e.kind === "latest")) s.latestOnly++;
|
|
153
|
+
if (isLongHold(e)) {
|
|
154
|
+
s.long++;
|
|
155
|
+
s.longMs += e.tailMs;
|
|
156
|
+
}
|
|
157
|
+
if (e.action) s.actions++;
|
|
158
|
+
for (const n of e.acknowledgements) {
|
|
159
|
+
const e = `${n.kind}:${n.source}`;
|
|
160
|
+
i.acks.set(e, (i.acks.get(e) ?? 0) + 1);
|
|
161
|
+
}
|
|
162
|
+
for (const n of e.heldWrites) i.writes.add(n.name);
|
|
163
|
+
if (e.interaction !== undefined) {
|
|
164
|
+
const n = formatOrigin(e.interaction);
|
|
165
|
+
i.interactions.set(n, (i.interactions.get(n) ?? 0) + 1);
|
|
166
|
+
const t = interactionBucket(e.interaction);
|
|
167
|
+
t.row.holds++;
|
|
168
|
+
t.row.heldMs += e.holdMs;
|
|
169
|
+
if (o) t.row.silentMs += e.holdMs;
|
|
170
|
+
if (e.holdMs > t.row.worstHoldMs) t.row.worstHoldMs = e.holdMs;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function recordFeedbackNavigation(e) {
|
|
175
|
+
const n = e.name ?? e.to;
|
|
176
|
+
if (n === undefined) return;
|
|
177
|
+
let t = feedbackNavigations.get(n);
|
|
178
|
+
if (t === undefined) {
|
|
179
|
+
t = {
|
|
180
|
+
name: n,
|
|
181
|
+
navigations: 0,
|
|
182
|
+
settledMs: 0,
|
|
183
|
+
worstMs: 0,
|
|
184
|
+
held: 0,
|
|
185
|
+
heldMs: 0,
|
|
186
|
+
silent: 0,
|
|
187
|
+
superseded: 0,
|
|
188
|
+
redirected: 0
|
|
189
|
+
};
|
|
190
|
+
feedbackNavigations.set(n, t);
|
|
191
|
+
}
|
|
192
|
+
t.navigations++;
|
|
193
|
+
if (e.redirects !== undefined) t.redirected++;
|
|
194
|
+
if (e.outcome === "superseded") {
|
|
195
|
+
t.superseded++;
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const i = e.settledMs;
|
|
199
|
+
t.settledMs += i;
|
|
200
|
+
if (i > t.worstMs) t.worstMs = i;
|
|
201
|
+
if (e.outcome === "held") {
|
|
202
|
+
t.held++;
|
|
203
|
+
t.heldMs += e.hold?.holdMs ?? i;
|
|
204
|
+
if (e.hold !== undefined && isSilentHold(e.hold)) t.silent++;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function rankedCounts(e, n) {
|
|
209
|
+
return [ ...e ].sort((e, n) => n[1] - e[1]).map(([e, t]) => ({
|
|
210
|
+
[n]: e,
|
|
211
|
+
holds: t
|
|
212
|
+
}));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
registerFold({
|
|
216
|
+
rerun: (e, n) => recordFeedbackRun(n),
|
|
217
|
+
hold: recordFeedbackHold,
|
|
218
|
+
navigation: recordFeedbackNavigation,
|
|
219
|
+
flightStart(e, n) {
|
|
220
|
+
const t = flightBucket(e);
|
|
221
|
+
t.flights++;
|
|
222
|
+
if (n) t.abandoned++;
|
|
223
|
+
},
|
|
224
|
+
flightLanded(e, n) {
|
|
225
|
+
const t = flightBucket(e);
|
|
226
|
+
t.landed++;
|
|
227
|
+
t.landedMs += n;
|
|
228
|
+
if (n > t.worstMs) t.worstMs = n;
|
|
229
|
+
},
|
|
230
|
+
fallback: trackFallback,
|
|
231
|
+
reset() {
|
|
232
|
+
feedbackSources.clear();
|
|
233
|
+
feedbackInteractions.clear();
|
|
234
|
+
feedbackNavigations.clear();
|
|
235
|
+
flightStats.clear();
|
|
236
|
+
fallbackStats.clear();
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* What the user waited on, folded from holds and the interaction on each
|
|
242
|
+
* re-run: `sources` ranks async sources by the silent time writes spent held
|
|
243
|
+
* behind them (with which affordances answered, how often, and which
|
|
244
|
+
* interactions were held); `interactions` ranks user events by the total time
|
|
245
|
+
* they cost — re-run work caused (long-flush hazard) beside time held
|
|
246
|
+
* (silent-hold hazard). Facts at every duration; SILENT_HOLD is the
|
|
247
|
+
* thresholded verdict. Three more tables round out the picture: `navigations`
|
|
248
|
+
* ranks routes by the time spent held navigating to them (folded from settled
|
|
249
|
+
* navigations), `flights` counts each async source's flights and how many
|
|
250
|
+
* were abandoned before landing (the re-ask storm), and `fallbacks` measures
|
|
251
|
+
* how long each loading boundary showed its fallback and how often that was a
|
|
252
|
+
* flash.
|
|
253
|
+
*/ function feedback() {
|
|
254
|
+
const e = [ ...feedbackNavigations.values() ].map(e => ({
|
|
255
|
+
...e
|
|
256
|
+
})).sort((e, n) => n.heldMs - e.heldMs || n.settledMs - e.settledMs);
|
|
257
|
+
const n = [ ...flightStats.values() ].map(e => ({
|
|
258
|
+
...e
|
|
259
|
+
})).sort((e, n) => n.abandoned - e.abandoned || n.flights - e.flights);
|
|
260
|
+
const t = [ ...fallbackStats.values() ].map(e => ({
|
|
261
|
+
...e.row
|
|
262
|
+
})).sort((e, n) => n.flashes - e.flashes || n.shownMs - e.shownMs);
|
|
263
|
+
const i = [ ...feedbackSources.values() ].map(e => ({
|
|
264
|
+
...e.row,
|
|
265
|
+
acknowledgedBy: rankedCounts(e.acks, "by"),
|
|
266
|
+
interactions: rankedCounts(e.interactions, "interaction"),
|
|
267
|
+
writes: [ ...e.writes ]
|
|
268
|
+
})).sort((e, n) => n.silentMs - e.silentMs || n.heldMs - e.heldMs);
|
|
269
|
+
// Ranked by the total time the user spent on it: held plus synchronous work.
|
|
270
|
+
const s = [ ...feedbackInteractions.values() ].map(e => ({
|
|
271
|
+
...e.row
|
|
272
|
+
})).sort((e, n) => n.heldMs + n.selfMs - (e.heldMs + e.selfMs));
|
|
273
|
+
return {
|
|
274
|
+
sources: i,
|
|
275
|
+
interactions: s,
|
|
276
|
+
navigations: e,
|
|
277
|
+
flights: n,
|
|
278
|
+
fallbacks: t
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export { feedback };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
let attrHooks = null;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The installed engine, registered on `globalThis` as well (the records
|
|
5
|
+
* channel's reason, see `Records`): a wire layer bundled without a framework
|
|
6
|
+
* import — `@solidjs/web`'s server-function client — stamps the records it
|
|
7
|
+
* emits through the engine's `currentOrigin`, and this is its reach. The
|
|
8
|
+
* module binding stays the core's own read (one null check per hook site);
|
|
9
|
+
* the registration mirrors it.
|
|
10
|
+
*/ const INSTALLED = Symbol.for("@solidjs/signals/observe/attribution");
|
|
11
|
+
|
|
12
|
+
function setAttributionHooks(t) {
|
|
13
|
+
attrHooks = t;
|
|
14
|
+
globalThis[INSTALLED] = t ?? undefined;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Run `fn` as the handler of a user interaction: every root write it performs
|
|
19
|
+
* (and every action step, effect or flight the write causes) is attributed to
|
|
20
|
+
* `ref` by whichever engine is installed. The web runtime wraps event
|
|
21
|
+
* dispatch in this; custom renderers and test harnesses call it themselves.
|
|
22
|
+
* With no engine installed it is `fn()` — the wiring, not the engine, so it
|
|
23
|
+
* lives in core and is reachable as `OBSERVE.attribution.withInteraction`.
|
|
24
|
+
*
|
|
25
|
+
* The `finally` is deliberate and safe under the try rule above: this
|
|
26
|
+
* function is referenced only from the `OBSERVE` object, which prod builds
|
|
27
|
+
* fold to `undefined`, so nothing retains it there.
|
|
28
|
+
*/ function withInteraction(t, n) {
|
|
29
|
+
// Pin the engine for the frame: a handler that disables it mid-way must
|
|
30
|
+
// still close the frame it opened (the engine tolerates a close after
|
|
31
|
+
// disable()), and one that enables it mid-way opened no frame to close.
|
|
32
|
+
const o = attrHooks;
|
|
33
|
+
if (o === null) return n();
|
|
34
|
+
o.interactionStart(t);
|
|
35
|
+
try {
|
|
36
|
+
return n();
|
|
37
|
+
} finally {
|
|
38
|
+
o.interactionEnd();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Run `fn` as a declared unit of work — a router's navigation: every root
|
|
44
|
+
* write it performs is attributed to `ref` (and, through it, to the enclosing
|
|
45
|
+
* interaction when there is one), so the hold those writes wait in, the
|
|
46
|
+
* re-runs they cause and the verdicts on them all carry the route's name
|
|
47
|
+
* instead of a bare signal's. Same contract as `withInteraction`: the wiring,
|
|
48
|
+
* not the engine; `fn()` with no engine installed. Reachable as
|
|
49
|
+
* `OBSERVE.attribution.withOrigin`.
|
|
50
|
+
*
|
|
51
|
+
* ```ts
|
|
52
|
+
* OBSERVE
|
|
53
|
+
* ? OBSERVE.attribution.withOrigin(
|
|
54
|
+
* { kind: "navigation", name: match.pattern, to, from, params: match.params },
|
|
55
|
+
* () => setLocation(to)
|
|
56
|
+
* )
|
|
57
|
+
* : setLocation(to);
|
|
58
|
+
* ```
|
|
59
|
+
*/ function withOrigin(t, n) {
|
|
60
|
+
const o = attrHooks;
|
|
61
|
+
if (o === null) return n();
|
|
62
|
+
o.originStart(t);
|
|
63
|
+
try {
|
|
64
|
+
return n();
|
|
65
|
+
} finally {
|
|
66
|
+
o.originEnd();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The provenance a root write performed now would carry, as the installed
|
|
72
|
+
* engine sees it (`AttributionHooks.currentOrigin`); `undefined` with no
|
|
73
|
+
* engine, or when nothing is in effect. Reachable as
|
|
74
|
+
* `OBSERVE.attribution.currentOrigin` — how a runtime stamps a fact of its
|
|
75
|
+
* own (a server-function call) with the interaction or navigation it ran
|
|
76
|
+
* for, so an observer joins the two by identity.
|
|
77
|
+
*/ function currentOrigin() {
|
|
78
|
+
const t = attrHooks;
|
|
79
|
+
return t === null ? undefined : t.currentOrigin();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export { attrHooks, currentOrigin, setAttributionHooks, withInteraction, withOrigin };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { nodeName, attribution } from "./attribution.js";
|
|
2
|
+
|
|
3
|
+
import { $REFRESH } from "./constants.js";
|
|
4
|
+
|
|
5
|
+
import { subjectOf } from "./dev.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Point queries over the engine's live state — the devtools/console view of
|
|
9
|
+
* one scope. Their own module so a records-only consumer never ships them;
|
|
10
|
+
* they read the engine's ring buffer and the graph, and register nothing.
|
|
11
|
+
*/
|
|
12
|
+
/** The node behind a memo/effect accessor, or the raw node passed through. */ function nodeOf(t) {
|
|
13
|
+
return t?.[$REFRESH] ?? t;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Re-run history for one node — pass a memo/effect accessor or raw node. */ function why(t) {
|
|
17
|
+
const n = nodeOf(t);
|
|
18
|
+
return attribution.history().filter(t => subjectOf(t) === n);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Current dependency names of one scope — the devtools subscription view. */ function subscriptions(t) {
|
|
22
|
+
const n = nodeOf(t);
|
|
23
|
+
const o = [];
|
|
24
|
+
for (let t = n?.ee ?? null; t !== null; t = t.te) o.push(nodeName(t.re));
|
|
25
|
+
return o;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { subscriptions, why };
|