vite-devtools-svelte 0.5.0 → 0.6.0
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/README.md +17 -5
- package/dist/analyzers/api.d.ts +17 -4
- package/dist/analyzers/assets.d.ts +0 -1
- package/dist/analyzers/build.d.ts +7 -1
- package/dist/analyzers/http.d.ts +9 -0
- package/dist/analyzers/module-graph.d.ts +9 -0
- package/dist/analyzers/og.d.ts +21 -4
- package/dist/analyzers/routes.d.ts +16 -1
- package/dist/client/assets/ApiPlayground-qRDX0tRk.js +3 -0
- package/dist/client/assets/Assets-yTFLB7w2.js +1 -0
- package/dist/client/assets/BuildAnalysis-C6yO6EJN.js +1 -0
- package/dist/client/assets/CaptureNotice-DykEK0Kp.js +1 -0
- package/dist/client/assets/Components-BTG8jKec.js +2 -0
- package/dist/client/assets/DataTable-C5C0HrMd.js +1 -0
- package/dist/client/assets/DataTable-gMBzprA7.css +1 -0
- package/dist/client/assets/ErrorDashboard-DHdptxQP.js +1 -0
- package/dist/client/assets/FpsMonitor-G1xRBjK3.js +1 -0
- package/dist/client/assets/Inspect-Ba_b5o73.js +4 -0
- package/dist/client/assets/{Inspector-C4wr2Zb4.js → Inspector-mTvtYmwt.js} +1 -1
- package/dist/client/assets/LiveControls-nrc1-cBk.js +1 -0
- package/dist/client/assets/LoadProfiler-eYXaLPba.js +1 -0
- package/dist/client/assets/ModuleGraph-Dp-bJUnD.js +1 -0
- package/dist/client/assets/OGPreview-BzZ_MAg7.js +1 -0
- package/dist/client/assets/Overview-Cbi5ayT1.js +1 -0
- package/dist/client/assets/Panel-CKQaY_Qq.js +1 -0
- package/dist/client/assets/Panel-b1jDvzob.css +1 -0
- package/dist/client/assets/ReactiveGraph-CQpsrwa7.js +19 -0
- package/dist/client/assets/RenderProfiler-CCG8KJ2a.js +1 -0
- package/dist/client/assets/Routes-Dg3ZrLhJ.js +1 -0
- package/dist/client/assets/SearchField-B5EQ5IIf.js +1 -0
- package/dist/client/assets/Segmented-Dz1OdcPr.js +1 -0
- package/dist/client/assets/SplitView-CxRUXpY5.js +1 -0
- package/dist/client/assets/StateTimeline-CLt2yN8k.js +1 -0
- package/dist/client/assets/TreeView-BYOJBzxO.js +1 -0
- package/dist/client/assets/VirtualList-Bfn8is0p.js +1 -0
- package/dist/client/assets/{browser-agent-rpc-BXhoSh1z-UMmvacmP.js → browser-agent-rpc-BXhoSh1z-CuoUGU_3.js} +1 -1
- package/dist/client/assets/capture.svelte-BLIttZ2Z.js +1 -0
- package/dist/client/assets/collections-uqk9p1aQ.js +1 -0
- package/dist/client/assets/format-BEILGi1v.js +1 -0
- package/dist/client/assets/{index-u73uo_Ri.css → index-BmEfdt6W.css} +1 -1
- package/dist/client/assets/{index-CuoAAt-d.js → index-Cf5oAFpK.js} +6 -6
- package/dist/client/assets/{reactive-BSjTkgqc.js → reactive-DuxZv-g7.js} +1 -1
- package/dist/client/assets/reactive-selection.svelte-iv05jhlt.js +1 -0
- package/dist/client/assets/versions-Bd19hz8p.js +1 -0
- package/dist/client/index.html +2 -2
- package/dist/index.mjs +1650 -1347
- package/dist/index.mjs.map +1 -1
- package/dist/mcp/issues.d.ts +10 -6
- package/dist/mcp/server.d.ts +0 -5
- package/dist/mcp/sessions.d.ts +8 -1
- package/dist/plugin.d.ts +18 -20
- package/dist/runtime/ids.d.ts +3 -0
- package/dist/runtime/index.d.ts +18 -0
- package/dist/runtime/transform.d.ts +22 -0
- package/dist/{collector.d.ts → server/collector.d.ts} +11 -16
- package/dist/{devframe.d.ts → server/devframe.d.ts} +10 -10
- package/dist/{mount.d.ts → server/mount.d.ts} +4 -9
- package/dist/{security.d.ts → server/security.d.ts} +10 -2
- package/package.json +9 -4
- package/skills/vite-devtools-svelte-perf-audit/SKILL.md +2 -2
- package/skills/vite-devtools-svelte-perf-fix/SKILL.md +3 -3
- package/dist/client/assets/ApiPlayground-B28mCUlr.js +0 -3
- package/dist/client/assets/Assets-BVT22xZ3.js +0 -1
- package/dist/client/assets/Badge-BBLKgCiU.css +0 -1
- package/dist/client/assets/Badge-x4Lbd8pc.js +0 -1
- package/dist/client/assets/BuildAnalysis-BscZFEWI.js +0 -1
- package/dist/client/assets/CaptureNotice-D0u6WNeR.js +0 -1
- package/dist/client/assets/Components-sw5zdu0k.js +0 -1
- package/dist/client/assets/DataTable-CdM2Vyvn.js +0 -1
- package/dist/client/assets/DataTable-CdVIFm8T.css +0 -1
- package/dist/client/assets/ErrorDashboard-BwGMVW_-.js +0 -1
- package/dist/client/assets/FpsMonitor-C0FeLiag.js +0 -1
- package/dist/client/assets/Inspect-B-jb8MaB.js +0 -3
- package/dist/client/assets/LoadProfiler-WeP1orWN.js +0 -1
- package/dist/client/assets/ModuleGraph-BYer7OEL.js +0 -1
- package/dist/client/assets/OGPreview-MRxnmCmb.js +0 -1
- package/dist/client/assets/Overview-CDBKKylY.js +0 -1
- package/dist/client/assets/ReactiveGraph-Bni33DP4.js +0 -5
- package/dist/client/assets/RenderProfiler-2rA8kCUf.js +0 -1
- package/dist/client/assets/Routes-BZ5XcmEt.js +0 -1
- package/dist/client/assets/SearchField-D85HSGV_.js +0 -1
- package/dist/client/assets/Segmented-BCcIUjA2.js +0 -1
- package/dist/client/assets/SplitView-DVk1HAf9.js +0 -1
- package/dist/client/assets/StateTimeline-DjTXe54T.js +0 -1
- package/dist/client/assets/TreeView-e7dNH8HW.js +0 -1
- package/dist/client/assets/VirtualList-Ba0GhFn8.js +0 -1
- package/dist/client/assets/capture.svelte-fve1ihQA.js +0 -1
- package/dist/client/assets/format-CW_NXiBg.js +0 -1
- package/dist/client/assets/reactive-selection.svelte-BmjgYyWR.js +0 -1
- package/dist/client/assets/versions-zyEGB_mz.js +0 -1
- package/dist/runtime.d.ts +0 -19
- /package/dist/client/assets/{versions-CMau2xlU.css → LiveControls-CMau2xlU.css} +0 -0
- /package/dist/{template-injector.d.ts → server/template-injector.d.ts} +0 -0
package/dist/index.mjs
CHANGED
|
@@ -1,39 +1,158 @@
|
|
|
1
|
-
import e from "node:
|
|
2
|
-
import t from "node:
|
|
1
|
+
import e from "node:crypto";
|
|
2
|
+
import t from "node:path";
|
|
3
3
|
import { launchEditor as n } from "devframe/utils/launch-editor";
|
|
4
|
-
import r from "fs";
|
|
5
|
-
import i from "
|
|
6
|
-
import a from "
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
4
|
+
import r from "node:fs";
|
|
5
|
+
import { McpServer as i } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import { StreamableHTTPServerTransport as a } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
7
|
+
import { z as o } from "zod";
|
|
8
|
+
import { createRequire as s } from "node:module";
|
|
9
9
|
import { defineDevframe as c, defineRpcFunction as l } from "devframe";
|
|
10
|
-
import {
|
|
10
|
+
import { parseSync as u } from "vite";
|
|
11
11
|
import d from "node:dns/promises";
|
|
12
12
|
import f from "node:net";
|
|
13
13
|
import p from "node:os";
|
|
14
14
|
import { initDevframe as m } from "devframe/initiate";
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
import { DEVTOOLS_MOUNT_PATH as h } from "@vitejs/devtools-kit/constants";
|
|
16
|
+
//#region src/analyzers/components.ts
|
|
17
|
+
function g(e) {
|
|
18
|
+
let n = t.join(e, "src");
|
|
19
|
+
if (!r.existsSync(n)) return [];
|
|
20
|
+
let i = [];
|
|
21
|
+
return _(n, i), i.map((n) => {
|
|
22
|
+
let i = "";
|
|
23
|
+
try {
|
|
24
|
+
i = r.readFileSync(n, "utf-8");
|
|
25
|
+
} catch {}
|
|
26
|
+
let a = ee(i, n, e), o = ne(n);
|
|
27
|
+
return {
|
|
28
|
+
file: t.relative(e, n),
|
|
29
|
+
name: o,
|
|
30
|
+
imports: a.map((n) => t.relative(e, n))
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function _(e, n) {
|
|
35
|
+
let i = r.readdirSync(e, { withFileTypes: !0 });
|
|
36
|
+
for (let r of i) {
|
|
37
|
+
let i = t.join(e, r.name);
|
|
38
|
+
if (r.isDirectory()) {
|
|
39
|
+
if (r.name === "node_modules" || r.name === ".svelte-kit") continue;
|
|
40
|
+
_(i, n);
|
|
41
|
+
} else r.name.endsWith(".svelte") && n.push(i);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function ee(e, n, r) {
|
|
45
|
+
let i = /* @__PURE__ */ new Set(), a = t.dirname(n), o = /import\s+[\w${}\s,*]+\s+from\s+['"]([^'"]+\.svelte)['"]/g, s;
|
|
46
|
+
for (; (s = o.exec(e)) !== null;) {
|
|
47
|
+
let e = s[1], t = te(e, a, r);
|
|
48
|
+
t && i.add(t);
|
|
49
|
+
}
|
|
50
|
+
return [...i];
|
|
51
|
+
}
|
|
52
|
+
function te(e, n, i) {
|
|
53
|
+
if (e.startsWith("$lib/") || e.startsWith("#lib/")) {
|
|
54
|
+
let n = t.join(i, "src", "lib", e.slice(5));
|
|
55
|
+
return r.existsSync(n) ? n : null;
|
|
56
|
+
}
|
|
57
|
+
if (e.startsWith(".")) {
|
|
58
|
+
let i = t.resolve(n, e);
|
|
59
|
+
if (r.existsSync(i)) return i;
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
function ne(e) {
|
|
64
|
+
return t.basename(e, ".svelte");
|
|
65
|
+
}
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region src/analyzers/project.ts
|
|
68
|
+
var re = 5e3, v = null, ie = null, ae = 0;
|
|
69
|
+
function y(e) {
|
|
70
|
+
let t = Date.now();
|
|
71
|
+
if (v && ie === e && t - ae < re) return v;
|
|
72
|
+
let n = ce(e);
|
|
73
|
+
return v = n, ie = e, ae = t, n;
|
|
74
|
+
}
|
|
75
|
+
function b(e) {
|
|
76
|
+
return typeof e == "string" && e !== "" ? e : void 0;
|
|
77
|
+
}
|
|
78
|
+
function x(e) {
|
|
79
|
+
return typeof e == "object" && !!e && !Array.isArray(e);
|
|
80
|
+
}
|
|
81
|
+
function oe(e) {
|
|
82
|
+
let t = {};
|
|
83
|
+
if (!x(e)) return t;
|
|
84
|
+
for (let [n, r] of Object.entries(e)) typeof r == "string" && (t[n] = r);
|
|
85
|
+
return t;
|
|
86
|
+
}
|
|
87
|
+
function se(e) {
|
|
88
|
+
let t = {};
|
|
89
|
+
try {
|
|
90
|
+
t = JSON.parse(r.readFileSync(e, "utf-8"));
|
|
91
|
+
} catch {}
|
|
92
|
+
let n = x(t) ? t : {};
|
|
93
|
+
return {
|
|
94
|
+
name: b(n.name),
|
|
95
|
+
version: b(n.version),
|
|
96
|
+
dependencies: oe(n.dependencies),
|
|
97
|
+
devDependencies: oe(n.devDependencies)
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function ce(e) {
|
|
101
|
+
let n = se(t.join(e, "package.json")), r = n.dependencies, i = n.devDependencies, a = (e) => b(r[e]) ?? b(i[e]);
|
|
102
|
+
return {
|
|
103
|
+
name: n.name ?? t.basename(e),
|
|
104
|
+
version: n.version ?? "0.0.0",
|
|
105
|
+
svelteVersion: S(e, "svelte") ?? a("svelte") ?? "unknown",
|
|
106
|
+
sveltekitVersion: S(e, "@sveltejs/kit") ?? a("@sveltejs/kit") ?? "unknown",
|
|
107
|
+
viteVersion: S(e, "vite") ?? a("vite") ?? "unknown",
|
|
108
|
+
dependencies: r,
|
|
109
|
+
devDependencies: i,
|
|
110
|
+
routesDir: le(e),
|
|
111
|
+
staticDir: ue(e)
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function S(e, n) {
|
|
115
|
+
try {
|
|
116
|
+
let i = t.join(e, "node_modules", n, "package.json");
|
|
117
|
+
if (r.existsSync(i)) {
|
|
118
|
+
let e = JSON.parse(r.readFileSync(i, "utf-8"));
|
|
119
|
+
return x(e) ? b(e.version) : void 0;
|
|
120
|
+
}
|
|
121
|
+
} catch {}
|
|
122
|
+
}
|
|
123
|
+
function le(e) {
|
|
124
|
+
let n = [t.join(e, "src", "routes"), t.join(e, "src", "pages")];
|
|
125
|
+
for (let e of n) if (r.existsSync(e)) return e;
|
|
126
|
+
return t.join(e, "src", "routes");
|
|
127
|
+
}
|
|
128
|
+
function ue(e) {
|
|
129
|
+
let n = [t.join(e, "static"), t.join(e, "public")];
|
|
130
|
+
for (let e of n) if (r.existsSync(e)) return e;
|
|
131
|
+
return t.join(e, "static");
|
|
132
|
+
}
|
|
133
|
+
//#endregion
|
|
18
134
|
//#region src/analyzers/routes.ts
|
|
19
|
-
function
|
|
135
|
+
function de(e) {
|
|
20
136
|
if (!r.existsSync(e)) return [];
|
|
21
137
|
let t = [];
|
|
22
|
-
return
|
|
138
|
+
return fe(e, e, t), t.toSorted((e, t) => e.path.localeCompare(t.path) || e.id.localeCompare(t.id));
|
|
23
139
|
}
|
|
24
|
-
function
|
|
25
|
-
let a = r.readdirSync(e, { withFileTypes: !0 }), o = []
|
|
140
|
+
function fe(e, n, i) {
|
|
141
|
+
let a = r.readdirSync(e, { withFileTypes: !0 }), o = [];
|
|
26
142
|
for (let r of a) {
|
|
27
143
|
if (r.isDirectory()) {
|
|
28
|
-
|
|
144
|
+
fe(t.join(e, r.name), n, i);
|
|
29
145
|
continue;
|
|
30
146
|
}
|
|
31
|
-
let a =
|
|
32
|
-
a &&
|
|
147
|
+
let a = me(r.name);
|
|
148
|
+
a && o.push({
|
|
149
|
+
type: a,
|
|
150
|
+
path: t.join(e, r.name)
|
|
151
|
+
});
|
|
33
152
|
}
|
|
34
|
-
if (
|
|
35
|
-
let r =
|
|
36
|
-
|
|
153
|
+
if (o.length > 0) {
|
|
154
|
+
let r = t.relative(n, e).split(t.sep).join("/"), { routePath: a, params: s } = ge(r);
|
|
155
|
+
i.push({
|
|
37
156
|
id: r || "/",
|
|
38
157
|
path: a,
|
|
39
158
|
pattern: a,
|
|
@@ -50,167 +169,711 @@ function ie(e, t, n) {
|
|
|
50
169
|
});
|
|
51
170
|
}
|
|
52
171
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
"+
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}[e];
|
|
69
|
-
return n ? {
|
|
70
|
-
type: n,
|
|
71
|
-
path: t
|
|
72
|
-
} : null;
|
|
172
|
+
var pe = /^\+(page|layout|error|server)(@[^/\\]*)?(?:\.(server))?\.(svelte|js|ts)$/;
|
|
173
|
+
function me(e) {
|
|
174
|
+
let t = pe.exec(e);
|
|
175
|
+
if (!t) return null;
|
|
176
|
+
let [, n, r, i, a] = t;
|
|
177
|
+
return a === "svelte" ? i ? null : n === "page" ? "page" : n === "layout" ? "layout" : n === "error" && !r ? "error" : null : r ? null : n === "server" ? i ? null : "endpoint" : n === "page" ? i ? "page-load-server" : "page-load" : n === "layout" ? i ? "layout-load-server" : "layout-load" : null;
|
|
178
|
+
}
|
|
179
|
+
var he = /^(\.\.\.)?(\w+)(?:=(\w+))?$/;
|
|
180
|
+
function ge(e) {
|
|
181
|
+
let t = [], n = [];
|
|
182
|
+
for (let r of e.split("/")) r && !/^\(.+\)$/.test(r) && n.push(_e(r, t));
|
|
183
|
+
return {
|
|
184
|
+
routePath: "/" + n.join("/"),
|
|
185
|
+
params: t
|
|
186
|
+
};
|
|
73
187
|
}
|
|
74
|
-
function
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (e
|
|
78
|
-
|
|
79
|
-
|
|
188
|
+
function _e(e, t) {
|
|
189
|
+
let n = "", r = 0;
|
|
190
|
+
for (; r < e.length;) {
|
|
191
|
+
if (e[r] !== "[") {
|
|
192
|
+
n += e[r], r++;
|
|
193
|
+
continue;
|
|
80
194
|
}
|
|
81
|
-
|
|
82
|
-
|
|
195
|
+
let i = e.startsWith("[[", r), a = e.indexOf(i ? "]]" : "]", r);
|
|
196
|
+
if (a === -1) {
|
|
197
|
+
n += e.slice(r);
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
let o = e.slice(r + (i ? 2 : 1), a), s = a + (i ? 2 : 1), c = i ? null : ve(o), l = he.exec(o);
|
|
201
|
+
if (c !== null) n += c;
|
|
202
|
+
else if (l) {
|
|
203
|
+
let [, e, r, a] = l, o = {
|
|
204
|
+
name: r,
|
|
205
|
+
optional: i,
|
|
206
|
+
rest: !!e
|
|
207
|
+
};
|
|
208
|
+
a && (o.matcher = a), t.push(o), n += e ? `*${r}` : `:${r}${i ? "?" : ""}`;
|
|
209
|
+
} else n += e.slice(r, s);
|
|
210
|
+
r = s;
|
|
211
|
+
}
|
|
212
|
+
return n;
|
|
83
213
|
}
|
|
84
|
-
function
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
214
|
+
function ve(e) {
|
|
215
|
+
let t = /^x\+([0-9a-f]{2})$/i.exec(e);
|
|
216
|
+
if (t) return String.fromCodePoint(Number.parseInt(t[1], 16));
|
|
217
|
+
let n = /^u\+([0-9a-f]{4,6})$/i.exec(e);
|
|
218
|
+
if (!n) return null;
|
|
219
|
+
let r = Number.parseInt(n[1], 16);
|
|
220
|
+
return r <= 1114111 ? String.fromCodePoint(r) : null;
|
|
221
|
+
}
|
|
222
|
+
//#endregion
|
|
223
|
+
//#region src/mcp/issues.ts
|
|
224
|
+
var ye = {
|
|
225
|
+
avgRenderTimeMs: 4,
|
|
226
|
+
renderCount: 30,
|
|
227
|
+
loadDurationMs: 200,
|
|
228
|
+
fpsDropThreshold: 40,
|
|
229
|
+
effectMaxDeps: 8
|
|
230
|
+
};
|
|
231
|
+
function be(e) {
|
|
232
|
+
let t = { ...ye };
|
|
233
|
+
for (let n of Object.keys(ye)) {
|
|
234
|
+
let r = e[n];
|
|
235
|
+
r !== void 0 && (t[n] = r);
|
|
236
|
+
}
|
|
237
|
+
return t;
|
|
238
|
+
}
|
|
239
|
+
function xe(e, t = {}) {
|
|
240
|
+
let n = be(t), r = [];
|
|
241
|
+
for (let t of e.renderProfiles) {
|
|
242
|
+
let e = t.renderCount > 0 ? t.totalRenderTime / t.renderCount : 0;
|
|
243
|
+
e >= n.avgRenderTimeMs && r.push({
|
|
244
|
+
id: `slow-render:${t.file}:${t.componentId}`,
|
|
245
|
+
kind: "slow-component-render",
|
|
246
|
+
severity: e >= n.avgRenderTimeMs * 4 ? "high" : e >= n.avgRenderTimeMs * 2 ? "medium" : "low",
|
|
247
|
+
summary: `${t.name} averages ${e.toFixed(2)}ms per render (${t.renderCount} renders)`,
|
|
248
|
+
file: t.file,
|
|
249
|
+
metric: {
|
|
250
|
+
avgRenderTimeMs: C(e),
|
|
251
|
+
renderCount: t.renderCount,
|
|
252
|
+
totalRenderTimeMs: C(t.totalRenderTime)
|
|
253
|
+
},
|
|
254
|
+
suggestedTool: "get_render_profile"
|
|
255
|
+
}), t.renderCount >= n.renderCount && r.push({
|
|
256
|
+
id: `over-render:${t.file}:${t.componentId}`,
|
|
257
|
+
kind: "over-rendered-component",
|
|
258
|
+
severity: t.renderCount >= n.renderCount * 8 ? "high" : t.renderCount >= n.renderCount * 3 ? "medium" : "low",
|
|
259
|
+
summary: `${t.name} rendered ${t.renderCount} times`,
|
|
260
|
+
file: t.file,
|
|
261
|
+
metric: {
|
|
262
|
+
renderCount: t.renderCount,
|
|
263
|
+
avgRenderTimeMs: C(e)
|
|
264
|
+
},
|
|
265
|
+
suggestedTool: "get_component_hotspots"
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
for (let t of e.loadProfiles) t.duration >= n.loadDurationMs && r.push({
|
|
269
|
+
id: `slow-load:${t.route}:${t.timestamp}`,
|
|
270
|
+
kind: "slow-load",
|
|
271
|
+
severity: t.duration >= n.loadDurationMs * 5 ? "high" : t.duration >= n.loadDurationMs * 2 ? "medium" : "low",
|
|
272
|
+
summary: `${t.type} load for ${t.route} took ${t.duration.toFixed(0)}ms`,
|
|
273
|
+
file: t.file,
|
|
274
|
+
metric: {
|
|
275
|
+
durationMs: C(t.duration),
|
|
276
|
+
dataSizeBytes: t.dataSize
|
|
277
|
+
},
|
|
278
|
+
suggestedTool: "get_load_waterfall"
|
|
279
|
+
});
|
|
280
|
+
let i = e.fpsSamples.filter((e) => e.fps < n.fpsDropThreshold);
|
|
281
|
+
if (i.length > 0) {
|
|
282
|
+
let t = i.reduce((e, t) => Math.min(e, t.fps), Infinity);
|
|
283
|
+
r.push({
|
|
284
|
+
id: `fps-drops:${e.fpsSamples[0]?.timestamp ?? 0}`,
|
|
285
|
+
kind: "fps-drop",
|
|
286
|
+
severity: t < 15 ? "high" : t < 30 ? "medium" : "low",
|
|
287
|
+
summary: `${i.length} FPS samples below ${n.fpsDropThreshold} (min ${t})`,
|
|
288
|
+
metric: {
|
|
289
|
+
dropCount: i.length,
|
|
290
|
+
minFps: t,
|
|
291
|
+
threshold: n.fpsDropThreshold
|
|
292
|
+
},
|
|
293
|
+
suggestedTool: "get_fps_drops"
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
let { inDegree: a } = we(e.reactiveGraph);
|
|
297
|
+
for (let t of e.reactiveGraph.nodes) if (t.type === "effect") {
|
|
298
|
+
let e = a.get(t.id) ?? 0;
|
|
299
|
+
e >= n.effectMaxDeps && r.push({
|
|
300
|
+
id: `effect-deps:${t.id}`,
|
|
301
|
+
kind: "effect-overconnected",
|
|
302
|
+
severity: e >= n.effectMaxDeps * 3 ? "high" : e >= n.effectMaxDeps * 2 ? "medium" : "low",
|
|
303
|
+
summary: `effect "${t.name}" depends on ${e} reactive values`,
|
|
304
|
+
file: t.componentFile,
|
|
305
|
+
metric: { depCount: e },
|
|
306
|
+
suggestedTool: "get_reactive_graph_problems"
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
return r.toSorted(Ce);
|
|
310
|
+
}
|
|
311
|
+
var Se = {
|
|
312
|
+
high: 0,
|
|
313
|
+
medium: 1,
|
|
314
|
+
low: 2
|
|
315
|
+
};
|
|
316
|
+
function Ce(e, t) {
|
|
317
|
+
return Se[e.severity] - Se[t.severity];
|
|
318
|
+
}
|
|
319
|
+
function we(e) {
|
|
320
|
+
let t = /* @__PURE__ */ new Map(), n = /* @__PURE__ */ new Map(), r = /* @__PURE__ */ new Set();
|
|
321
|
+
for (let i of e.edges) {
|
|
322
|
+
let e = JSON.stringify([i.from, i.to]);
|
|
323
|
+
r.has(e) || (r.add(e), t.set(i.to, (t.get(i.to) ?? 0) + 1), n.set(i.from, (n.get(i.from) ?? 0) + 1));
|
|
324
|
+
}
|
|
325
|
+
return {
|
|
326
|
+
inDegree: t,
|
|
327
|
+
outDegree: n
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
function C(e) {
|
|
331
|
+
return Math.round(e * 100) / 100;
|
|
332
|
+
}
|
|
333
|
+
function Te(e, t = {}) {
|
|
334
|
+
let n = be(t), { inDegree: r, outDegree: i } = we(e), a = [], o = [], s = [];
|
|
335
|
+
for (let t of e.nodes) {
|
|
336
|
+
let e = r.get(t.id) ?? 0, c = i.get(t.id) ?? 0;
|
|
337
|
+
t.type === "effect" && e >= n.effectMaxDeps && a.push({
|
|
338
|
+
id: t.id,
|
|
339
|
+
name: t.name,
|
|
340
|
+
file: t.componentFile,
|
|
341
|
+
depCount: e
|
|
342
|
+
}), t.type !== "template" && (t.type === "derived" && t.unevaluated ? o.push({
|
|
343
|
+
id: t.id,
|
|
344
|
+
name: t.name,
|
|
345
|
+
file: t.componentFile
|
|
346
|
+
}) : e === 0 && c === 0 && !t.untrackedDeps && s.push({
|
|
347
|
+
id: t.id,
|
|
348
|
+
name: t.name,
|
|
349
|
+
type: t.type,
|
|
350
|
+
file: t.componentFile
|
|
351
|
+
}));
|
|
352
|
+
}
|
|
353
|
+
return {
|
|
354
|
+
effects: a,
|
|
355
|
+
orphanDeriveds: o,
|
|
356
|
+
isolatedNodes: s
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
//#endregion
|
|
360
|
+
//#region src/mcp/sessions.ts
|
|
361
|
+
var Ee = 1, De = 5, Oe = 1, ke = 30;
|
|
362
|
+
function w(e) {
|
|
363
|
+
return {
|
|
364
|
+
renderProfiles: e.getRenderProfiles().map((e) => ({
|
|
365
|
+
componentId: e.componentId,
|
|
366
|
+
file: e.file,
|
|
367
|
+
name: e.name,
|
|
368
|
+
renderCount: e.renderCount,
|
|
369
|
+
totalRenderTime: e.totalRenderTime
|
|
370
|
+
})),
|
|
371
|
+
loadProfilesCount: e.getLoadProfiles().length,
|
|
372
|
+
fpsCount: e.getFpsSamples().length,
|
|
373
|
+
takenAt: Date.now()
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
function T(e, t, n) {
|
|
377
|
+
return Math.abs(e) < t ? "unchanged" : n ? e < 0 ? "improved" : "regressed" : e > 0 ? "improved" : "regressed";
|
|
378
|
+
}
|
|
379
|
+
var Ae = /^s_[0-9a-z]{1,16}_[0-9a-f]{6}$/;
|
|
380
|
+
function E(e) {
|
|
381
|
+
return typeof e == "string" && Ae.test(e);
|
|
382
|
+
}
|
|
383
|
+
var je = class {
|
|
384
|
+
sessions = /* @__PURE__ */ new Map();
|
|
385
|
+
active = null;
|
|
386
|
+
persistDir;
|
|
387
|
+
getters;
|
|
388
|
+
constructor(e) {
|
|
389
|
+
this.persistDir = t.resolve(e.persistDir), this.getters = e.getters;
|
|
390
|
+
}
|
|
391
|
+
start(t, n) {
|
|
392
|
+
if (this.active) throw Error(`A session is already active: ${this.active}. Call end_session before starting a new one.`);
|
|
393
|
+
let r = `s_${Date.now().toString(36)}_${e.randomBytes(3).toString("hex")}`, i = {
|
|
394
|
+
id: r,
|
|
395
|
+
label: t,
|
|
396
|
+
startedAt: Date.now(),
|
|
397
|
+
persist: n,
|
|
398
|
+
startSnapshot: w(this.getters),
|
|
399
|
+
loadProfiles: [],
|
|
400
|
+
fpsSamples: []
|
|
401
|
+
};
|
|
402
|
+
return this.sessions.set(r, i), this.active = r, i;
|
|
403
|
+
}
|
|
404
|
+
recordLoadProfile(e) {
|
|
405
|
+
if (!this.active) return;
|
|
406
|
+
let t = this.sessions.get(this.active);
|
|
407
|
+
t && t.loadProfiles.push(e);
|
|
408
|
+
}
|
|
409
|
+
recordFpsSample(e) {
|
|
410
|
+
if (!this.active) return;
|
|
411
|
+
let t = this.sessions.get(this.active);
|
|
412
|
+
t && t.fpsSamples.push(e);
|
|
413
|
+
}
|
|
414
|
+
end(e) {
|
|
415
|
+
if (!this.active) throw Error("No active session");
|
|
416
|
+
let t = this.sessions.get(this.active);
|
|
417
|
+
if (!t) throw Error("Active session missing from store");
|
|
418
|
+
return t.endedAt = Date.now(), t.endSnapshot = w(this.getters), this.active = null, e === "discard" ? this.sessions.delete(t.id) : (e === "disk" || t.persist) && this.persistToDisk(t), t;
|
|
419
|
+
}
|
|
420
|
+
get(e) {
|
|
421
|
+
return E(e) ? this.sessions.get(e) || this.loadFromDisk(e) : void 0;
|
|
422
|
+
}
|
|
423
|
+
list() {
|
|
424
|
+
let e = /* @__PURE__ */ new Set(), t = [];
|
|
425
|
+
for (let n of this.sessions.values()) e.add(n.id), t.push({
|
|
426
|
+
id: n.id,
|
|
427
|
+
label: n.label,
|
|
428
|
+
startedAt: n.startedAt,
|
|
429
|
+
endedAt: n.endedAt,
|
|
430
|
+
persisted: r.existsSync(this.pathFor(n.id)),
|
|
431
|
+
active: this.active === n.id
|
|
432
|
+
});
|
|
433
|
+
try {
|
|
434
|
+
if (r.existsSync(this.persistDir)) for (let n of r.readdirSync(this.persistDir)) {
|
|
435
|
+
if (!n.endsWith(".json")) continue;
|
|
436
|
+
let r = n.slice(0, -5);
|
|
437
|
+
if (!E(r) || e.has(r)) continue;
|
|
438
|
+
let i = this.loadFromDisk(r);
|
|
439
|
+
i && t.push({
|
|
440
|
+
id: i.id,
|
|
441
|
+
label: i.label,
|
|
442
|
+
startedAt: i.startedAt,
|
|
443
|
+
endedAt: i.endedAt,
|
|
444
|
+
persisted: !0,
|
|
445
|
+
active: !1
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
} catch {}
|
|
449
|
+
return t.toSorted((e, t) => t.startedAt - e.startedAt);
|
|
450
|
+
}
|
|
451
|
+
delete(e) {
|
|
452
|
+
if (!E(e)) return !1;
|
|
453
|
+
let t = this.sessions.delete(e), n = !1;
|
|
454
|
+
try {
|
|
455
|
+
let t = this.pathFor(e);
|
|
456
|
+
r.existsSync(t) && (r.unlinkSync(t), n = !0);
|
|
457
|
+
} catch {}
|
|
458
|
+
return this.active === e && (this.active = null), t || n;
|
|
459
|
+
}
|
|
460
|
+
delta(e) {
|
|
461
|
+
let t = this.get(e);
|
|
462
|
+
if (!t) throw Error(`Session not found: ${e}`);
|
|
463
|
+
return this.deltaOf(t);
|
|
464
|
+
}
|
|
465
|
+
deltaOf(e) {
|
|
466
|
+
if (!e.endSnapshot || e.endedAt === void 0) throw Error(`Session ${e.id} has not been ended yet`);
|
|
467
|
+
let t = new Map(e.startSnapshot.renderProfiles.map((e) => [e.componentId, e])), n = e.endSnapshot.renderProfiles.map((e) => {
|
|
468
|
+
let n = t.get(e.componentId), r = n?.renderCount ?? 0, i = n?.totalRenderTime ?? 0, a = e.renderCount - r, o = e.totalRenderTime - i;
|
|
469
|
+
return {
|
|
470
|
+
componentId: e.componentId,
|
|
471
|
+
file: e.file,
|
|
472
|
+
name: e.name,
|
|
473
|
+
renderCountDelta: a,
|
|
474
|
+
totalRenderTimeDelta: o,
|
|
475
|
+
avgRenderTimeDelta: a > 0 ? o / a : 0
|
|
476
|
+
};
|
|
477
|
+
}).filter((e) => e.renderCountDelta > 0).toSorted((e, t) => t.totalRenderTimeDelta - e.totalRenderTimeDelta), r = e.loadProfiles.map((e) => e.duration), i = Me(r), a = Ne(r, 95), o = e.fpsSamples.map((e) => e.fps), s = Me(o), c = o.length > 0 ? o.reduce((e, t) => Math.min(e, t), Infinity) : 0, l = o.filter((e) => e < ke).length;
|
|
478
|
+
return {
|
|
479
|
+
durationMs: e.endedAt - e.startedAt,
|
|
480
|
+
components: n,
|
|
481
|
+
loadProfiles: {
|
|
482
|
+
count: e.loadProfiles.length,
|
|
483
|
+
avgDuration: i,
|
|
484
|
+
p95Duration: a
|
|
485
|
+
},
|
|
486
|
+
fps: {
|
|
487
|
+
samples: o.length,
|
|
488
|
+
avg: s,
|
|
489
|
+
min: c,
|
|
490
|
+
drops: l
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
compare(e, t) {
|
|
495
|
+
let n = this.delta(e), r = this.delta(t), i = this.get(e), a = this.get(t), o = n.components.reduce((e, t) => e + t.totalRenderTimeDelta, 0), s = r.components.reduce((e, t) => e + t.totalRenderTimeDelta, 0), c = s - o, l = r.loadProfiles.avgDuration - n.loadProfiles.avgDuration, u = r.fps.avg - n.fps.avg;
|
|
496
|
+
return {
|
|
497
|
+
a: {
|
|
498
|
+
id: i.id,
|
|
499
|
+
label: i.label
|
|
500
|
+
},
|
|
501
|
+
b: {
|
|
502
|
+
id: a.id,
|
|
503
|
+
label: a.label
|
|
504
|
+
},
|
|
505
|
+
render: {
|
|
506
|
+
totalRenderTimeDeltaA: o,
|
|
507
|
+
totalRenderTimeDeltaB: s,
|
|
508
|
+
diff: c,
|
|
509
|
+
verdict: T(c, Ee, !0)
|
|
510
|
+
},
|
|
511
|
+
load: {
|
|
512
|
+
avgA: n.loadProfiles.avgDuration,
|
|
513
|
+
avgB: r.loadProfiles.avgDuration,
|
|
514
|
+
diff: l,
|
|
515
|
+
verdict: T(l, De, !0)
|
|
516
|
+
},
|
|
517
|
+
fps: {
|
|
518
|
+
avgA: n.fps.avg,
|
|
519
|
+
avgB: r.fps.avg,
|
|
520
|
+
diff: u,
|
|
521
|
+
verdict: T(u, Oe, !1)
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
pathFor(e) {
|
|
526
|
+
if (!E(e)) throw Error("Invalid session id");
|
|
527
|
+
let n = t.resolve(this.persistDir, `${e}.json`);
|
|
528
|
+
if (t.dirname(n) !== this.persistDir) throw Error("Invalid session id");
|
|
529
|
+
return n;
|
|
530
|
+
}
|
|
531
|
+
persistToDisk(e) {
|
|
532
|
+
try {
|
|
533
|
+
r.mkdirSync(this.persistDir, { recursive: !0 }), r.writeFileSync(this.pathFor(e.id), JSON.stringify(e, null, 2), { mode: 384 });
|
|
534
|
+
} catch {}
|
|
535
|
+
}
|
|
536
|
+
loadFromDisk(e) {
|
|
537
|
+
try {
|
|
538
|
+
let t = r.readFileSync(this.pathFor(e), "utf-8"), n = JSON.parse(t);
|
|
539
|
+
return Fe(n) && n.id === e ? n : void 0;
|
|
540
|
+
} catch {
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
function Me(e) {
|
|
546
|
+
return e.length === 0 ? 0 : e.reduce((e, t) => e + t, 0) / e.length;
|
|
547
|
+
}
|
|
548
|
+
function Ne(e, t) {
|
|
549
|
+
if (e.length === 0) return 0;
|
|
550
|
+
let n = e.toSorted((e, t) => e - t);
|
|
551
|
+
return n[Math.max(1, Math.ceil(t / 100 * n.length)) - 1];
|
|
552
|
+
}
|
|
553
|
+
var D = (e) => typeof e == "object" && !!e, O = (e) => typeof e == "number" && Number.isFinite(e);
|
|
554
|
+
function Pe(e) {
|
|
555
|
+
return D(e) && Array.isArray(e.renderProfiles) && e.renderProfiles.every((e) => D(e) && O(e.componentId) && typeof e.file == "string" && typeof e.name == "string" && O(e.renderCount) && O(e.totalRenderTime));
|
|
556
|
+
}
|
|
557
|
+
function Fe(e) {
|
|
558
|
+
return D(e) && typeof e.id == "string" && typeof e.label == "string" && O(e.startedAt) && (e.endedAt === void 0 || O(e.endedAt)) && Pe(e.startSnapshot) && (e.endSnapshot === void 0 || Pe(e.endSnapshot)) && Array.isArray(e.loadProfiles) && e.loadProfiles.every((e) => D(e) && O(e.duration)) && Array.isArray(e.fpsSamples) && e.fpsSamples.every((e) => D(e) && O(e.fps));
|
|
559
|
+
}
|
|
560
|
+
var Ie = {
|
|
561
|
+
default: 2048,
|
|
562
|
+
max: 32768
|
|
563
|
+
};
|
|
564
|
+
function Le(e, t) {
|
|
565
|
+
let n;
|
|
566
|
+
try {
|
|
567
|
+
n = JSON.stringify(e);
|
|
568
|
+
} catch {
|
|
569
|
+
return { omitted: "not serializable" };
|
|
570
|
+
}
|
|
571
|
+
return n === void 0 || n.length <= t ? e : {
|
|
572
|
+
omitted: "too large",
|
|
573
|
+
chars: n.length,
|
|
574
|
+
preview: n.slice(0, Math.min(200, t))
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
var k = o.string().max(64).regex(Ae, "not a session id"), A = (e) => ({ content: [{
|
|
578
|
+
type: "text",
|
|
579
|
+
text: typeof e == "string" ? e : JSON.stringify(e, null, 2)
|
|
580
|
+
}] });
|
|
581
|
+
function Re(e) {
|
|
582
|
+
let t = new i({
|
|
583
|
+
name: "vite-devtools-svelte",
|
|
584
|
+
version: "0.1.0"
|
|
585
|
+
});
|
|
586
|
+
if (t.registerTool("list_performance_issues", {
|
|
587
|
+
title: "List performance issues",
|
|
588
|
+
description: "Cross-cuts render/reactive/load/fps metrics and returns ranked issues. The entry point for AI-driven performance audits. Each issue carries `suggestedTool` naming the detail tool to call next.",
|
|
589
|
+
inputSchema: {
|
|
590
|
+
avgRenderTimeMs: o.number().positive().optional(),
|
|
591
|
+
renderCount: o.number().int().min(1).optional(),
|
|
592
|
+
loadDurationMs: o.number().positive().optional(),
|
|
593
|
+
fpsDropThreshold: o.number().min(1).max(120).optional(),
|
|
594
|
+
effectMaxDeps: o.number().int().min(1).optional()
|
|
595
|
+
}
|
|
596
|
+
}, async (t) => {
|
|
597
|
+
let n = t, r = await e.getReactiveGraph(), i = xe({
|
|
598
|
+
renderProfiles: e.getRenderProfiles(),
|
|
599
|
+
reactiveGraph: r,
|
|
600
|
+
loadProfiles: e.getLoadProfiles(),
|
|
601
|
+
fpsSamples: e.getFpsSamples()
|
|
602
|
+
}, n);
|
|
603
|
+
return A({
|
|
604
|
+
count: i.length,
|
|
605
|
+
issues: i
|
|
94
606
|
});
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
607
|
+
}), t.registerTool("get_component_hotspots", {
|
|
608
|
+
title: "Component render hotspots",
|
|
609
|
+
description: "Top components by total render time, with render count and per-render average.",
|
|
610
|
+
inputSchema: { topN: o.number().int().min(1).max(200).optional() }
|
|
611
|
+
}, ({ topN: t = 20 }) => A([...e.getRenderProfiles()].map((e) => ({
|
|
612
|
+
file: e.file,
|
|
613
|
+
name: e.name,
|
|
614
|
+
componentId: e.componentId,
|
|
615
|
+
renderCount: e.renderCount,
|
|
616
|
+
totalRenderTimeMs: j(e.totalRenderTime),
|
|
617
|
+
avgRenderTimeMs: j(e.renderCount > 0 ? e.totalRenderTime / e.renderCount : 0),
|
|
618
|
+
lastRenderTimeMs: j(e.lastRenderTime),
|
|
619
|
+
lastRenderAt: e.lastRenderAt
|
|
620
|
+
})).toSorted((e, t) => t.totalRenderTimeMs - e.totalRenderTimeMs).slice(0, t))), t.registerTool("get_reactive_graph_problems", {
|
|
621
|
+
title: "Reactive graph problems",
|
|
622
|
+
description: "Classified reactive graph issues: over-connected effects, orphan deriveds (declared but never evaluated so far — may still be read later, e.g. in a branch not shown yet), isolated nodes (no tracked dependency or reader; reads from the markup count as readers). Returns categories instead of the full graph.",
|
|
623
|
+
inputSchema: { effectMaxDeps: o.number().int().min(1).optional() }
|
|
624
|
+
}, async ({ effectMaxDeps: t }) => A(Te(await e.getReactiveGraph(), { effectMaxDeps: t }))), t.registerTool("get_load_waterfall", {
|
|
625
|
+
title: "SvelteKit load waterfall",
|
|
626
|
+
description: "Load profiles grouped by route, with timing and data size. Optionally filtered by route.",
|
|
627
|
+
inputSchema: { route: o.string().optional() }
|
|
628
|
+
}, ({ route: t }) => {
|
|
629
|
+
let n = e.getLoadProfiles();
|
|
630
|
+
t && (n = n.filter((e) => e.route === t));
|
|
631
|
+
let r = /* @__PURE__ */ new Map();
|
|
632
|
+
for (let e of n) {
|
|
633
|
+
let t = r.get(e.route) ?? [];
|
|
634
|
+
t.push(e), r.set(e.route, t);
|
|
635
|
+
}
|
|
636
|
+
return A([...r.entries()].map(([e, t]) => {
|
|
637
|
+
let n = t.map((e) => e.duration);
|
|
638
|
+
return {
|
|
639
|
+
route: e,
|
|
640
|
+
file: t[0]?.file,
|
|
641
|
+
count: t.length,
|
|
642
|
+
avgDuration: j(ze(n)),
|
|
643
|
+
maxDuration: j(n.reduce((e, t) => Math.max(e, t), -Infinity)),
|
|
644
|
+
totalDataBytes: t.reduce((e, t) => e + t.dataSize, 0),
|
|
645
|
+
samples: t
|
|
646
|
+
};
|
|
647
|
+
}).toSorted((e, t) => t.avgDuration - e.avgDuration));
|
|
648
|
+
}), t.registerTool("get_fps_drops", {
|
|
649
|
+
title: "FPS drops",
|
|
650
|
+
description: "Samples whose FPS fell below `threshold`. Returns timestamp + fps for each.",
|
|
651
|
+
inputSchema: {
|
|
652
|
+
threshold: o.number().min(1).max(120).optional(),
|
|
653
|
+
sinceMs: o.number().nonnegative().optional()
|
|
654
|
+
}
|
|
655
|
+
}, ({ threshold: t = 40, sinceMs: n }) => {
|
|
656
|
+
let r = e.getFpsSamples();
|
|
657
|
+
if (n !== void 0) {
|
|
658
|
+
let e = Date.now() - n;
|
|
659
|
+
r = r.filter((t) => t.timestamp >= e);
|
|
660
|
+
}
|
|
661
|
+
let i = r.filter((e) => e.fps < t);
|
|
662
|
+
return A({
|
|
663
|
+
threshold: t,
|
|
664
|
+
sampleCount: r.length,
|
|
665
|
+
dropCount: i.length,
|
|
666
|
+
minFps: i.length > 0 ? i.reduce((e, t) => Math.min(e, t.fps), Infinity) : null,
|
|
667
|
+
drops: i
|
|
668
|
+
});
|
|
669
|
+
}), t.registerTool("get_render_profile", {
|
|
670
|
+
title: "Render profile for a specific file",
|
|
671
|
+
description: "Returns render profile entries matching the given component file (substring match).",
|
|
672
|
+
inputSchema: { file: o.string() }
|
|
673
|
+
}, ({ file: t }) => A(e.getRenderProfiles().filter((e) => e.file.includes(t)).map((e) => ({
|
|
674
|
+
componentId: e.componentId,
|
|
675
|
+
file: e.file,
|
|
676
|
+
name: e.name,
|
|
677
|
+
initTime: e.initTime,
|
|
678
|
+
renderCount: e.renderCount,
|
|
679
|
+
totalRenderTime: e.totalRenderTime,
|
|
680
|
+
lastRenderTime: e.lastRenderTime,
|
|
681
|
+
lastRenderAt: e.lastRenderAt,
|
|
682
|
+
totalRenderTimeMs: j(e.totalRenderTime),
|
|
683
|
+
avgRenderTimeMs: j(e.renderCount > 0 ? e.totalRenderTime / e.renderCount : 0)
|
|
684
|
+
})))), t.registerTool("get_project_info", {
|
|
685
|
+
title: "Project info",
|
|
686
|
+
description: "Package name/version, Svelte / SvelteKit / Vite versions, dependency lists.",
|
|
687
|
+
inputSchema: {}
|
|
688
|
+
}, () => A(e.getProject())), t.registerTool("get_routes", {
|
|
689
|
+
title: "SvelteKit routes",
|
|
690
|
+
description: "Static analysis of the SvelteKit routes tree.",
|
|
691
|
+
inputSchema: {}
|
|
692
|
+
}, () => A(e.getRoutes())), t.registerTool("get_live_components", {
|
|
693
|
+
title: "Currently mounted components",
|
|
694
|
+
description: "Component instances with file, parent, mounted status as currently mounted in the browser (an array, parents first). With `includeMeta: true` the answer is `{ epoch, total, captured, truncated, components }` instead, limited to `limit` (default 1000): component ids are only valid within that `epoch` (one page load), so pass both to get_reactive_scope.",
|
|
695
|
+
inputSchema: {
|
|
696
|
+
includeMeta: o.boolean().optional(),
|
|
697
|
+
limit: o.number().int().min(1).max(5e4).optional()
|
|
698
|
+
}
|
|
699
|
+
}, ({ includeMeta: t, limit: n }) => {
|
|
700
|
+
if (!t || !e.getLiveSnapshot) {
|
|
701
|
+
let t = e.getLiveComponents();
|
|
702
|
+
return A(n === void 0 ? t : t.slice(0, n));
|
|
703
|
+
}
|
|
704
|
+
let r = e.getLiveSnapshot(), i = n ?? 1e3;
|
|
705
|
+
return A({
|
|
706
|
+
epoch: r.epoch,
|
|
707
|
+
total: r.total,
|
|
708
|
+
captured: r.components.length,
|
|
709
|
+
truncated: r.total > r.components.length || r.components.length > i,
|
|
710
|
+
components: r.components.slice(0, i)
|
|
711
|
+
});
|
|
712
|
+
}), t.registerTool("get_component_relations", {
|
|
713
|
+
title: "Component import graph",
|
|
714
|
+
description: "Static import relations between .svelte components.",
|
|
715
|
+
inputSchema: {}
|
|
716
|
+
}, () => A(e.getComponentRelations())), e.getReactiveSummary) {
|
|
717
|
+
let n = e.getReactiveSummary;
|
|
718
|
+
t.registerTool("get_reactive_summary", {
|
|
719
|
+
title: "Reactive overview (top components)",
|
|
720
|
+
description: "Busiest component instances from runtime counters over all instances, without capturing the graph. Counts are sampled state changes (at most one per state per 200 ms) and renders within the window, not rates. Tracked: state created during component init and shared state of .svelte.js/.ts modules (rows with kind \"module\"; their componentId is a module scope id, usable with get_reactive_scope). `rows` + `other` add up to the totals; null means unknown. Use get_reactive_scope with a componentId to look at one instance. The same request is answered from a cache for up to 1 s; `window.until` says when the answer was computed.",
|
|
721
|
+
inputSchema: {
|
|
722
|
+
topK: o.number().int().min(1).max(200).optional(),
|
|
723
|
+
windowMs: o.number().int().min(1e3).max(6e4).optional()
|
|
724
|
+
}
|
|
725
|
+
}, async ({ topK: e, windowMs: t }) => A(await n({
|
|
726
|
+
topK: e,
|
|
727
|
+
windowMs: t
|
|
728
|
+
})));
|
|
729
|
+
}
|
|
730
|
+
if (e.getReactiveScope) {
|
|
731
|
+
let n = e.getReactiveScope;
|
|
732
|
+
t.registerTool("get_reactive_scope", {
|
|
733
|
+
title: "Reactive graph of one component",
|
|
734
|
+
description: "$state/$derived/$effect nodes of one component instance and their direct neighbours, built in the app within the caps. Edges mean 'can affect' (current dependencies), not a recorded cause. `componentId` requires the `epoch` it came from (get_live_components with includeMeta): after a reload the answer is empty with staleReason 'epoch-changed' instead of another instance. Omit componentId only for the whole-app graph (capped; see total/truncated). The same request may be answered from a cache for up to 1 s; `computedAt` says when the app built the graph (null when unknown or for an empty fallback). `stale: true` only marks a fallback after the app did not answer (an earlier answer with its own computedAt, or empty).",
|
|
735
|
+
inputSchema: {
|
|
736
|
+
componentId: o.number().int().nonnegative().optional(),
|
|
737
|
+
epoch: o.string().max(200).optional(),
|
|
738
|
+
maxNodes: o.number().int().min(1).max(5e3).optional(),
|
|
739
|
+
maxEdges: o.number().int().min(1).max(2e4).optional()
|
|
740
|
+
}
|
|
741
|
+
}, async ({ componentId: e, epoch: t, maxNodes: r, maxEdges: i }) => e !== void 0 && t === void 0 ? {
|
|
742
|
+
content: [{
|
|
743
|
+
type: "text",
|
|
744
|
+
text: "componentId requires epoch (from get_live_components with includeMeta: true)"
|
|
745
|
+
}],
|
|
746
|
+
isError: !0
|
|
747
|
+
} : A(await n({
|
|
748
|
+
componentId: e,
|
|
749
|
+
epoch: t,
|
|
750
|
+
maxNodes: r,
|
|
751
|
+
maxEdges: i
|
|
752
|
+
})));
|
|
753
|
+
}
|
|
754
|
+
if (e.getStateTimelineDelta) {
|
|
755
|
+
let n = e.getStateTimelineDelta;
|
|
756
|
+
t.registerTool("get_state_timeline", {
|
|
757
|
+
title: "State changes since a cursor",
|
|
758
|
+
description: "Sampled $state changes (200 ms) after `since` (the cursor from the previous call). Contains values of state in the running app. `reset: true` means the cursor was stale and this is the whole buffer. At most `limit` newest entries are returned; `omitted` counts older ones in the range that were left out. Values larger than `maxValueChars` (JSON) are replaced by a size summary. Timestamps are detection times, so order within about a second is not causal.",
|
|
759
|
+
inputSchema: {
|
|
760
|
+
since: o.number().int().nonnegative().optional(),
|
|
761
|
+
limit: o.number().int().min(1).max(500).optional(),
|
|
762
|
+
maxValueChars: o.number().int().min(16).max(Ie.max).optional()
|
|
763
|
+
}
|
|
764
|
+
}, ({ since: e, limit: t, maxValueChars: r }) => {
|
|
765
|
+
let i = n(e), a = t ?? 100, o = r ?? Ie.default, s = Math.max(0, i.changes.length - a), c = i.changes.slice(s).map((e) => ({
|
|
766
|
+
...e,
|
|
767
|
+
oldValue: Le(e.oldValue, o),
|
|
768
|
+
newValue: Le(e.newValue, o)
|
|
769
|
+
}));
|
|
770
|
+
return A({
|
|
771
|
+
...i,
|
|
772
|
+
changes: c,
|
|
773
|
+
omitted: s,
|
|
774
|
+
maxValueChars: o
|
|
775
|
+
});
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
if (e.getCaptureInfo) {
|
|
779
|
+
let n = e.getCaptureInfo;
|
|
780
|
+
t.registerTool("get_capture_info", {
|
|
781
|
+
title: "What the DevTools hold versus what the app reported",
|
|
782
|
+
description: "Per dataset: captured count, total (null = unknown), truncated, selection policy and dropped counts by reason. Read this before drawing conclusions from capped data.",
|
|
783
|
+
inputSchema: {}
|
|
784
|
+
}, () => A(n()));
|
|
785
|
+
}
|
|
786
|
+
return t.registerTool("start_session", {
|
|
787
|
+
title: "Start measurement session",
|
|
788
|
+
description: "Begin capturing metrics under a labelled session. Required before compare_sessions. Set `persist:true` to write to disk on end; otherwise the session lives in memory only.",
|
|
789
|
+
inputSchema: {
|
|
790
|
+
label: o.string().max(256),
|
|
791
|
+
persist: o.boolean().optional()
|
|
792
|
+
}
|
|
793
|
+
}, ({ label: t, persist: n = !1 }) => {
|
|
794
|
+
let r = e.sessions.start(t, n);
|
|
795
|
+
return A({
|
|
796
|
+
id: r.id,
|
|
797
|
+
label: r.label,
|
|
798
|
+
startedAt: r.startedAt,
|
|
799
|
+
persist: r.persist
|
|
800
|
+
});
|
|
801
|
+
}), t.registerTool("end_session", {
|
|
802
|
+
title: "End current measurement session",
|
|
803
|
+
description: "Closes the active session and returns its delta. `keep` decides disposal: \"memory\" keeps it for this dev-server lifetime, \"disk\" writes to .vite-devtools-svelte/sessions/, \"discard\" deletes it.",
|
|
804
|
+
inputSchema: { keep: o.enum([
|
|
805
|
+
"memory",
|
|
806
|
+
"disk",
|
|
807
|
+
"discard"
|
|
808
|
+
]).optional() }
|
|
809
|
+
}, ({ keep: t = "memory" }) => {
|
|
810
|
+
let n = e.sessions.end(t), r = e.sessions.deltaOf(n);
|
|
811
|
+
return A({
|
|
812
|
+
id: n.id,
|
|
813
|
+
label: n.label,
|
|
814
|
+
startedAt: n.startedAt,
|
|
815
|
+
endedAt: n.endedAt,
|
|
816
|
+
keep: t,
|
|
817
|
+
delta: r
|
|
99
818
|
});
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
matcher: e[1]
|
|
107
|
-
});
|
|
819
|
+
}), t.registerTool("compare_sessions", {
|
|
820
|
+
title: "Compare two sessions",
|
|
821
|
+
description: "Diff render / load / fps metrics between two ended sessions. Each section carries `verdict`: improved | regressed | unchanged.",
|
|
822
|
+
inputSchema: {
|
|
823
|
+
a: k,
|
|
824
|
+
b: k
|
|
108
825
|
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
let t = i.join(e, "package.json"), n = r.existsSync(t) ? JSON.parse(r.readFileSync(t, "utf-8")) : {}, a = n.dependencies || {}, o = n.devDependencies || {};
|
|
123
|
-
return {
|
|
124
|
-
name: n.name || i.basename(e),
|
|
125
|
-
version: n.version || "0.0.0",
|
|
126
|
-
svelteVersion: y(e, "svelte") || a.svelte || o.svelte || "unknown",
|
|
127
|
-
sveltekitVersion: y(e, "@sveltejs/kit") || a["@sveltejs/kit"] || o["@sveltejs/kit"] || "unknown",
|
|
128
|
-
viteVersion: y(e, "vite") || a.vite || o.vite || "unknown",
|
|
129
|
-
dependencies: a,
|
|
130
|
-
devDependencies: o,
|
|
131
|
-
routesDir: ue(e),
|
|
132
|
-
staticDir: de(e)
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
function y(e, t) {
|
|
136
|
-
try {
|
|
137
|
-
let n = i.join(e, "node_modules", t, "package.json");
|
|
138
|
-
if (r.existsSync(n)) return JSON.parse(r.readFileSync(n, "utf-8")).version;
|
|
139
|
-
} catch {}
|
|
140
|
-
return null;
|
|
141
|
-
}
|
|
142
|
-
function ue(e) {
|
|
143
|
-
let t = [i.join(e, "src", "routes"), i.join(e, "src", "pages")];
|
|
144
|
-
for (let e of t) if (r.existsSync(e)) return e;
|
|
145
|
-
return i.join(e, "src", "routes");
|
|
146
|
-
}
|
|
147
|
-
function de(e) {
|
|
148
|
-
let t = [i.join(e, "static"), i.join(e, "public")];
|
|
149
|
-
for (let e of t) if (r.existsSync(e)) return e;
|
|
150
|
-
return i.join(e, "static");
|
|
151
|
-
}
|
|
152
|
-
//#endregion
|
|
153
|
-
//#region src/analyzers/components.ts
|
|
154
|
-
function b(e) {
|
|
155
|
-
let t = i.join(e, "src");
|
|
156
|
-
if (!r.existsSync(t)) return [];
|
|
157
|
-
let n = [];
|
|
158
|
-
return x(t, n), n.map((t) => {
|
|
159
|
-
let n = fe(r.readFileSync(t, "utf-8"), t), a = he(t);
|
|
160
|
-
return {
|
|
161
|
-
file: i.relative(e, t),
|
|
162
|
-
name: a,
|
|
163
|
-
imports: n.map((t) => i.relative(e, t))
|
|
826
|
+
}, ({ a: t, b: n }) => A(e.sessions.compare(t, n))), t.registerTool("list_sessions", {
|
|
827
|
+
title: "List sessions",
|
|
828
|
+
description: "In-memory + on-disk sessions, most recent first.",
|
|
829
|
+
inputSchema: {}
|
|
830
|
+
}, () => A(e.sessions.list())), t.registerTool("load_session", {
|
|
831
|
+
title: "Load a session by id",
|
|
832
|
+
description: "Returns the full session record (including delta if ended) by id.",
|
|
833
|
+
inputSchema: { id: k }
|
|
834
|
+
}, ({ id: t }) => {
|
|
835
|
+
let n = e.sessions.get(t);
|
|
836
|
+
if (!n) return {
|
|
837
|
+
...A(`Session not found: ${t}`),
|
|
838
|
+
isError: !0
|
|
164
839
|
};
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
840
|
+
let r = n.endedAt === void 0 ? null : e.sessions.deltaOf(n);
|
|
841
|
+
return A({
|
|
842
|
+
...n,
|
|
843
|
+
delta: r
|
|
844
|
+
});
|
|
845
|
+
}), t.registerTool("delete_session", {
|
|
846
|
+
title: "Delete a session",
|
|
847
|
+
description: "Removes a session from memory and disk.",
|
|
848
|
+
inputSchema: { id: k }
|
|
849
|
+
}, ({ id: t }) => A({ deleted: e.sessions.delete(t) })), t;
|
|
176
850
|
}
|
|
177
|
-
function
|
|
178
|
-
|
|
179
|
-
for (; (o = a.exec(e)) !== null;) {
|
|
180
|
-
let e = o[1], i = pe(e, r, t);
|
|
181
|
-
i && n.push(i);
|
|
182
|
-
}
|
|
183
|
-
return n;
|
|
851
|
+
function ze(e) {
|
|
852
|
+
return e.length === 0 ? 0 : e.reduce((e, t) => e + t, 0) / e.length;
|
|
184
853
|
}
|
|
185
|
-
function
|
|
186
|
-
|
|
187
|
-
let t = me(n);
|
|
188
|
-
if (t) {
|
|
189
|
-
let n = i.join(t, "src", "lib", e.slice(5));
|
|
190
|
-
if (r.existsSync(n)) return n;
|
|
191
|
-
}
|
|
192
|
-
return null;
|
|
193
|
-
}
|
|
194
|
-
if (e.startsWith(".")) {
|
|
195
|
-
let n = i.resolve(t, e);
|
|
196
|
-
if (r.existsSync(n)) return n;
|
|
197
|
-
}
|
|
198
|
-
return null;
|
|
854
|
+
function j(e) {
|
|
855
|
+
return Math.round(e * 100) / 100;
|
|
199
856
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
for (; t !== i.dirname(t);) {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
857
|
+
//#endregion
|
|
858
|
+
//#region src/runtime/client.js?raw
|
|
859
|
+
var Be = "// FNV-1a over UTF-16 code units: cheap change detection for large snapshots.\nfunction __hash(str) {\n let h = 0x811c9dc5\n for (let i = 0; i < str.length; i++) {\n // oxlint-disable-next-line unicorn/prefer-code-point -- hashes UTF-16 code units on purpose (one per index, no surrogate decoding)\n h ^= str.charCodeAt(i)\n h = Math.imul(h, 0x01000193)\n }\n return (h >>> 0).toString(36)\n}\n\n// Wire shape of one component in a components message.\nfunction __componentEntry(instance) {\n return {\n id: instance.id,\n file: instance.file,\n name: instance.name,\n parentId: instance.parentId,\n mounted: instance.mounted,\n }\n}\n\n// An integer within [lo, hi], or `d` when `v` is not a finite number.\nfunction __clampInt(v, lo, hi, d) {\n const n = Math.floor(Number(v))\n return Number.isFinite(n) ? Math.min(hi, Math.max(lo, n)) : d\n}\n\nif (typeof window !== 'undefined' && !window.__SVELTE_DEVTOOLS__) {\n const __NO_VALUE = Symbol('no value')\n // State timeline ring (§6.7 C): every read sees at most 500 entries and\n // __TIMELINE_BYTES (also the server's buffer budget). The raw array may\n // hold up to __TIMELINE_TRIM_AT (2 x the 500 cap) between bulk trims.\n const __TIMELINE_BYTES = 4 * 1024 * 1024\n const __TIMELINE_TRIM_AT = 1000\n const __SVELTE_DT = {\n _nextId: 0,\n _instances: new Map(),\n _stack: [],\n _pendingParent: undefined,\n _pendingFile: null,\n _debounceTimer: null,\n _listeners: new Set(),\n\n // Phase 2: Profiling data\n _profiles: new Map(),\n _initStartTimes: new Map(),\n _profileDebounceTimer: null,\n\n // Phase 2: Reactive graph data\n _reactiveNodes: new Map(),\n _reactiveProxies: new Map(),\n\n // componentId -> Set<nodeId>. Lets unmount purge a component's reactive\n // nodes without scanning every node of the app (was O(components x nodes)).\n _nodesByComponent: new Map(),\n\n // Phase 3: State timeline\n _stateTimeline: [],\n _stateSnapshots: new Map(),\n _timelineDebounceTimer: null,\n // signal (or proxy marker) -> nodeId, set at track time and removed by\n // _forgetNode: a scoped graph resolves dependencies without scanning\n // every node (§6.7 D).\n _idBySignal: new WeakMap(),\n // proxy -> nodeId (tag_proxy nodes; the marker above is per node)\n _idByProxy: new WeakMap(),\n // Svelte component context -> component id (set by the wrapper on mount)\n _idByContext: new WeakMap(),\n // componentId -> (name -> last suffix) for names declared more than once\n // per instance ({@const} in {#each} items, several $state class instances)\n _nameSeq: new Map(),\n // Module scopes: signals created while a .svelte.js/.ts module body runs\n // (shared state such as `export const cart = $state(...)`) belong to a\n // scope per module file. Ids come from the component id counter (never\n // reused, non-negative) but scopes are not components: they are not in\n // _instances / the component tree, and live as long as the page.\n _modules: new Map(),\n _moduleByFile: new Map(),\n // $state node name -> (scope id -> Set<nodeId>): resolves a proxy's property source\n // (labelled '<name>.prop' by Svelte in dev) to the node of its proxy —\n // a tag_proxy node, or a state signal holding a proxy (reassigned object\n // state, class fields)\n _proxyNames: new Map(),\n // file of the module body running now (set by the module transform)\n _moduleFile: null,\n // componentId -> { effect, effect_pre } counters: effect names are\n // numbered per component, so instances of one file match\n _effectSeq: new Map(),\n // Every tracked node id (dense, swap-remove like _pollIds) for the\n // incremental liveness sweep, and its cursor.\n _sweepIds: [],\n _sweepIndex: new Map(),\n _sweepCursor: 0,\n // Polling bookkeeping (§6.7 D): $state node ids in a dense array kept up\n // to date incrementally (_pollIndex: nodeId -> position; removal is\n // swap-with-last), a cursor carried across ticks, nodes found changed but\n // not yet serialized (_pollDirty), and per-node change hints\n // (nodeId -> { ref, wv, nextCheckAt }).\n _pollIds: [],\n _pollIndex: new Map(),\n _pollCursor: 0,\n _pollDirty: new Set(),\n // Full-sweep bookkeeping: how long the last complete pass over all $state\n // nodes took (ms), for disclosure (= the worst-case detection delay).\n _pollSweep: { startedAt: 0, lastMs: null, nodes: 0 },\n _pollMeta: new Map(),\n _deepCredit: 0,\n _lastSnapshotSize: new Map(),\n _timelineSeq: 0,\n _pushedSeq: 0,\n _timelineReset: false,\n _entryInfo: new WeakMap(),\n // Sum of _entryInfo.bytes over _stateTimeline (runtime byte budget).\n _timelineBytes: 0,\n // Disclosure since the last push (§6.7 C): unsent entries the ring removed\n // (by count / by bytes) and changes recorded only as a size summary.\n _timelineDropped: { 'runtime-count': 0, 'runtime-bytes': 0 },\n _valueTooLarge: 0,\n\n // Overview aggregate (§6.7 I/J), O(1) per event: registered nodes per\n // component and type (+ app totals), and per-component activity in a\n // ring of 60 one-second buckets (sampled changes, renders, render ms,\n // plus how long sampling ran in that second).\n _nodeCounts: new Map(),\n _nodeTotals: { state: 0, derived: 0, effect: 0 },\n _activity: [],\n _epoch: Math.random().toString(36).slice(2) + Date.now().toString(36),\n\n // --- Activity subscription (docs/devframe-migration.md §6.3) ---\n // Inactive until the server says a DevTools client (UI tab or MCP lease)\n // is watching: no state polling, FPS rAF loop, or periodic HMR sends.\n _active: false,\n _componentsCost: { ms: 0, bytes: 0 },\n _componentDeltas: false,\n _pendingAdded: new Set(),\n _pendingRemoved: new Set(),\n _lastFullAt: 0,\n _resync: false,\n _profilesCost: { ms: 0, bytes: 0 },\n // = collector LIMITS.renderProfiles (pinned by a unit test): the server\n // keeps only the newest entries, so sending more is wasted bytes.\n _profileCap: 5000,\n _sampling: null,\n _baselining: false,\n // Polled state nodes not observed yet (no seed value). Their first\n // observation is a seed, not a change: no timeline entry, no activity\n // count, no drops — the old value is unknown, not null (review B-1/B-2).\n _unseeded: new Set(),\n // pendingNodes last sent on a state-timeline message (null = none yet)\n _sentPendingNodes: null,\n _fpsGen: 0,\n\n register(file) {\n const id = this._nextId++\n // Parent: the wrapper's context-derived hint (components created by a\n // later block update, review B1b) when it names a live instance, else\n // the synchronous init stack (children created during the parent's own\n // init, before the parent is mapped).\n const hint = this._pendingParent\n this._pendingParent = undefined\n const parentId =\n hint !== undefined && hint !== null && this._instances.has(hint)\n ? hint\n : this._stack.length > 0\n ? this._stack.at(-1)\n : null\n const baseName = file.slice(file.lastIndexOf('/') + 1).replace('.svelte', '')\n const name = baseName === '' ? 'Unknown' : baseName\n // children is a Set so unmounting one row of a 5 000-row list is O(1).\n this._instances.set(id, { id, file, name, parentId, mounted: true, children: new Set() })\n if (parentId !== null) {\n const parent = this._instances.get(parentId)\n if (parent) parent.children.add(id)\n }\n this._stack.push(id)\n if (this._active) this._pendingAdded.add(id)\n this._scheduleUpdate()\n return id\n },\n\n registered(id) {\n const idx = this._stack.indexOf(id)\n if (idx !== -1) this._stack.splice(idx, 1)\n },\n\n mount(id) {\n const instance = this._instances.get(id)\n if (instance && !instance.mounted) {\n instance.mounted = true\n if (this._active) this._pendingAdded.add(id)\n this._scheduleUpdate()\n }\n },\n\n unmount(id) {\n const instance = this._instances.get(id)\n if (instance) {\n if (instance.parentId !== null) {\n const parent = this._instances.get(instance.parentId)\n if (parent) parent.children.delete(id)\n }\n this._removeChildren(id)\n this._cleanupComponent(id)\n }\n this._scheduleUpdate()\n this._scheduleProfileUpdate()\n },\n\n // Remounts get a fresh id, so entries keyed by a dead id would otherwise\n // survive forever — every per-component map must be purged here.\n _cleanupComponent(id) {\n // Delta bookkeeping: an id added and removed between two pushes was\n // never sent, so it cancels out.\n if (this._active && !this._pendingAdded.delete(id)) this._pendingRemoved.add(id)\n this._cleanupReactiveNodes(id)\n this._effectSeq.delete(id)\n this._nameSeq.delete(id)\n this._instances.delete(id)\n this._profiles.delete(id)\n this._initStartTimes.delete(id)\n },\n\n _cleanupReactiveNodes(componentId) {\n const nodeIds = this._nodesByComponent.get(componentId)\n if (!nodeIds) return\n this._nodesByComponent.delete(componentId)\n // a copy: _forgetNode() deletes from this set\n for (const nodeId of Array.from(nodeIds)) this._forgetNode(nodeId)\n },\n\n _forgetNode(nodeId) {\n const entry = this._reactiveNodes.get(nodeId)\n if (entry) {\n this._countNode(entry.meta.componentId, entry.meta.type, -1)\n const set = this._nodesByComponent.get(entry.meta.componentId)\n if (set) set.delete(nodeId)\n }\n const signal = entry?.signal?.deref()\n if (signal && this._idBySignal.get(signal) === nodeId) this._idBySignal.delete(signal)\n const proxy = this._reactiveProxies.get(nodeId)?.deref()\n if (proxy && this._idByProxy.get(proxy) === nodeId) this._idByProxy.delete(proxy)\n if (entry?.meta.type === 'state') {\n const named = this._proxyNames.get(entry.meta.name)\n const inScope = named?.get(entry.meta.componentId)\n if (inScope) {\n inScope.delete(nodeId)\n if (inScope.size === 0) named.delete(entry.meta.componentId)\n if (named.size === 0) this._proxyNames.delete(entry.meta.name)\n }\n }\n this._pollRemove(nodeId)\n this._sweepRemove(nodeId)\n this._pollDirty.delete(nodeId)\n this._reactiveNodes.delete(nodeId)\n this._reactiveProxies.delete(nodeId)\n this._stateSnapshots.delete(nodeId)\n if (this._stateSnapshotStrs) this._stateSnapshotStrs.delete(nodeId)\n this._pollMeta.delete(nodeId)\n this._lastSnapshotSize.delete(nodeId)\n },\n\n // A component instance or a module scope that is still live.\n _scopeLive(id) {\n return this._instances.has(id) || this._modules.has(id)\n },\n\n _scopeFile(id) {\n const scope = this._instances.get(id) ?? this._modules.get(id)\n return scope ? scope.file : ''\n },\n\n _enterModule(file) {\n this._moduleFile = file\n // a module body that throws (or awaits) never reaches _leaveModule\n queueMicrotask(() => {\n if (this._moduleFile === file) this._moduleFile = null\n })\n },\n\n _leaveModule() {\n this._moduleFile = null\n },\n\n trackModuleSignal(kind, target, name) {\n const file = this._moduleFile\n let id = this._moduleByFile.get(file)\n if (id === undefined) {\n id = this._nextId++\n const lastSegment = file.slice(file.lastIndexOf('/') + 1)\n const base = lastSegment === '' ? file : lastSegment\n this._modules.set(id, { id, file, name: base, kind: 'module' })\n this._moduleByFile.set(file, id)\n }\n if (kind === 'proxy') this.trackProxy(target, name, id, null)\n else if (kind === 'derived') this.trackDerived(target, name, id, null)\n else this.trackState(target, name, id, null)\n },\n\n // A node is live while its signal is reachable, its component is mounted,\n // and the effect it was created in (an {#each} item, an effect run) has\n // not been destroyed. Svelte nulls effect.fn when it destroys an effect\n // (internal/client destroy_effect), which is checked instead of the\n // internal DESTROYED flag value. Returns the signal or null.\n _liveSignal(nodeId) {\n const entry = this._reactiveNodes.get(nodeId)\n if (!entry) return null\n const signal = entry.signal.deref()\n if (signal && this._scopeLive(entry.meta.componentId) && this._ownerAlive(entry)) {\n if (!entry.effect) return signal\n // a bound effect is destroyed when Svelte clears its callback\n if (signal.fn !== null) return signal\n }\n this._forgetNode(nodeId)\n return null\n },\n\n _ownerAlive(entry) {\n if (!entry.owner) return true\n const owner = entry.owner.deref()\n return !!owner && owner.fn !== null\n },\n\n _sweepAdd(nodeId) {\n if (this._sweepIndex.has(nodeId)) return\n this._sweepIndex.set(nodeId, this._sweepIds.length)\n this._sweepIds.push(nodeId)\n },\n\n _sweepRemove(nodeId) {\n const i = this._sweepIndex.get(nodeId)\n if (i === undefined) return\n const ids = this._sweepIds\n const last = ids.pop()\n this._sweepIndex.delete(nodeId)\n if (i < ids.length) {\n ids[i] = last\n this._sweepIndex.set(last, i)\n }\n },\n\n // Liveness of up to `max` nodes, continuing from a cursor (each poll\n // tick): nodes of destroyed {#each} items / effect runs leave the counts\n // and the graph without waiting for a graph request or GC.\n _sweepNodes(max) {\n const ids = this._sweepIds\n for (let n = 0; n < max && ids.length > 0; n++) {\n if (this._sweepCursor >= ids.length) this._sweepCursor = 0\n const nodeId = ids[this._sweepCursor]\n // a forgotten node's slot is refilled by the last id: re-check it\n if (this._liveSignal(nodeId)) this._sweepCursor++\n }\n },\n\n // Unique node id for a signal: componentId:name, or componentId:name#k\n // when this instance already holds a live node of that name for another\n // signal. The same signal (re-tagged) keeps its id.\n _nodeIdFor(identity, byIdentity, componentId, name) {\n const known = byIdentity.get(identity)\n if (known !== undefined && this._reactiveNodes.has(known)) return known\n const base = componentId + ':' + name\n if (!this._reactiveNodes.has(base) || !this._liveSignal(base)) return base\n let seq = this._nameSeq.get(componentId)\n if (!seq) {\n seq = new Map()\n this._nameSeq.set(componentId, seq)\n }\n let k = seq.get(name) ?? 1\n let id\n do {\n k++\n id = base + '#' + k\n } while (this._reactiveNodes.has(id) && this._liveSignal(id))\n seq.set(name, k)\n return id\n },\n\n _registerNode(nodeId, componentId, type, name, signalRef, owner, extra) {\n this._trackNodeCount(nodeId, componentId, type)\n const entry = {\n signal: signalRef,\n owner: owner ? new WeakRef(owner) : null,\n meta: { id: nodeId, type, name, componentId, componentFile: this._scopeFile(componentId) },\n }\n if (extra) Object.assign(entry, extra)\n this._reactiveNodes.set(nodeId, entry)\n this._indexNode(nodeId, componentId)\n this._sweepAdd(nodeId)\n return entry\n },\n\n _indexNode(nodeId, componentId) {\n let set = this._nodesByComponent.get(componentId)\n if (!set) {\n set = new Set()\n this._nodesByComponent.set(componentId, set)\n }\n set.add(nodeId)\n },\n\n _pollAdd(nodeId) {\n if (this._pollIndex.has(nodeId)) return\n if (!this._stateSnapshots.has(nodeId)) this._unseeded.add(nodeId)\n this._pollIndex.set(nodeId, this._pollIds.length)\n this._pollIds.push(nodeId)\n },\n\n // O(1): the last id takes the removed slot. If that slot is behind the\n // cursor, the moved id is visited in the next sweep instead of this one\n // (still visited within a bounded number of ticks).\n // Before a track* call replaces/creates nodeId: move the count from the\n // previous registration (same name re-tracked) to the new one.\n _trackNodeCount(nodeId, componentId, type) {\n const prev = this._reactiveNodes.get(nodeId)\n if (prev) this._countNode(prev.meta.componentId, prev.meta.type, -1)\n this._countNode(componentId, type, 1)\n },\n\n _countNode(componentId, type, d) {\n const kind = type === 'derived' || type === 'effect' ? type : 'state'\n let c = this._nodeCounts.get(componentId)\n if (!c) {\n if (d < 0) return\n c = { state: 0, derived: 0, effect: 0 }\n this._nodeCounts.set(componentId, c)\n }\n c[kind] += d\n this._nodeTotals[kind] += d\n if (c.state + c.derived + c.effect <= 0) this._nodeCounts.delete(componentId)\n },\n\n _bucket() {\n const sec = Math.floor(performance.now() / 1000)\n const slot = ((sec % 60) + 60) % 60\n let b = this._activity[slot]\n if (!b || b.second !== sec) {\n b = { second: sec, sampledMs: 0, rows: new Map() }\n this._activity[slot] = b\n }\n return b\n },\n\n _activityRow(componentId) {\n const rows = this._bucket().rows\n let r = rows.get(componentId)\n if (!r) {\n r = { changes: 0, renders: 0, renderMs: 0 }\n rows.set(componentId, r)\n }\n return r\n },\n\n _pollRemove(nodeId) {\n const i = this._pollIndex.get(nodeId)\n if (i === undefined) return\n this._unseeded.delete(nodeId)\n const ids = this._pollIds\n const last = ids.pop()\n this._pollIndex.delete(nodeId)\n if (i < ids.length) {\n ids[i] = last\n this._pollIndex.set(last, i)\n }\n },\n\n _removeChildren(parentId) {\n const parent = this._instances.get(parentId)\n if (!parent) return\n // a copy: _cleanupComponent() changes this set\n for (const childId of Array.from(parent.children)) {\n this._removeChildren(childId)\n this._cleanupComponent(childId)\n }\n },\n\n // Full-list pushes are throttled, not debounced: a pending timer is not\n // reset, so continuous churn still produces updates (a debounce would\n // starve), and the interval adapts to the last push's cost so a 10 000+\n // instance app can't spend its main thread serializing (review P3):\n // max(min, 20 x last send ms, 1 s when the last payload exceeded ~1 MB).\n _sendInterval(cost, min) {\n return Math.max(min, 20 * cost.ms, cost.bytes > 1000000 ? 1000 : 0)\n },\n\n _scheduleUpdate() {\n if (!this._active || this._debounceTimer) return\n this._debounceTimer = setTimeout(\n () => {\n this._debounceTimer = null\n this._sendUpdate()\n },\n this._sendInterval(this._componentsCost, 100),\n )\n },\n\n // components push (§6.5). Full form { epoch, components } replaces the\n // server's tree for this epoch: sent on activation, when the server does\n // not advertise componentDeltas, as a periodic checkpoint, or when the\n // delta would be larger than the tree. Otherwise the delta form\n // { epoch, added, removed } (removed ids first, ≤ 2 000 entries per\n // message, added in registration order = parents first).\n _sendUpdate(forceFull) {\n // Nobody watching: keep the registry, skip the O(N) payload. A full\n // snapshot is sent on activation (§6.3).\n if (!this._active) return\n const t0 = performance.now()\n const CHUNK = 2000\n const CHECKPOINT_MS = 30000\n const pending = this._pendingAdded.size + this._pendingRemoved.size\n const full =\n forceFull === true ||\n !this._componentDeltas ||\n t0 - this._lastFullAt >= CHECKPOINT_MS ||\n pending > this._instances.size\n let bytes = 0\n const msgs = []\n if (full) {\n const components = []\n for (const [, instance] of this._instances) {\n if (instance.mounted) {\n components.push(__componentEntry(instance))\n bytes += instance.file.length + instance.name.length + 72\n }\n }\n msgs.push({ epoch: this._epoch, reset: true, components })\n this._lastFullAt = t0\n } else if (pending > 0) {\n const removed = [...this._pendingRemoved]\n const added = []\n for (const id of this._pendingAdded) {\n const instance = this._instances.get(id)\n if (instance?.mounted) {\n added.push(__componentEntry(instance))\n bytes += instance.file.length + instance.name.length + 72\n }\n }\n bytes += removed.length * 8\n for (let r = 0; r < removed.length; r += CHUNK) {\n msgs.push({ epoch: this._epoch, added: [], removed: removed.slice(r, r + CHUNK) })\n }\n for (let a = 0; a < added.length; a += CHUNK) {\n msgs.push({ epoch: this._epoch, added: added.slice(a, a + CHUNK), removed: [] })\n }\n }\n this._pendingAdded.clear()\n this._pendingRemoved.clear()\n if (import.meta.hot) {\n for (const msg of msgs) import.meta.hot.send('svelte-devtools:components', msg)\n }\n this._componentsCost = { ms: performance.now() - t0, bytes }\n },\n\n getTree() {\n const components = []\n for (const [, instance] of this._instances) {\n if (instance.mounted) {\n components.push({\n id: instance.id,\n file: instance.file,\n name: instance.name,\n parentId: instance.parentId,\n mounted: instance.mounted,\n })\n }\n }\n return components\n },\n\n // --- Phase 2: Render Profiling ---\n\n startInit(id) {\n this._initStartTimes.set(id, performance.now())\n },\n\n endInit(id) {\n const start = this._initStartTimes.get(id)\n if (start === undefined) return\n const initTime = performance.now() - start\n this._initStartTimes.delete(id)\n const instance = this._instances.get(id)\n if (!instance) return\n this._profiles.set(id, {\n componentId: id,\n file: instance.file,\n name: instance.name,\n initTime,\n renderCount: 0,\n totalRenderTime: 0,\n lastRenderTime: 0,\n lastRenderAt: Date.now(),\n })\n this._scheduleProfileUpdate()\n },\n\n recordRender(id) {\n const profile = this._profiles.get(id)\n if (profile) {\n profile.renderCount++\n profile.lastRenderAt = Date.now()\n } else {\n const instance = this._instances.get(id)\n if (!instance) return\n this._profiles.set(id, {\n componentId: id,\n file: instance.file,\n name: instance.name,\n initTime: 0,\n renderCount: 1,\n totalRenderTime: 0,\n lastRenderTime: 0,\n lastRenderAt: Date.now(),\n })\n }\n this._activityRow(id).renders++\n this._scheduleProfileUpdate()\n },\n\n recordRenderTime(id, duration) {\n const profile = this._profiles.get(id)\n if (profile) {\n profile.totalRenderTime += duration\n profile.lastRenderTime = duration\n this._activityRow(id).renderMs += duration\n }\n },\n\n // Profiles push (review P-1 Option A; contract in\n // docs/performance-status.md). Throttled like components, but the floor\n // grows with the payload: every push carries the whole retained map, so\n // a large app coalesces bursts instead of resending ~1 MB twice a second.\n // Hard cap 2 s (contract R6): a slow serialization must not stretch the\n // latency beyond it.\n _profileInterval() {\n const cost = this._profilesCost\n return Math.min(\n 2000,\n Math.max(500, 20 * cost.ms, cost.bytes > 1000000 ? 2000 : cost.bytes > 250000 ? 1000 : 0),\n )\n },\n\n _scheduleProfileUpdate() {\n if (!this._active || this._profileDebounceTimer) return\n this._profileDebounceTimer = setTimeout(() => {\n this._profileDebounceTimer = null\n this._sendProfileUpdate()\n }, this._profileInterval())\n },\n\n // Also the forced path (activation / resync): a pending throttled push is\n // cancelled so the same state is not sent twice.\n _sendProfileUpdate() {\n if (this._profileDebounceTimer) {\n clearTimeout(this._profileDebounceTimer)\n this._profileDebounceTimer = null\n }\n if (!this._active) return\n const t0 = performance.now()\n const all = Array.from(this._profiles.values())\n // Exactly what the collector retains: its tail, in the same order.\n const profiles =\n all.length > this._profileCap ? all.slice(all.length - this._profileCap) : all\n if (import.meta.hot) {\n // total = all retained profiles; the message carries the newest-mounted\n // tail (§6.7 J IA1 capture policy 'newest-mounted').\n import.meta.hot.send('svelte-devtools:profiles', {\n epoch: this._epoch,\n profiles,\n total: all.length,\n })\n }\n // Payload size from a small sample (entries differ mainly by file path).\n const k = Math.min(8, profiles.length)\n let sampled = 0\n for (let i = 0; i < k; i++) sampled += JSON.stringify(profiles[i]).length\n const bytes = k > 0 ? Math.round((sampled / k) * profiles.length) : 0\n this._profilesCost = { ms: performance.now() - t0, bytes }\n },\n\n getProfiles() {\n return Array.from(this._profiles.values())\n },\n\n resetProfiles() {\n this._profiles.clear()\n this._scheduleProfileUpdate()\n },\n\n // --- Phase 2: Reactive Graph Tracking ---\n\n // `owner` (optional): the effect active when the signal was created; the\n // node dies with it (an {#each} item's {@const}, a class instance created\n // in an effect run).\n trackState(signal, name, componentId, owner) {\n const nodeId = this._nodeIdFor(signal, this._idBySignal, componentId, name)\n this._registerNode(nodeId, componentId, 'state', name, new WeakRef(signal), owner)\n this._idBySignal.set(signal, nodeId)\n this._nameProxyRoot(nodeId, name, componentId)\n this._pollAdd(nodeId)\n },\n\n trackProxy(proxy, name, componentId, owner) {\n const nodeId = this._nodeIdFor(proxy, this._idByProxy, componentId, name)\n this._reactiveProxies.set(nodeId, new WeakRef(proxy))\n // The proxy itself stays weakly held (_reactiveProxies). The marker is\n // held strongly so the node is not GC-dropped while the component lives;\n // _cleanupComponent removes it on unmount.\n const marker = { v: '(proxy)', _isProxy: true }\n this._registerNode(nodeId, componentId, 'state', name, { deref: () => marker }, owner)\n this._idBySignal.set(marker, nodeId)\n this._idByProxy.set(proxy, nodeId)\n this._nameProxyRoot(nodeId, name, componentId)\n this._pollAdd(nodeId)\n },\n\n trackDerived(signal, name, componentId, owner) {\n const nodeId = this._nodeIdFor(signal, this._idBySignal, componentId, name)\n this._registerNode(nodeId, componentId, 'derived', name, new WeakRef(signal), owner)\n this._idBySignal.set(signal, nodeId)\n },\n\n // A user effect object (already created). Kept for callers that have one.\n trackEffect(effect, name, componentId) {\n const nodeId = componentId + ':' + name\n const target = effect ?? { v: undefined, _isEffect: true }\n this._registerNode(\n nodeId,\n componentId,\n 'effect',\n name,\n effect ? new WeakRef(target) : { deref: () => target },\n )\n this._idBySignal.set(target, nodeId)\n return effect\n },\n\n // $effect / $effect.pre being created now (the wrapper). The effect object\n // may not exist yet (deferred to mount): the node holds a placeholder\n // until bindEffect() is called on the effect's first run. kind: 'effect'\n // | 'effect_pre'; names are effect_1, effect_pre_1, ... per component.\n trackUserEffect(kind, componentId, owner) {\n let seq = this._effectSeq.get(componentId)\n if (!seq) {\n seq = { effect: 0, effect_pre: 0 }\n this._effectSeq.set(componentId, seq)\n }\n const name = kind + '_' + ++seq[kind]\n const nodeId = componentId + ':' + name\n const placeholder = { v: undefined, _isEffect: true }\n this._registerNode(nodeId, componentId, 'effect', name, { deref: () => placeholder }, owner)\n return nodeId\n },\n\n bindEffect(nodeId, effect) {\n const entry = nodeId !== null && this._reactiveNodes.get(nodeId)\n if (!entry || !effect || typeof effect !== 'object') return\n entry.signal = new WeakRef(effect)\n entry.effect = true\n this._idBySignal.set(effect, nodeId)\n },\n\n // The component's mount hook effect (the wrapper's, created at pop): its\n // parent is the effect the component's markup effects were created in.\n setComponentEffect(id, effect) {\n const instance = this._instances.get(id)\n if (instance) instance.hook = new WeakRef(effect)\n },\n\n // Markup effects of a component: the effects of its template\n // (template_effect, {#if}/{#each}/... blocks, <svelte:head>), i.e. the\n // effects under the hook's parent created in the component's context\n // (effect.ctx), without descending into other components or the\n // tracked $effect nodes. Svelte links child effects via first/next.\n _markupEffects(id) {\n const instance = this._instances.get(id)\n const hook = instance?.hook?.deref()\n if (!hook?.ctx || !hook.parent) return []\n const ctx = hook.ctx\n const out = []\n const stack = []\n for (let e = hook.parent.first; e; e = e.next) stack.push(e)\n while (stack.length > 0) {\n const e = stack.pop()\n if (e === hook || e.ctx !== ctx) continue\n if (e.deps && !this._idBySignal.has(e)) out.push(e)\n for (let c = e.first; c; c = c.next) stack.push(c)\n }\n return out\n },\n\n // The tracked node a proxy's property source belongs to. Svelte (dev)\n // labels a proxy's sources after the proxy's path ('todos[0].text',\n // 'cart.items.length', 'todos version'); the root name is a $state node\n // name. Many live nodes may share the name (one per instance), so the\n // candidates are looked up per scope: the reader's component, then its\n // ancestors (props flow down), then module scopes, then all — each\n // narrowed by the source's value at that path (identity for objects).\n // Cost O(label + ancestry depth) for the common cases. null when\n // unresolved.\n _sourceOwner(source, readerCid, liveSignal) {\n const label = source.label\n if (typeof label !== 'string' || this._proxyNames.size === 0) return null\n let named = null\n let rest = ''\n for (let i = label.length; i > 0; i--) {\n const ch = i === label.length ? '' : label[i]\n if (ch !== '' && ch !== '.' && ch !== '[' && ch !== ' ') continue\n named = this._proxyNames.get(label.slice(0, i))\n if (named) {\n rest = label.slice(i)\n break\n }\n }\n if (!named) return null\n let path\n // live candidates whose value at the source's path is the source's\n // value; without a property path (' version'), all live candidates\n const fits = ids => {\n const live = [...ids].filter(id => liveSignal(id))\n if (live.length === 0) return live\n if (path === undefined) path = this._labelPath(rest)\n if (!path) return live\n const v = source.v\n return live.filter(id => {\n const at = this._valueAt(this._rootValue(id), path)\n return (\n at !== __NO_VALUE && (Object.is(at, v) || (typeof v === 'symbol' && at === undefined))\n )\n })\n }\n const scopes = []\n for (let cid = readerCid; cid !== null && cid !== undefined;) {\n scopes.push(cid)\n const instance = this._instances.get(cid)\n cid = instance ? instance.parentId : null\n }\n // Module scopes holding the name. Iterated from the module list, not\n // from `named`: a name shared by every row of a list (one scope per\n // instance) made this O(instances) per source, O(n²) per graph build.\n for (const cid of this._modules.keys()) if (named.has(cid)) scopes.push(cid)\n for (const cid of scopes) {\n const ids = named.get(cid)\n const found = ids ? fits(ids) : []\n if (found.length > 0) return found[0]\n }\n // not in the reader's ancestry or a module: any matching node, else the\n // nearest live one by name\n const all = []\n for (const ids of named.values()) for (const id of ids) all.push(id)\n const found = fits(all)\n if (found.length > 0) return found[0]\n for (const cid of scopes) for (const id of named.get(cid) ?? []) if (liveSignal(id)) return id\n return all.find(id => liveSignal(id)) ?? null\n },\n\n // '.items[0].text' -> ['items', '0', 'text']; null for ' version' and\n // anything else that is not a property path.\n _labelPath(rest) {\n const path = []\n const re = /\\.([A-Za-z_$][\\w$]*)|\\[(\\d+)\\]|\\['((?:[^'\\\\]|\\\\.)*)'\\]/y\n let m\n while (re.lastIndex < rest.length && (m = re.exec(rest))) path.push(m[1] ?? m[2] ?? m[3])\n return re.lastIndex === rest.length || rest.length === 0 ? path : null\n },\n\n // Own-property walk (getOwnPropertyDescriptor does not create sources on\n // a Svelte proxy, unlike reading a missing property).\n _valueAt(obj, path) {\n let v = obj\n for (const key of path) {\n if (v === null || (typeof v !== 'object' && typeof v !== 'function')) return __NO_VALUE\n let d\n try {\n d = Object.getOwnPropertyDescriptor(v, key)\n } catch {\n return __NO_VALUE\n }\n if (!d) return __NO_VALUE\n v = 'value' in d ? d.value : __NO_VALUE\n if (v === __NO_VALUE) return v\n }\n return v\n },\n\n _nameProxyRoot(nodeId, name, componentId) {\n let named = this._proxyNames.get(name)\n if (!named) {\n named = new Map()\n this._proxyNames.set(name, named)\n }\n let inScope = named.get(componentId)\n if (!inScope) {\n inScope = new Set()\n named.set(componentId, inScope)\n }\n inScope.add(nodeId)\n },\n\n // The proxy a $state node holds now (tag_proxy: the proxy itself; a\n // state signal: its current value), or undefined.\n _rootValue(nodeId) {\n const proxy = this._reactiveProxies.get(nodeId)\n if (proxy) return proxy.deref()\n const signal = this._reactiveNodes.get(nodeId)?.signal.deref()\n return signal ? signal.v : undefined\n },\n\n // A component whose init threw (the wrapper unwinds its stack): it never\n // mounted and never will, so it leaves the tree and the init stack.\n abortInit(id) {\n const idx = this._stack.indexOf(id)\n if (idx !== -1) this._stack.splice(idx, 1)\n const instance = this._instances.get(id)\n if (!instance) return\n if (instance.parentId !== null) {\n const parent = this._instances.get(instance.parentId)\n if (parent) parent.children.delete(id)\n }\n this._removeChildren(id)\n this._cleanupComponent(id)\n this._scheduleUpdate()\n },\n\n _graphNode(nodeId, entry, signal) {\n const node = { ...entry.meta }\n if (signal._isProxy) {\n const proxy = this._reactiveProxies.get(nodeId)?.deref()\n if (proxy) {\n try {\n node.value = Array.isArray(proxy)\n ? '[' + proxy.length + ']'\n : '{' + Object.keys(proxy).length + '}'\n } catch {\n node.value = '(proxy)'\n }\n }\n } else if (node.type === 'derived' && typeof signal.v === 'symbol') {\n // Svelte computes a derived lazily: until something reads it, v is\n // the UNINITIALIZED marker (a symbol)\n node.unevaluated = true\n } else if (node.type !== 'effect' && signal.v !== undefined && typeof signal.v !== 'symbol') {\n try {\n const v = signal.v\n if (\n typeof v === 'number' ||\n typeof v === 'string' ||\n typeof v === 'boolean' ||\n v === null\n ) {\n node.value = v\n } else {\n node.value = '(object)'\n }\n } catch {\n /* ignore */\n }\n }\n return node\n },\n\n // Reactive graph (§6.7 A/D). Caps are applied while building: past\n // maxNodes no node objects are created and only edges whose endpoints\n // are both included are emitted (the rest are counted in edgesOmitted).\n // - scoped (componentId): that component's nodes plus the tracked nodes\n // directly connected to them (via deps / reactions through untracked\n // intermediates). Dependencies are resolved through _idBySignal, so\n // the cost is O(scope + neighbours), not O(all nodes). The walk is\n // completed even past the cap, so total.nodes/edges are exact.\n // - global: nodes in registration order up to maxNodes ('global-head'),\n // with their incoming dependency edges. Nodes past the cap are not\n // dereferenced; total.nodes is the registered count (may include\n // nodes whose signal was already collected, nodesKind 'registered')\n // and total.edges is null when the walk stopped early (not guessed).\n // Template consumers: the compiler turns every dynamic part of the markup\n // ({expr}, attributes, {#if}/{#each}/{#key}/{#await} conditions,\n // <svelte:head>) into effects that are not tracked one by one. A read\n // from such an effect is reported as an edge to the synthetic node\n // '<componentId>:(template)' (type 'template') of the component the\n // effect belongs to (effect.ctx), so a value only the markup reads is\n // not shown without readers.\n getReactiveGraph(componentId, caps) {\n const scoped = componentId !== undefined && componentId !== null\n const maxNodes = this._graphCap(caps?.maxNodes, 5000)\n const maxEdges = this._graphCap(caps?.maxEdges, 20000)\n const live = new Map()\n // Live signal of a tracked node, or null (forgets dead / unmounted nodes).\n const liveSignal = nodeId => {\n if (live.has(nodeId)) return live.get(nodeId)\n const signal = this._liveSignal(nodeId)\n live.set(nodeId, signal)\n return signal\n }\n const idOf = dep => {\n const id = this._idBySignal.get(dep)\n return id !== undefined && liveSignal(id) === dep ? id : null\n }\n // Synthetic template nodes reached in this build: id -> componentId.\n const templates = new Map()\n // Template node of an untracked effect (no reactions: not a derived),\n // or null when its component is unknown / unmounted.\n const templateOf = effect => {\n if (!effect || 'reactions' in effect || !effect.ctx) return null\n const cid = this._idByContext.get(effect.ctx)\n if (cid === undefined || !this._instances.has(cid)) return null\n const id = cid + ':(template)'\n templates.set(id, cid)\n return id\n }\n\n const included = new Set()\n const seen = new Set()\n const edges = []\n const edgeSet = new Set()\n let edgesOmitted = 0\n let truncated = false\n const see = id => {\n seen.add(id)\n if (!included.has(id) && included.size < maxNodes) included.add(id)\n return included.has(id)\n }\n const addEdge = (from, to) => {\n const key = from + '>' + to\n if (from === to || edgeSet.has(key)) return\n edgeSet.add(key)\n const inFrom = see(from)\n const inTo = see(to)\n if (inFrom && inTo && edges.length < maxEdges) edges.push({ from, to })\n else edgesOmitted++\n }\n // Walk a signal's neighbours (deps or reactions) through untracked\n // intermediates until tracked nodes are reached. onTemplate (reactions\n // only) receives the template node of each untracked effect reached.\n // On the deps side an untracked leaf source (a proxy's property) is\n // resolved to its proxy's node (readerCid: the reading component).\n // nodeId -> dependencies on signals devtools does not track (created\n // in node_modules, e.g. SvelteKit's page state, or outside any\n // component/module body): reported so such a node is not \"isolated\"\n const untracked = new Map()\n const walk = (start, field, onHit, onTemplate, readerCid, onUntracked) => {\n if (!start) return\n const visited = new Set()\n const queue = [...start]\n // Index cursor instead of queue.shift(): shift is O(queue) per pop.\n for (let qi = 0; qi < queue.length; qi++) {\n const dep = queue[qi]\n if (!dep || visited.has(dep)) continue\n visited.add(dep)\n const id = idOf(dep)\n if (id) {\n if (onHit) onHit(id)\n } else if (dep[field]) {\n for (const d of dep[field]) queue.push(d)\n } else if (onTemplate) {\n const t = templateOf(dep)\n if (t) onTemplate(t)\n } else if (field === 'deps' && onHit && !('fn' in dep)) {\n const owner = this._sourceOwner(dep, readerCid, liveSignal)\n if (owner) onHit(owner)\n else if (onUntracked) onUntracked()\n }\n }\n }\n const nodeCid = nodeId => this._reactiveNodes.get(nodeId).meta.componentId\n const countUntracked = nodeId => () => untracked.set(nodeId, (untracked.get(nodeId) ?? 0) + 1)\n // Reads of a component's markup effects: edges into its template node\n // (filter: keep only edges from these node ids, for neighbours).\n const markupEdges = (cid, filter) => {\n const effects = this._markupEffects(cid)\n if (effects.length === 0) return\n const t = cid + ':(template)'\n for (const effect of effects) {\n walk(\n effect.deps,\n 'deps',\n id => {\n if (filter && !filter(id)) return\n templates.set(t, cid)\n addEdge(id, t)\n },\n null,\n cid,\n )\n }\n }\n\n let totalNodes = 0\n let totalEdges = null\n if (scoped) {\n // a copy: liveSignal() forgets dead nodes during the loop\n for (const nodeId of Array.from(this._nodesByComponent.get(componentId) ?? [])) {\n const signal = liveSignal(nodeId)\n if (!signal) continue\n see(nodeId)\n walk(\n signal.deps,\n 'deps',\n depId => addEdge(depId, nodeId),\n null,\n componentId,\n countUntracked(nodeId),\n )\n const toReader = rId => addEdge(nodeId, rId)\n walk(signal.reactions, 'reactions', toReader, toReader)\n }\n if (this._instances.has(componentId)) markupEdges(componentId, null)\n // Readers in direct children (props): a proxy has no reactions list\n // of its own, so its readers are found from their dependencies.\n const own = id => nodeCid(id) === componentId\n const instance = this._instances.get(componentId)\n for (const child of instance ? instance.children : []) {\n // a copy: liveSignal() forgets dead nodes during the loop\n for (const nodeId of Array.from(this._nodesByComponent.get(child) ?? [])) {\n const signal = liveSignal(nodeId)\n if (signal)\n walk(signal.deps, 'deps', depId => own(depId) && addEdge(depId, nodeId), null, child)\n }\n markupEdges(child, own)\n }\n totalNodes = seen.size\n totalEdges = edgeSet.size\n truncated = included.size < seen.size || edgesOmitted > 0\n } else {\n let complete = true\n // a copy: liveSignal() forgets dead nodes during the loop\n for (const nodeId of Array.from(this._reactiveNodes.keys())) {\n if (included.size >= maxNodes && !included.has(nodeId)) {\n complete = false\n break\n }\n const signal = liveSignal(nodeId)\n if (!signal) continue\n see(nodeId)\n walk(\n signal.deps,\n 'deps',\n depId => addEdge(depId, nodeId),\n null,\n nodeCid(nodeId),\n countUntracked(nodeId),\n )\n // tracked readers are found from their own deps; only template\n // readers need the reactions side\n walk(signal.reactions, 'reactions', null, t => addEdge(nodeId, t))\n }\n if (complete) for (const cid of this._instances.keys()) markupEdges(cid, null)\n // registered count (+ template nodes reached), minus nodes found\n // dead while building\n totalNodes = this._reactiveNodes.size + templates.size\n totalEdges = complete ? edgeSet.size : null\n truncated = !complete || edgesOmitted > 0\n }\n\n const nodes = []\n for (const nodeId of included) {\n const cid = templates.get(nodeId)\n if (cid !== undefined) {\n const instance = this._instances.get(cid)\n nodes.push({\n id: nodeId,\n type: 'template',\n name: '(template)',\n componentId: cid,\n componentFile: instance ? instance.file : '',\n })\n continue\n }\n const signal = liveSignal(nodeId)\n if (!signal) continue\n const node = this._graphNode(nodeId, this._reactiveNodes.get(nodeId), signal)\n if (untracked.has(nodeId)) node.untrackedDeps = untracked.get(nodeId)\n nodes.push(node)\n }\n return {\n scope: scoped ? componentId : null,\n nodes,\n edges,\n total: { nodes: totalNodes, nodesKind: 'registered', edges: totalEdges },\n truncated,\n edgesOmitted,\n policy: scoped ? 'scoped' : 'global-head',\n }\n },\n\n _graphCap(value, max) {\n const n = Math.floor(Number(value))\n return Number.isFinite(n) && n >= 1 ? Math.min(n, max) : max\n },\n\n // Request (§6.7 A + review M1): { requestId?, epoch?, componentId?,\n // maxNodes?, maxEdges? }. A request for another page load (epoch) is not\n // answered by this one; the reply echoes requestId and epoch. An old\n // server sends {} → global graph with the default caps (= its LIMITS).\n sendReactiveGraph(request) {\n const req = request ?? {}\n if (req.epoch !== undefined && req.epoch !== null && req.epoch !== this._epoch) return\n const cid = typeof req.componentId === 'number' ? req.componentId : null\n const graph = this.getReactiveGraph(cid, { maxNodes: req.maxNodes, maxEdges: req.maxEdges })\n graph.epoch = this._epoch\n // freshness (review): when this answer was built; caches keep it\n graph.computedAt = Date.now()\n if (req.requestId !== undefined) graph.requestId = req.requestId\n if (import.meta.hot) {\n import.meta.hot.send('svelte-devtools:reactive-graph', graph)\n }\n },\n\n // Overview aggregate (§6.7 I/J). Built from the activity buckets of the\n // window (whole seconds: the current partial second plus the previous\n // ones, ceil(windowMs / 1000) buckets) — cost O(components active in the\n // window), no graph, no node scan. Rows rank by sampled changes, then\n // renders, then render ms; rows + other = total (components and nodes).\n getReactiveSummary(opts) {\n const o = opts ?? {}\n const topK = __clampInt(o.topK, 1, 200, 50)\n const windowMs = __clampInt(o.windowMs, 1000, 60000, 10000)\n const nowSec = Math.floor(performance.now() / 1000)\n const agg = new Map()\n let sampledMs = 0\n for (let k = 0; k < Math.ceil(windowMs / 1000); k++) {\n const sec = nowSec - k\n const b = this._activity[((sec % 60) + 60) % 60]\n if (!b || b.second !== sec) continue\n sampledMs += b.sampledMs\n for (const [cid, r] of b.rows) {\n if (!this._scopeLive(cid)) continue\n let a = agg.get(cid)\n if (!a) {\n a = { changes: 0, renders: 0, renderMs: 0 }\n agg.set(cid, a)\n }\n a.changes += r.changes\n a.renders += r.renders\n a.renderMs += r.renderMs\n }\n }\n const ranked = [...agg].toSorted(\n (x, y) =>\n y[1].changes - x[1].changes ||\n y[1].renders - x[1].renders ||\n y[1].renderMs - x[1].renderMs ||\n x[0] - y[0],\n )\n let rowNodes = 0\n const rows = ranked.slice(0, topK).map(([cid, a]) => {\n const n = this._nodeCounts.get(cid) ?? { state: 0, derived: 0, effect: 0 }\n rowNodes += n.state + n.derived + n.effect\n const row = {\n componentId: cid,\n file: this._scopeFile(cid),\n nodes: { state: n.state, derived: n.derived, effect: n.effect },\n changes: a.changes,\n renders: a.renders,\n renderMs: Math.round(a.renderMs * 1000) / 1000,\n }\n // module scope (shared .svelte.js state), not a component instance\n if (this._modules.has(cid)) row.kind = 'module'\n return row\n })\n const t = this._nodeTotals\n // module scopes count as rows like components (rows + other = total)\n const scopes = this._instances.size + this._modules.size\n const until = Date.now()\n return {\n epoch: this._epoch,\n window: {\n ms: windowMs,\n since: until - windowMs,\n until,\n sampledActiveMs: Math.min(windowMs, sampledMs),\n },\n policy: 'sampled-200ms',\n coverage: 'component-init',\n components: { total: scopes, withActivity: agg.size },\n rows,\n other: {\n components: scopes - rows.length,\n nodes: t.state + t.derived + t.effect - rowNodes,\n },\n truncated: agg.size > topK,\n capabilities: { valueInspection: false, signalHistory: false, writeCause: false },\n // Seed coverage: changes of pending nodes are not observable yet.\n baseline: this._baselineInfo(),\n }\n },\n\n // Request { requestId?, epoch?, topK?, windowMs? } (same correlation as\n // the graph, review M1).\n sendReactiveSummary(request) {\n const req = request ?? {}\n if (req.epoch !== undefined && req.epoch !== null && req.epoch !== this._epoch) return\n const summary = this.getReactiveSummary(req)\n if (req.requestId !== undefined) summary.requestId = req.requestId\n if (import.meta.hot) {\n import.meta.hot.send('svelte-devtools:reactive-summary', summary)\n }\n },\n\n // --- Phase 3: State Timeline ---\n\n // Current value of a $state node: the signal value, or for tag_proxy\n // nodes (non-reassigned object/array state) the live proxy itself.\n // Returns a sentinel when the value can't be read.\n _readStateValue(nodeId, signal) {\n if (signal._isProxy) {\n const proxy = this._reactiveProxies.get(nodeId)?.deref()\n return proxy === undefined ? __NO_VALUE : proxy\n }\n const v = signal.v\n // Svelte's UNINITIALIZED marker is a symbol; functions aren't state data.\n return typeof v === 'symbol' || typeof v === 'function' ? __NO_VALUE : v\n },\n\n // Runs every 200 ms in the user's app, so it must stay cheap regardless of\n // how much state the app holds (§6.7 D).\n // 1. Hint pass, time-sliced: at most HINT_NODES $state nodes per tick,\n // continuing from a cursor carried across ticks, so a full sweep over\n // n nodes takes ceil(n / HINT_NODES) ticks (_pollSweep.lastMs is the\n // measured sweep time = worst-case detection delay). Per node O(1):\n // primitives are compared directly and recorded at once; objects\n // whose reference or Svelte write version (wv) changed are added to\n // _pollDirty, which persists across ticks until serialized.\n // 2. Serialization work is limited to BUDGET_MS per tick (dirty nodes\n // carry over, nothing is dropped).\n // 3. In-place deep mutations (proxies don't bump an outer wv) are found by\n // time-based re-checks: each object is re-verified no earlier than\n // nextCheckAt = now + max(RECHECK_MS, 50 x its serialization cost),\n // paid from a DEEP_MS-per-tick credit, so the amortized cost is bounded\n // however many large states the app holds.\n // 4. Values serializing above MAX_SNAPSHOT_CHARS are kept as length+hash\n // (not the string) and appear in the timeline as a summary.\n // Writes between two visits of a node are observed as one change (the\n // timeline is a sample, policy 'sampled-200ms'; not counted as dropped).\n _pollStateValues(baseline) {\n const HINT_NODES = 4096\n const SWEEP_NODES = 2048\n const BUDGET_MS = 2\n const DEEP_MS = 0.5\n const now = performance.now()\n if (!baseline) {\n // nodes of destroyed {#each} items / effect runs leave the counts\n this._sweepNodes(SWEEP_NODES)\n this._deepCredit = Math.min(10 * DEEP_MS, (this._deepCredit || 0) + DEEP_MS)\n // this tick samples 200 ms of wall time (window.sampledActiveMs)\n const bucket = this._bucket()\n bucket.sampledMs = Math.min(1000, bucket.sampledMs + 200)\n }\n const ids = this._pollIds\n const due = []\n const limit = Math.min(HINT_NODES, ids.length)\n for (let visited = 0; visited < limit && ids.length > 0; visited++) {\n if (this._pollCursor >= ids.length) {\n this._pollCursor = 0\n if (!baseline) {\n if (this._pollSweep.startedAt) this._pollSweep.lastMs = now - this._pollSweep.startedAt\n this._pollSweep.startedAt = now\n this._pollSweep.nodes = ids.length\n }\n }\n if (!baseline && !this._pollSweep.startedAt) this._pollSweep.startedAt = now\n const nodeId = ids[this._pollCursor]\n const entry = this._reactiveNodes.get(nodeId)\n if (entry?.meta.type !== 'state') {\n // re-tracked as another type: drop from the poll list (slot reused)\n this._pollRemove(nodeId)\n continue\n }\n if (!this._scopeLive(entry.meta.componentId)) {\n this._forgetNode(nodeId) // swaps the last id into this slot\n continue\n }\n this._pollCursor++\n const signal = entry.signal.deref()\n const value = signal ? this._readStateValue(nodeId, signal) : __NO_VALUE\n if (value === __NO_VALUE) {\n // nothing readable (collected, uninitialized, function): not pending\n this._unseeded.delete(nodeId)\n continue\n }\n if (value === null || typeof value !== 'object') {\n const had = this._stateSnapshots.has(nodeId)\n const prev = this._stateSnapshots.get(nodeId)\n if (had && Object.is(prev, value)) continue\n // Forget the object key so object -> primitive -> same object again\n // is still recorded as a change.\n if (this._stateSnapshotStrs) this._stateSnapshotStrs.delete(nodeId)\n this._pollDirty.delete(nodeId)\n if (!had) {\n // first observation: seed only\n this._stateSnapshots.set(nodeId, value)\n this._unseeded.delete(nodeId)\n continue\n }\n this._recordChange(\n nodeId,\n entry,\n prev,\n value,\n typeof value === 'string' ? 2 * value.length : 64,\n )\n continue\n }\n let meta = this._pollMeta.get(nodeId)\n if (!meta) {\n meta = { ref: undefined, wv: undefined, nextCheckAt: 0 }\n this._pollMeta.set(nodeId, meta)\n }\n if (meta.ref !== value || meta.wv !== signal.wv) this._pollDirty.add(nodeId)\n else if (now >= meta.nextCheckAt) due.push(nodeId)\n }\n const start = performance.now()\n for (const nodeId of this._pollDirty) {\n if (performance.now() - start > BUDGET_MS) return\n this._pollDirty.delete(nodeId)\n this._serializeNode(nodeId, false)\n }\n for (const nodeId of due) {\n if (this._deepCredit <= 0 || performance.now() - start > BUDGET_MS) return\n this._serializeNode(nodeId, true)\n }\n },\n\n // Activation (late consumer): nodes registered while inactive have no\n // seed yet, so a write before their first visit could not be seen as a\n // change. The first slice is seeded at activation instead of 200 ms later:\n // ONE normal pass (<= HINT_NODES nodes, the per-tick serialization budget;\n // the rest follow on the usual ticks). Seeds record nothing; readiness is\n // disclosed in the summary as baseline { complete, pendingNodes }.\n _pollBaseline() {\n this._baselining = true\n try {\n this._pollStateValues(true)\n } catch {\n /* never break activation */\n } finally {\n this._baselining = false\n }\n },\n\n _serializeNode(nodeId, deep) {\n const MAX_SNAPSHOT_CHARS = 32768\n const RECHECK_MS = 1000\n const entry = this._reactiveNodes.get(nodeId)\n const meta = this._pollMeta.get(nodeId)\n if (!entry || !meta) return\n const signal = entry.signal.deref()\n if (!signal) return\n const value = this._readStateValue(nodeId, signal)\n if (value === __NO_VALUE || value === null || typeof value !== 'object') return\n try {\n const t0 = performance.now()\n const str = JSON.stringify(value)\n const cost = performance.now() - t0\n if (deep) this._deepCredit -= cost\n meta.ref = value\n meta.wv = signal.wv\n meta.nextCheckAt = t0 + Math.max(RECHECK_MS, 50 * cost)\n // Small values keep the string (exact compare); large ones only a\n // length+hash so a 5 MB array doesn't pin 5 MB per node.\n const key =\n str === undefined\n ? ''\n : str.length > MAX_SNAPSHOT_CHARS\n ? str.length + ':' + __hash(str)\n : str\n this._stateSnapshotStrs ??= new Map()\n const hadKey = this._stateSnapshotStrs.has(nodeId)\n if (hadKey && this._stateSnapshotStrs.get(nodeId) === key) return\n this._stateSnapshotStrs.set(nodeId, key)\n const tooLarge = str !== undefined && str.length > MAX_SNAPSHOT_CHARS\n const snapshot =\n str === undefined\n ? null\n : tooLarge\n ? '(object: ' + str.length + ' chars, too large to snapshot)'\n : JSON.parse(str)\n const size = typeof snapshot === 'string' ? 64 : str.length\n if (!this._stateSnapshots.has(nodeId)) {\n // first observation: seed only (no entry, no counters)\n this._stateSnapshots.set(nodeId, snapshot)\n this._lastSnapshotSize.set(nodeId, size)\n this._unseeded.delete(nodeId)\n return\n }\n if (tooLarge) this._valueTooLarge++\n const prev = this._stateSnapshots.get(nodeId)\n this._recordChange(\n nodeId,\n entry,\n prev,\n snapshot,\n size + (this._lastSnapshotSize.get(nodeId) ?? 0),\n )\n this._lastSnapshotSize.set(nodeId, size)\n } catch {\n // non-serializable (cycles, BigInt): never observable, so not pending\n this._unseeded.delete(nodeId)\n }\n },\n\n _recordChange(nodeId, entry, oldValue, newValue, approxBytes) {\n const change = {\n id: nodeId,\n name: entry.meta.name,\n componentFile: entry.meta.componentFile,\n oldValue,\n newValue,\n timestamp: Date.now(),\n }\n // 0 (an empty string) or no estimate: assume a small value.\n const bytes = (approxBytes > 0 ? approxBytes : 64) + 192\n // seq stays runtime-internal: the server assigns its own seq (§6.4).\n this._entryInfo.set(change, { seq: ++this._timelineSeq, bytes })\n this._stateTimeline.push(change)\n this._timelineBytes += bytes\n // sampled change count (§6.7 J IA2): independent of the ring\n this._activityRow(entry.meta.componentId).changes++\n this._stateSnapshots.set(nodeId, newValue)\n // The ring (_trimTimeline) is applied in bulk, not with one splice per\n // change (a poll tick can record thousands): before every read (each\n // push — with a hot channel and an active consumer, at least every 200\n // changes — the full snapshot, getStateTimeline() and clear), and here\n // once more than __TIMELINE_TRIM_AT entries are held or the byte budget\n // is exceeded, which bounds memory when nothing reads or pushes.\n if (\n this._stateTimeline.length > __TIMELINE_TRIM_AT ||\n this._timelineBytes > __TIMELINE_BYTES\n ) {\n this._trimTimeline()\n }\n this._scheduleTimelineUpdate()\n },\n\n // Ring: at most 500 entries and __TIMELINE_BYTES (the newest entry is\n // always kept). Removed entries that were never pushed are disclosed\n // as dropped (§6.7 C); pushed ones are already on the server.\n _trimTimeline() {\n const all = this._stateTimeline\n let cut = 0\n let bytesLeft = this._timelineBytes\n while (cut < all.length - 1 && (all.length - cut > 500 || bytesLeft > __TIMELINE_BYTES)) {\n const info = this._entryInfo.get(all[cut])\n const reason = all.length - cut > 500 ? 'runtime-count' : 'runtime-bytes'\n if (info.seq > this._pushedSeq) this._timelineDropped[reason]++\n bytesLeft -= info.bytes\n cut++\n }\n if (cut > 0) {\n all.splice(0, cut)\n this._timelineBytes = bytesLeft\n }\n },\n\n // Push (§6.7 C): a throttle, not a debounce — a pending timer is never\n // re-armed, so unsent entries leave within 300 ms of the first one even\n // under continuous change — plus an immediate flush once 200 entries\n // (one message) are unsent, since one poll tick can record thousands.\n _scheduleTimelineUpdate() {\n if (!this._active || this._baselining) return\n if (this._timelineSeq - this._pushedSeq >= 200) {\n this._flushTimeline()\n return\n }\n if (this._timelineDebounceTimer) return\n this._timelineDebounceTimer = setTimeout(() => {\n this._timelineDebounceTimer = null\n this._flushTimeline()\n }, 300)\n },\n\n _flushTimeline() {\n if (this._timelineDebounceTimer) {\n clearTimeout(this._timelineDebounceTimer)\n this._timelineDebounceTimer = null\n }\n if (!import.meta.hot) return\n for (const msg of this._timelineDeltas()) {\n import.meta.hot.send('svelte-devtools:state-timeline', msg)\n }\n },\n\n // Disclosure counters since the previous push, attached to (and reset\n // by) the next message: dropped [{ reason, count }] and valueTooLarge.\n _takeTimelineDisclosure(msg) {\n msg.baseline = this._baselineInfo()\n this._sentPendingNodes = msg.baseline.pendingNodes\n const dropped = []\n for (const reason of ['runtime-count', 'runtime-bytes']) {\n const count = this._timelineDropped[reason]\n if (count > 0) dropped.push({ reason, count })\n this._timelineDropped[reason] = 0\n }\n if (dropped.length > 0) msg.dropped = dropped\n if (this._valueTooLarge > 0) msg.valueTooLarge = this._valueTooLarge\n this._valueTooLarge = 0\n return msg\n },\n\n // Seed coverage (review B-2), on every state-timeline message and the\n // summary: pendingNodes = polled $state nodes without a first sample;\n // their writes are not observable yet.\n _baselineInfo() {\n return { complete: this._unseeded.size === 0, pendingNodes: this._unseeded.size }\n },\n\n _hasTimelineDisclosure() {\n return (\n this._sentPendingNodes !== this._unseeded.size ||\n this._timelineDropped['runtime-count'] > 0 ||\n this._timelineDropped['runtime-bytes'] > 0 ||\n this._valueTooLarge > 0\n )\n },\n\n // Delta push (docs/devframe-migration.md §6.4): only entries recorded\n // since the previous push, ≤ 200 per message, tagged with a per-page-load\n // epoch; reset after clearStateTimeline(). A message with no changes is\n // still sent when there is something to disclose (all unsent dropped).\n _timelineDeltas() {\n this._trimTimeline()\n const all = this._stateTimeline\n let start = all.length\n while (start > 0 && this._entryInfo.get(all[start - 1]).seq > this._pushedSeq) start--\n const pending = all.slice(start)\n const msgs = []\n const first = this._timelineReset || this._hasTimelineDisclosure()\n // A first message goes out even with no pending changes (disclosure only).\n const end = first ? Math.max(pending.length, 1) : pending.length\n for (let i = 0; i < end; i += 200) {\n const msg = { epoch: this._epoch, changes: pending.slice(i, i + 200) }\n if (i === 0 && this._timelineReset) msg.reset = true\n if (i === 0) this._takeTimelineDisclosure(msg)\n else msg.baseline = this._baselineInfo()\n msgs.push(msg)\n }\n this._timelineReset = false\n this._pushedSeq = this._timelineSeq\n return msgs\n },\n\n // Full buffer (reset: true) — reply to request-state-timeline and the\n // activation snapshot. Newest entries first up to __TIMELINE_BYTES, the\n // server's own buffer budget, so nothing is sent that it would drop.\n _timelineFull() {\n this._trimTimeline()\n const all = this._stateTimeline\n let start = all.length\n let bytes = 0\n while (start > 0) {\n const size = this._entryInfo.get(all[start - 1]).bytes\n if (bytes + size > __TIMELINE_BYTES && start < all.length) break\n bytes += size\n start--\n }\n this._timelineReset = false\n this._pushedSeq = this._timelineSeq\n return this._takeTimelineDisclosure({\n epoch: this._epoch,\n changes: all.slice(start),\n reset: true,\n })\n },\n\n getStateTimeline() {\n this._trimTimeline()\n return this._stateTimeline\n },\n\n clearStateTimeline() {\n // Ring first: unsent entries past the cap are disclosed as dropped,\n // exactly as if they had been removed when recorded.\n this._trimTimeline()\n this._stateTimeline = []\n this._timelineBytes = 0\n this._pushedSeq = this._timelineSeq\n this._timelineReset = true\n this._scheduleTimelineUpdate()\n },\n\n // --- FPS Monitoring ---\n // Uses requestAnimationFrame to measure frame rate with minimal overhead.\n // Only stores timestamps - no allocations per frame beyond a single array push.\n _fpsFrameTimes: [],\n\n _fpsLoop(gen) {\n if (gen !== this._fpsGen) return\n this._fpsFrameTimes.push(performance.now())\n requestAnimationFrame(() => this._fpsLoop(gen))\n },\n\n _isVisible() {\n if (typeof document === 'undefined') return true\n return document.visibilityState !== 'hidden'\n },\n\n _setSubscription(data) {\n const wasActive = this._active\n this._active = !!data?.active\n this._componentDeltas = !!data?.componentDeltas\n if (!this._active) {\n this._pendingAdded.clear()\n this._pendingRemoved.clear()\n }\n // resync: the server holds no full base for this epoch (§6.5, review\n // C1), or we reconnected to a possibly restarted server.\n const resync = this._resync || !!data?.resync\n this._resync = false\n if (this._active && !wasActive) this._pollBaseline()\n if (this._active && (!wasActive || resync)) {\n // Collector state may be stale or empty (first activation, resync, or a\n // reconnect to a possibly restarted server): resend everything once, in\n // the order components -> profiles -> timeline(reset: true); the\n // reset push marks the activation snapshot complete (D4).\n this._sendUpdate(true)\n this._sendProfileUpdate()\n if (import.meta.hot) {\n import.meta.hot.send('svelte-devtools:state-timeline', this._timelineFull())\n }\n }\n this._syncSampling()\n },\n\n // Sampling (state poll + FPS) runs only while active AND the app tab is\n // visible.\n _syncSampling() {\n const want = this._active && this._isVisible()\n if (want && !this._sampling) {\n this._fpsFrameTimes = []\n const gen = ++this._fpsGen\n this._sampling = {\n poll: setInterval(() => {\n this._pollStateValues()\n // seed progress changed: push it even without changes (B-2)\n if (this._sentPendingNodes !== this._unseeded.size) this._scheduleTimelineUpdate()\n }, 200),\n fps: setInterval(() => {\n this._sampleFps()\n }, 500),\n }\n requestAnimationFrame(() => this._fpsLoop(gen))\n } else if (!want && this._sampling) {\n clearInterval(this._sampling.poll)\n clearInterval(this._sampling.fps)\n this._sampling = null\n this._fpsGen++\n this._fpsFrameTimes = []\n }\n },\n\n _sampleFps() {\n const now = performance.now()\n // Remove frame timestamps older than 1 second\n const cutoff = now - 1000\n let i = 0\n while (i < this._fpsFrameTimes.length && this._fpsFrameTimes[i] < cutoff) i++\n if (i > 0) this._fpsFrameTimes.splice(0, i)\n const fps = this._fpsFrameTimes.length\n if (import.meta.hot) {\n import.meta.hot.send('svelte-devtools:fps', { timestamp: Date.now(), fps })\n }\n },\n }\n\n window.__SVELTE_DEVTOOLS__ = __SVELTE_DT\n\n // State polling and FPS sampling start on the server's subscription\n // message (§6.3) and pause while the app tab is hidden.\n if (typeof document !== 'undefined' && typeof document.addEventListener === 'function') {\n document.addEventListener('visibilitychange', () => __SVELTE_DT._syncSampling())\n }\n\n // Phase 3: Capture runtime errors\n window.addEventListener('error', event => {\n if (import.meta.hot) {\n import.meta.hot.send('svelte-devtools:runtime-error', {\n message: event.message,\n file: event.filename,\n line: event.lineno,\n column: event.colno,\n stack: event.error?.stack ?? '',\n timestamp: Date.now(),\n })\n }\n })\n window.addEventListener('unhandledrejection', event => {\n if (import.meta.hot) {\n const reason = event.reason\n const message = reason?.message\n import.meta.hot.send('svelte-devtools:runtime-error', {\n // No (or an empty / non-string) message: the stringified reason.\n message: typeof message === 'string' && message !== '' ? message : String(reason),\n stack: reason?.stack ?? '',\n timestamp: Date.now(),\n })\n }\n })\n\n // Listen for reactive graph requests from server\n // Pull requests are answered regardless of the subscription state.\n if (import.meta.hot) {\n import.meta.hot.on('svelte-devtools:subscription', data => {\n __SVELTE_DT._setSubscription(data)\n })\n import.meta.hot.on('vite:ws:connect', () => {\n // The server may have restarted with an empty collector: the reply to\n // runtime-ready is treated as a fresh activation (full snapshot).\n __SVELTE_DT._resync = true\n import.meta.hot.send('svelte-devtools:runtime-ready', {})\n })\n import.meta.hot.send('svelte-devtools:runtime-ready', {})\n import.meta.hot.on('svelte-devtools:request-reactive-graph', data => {\n __SVELTE_DT.sendReactiveGraph(data)\n })\n import.meta.hot.on('svelte-devtools:request-reactive-summary', data => {\n __SVELTE_DT.sendReactiveSummary(data)\n })\n import.meta.hot.on('svelte-devtools:request-state-timeline', () => {\n import.meta.hot.send('svelte-devtools:state-timeline', __SVELTE_DT._timelineFull())\n })\n import.meta.hot.on('svelte-devtools:clear-state-timeline', () => {\n __SVELTE_DT.clearStateTimeline()\n })\n }\n}\n", Ve = "import * as __svelte_original from 'svelte/internal/client'\nexport * from 'svelte/internal/client'\n\nfunction __dt() {\n return typeof window === 'undefined' ? null : window.__SVELTE_DEVTOOLS__\n}\n\n// Component ID stack (parallel to runtime._stack but local to wrapper)\nconst __idStack = []\nfunction __currentId() {\n return __idStack.length > 0 ? __idStack.at(-1) : null\n}\n\n// Tracks the most recently created signal for type determination in tag()\nconst __pendingSignal = { ref: null, type: null }\n\n// Component context -> devtools id (review B1b). A component created later\n// by a block update ({#each}/{#if} after mount) runs outside its parent's\n// push..pop, where the runtime's init stack is empty (the wrapper stack\n// below covers the wrapped block helpers; this covers the rest). Svelte (5.56.8,\n// internal/client) exports active_effect; every effect records the\n// component context it was created in (effect.ctx) and runs with that\n// context restored. The block effect that creates the child belongs to the\n// parent's template, so active_effect.ctx at push time IS the parent's\n// context. Each context is mapped to its id once the component mounted\n// (inside its own deferred effect, where active_effect.ctx is the\n// component's own context). The map lives on the runtime (dt._idByContext)\n// so the reactive graph can name the component of any effect it reaches.\n// Weak map: no leak, no Svelte state touched.\nfunction __parentFromContext() {\n const dt = __dt()\n const effect = __svelte_original.active_effect\n const ctx = effect ? effect.ctx : null\n return dt && ctx && dt._idByContext ? dt._idByContext.get(ctx) : undefined\n}\n\n// Owner of a signal/effect created right now: the component whose init (or\n// wrapped block callback) is running, else — outside init, e.g. an effect\n// created while another effect runs — the component of the active effect.\nfunction __owner() {\n const cid = __currentId()\n if (cid !== null) return cid\n const fromContext = __parentFromContext()\n return fromContext === undefined ? null : fromContext\n}\n\n// --- Component Lifecycle ---\n//\n// Observer must not break the observed: every devtools side-effect below is\n// wrapped in try/catch so a bug or an out-of-shape runtime cannot tear down\n// the user's app. The original Svelte function is always called, and its\n// return value always returned, regardless of devtools failures.\n\nexport function push() {\n // Parent hint, read before this component's own id exists:\n // 1. the wrapper stack: the component whose init is running (children\n // rendered directly by a template, where active_effect still belongs to\n // an outer block), or the block owner re-pushed by __wrapBlockFn for a\n // later {#each}/{#if}/{#key} update (see Block Attribution below);\n // 2. else the context-derived owner, for re-creation paths the wrapper\n // does not wrap (snippet, svelte:element, ...).\n let parentHint = null\n try {\n parentHint = __currentId()\n } catch {}\n if (parentHint === null) {\n try {\n parentHint = __parentFromContext()\n } catch {}\n }\n const result = __svelte_original.push.apply(null, arguments)\n try {\n const dt = __dt()\n if (dt) {\n const file = dt._pendingFile ?? 'Unknown'\n dt._pendingFile = null\n dt._pendingParent = parentHint\n const id = dt.register(file)\n __idStack.push(id)\n dt.startInit(id)\n try {\n __svelte_original.user_effect(() => {\n try {\n const own = __svelte_original.active_effect\n if (own?.ctx) dt._idByContext.set(own.ctx, id)\n if (own) dt.setComponentEffect(id, own)\n } catch {}\n return () => {\n try {\n dt.unmount(id)\n } catch {}\n }\n })\n } catch {}\n }\n } catch {}\n return result\n}\n\nexport function pop() {\n // Capture devtools errors but ALWAYS forward to the original pop so the\n // Svelte component stack stays balanced.\n try {\n const dt = __dt()\n if (dt && __idStack.length > 0) {\n const id = __idStack.pop()\n try {\n dt.endInit(id)\n } catch {}\n try {\n dt.registered(id)\n } catch {}\n }\n } catch {}\n return __svelte_original.pop.apply(null, arguments)\n}\n\n// --- Signal Creation (type markers) ---\n\nexport function state() {\n const signal = __svelte_original.state.apply(null, arguments)\n try {\n __pendingSignal.ref = signal\n __pendingSignal.type = 'state'\n } catch {}\n return signal\n}\n\nexport function derived() {\n const signal = __svelte_original.derived.apply(null, arguments)\n try {\n __pendingSignal.ref = signal\n __pendingSignal.type = 'derived'\n } catch {}\n return signal\n}\n\nexport function proxy() {\n const p = __svelte_original.proxy.apply(null, arguments)\n try {\n __pendingSignal.ref = p\n __pendingSignal.type = 'proxy'\n } catch {}\n return p\n}\n\n// --- Signal Tagging (Svelte dev mode) ---\n\nexport function tag(signal, name) {\n const result = __svelte_original.tag.apply(null, arguments)\n try {\n const dt = __dt()\n const cid = __owner()\n const type = __pendingSignal.ref === signal ? __pendingSignal.type : 'state'\n if (dt && cid !== null) {\n const owner = __svelte_original.active_effect\n if (type === 'derived') {\n dt.trackDerived(signal, name, cid, owner)\n } else {\n dt.trackState(signal, name, cid, owner)\n }\n } else if (dt?._moduleFile) {\n // created while a .svelte.js/.ts module body runs (shared state)\n dt.trackModuleSignal(type === 'derived' ? 'derived' : 'state', signal, name)\n }\n __pendingSignal.ref = null\n __pendingSignal.type = null\n } catch {}\n return result\n}\n\nexport function tag_proxy(value, name) {\n const result = __svelte_original.tag_proxy.apply(null, arguments)\n try {\n const dt = __dt()\n const cid = __owner()\n if (dt && cid !== null) {\n dt.trackProxy(value, name, cid, __svelte_original.active_effect)\n } else if (dt?._moduleFile) {\n dt.trackModuleSignal('proxy', value, name)\n }\n __pendingSignal.ref = null\n __pendingSignal.type = null\n } catch {}\n return result\n}\n\n// --- Effect Tracking ---\n//\n// A top-level $effect in a component is deferred by Svelte until mount:\n// user_effect() returns undefined and the effect object is created later,\n// in pop(). So the node is registered now (named per component) and bound\n// to the real effect object on its first run, where active_effect IS the\n// effect; the Svelte runtime then keeps effect.deps current, which is what\n// getReactiveGraph() reads to build edges. The wrapped callback forwards\n// this, arguments and the return value (teardown) unchanged.\n\nfunction __trackUserEffect(original, kind, args) {\n let cid = null\n let dt = null\n let fn = null\n try {\n dt = __dt()\n fn = args[0]\n if (dt && typeof fn === 'function') cid = __owner()\n } catch {}\n if (cid === null) return original.apply(null, args)\n let nodeId = null\n let bound = false\n const wrapped = Array.prototype.slice.call(args)\n wrapped[0] = function () {\n if (!bound) {\n bound = true\n try {\n dt.bindEffect(nodeId, __svelte_original.active_effect)\n } catch {}\n }\n return fn.apply(this, arguments)\n }\n try {\n nodeId = dt.trackUserEffect(kind, cid, __svelte_original.active_effect)\n } catch {}\n const result = original.apply(null, wrapped)\n if (result && !bound) {\n bound = true\n try {\n dt.bindEffect(nodeId, result)\n } catch {}\n }\n return result\n}\n\nexport function user_effect() {\n return __trackUserEffect(__svelte_original.user_effect, 'effect', arguments)\n}\n\nexport function user_pre_effect() {\n return __trackUserEffect(__svelte_original.user_pre_effect, 'effect_pre', arguments)\n}\n\n// --- Render Profiling ---\n//\n// Svelte 5 has no virtual DOM: the compiler wraps every dynamic part of a\n// template in a template_effect (deferred_template_effect for <svelte:head>\n// <title>). A re-run of such an effect IS the component updating the DOM, so\n// these two functions are the measurement points for renders / render time.\n//\n// The first run of an effect is the mount-time paint, already covered by\n// initTime, so it is skipped (initialRun). One state change re-runs several\n// effects of the same component, so durations are pooled per microtask and\n// recorded once per flush: recordRender() +1, recordRenderTime() the sum.\n\nconst __pendingRenderDurations = new Map()\nlet __renderFlushScheduled = false\n\nfunction __flushRenderDurations() {\n __renderFlushScheduled = false\n const dt = __dt()\n for (const [cid, duration] of __pendingRenderDurations) {\n if (dt) {\n try {\n dt.recordRender(cid)\n } catch {}\n try {\n dt.recordRenderTime(cid, duration)\n } catch {}\n }\n }\n __pendingRenderDurations.clear()\n}\n\nfunction __recordRenderDuration(cid, duration) {\n __pendingRenderDurations.set(cid, (__pendingRenderDurations.get(cid) ?? 0) + duration)\n if (!__renderFlushScheduled) {\n __renderFlushScheduled = true\n queueMicrotask(__flushRenderDurations)\n }\n}\n\n// Returns a (possibly new) args array whose first element times each re-run\n// of the effect closure. The owning component is whatever id is on top of\n// __idStack at effect-creation time. User exceptions propagate untouched;\n// only the args are built inside try so the original is called exactly once.\nfunction __wrapTemplateEffect(args) {\n const cid = __owner()\n const fn = args[0]\n if (cid === null || typeof fn !== 'function') return args\n const wrapped = Array.prototype.slice.call(args)\n let initialRun = true\n wrapped[0] = function () {\n if (initialRun) {\n initialRun = false\n return fn.apply(this, arguments)\n }\n const start = performance.now()\n try {\n return fn.apply(this, arguments)\n } finally {\n try {\n __recordRenderDuration(cid, performance.now() - start)\n } catch {}\n }\n }\n return wrapped\n}\n\nexport function template_effect() {\n let args = arguments\n try {\n args = __wrapTemplateEffect(arguments)\n } catch {}\n return __svelte_original.template_effect.apply(null, args)\n}\n\nexport function deferred_template_effect() {\n let args = arguments\n try {\n args = __wrapTemplateEffect(arguments)\n } catch {}\n return __svelte_original.deferred_template_effect.apply(null, args)\n}\n\n// --- Block Attribution ---\n//\n// Svelte creates render effects synchronously at creation time. At mount this\n// happens during the component function (between push/pop), so __idStack has\n// the owner. But effects for {#each} items added later or re-created {#if}\n// branches are created during a batch flush, when the stack is empty — they\n// would never be attributed. Block helpers themselves ARE always called\n// during their owner's init (or inside an outer block's callback), so the\n// owner id is on the stack at block-creation time. __wrapBlock captures it\n// and re-pushes it around every function argument the block invokes later,\n// so effects created inside land on the innermost owner. Child components\n// push their own id on top, keeping nested attribution correct.\n\n// A component whose init throws (caught by <svelte:boundary>, or by the\n// block's caller) never reaches pop(): its id would stay on both stacks and\n// every later signal/child would be attributed to it. On the way out — normal\n// or thrown — the stack is cut back to its depth on entry, and ids left\n// above it are dropped as aborted inits (registered, never mounted).\nfunction __wrapBlockFn(fn, cid) {\n return function () {\n let depth = -1\n try {\n depth = __idStack.length\n __idStack.push(cid)\n } catch {}\n try {\n return fn.apply(this, arguments)\n } finally {\n if (depth !== -1) {\n try {\n if (__idStack.length > depth + 1) {\n const dt = __dt()\n const aborted = __idStack.slice(depth + 1)\n for (let i = aborted.length - 1; i >= 0; i--) {\n try {\n if (dt) dt.abortInit(aborted[i])\n } catch {}\n }\n }\n __idStack.length = depth\n } catch {}\n }\n }\n }\n}\n\nfunction __wrapBlock(args) {\n const cid = __owner()\n if (cid === null) return args\n const wrapped = Array.prototype.slice.call(args)\n for (let i = 0; i < wrapped.length; i++) {\n if (typeof wrapped[i] === 'function') wrapped[i] = __wrapBlockFn(wrapped[i], cid)\n }\n return wrapped\n}\n\nexport function each() {\n let args = arguments\n try {\n args = __wrapBlock(arguments)\n } catch {}\n return __svelte_original.each.apply(null, args)\n}\n\nexport function key() {\n let args = arguments\n try {\n args = __wrapBlock(arguments)\n } catch {}\n return __svelte_original.key.apply(null, args)\n}\n\nexport function component() {\n let args = arguments\n try {\n args = __wrapBlock(arguments)\n } catch {}\n return __svelte_original.component.apply(null, args)\n}\n\nexport function boundary() {\n let args = arguments\n try {\n args = __wrapBlock(arguments)\n } catch {}\n return __svelte_original.boundary.apply(null, args)\n}\n\n// 'if' and 'await' are reserved words, so they cannot be declared as\n// function names and are exported via aliases instead.\nfunction __if_block() {\n let args = arguments\n try {\n args = __wrapBlock(arguments)\n } catch {}\n return __svelte_original.if.apply(null, args)\n}\n\nfunction __await_block() {\n let args = arguments\n try {\n args = __wrapBlock(arguments)\n } catch {}\n return __svelte_original.await.apply(null, args)\n}\n\nexport { __if_block as if, __await_block as await }\n", M = "virtual:svelte-devtools-runtime", He = "\0virtual:svelte-devtools-runtime", Ue = "\0svelte-devtools:wrapped-client", We = Ve, Ge = Be;
|
|
860
|
+
//#endregion
|
|
861
|
+
//#region src/runtime/transform.ts
|
|
862
|
+
function Ke(e, t) {
|
|
863
|
+
let n = /^[ \t]*\$\.push\(\$\$props\b/m.exec(e);
|
|
864
|
+
if (!n) return null;
|
|
865
|
+
let r = n.index + n[0].indexOf("$.push("), i = `if (typeof window !== 'undefined' && window.__SVELTE_DEVTOOLS__) { window.__SVELTE_DEVTOOLS__._pendingFile = ${JSON.stringify(t)}; } `;
|
|
866
|
+
return `import '${M}';` + e.slice(0, r) + i + e.slice(r);
|
|
207
867
|
}
|
|
208
|
-
|
|
209
|
-
|
|
868
|
+
var qe = /\.svelte\.[cm]?[jt]s$/;
|
|
869
|
+
function Je(e, t) {
|
|
870
|
+
if (e.includes("svelte/internal/server")) return null;
|
|
871
|
+
let n = JSON.stringify(t), r = "(typeof window !== 'undefined' && window.__SVELTE_DEVTOOLS__)";
|
|
872
|
+
return `import '${M}';if (${r}) { window.__SVELTE_DEVTOOLS__._enterModule(${n}); }` + e + `\n;if (${r}) { window.__SVELTE_DEVTOOLS__._leaveModule(); }\n`;
|
|
210
873
|
}
|
|
211
874
|
//#endregion
|
|
212
|
-
//#region src/
|
|
213
|
-
var S = "virtual:svelte-devtools-runtime", ge = "\0virtual:svelte-devtools-runtime", _e = "\0svelte-devtools:wrapped-client", ve = "\nimport * as __svelte_original from 'svelte/internal/client';\nexport * from 'svelte/internal/client';\n\nfunction __dt() {\n return typeof window !== 'undefined' ? window.__SVELTE_DEVTOOLS__ : null;\n}\n\n// Component ID stack (parallel to runtime._stack but local to wrapper)\nconst __idStack = [];\nfunction __currentId() {\n return __idStack.length > 0 ? __idStack[__idStack.length - 1] : null;\n}\n\n// Tracks the most recently created signal for type determination in tag()\nconst __pendingSignal = { ref: null, type: null };\n\n// Component context -> devtools id (review B1b). A component created later\n// by a block update ({#each}/{#if} after mount) runs outside its parent's\n// push..pop, where the runtime's init stack is empty (the wrapper stack\n// below covers the wrapped block helpers; this covers the rest). Svelte (5.56.8,\n// internal/client) exports active_effect; every effect records the\n// component context it was created in (effect.ctx) and runs with that\n// context restored. The block effect that creates the child belongs to the\n// parent's template, so active_effect.ctx at push time IS the parent's\n// context. Each context is mapped to its id once the component mounted\n// (inside its own deferred effect, where active_effect.ctx is the\n// component's own context). The map lives on the runtime (dt._idByContext)\n// so the reactive graph can name the component of any effect it reaches.\n// Weak map: no leak, no Svelte state touched.\nfunction __parentFromContext() {\n const dt = __dt();\n const effect = __svelte_original.active_effect;\n const ctx = effect ? effect.ctx : null;\n return dt && ctx && dt._idByContext ? dt._idByContext.get(ctx) : undefined;\n}\n\n// Owner of a signal/effect created right now: the component whose init (or\n// wrapped block callback) is running, else — outside init, e.g. an effect\n// created while another effect runs — the component of the active effect.\nfunction __owner() {\n const cid = __currentId();\n if (cid !== null) return cid;\n const fromContext = __parentFromContext();\n return fromContext === undefined ? null : fromContext;\n}\n\n// --- Component Lifecycle ---\n//\n// Observer must not break the observed: every devtools side-effect below is\n// wrapped in try/catch so a bug or an out-of-shape runtime cannot tear down\n// the user's app. The original Svelte function is always called, and its\n// return value always returned, regardless of devtools failures.\n\nexport function push() {\n // Parent hint, read before this component's own id exists:\n // 1. the wrapper stack: the component whose init is running (children\n // rendered directly by a template, where active_effect still belongs to\n // an outer block), or the block owner re-pushed by __wrapBlockFn for a\n // later {#each}/{#if}/{#key} update (see Block Attribution below);\n // 2. else the context-derived owner, for re-creation paths the wrapper\n // does not wrap (snippet, svelte:element, ...).\n let parentHint = null;\n try { parentHint = __currentId(); } catch {}\n if (parentHint === null) {\n try { parentHint = __parentFromContext(); } catch {}\n }\n const result = __svelte_original.push.apply(null, arguments);\n try {\n const dt = __dt();\n if (dt) {\n const file = dt._pendingFile || 'Unknown';\n dt._pendingFile = null;\n dt._pendingParent = parentHint;\n const id = dt.register(file);\n __idStack.push(id);\n dt.startInit(id);\n try {\n __svelte_original.user_effect(() => {\n try {\n const own = __svelte_original.active_effect;\n if (own && own.ctx) dt._idByContext.set(own.ctx, id);\n if (own) dt.setComponentEffect(id, own);\n } catch {}\n return () => { try { dt.unmount(id); } catch {} };\n });\n } catch {}\n }\n } catch {}\n return result;\n}\n\nexport function pop() {\n // Capture devtools errors but ALWAYS forward to the original pop so the\n // Svelte component stack stays balanced.\n try {\n const dt = __dt();\n if (dt && __idStack.length > 0) {\n const id = __idStack.pop();\n try { dt.endInit(id); } catch {}\n try { dt.registered(id); } catch {}\n }\n } catch {}\n return __svelte_original.pop.apply(null, arguments);\n}\n\n// --- Signal Creation (type markers) ---\n\nexport function state() {\n const signal = __svelte_original.state.apply(null, arguments);\n try {\n __pendingSignal.ref = signal;\n __pendingSignal.type = 'state';\n } catch {}\n return signal;\n}\n\nexport function derived() {\n const signal = __svelte_original.derived.apply(null, arguments);\n try {\n __pendingSignal.ref = signal;\n __pendingSignal.type = 'derived';\n } catch {}\n return signal;\n}\n\nexport function proxy() {\n const p = __svelte_original.proxy.apply(null, arguments);\n try {\n __pendingSignal.ref = p;\n __pendingSignal.type = 'proxy';\n } catch {}\n return p;\n}\n\n// --- Signal Tagging (Svelte dev mode) ---\n\nexport function tag(signal, name) {\n const result = __svelte_original.tag.apply(null, arguments);\n try {\n const dt = __dt();\n const cid = __owner();\n const type = (__pendingSignal.ref === signal) ? __pendingSignal.type : 'state';\n if (dt && cid !== null) {\n const owner = __svelte_original.active_effect;\n if (type === 'derived') {\n dt.trackDerived(signal, name, cid, owner);\n } else {\n dt.trackState(signal, name, cid, owner);\n }\n } else if (dt && dt._moduleFile) {\n // created while a .svelte.js/.ts module body runs (shared state)\n dt.trackModuleSignal(type === 'derived' ? 'derived' : 'state', signal, name);\n }\n __pendingSignal.ref = null;\n __pendingSignal.type = null;\n } catch {}\n return result;\n}\n\nexport function tag_proxy(proxy, name) {\n const result = __svelte_original.tag_proxy.apply(null, arguments);\n try {\n const dt = __dt();\n const cid = __owner();\n if (dt && cid !== null) {\n dt.trackProxy(proxy, name, cid, __svelte_original.active_effect);\n } else if (dt && dt._moduleFile) {\n dt.trackModuleSignal('proxy', proxy, name);\n }\n __pendingSignal.ref = null;\n __pendingSignal.type = null;\n } catch {}\n return result;\n}\n\n// --- Effect Tracking ---\n//\n// A top-level $effect in a component is deferred by Svelte until mount:\n// user_effect() returns undefined and the effect object is created later,\n// in pop(). So the node is registered now (named per component) and bound\n// to the real effect object on its first run, where active_effect IS the\n// effect; the Svelte runtime then keeps effect.deps current, which is what\n// getReactiveGraph() reads to build edges. The wrapped callback forwards\n// this, arguments and the return value (teardown) unchanged.\n\nfunction __trackUserEffect(original, kind, args) {\n let cid = null;\n let dt = null;\n let fn = null;\n try {\n dt = __dt();\n fn = args[0];\n if (dt && typeof fn === 'function') cid = __owner();\n } catch {}\n if (cid === null) return original.apply(null, args);\n let nodeId = null;\n let bound = false;\n const wrapped = Array.prototype.slice.call(args);\n wrapped[0] = function () {\n if (!bound) {\n bound = true;\n try { dt.bindEffect(nodeId, __svelte_original.active_effect); } catch {}\n }\n return fn.apply(this, arguments);\n };\n try { nodeId = dt.trackUserEffect(kind, cid, __svelte_original.active_effect); } catch {}\n const result = original.apply(null, wrapped);\n if (result && !bound) {\n bound = true;\n try { dt.bindEffect(nodeId, result); } catch {}\n }\n return result;\n}\n\nexport function user_effect() {\n return __trackUserEffect(__svelte_original.user_effect, 'effect', arguments);\n}\n\nexport function user_pre_effect() {\n return __trackUserEffect(__svelte_original.user_pre_effect, 'effect_pre', arguments);\n}\n\n// --- Render Profiling ---\n//\n// Svelte 5 has no virtual DOM: the compiler wraps every dynamic part of a\n// template in a template_effect (deferred_template_effect for <svelte:head>\n// <title>). A re-run of such an effect IS the component updating the DOM, so\n// these two functions are the measurement points for renders / render time.\n//\n// The first run of an effect is the mount-time paint, already covered by\n// initTime, so it is skipped (initialRun). One state change re-runs several\n// effects of the same component, so durations are pooled per microtask and\n// recorded once per flush: recordRender() +1, recordRenderTime() the sum.\n\nconst __pendingRenderDurations = new Map();\nlet __renderFlushScheduled = false;\n\nfunction __flushRenderDurations() {\n __renderFlushScheduled = false;\n const dt = __dt();\n for (const [cid, duration] of __pendingRenderDurations) {\n if (dt) {\n try { dt.recordRender(cid); } catch {}\n try { dt.recordRenderTime(cid, duration); } catch {}\n }\n }\n __pendingRenderDurations.clear();\n}\n\nfunction __recordRenderDuration(cid, duration) {\n __pendingRenderDurations.set(cid, (__pendingRenderDurations.get(cid) || 0) + duration);\n if (!__renderFlushScheduled) {\n __renderFlushScheduled = true;\n queueMicrotask(__flushRenderDurations);\n }\n}\n\n// Returns a (possibly new) args array whose first element times each re-run\n// of the effect closure. The owning component is whatever id is on top of\n// __idStack at effect-creation time. User exceptions propagate untouched;\n// only the args are built inside try so the original is called exactly once.\nfunction __wrapTemplateEffect(args) {\n const cid = __owner();\n const fn = args[0];\n if (cid === null || typeof fn !== 'function') return args;\n const wrapped = Array.prototype.slice.call(args);\n let initialRun = true;\n wrapped[0] = function () {\n if (initialRun) {\n initialRun = false;\n return fn.apply(this, arguments);\n }\n const start = performance.now();\n try {\n return fn.apply(this, arguments);\n } finally {\n try { __recordRenderDuration(cid, performance.now() - start); } catch {}\n }\n };\n return wrapped;\n}\n\nexport function template_effect() {\n let args = arguments;\n try { args = __wrapTemplateEffect(arguments); } catch {}\n return __svelte_original.template_effect.apply(null, args);\n}\n\nexport function deferred_template_effect() {\n let args = arguments;\n try { args = __wrapTemplateEffect(arguments); } catch {}\n return __svelte_original.deferred_template_effect.apply(null, args);\n}\n\n// --- Block Attribution ---\n//\n// Svelte creates render effects synchronously at creation time. At mount this\n// happens during the component function (between push/pop), so __idStack has\n// the owner. But effects for {#each} items added later or re-created {#if}\n// branches are created during a batch flush, when the stack is empty — they\n// would never be attributed. Block helpers themselves ARE always called\n// during their owner's init (or inside an outer block's callback), so the\n// owner id is on the stack at block-creation time. __wrapBlock captures it\n// and re-pushes it around every function argument the block invokes later,\n// so effects created inside land on the innermost owner. Child components\n// push their own id on top, keeping nested attribution correct.\n\n// A component whose init throws (caught by <svelte:boundary>, or by the\n// block's caller) never reaches pop(): its id would stay on both stacks and\n// every later signal/child would be attributed to it. On the way out — normal\n// or thrown — the stack is cut back to its depth on entry, and ids left\n// above it are dropped as aborted inits (registered, never mounted).\nfunction __wrapBlockFn(fn, cid) {\n return function () {\n let depth = -1;\n try { depth = __idStack.length; __idStack.push(cid); } catch {}\n try {\n return fn.apply(this, arguments);\n } finally {\n if (depth !== -1) {\n try {\n if (__idStack.length > depth + 1) {\n const dt = __dt();\n const aborted = __idStack.slice(depth + 1);\n for (let i = aborted.length - 1; i >= 0; i--) {\n try { if (dt) dt.abortInit(aborted[i]); } catch {}\n }\n }\n __idStack.length = depth;\n } catch {}\n }\n }\n };\n}\n\nfunction __wrapBlock(args) {\n const cid = __owner();\n if (cid === null) return args;\n const wrapped = Array.prototype.slice.call(args);\n for (let i = 0; i < wrapped.length; i++) {\n if (typeof wrapped[i] === 'function') wrapped[i] = __wrapBlockFn(wrapped[i], cid);\n }\n return wrapped;\n}\n\nexport function each() {\n let args = arguments;\n try { args = __wrapBlock(arguments); } catch {}\n return __svelte_original.each.apply(null, args);\n}\n\nexport function key() {\n let args = arguments;\n try { args = __wrapBlock(arguments); } catch {}\n return __svelte_original.key.apply(null, args);\n}\n\nexport function component() {\n let args = arguments;\n try { args = __wrapBlock(arguments); } catch {}\n return __svelte_original.component.apply(null, args);\n}\n\nexport function boundary() {\n let args = arguments;\n try { args = __wrapBlock(arguments); } catch {}\n return __svelte_original.boundary.apply(null, args);\n}\n\n// 'if' and 'await' are reserved words, so they cannot be declared as\n// function names and are exported via aliases instead.\nfunction __if_block() {\n let args = arguments;\n try { args = __wrapBlock(arguments); } catch {}\n return __svelte_original['if'].apply(null, args);\n}\n\nfunction __await_block() {\n let args = arguments;\n try { args = __wrapBlock(arguments); } catch {}\n return __svelte_original['await'].apply(null, args);\n}\n\nexport { __if_block as if, __await_block as await };\n", ye = "\nif (typeof window !== 'undefined' && !window.__SVELTE_DEVTOOLS__) {\n const __NO_VALUE = Symbol('no value');\n // State timeline ring (§6.7 C): every read sees at most 500 entries and\n // __TIMELINE_BYTES (also the server's buffer budget). The raw array may\n // hold up to __TIMELINE_TRIM_AT (2 x the 500 cap) between bulk trims.\n const __TIMELINE_BYTES = 4 * 1024 * 1024;\n const __TIMELINE_TRIM_AT = 1000;\n // FNV-1a over UTF-16 code units: cheap change detection for large snapshots.\n const __hash = (str) => {\n let h = 0x811c9dc5;\n for (let i = 0; i < str.length; i++) {\n h ^= str.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return (h >>> 0).toString(36);\n };\n const __SVELTE_DT = {\n _nextId: 0,\n _instances: new Map(),\n _stack: [],\n _pendingParent: undefined,\n _pendingFile: null,\n _debounceTimer: null,\n _listeners: new Set(),\n\n // Phase 2: Profiling data\n _profiles: new Map(),\n _initStartTimes: new Map(),\n _profileDebounceTimer: null,\n\n // Phase 2: Reactive graph data\n _reactiveNodes: new Map(),\n _reactiveProxies: new Map(),\n\n // componentId -> Set<nodeId>. Lets unmount purge a component's reactive\n // nodes without scanning every node of the app (was O(components x nodes)).\n _nodesByComponent: new Map(),\n\n // Phase 3: State timeline\n _stateTimeline: [],\n _stateSnapshots: new Map(),\n _timelineDebounceTimer: null,\n // signal (or proxy marker) -> nodeId, set at track time and removed by\n // _forgetNode: a scoped graph resolves dependencies without scanning\n // every node (§6.7 D).\n _idBySignal: new WeakMap(),\n // proxy -> nodeId (tag_proxy nodes; the marker above is per node)\n _idByProxy: new WeakMap(),\n // Svelte component context -> component id (set by the wrapper on mount)\n _idByContext: new WeakMap(),\n // componentId -> (name -> last suffix) for names declared more than once\n // per instance ({@const} in {#each} items, several $state class instances)\n _nameSeq: new Map(),\n // Module scopes: signals created while a .svelte.js/.ts module body runs\n // (shared state such as `export const cart = $state(...)`) belong to a\n // scope per module file. Ids come from the component id counter (never\n // reused, non-negative) but scopes are not components: they are not in\n // _instances / the component tree, and live as long as the page.\n _modules: new Map(),\n _moduleByFile: new Map(),\n // $state node name -> (scope id -> Set<nodeId>): resolves a proxy's property source\n // (labelled '<name>.prop' by Svelte in dev) to the node of its proxy —\n // a tag_proxy node, or a state signal holding a proxy (reassigned object\n // state, class fields)\n _proxyNames: new Map(),\n // file of the module body running now (set by the module transform)\n _moduleFile: null,\n // componentId -> { effect, effect_pre } counters: effect names are\n // numbered per component, so instances of one file match\n _effectSeq: new Map(),\n // Every tracked node id (dense, swap-remove like _pollIds) for the\n // incremental liveness sweep, and its cursor.\n _sweepIds: [],\n _sweepIndex: new Map(),\n _sweepCursor: 0,\n // Polling bookkeeping (§6.7 D): $state node ids in a dense array kept up\n // to date incrementally (_pollIndex: nodeId -> position; removal is\n // swap-with-last), a cursor carried across ticks, nodes found changed but\n // not yet serialized (_pollDirty), and per-node change hints\n // (nodeId -> { ref, wv, nextCheckAt }).\n _pollIds: [],\n _pollIndex: new Map(),\n _pollCursor: 0,\n _pollDirty: new Set(),\n // Full-sweep bookkeeping: how long the last complete pass over all $state\n // nodes took (ms), for disclosure (= the worst-case detection delay).\n _pollSweep: { startedAt: 0, lastMs: null, nodes: 0 },\n _pollMeta: new Map(),\n _deepCredit: 0,\n _lastSnapshotSize: new Map(),\n _timelineSeq: 0,\n _pushedSeq: 0,\n _timelineReset: false,\n _entryInfo: new WeakMap(),\n // Sum of _entryInfo.bytes over _stateTimeline (runtime byte budget).\n _timelineBytes: 0,\n // Disclosure since the last push (§6.7 C): unsent entries the ring removed\n // (by count / by bytes) and changes recorded only as a size summary.\n _timelineDropped: { 'runtime-count': 0, 'runtime-bytes': 0 },\n _valueTooLarge: 0,\n\n // Overview aggregate (§6.7 I/J), O(1) per event: registered nodes per\n // component and type (+ app totals), and per-component activity in a\n // ring of 60 one-second buckets (sampled changes, renders, render ms,\n // plus how long sampling ran in that second).\n _nodeCounts: new Map(),\n _nodeTotals: { state: 0, derived: 0, effect: 0 },\n _activity: [],\n _epoch: Math.random().toString(36).slice(2) + Date.now().toString(36),\n\n // --- Activity subscription (docs/devframe-migration.md §6.3) ---\n // Inactive until the server says a DevTools client (UI tab or MCP lease)\n // is watching: no state polling, FPS rAF loop, or periodic HMR sends.\n _active: false,\n _componentsCost: { ms: 0, bytes: 0 },\n _componentDeltas: false,\n _pendingAdded: new Set(),\n _pendingRemoved: new Set(),\n _lastFullAt: 0,\n _resync: false,\n _profilesCost: { ms: 0, bytes: 0 },\n // = collector LIMITS.renderProfiles (pinned by a unit test): the server\n // keeps only the newest entries, so sending more is wasted bytes.\n _profileCap: 5000,\n _sampling: null,\n _baselining: false,\n // Polled state nodes not observed yet (no seed value). Their first\n // observation is a seed, not a change: no timeline entry, no activity\n // count, no drops — the old value is unknown, not null (review B-1/B-2).\n _unseeded: new Set(),\n // pendingNodes last sent on a state-timeline message (null = none yet)\n _sentPendingNodes: null,\n _fpsGen: 0,\n\n register(file) {\n const id = this._nextId++;\n // Parent: the wrapper's context-derived hint (components created by a\n // later block update, review B1b) when it names a live instance, else\n // the synchronous init stack (children created during the parent's own\n // init, before the parent is mapped).\n const hint = this._pendingParent;\n this._pendingParent = undefined;\n const parentId =\n hint !== undefined && hint !== null && this._instances.has(hint)\n ? hint\n : this._stack.length > 0 ? this._stack[this._stack.length - 1] : null;\n const name = file.split('/').pop()?.replace('.svelte', '') || 'Unknown';\n // children is a Set so unmounting one row of a 5 000-row list is O(1).\n this._instances.set(id, { id, file, name, parentId, mounted: true, children: new Set() });\n if (parentId !== null) {\n const parent = this._instances.get(parentId);\n if (parent) parent.children.add(id);\n }\n this._stack.push(id);\n if (this._active) this._pendingAdded.add(id);\n this._scheduleUpdate();\n return id;\n },\n\n registered(id) {\n const idx = this._stack.indexOf(id);\n if (idx !== -1) this._stack.splice(idx, 1);\n },\n\n mount(id) {\n const instance = this._instances.get(id);\n if (instance && !instance.mounted) {\n instance.mounted = true;\n if (this._active) this._pendingAdded.add(id);\n this._scheduleUpdate();\n }\n },\n\n unmount(id) {\n const instance = this._instances.get(id);\n if (instance) {\n if (instance.parentId !== null) {\n const parent = this._instances.get(instance.parentId);\n if (parent) parent.children.delete(id);\n }\n this._removeChildren(id);\n this._cleanupComponent(id);\n }\n this._scheduleUpdate();\n this._scheduleProfileUpdate();\n },\n\n // Remounts get a fresh id, so entries keyed by a dead id would otherwise\n // survive forever — every per-component map must be purged here.\n _cleanupComponent(id) {\n // Delta bookkeeping: an id added and removed between two pushes was\n // never sent, so it cancels out.\n if (this._active && !this._pendingAdded.delete(id)) this._pendingRemoved.add(id);\n this._cleanupReactiveNodes(id);\n this._effectSeq.delete(id);\n this._nameSeq.delete(id);\n this._instances.delete(id);\n this._profiles.delete(id);\n this._initStartTimes.delete(id);\n },\n\n _cleanupReactiveNodes(componentId) {\n const nodeIds = this._nodesByComponent.get(componentId);\n if (!nodeIds) return;\n this._nodesByComponent.delete(componentId);\n for (const nodeId of [...nodeIds]) this._forgetNode(nodeId);\n },\n\n _forgetNode(nodeId) {\n const entry = this._reactiveNodes.get(nodeId);\n if (entry) {\n this._countNode(entry.meta.componentId, entry.meta.type, -1);\n const set = this._nodesByComponent.get(entry.meta.componentId);\n if (set) set.delete(nodeId);\n }\n const signal = entry && entry.signal.deref();\n if (signal && this._idBySignal.get(signal) === nodeId) this._idBySignal.delete(signal);\n const proxy = this._reactiveProxies.get(nodeId)?.deref();\n if (proxy && this._idByProxy.get(proxy) === nodeId) this._idByProxy.delete(proxy);\n if (entry && entry.meta.type === 'state') {\n const named = this._proxyNames.get(entry.meta.name);\n const inScope = named && named.get(entry.meta.componentId);\n if (inScope) {\n inScope.delete(nodeId);\n if (inScope.size === 0) named.delete(entry.meta.componentId);\n if (named.size === 0) this._proxyNames.delete(entry.meta.name);\n }\n }\n this._pollRemove(nodeId);\n this._sweepRemove(nodeId);\n this._pollDirty.delete(nodeId);\n this._reactiveNodes.delete(nodeId);\n this._reactiveProxies.delete(nodeId);\n this._stateSnapshots.delete(nodeId);\n if (this._stateSnapshotStrs) this._stateSnapshotStrs.delete(nodeId);\n this._pollMeta.delete(nodeId);\n this._lastSnapshotSize.delete(nodeId);\n },\n\n // A component instance or a module scope that is still live.\n _scopeLive(id) {\n return this._instances.has(id) || this._modules.has(id);\n },\n\n _scopeFile(id) {\n const scope = this._instances.get(id) || this._modules.get(id);\n return scope ? scope.file : '';\n },\n\n _enterModule(file) {\n this._moduleFile = file;\n // a module body that throws (or awaits) never reaches _leaveModule\n queueMicrotask(() => {\n if (this._moduleFile === file) this._moduleFile = null;\n });\n },\n\n _leaveModule() {\n this._moduleFile = null;\n },\n\n trackModuleSignal(kind, target, name) {\n const file = this._moduleFile;\n let id = this._moduleByFile.get(file);\n if (id === undefined) {\n id = this._nextId++;\n const base = file.split('/').pop() || file;\n this._modules.set(id, { id, file, name: base, kind: 'module' });\n this._moduleByFile.set(file, id);\n }\n if (kind === 'proxy') this.trackProxy(target, name, id, null);\n else if (kind === 'derived') this.trackDerived(target, name, id, null);\n else this.trackState(target, name, id, null);\n },\n\n // A node is live while its signal is reachable, its component is mounted,\n // and the effect it was created in (an {#each} item, an effect run) has\n // not been destroyed. Svelte nulls effect.fn when it destroys an effect\n // (internal/client destroy_effect), which is checked instead of the\n // internal DESTROYED flag value. Returns the signal or null.\n _liveSignal(nodeId) {\n const entry = this._reactiveNodes.get(nodeId);\n if (!entry) return null;\n const signal = entry.signal.deref();\n if (signal && this._scopeLive(entry.meta.componentId) && this._ownerAlive(entry)) {\n if (!entry.effect) return signal;\n // a bound effect is destroyed when Svelte clears its callback\n if (signal.fn !== null) return signal;\n }\n this._forgetNode(nodeId);\n return null;\n },\n\n _ownerAlive(entry) {\n if (!entry.owner) return true;\n const owner = entry.owner.deref();\n return !!owner && owner.fn !== null;\n },\n\n _sweepAdd(nodeId) {\n if (this._sweepIndex.has(nodeId)) return;\n this._sweepIndex.set(nodeId, this._sweepIds.length);\n this._sweepIds.push(nodeId);\n },\n\n _sweepRemove(nodeId) {\n const i = this._sweepIndex.get(nodeId);\n if (i === undefined) return;\n const ids = this._sweepIds;\n const last = ids.pop();\n this._sweepIndex.delete(nodeId);\n if (i < ids.length) {\n ids[i] = last;\n this._sweepIndex.set(last, i);\n }\n },\n\n // Liveness of up to `max` nodes, continuing from a cursor (each poll\n // tick): nodes of destroyed {#each} items / effect runs leave the counts\n // and the graph without waiting for a graph request or GC.\n _sweepNodes(max) {\n const ids = this._sweepIds;\n for (let n = 0; n < max && ids.length > 0; n++) {\n if (this._sweepCursor >= ids.length) this._sweepCursor = 0;\n const nodeId = ids[this._sweepCursor];\n // a forgotten node's slot is refilled by the last id: re-check it\n if (this._liveSignal(nodeId)) this._sweepCursor++;\n }\n },\n\n // Unique node id for a signal: componentId:name, or componentId:name#k\n // when this instance already holds a live node of that name for another\n // signal. The same signal (re-tagged) keeps its id.\n _nodeIdFor(identity, byIdentity, componentId, name) {\n const known = byIdentity.get(identity);\n if (known !== undefined && this._reactiveNodes.has(known)) return known;\n const base = componentId + ':' + name;\n if (!this._reactiveNodes.has(base) || !this._liveSignal(base)) return base;\n let seq = this._nameSeq.get(componentId);\n if (!seq) {\n seq = new Map();\n this._nameSeq.set(componentId, seq);\n }\n let k = seq.get(name) || 1;\n let id;\n do {\n k++;\n id = base + '#' + k;\n } while (this._reactiveNodes.has(id) && this._liveSignal(id));\n seq.set(name, k);\n return id;\n },\n\n _registerNode(nodeId, componentId, type, name, signalRef, owner, extra) {\n this._trackNodeCount(nodeId, componentId, type);\n const entry = {\n signal: signalRef,\n owner: owner ? new WeakRef(owner) : null,\n meta: { id: nodeId, type, name, componentId, componentFile: this._scopeFile(componentId) },\n };\n if (extra) Object.assign(entry, extra);\n this._reactiveNodes.set(nodeId, entry);\n this._indexNode(nodeId, componentId);\n this._sweepAdd(nodeId);\n return entry;\n },\n\n _indexNode(nodeId, componentId) {\n let set = this._nodesByComponent.get(componentId);\n if (!set) {\n set = new Set();\n this._nodesByComponent.set(componentId, set);\n }\n set.add(nodeId);\n },\n\n _pollAdd(nodeId) {\n if (this._pollIndex.has(nodeId)) return;\n if (!this._stateSnapshots.has(nodeId)) this._unseeded.add(nodeId);\n this._pollIndex.set(nodeId, this._pollIds.length);\n this._pollIds.push(nodeId);\n },\n\n // O(1): the last id takes the removed slot. If that slot is behind the\n // cursor, the moved id is visited in the next sweep instead of this one\n // (still visited within a bounded number of ticks).\n // Before a track* call replaces/creates nodeId: move the count from the\n // previous registration (same name re-tracked) to the new one.\n _trackNodeCount(nodeId, componentId, type) {\n const prev = this._reactiveNodes.get(nodeId);\n if (prev) this._countNode(prev.meta.componentId, prev.meta.type, -1);\n this._countNode(componentId, type, 1);\n },\n\n _countNode(componentId, type, d) {\n const kind = type === 'derived' || type === 'effect' ? type : 'state';\n let c = this._nodeCounts.get(componentId);\n if (!c) {\n if (d < 0) return;\n c = { state: 0, derived: 0, effect: 0 };\n this._nodeCounts.set(componentId, c);\n }\n c[kind] += d;\n this._nodeTotals[kind] += d;\n if (c.state + c.derived + c.effect <= 0) this._nodeCounts.delete(componentId);\n },\n\n _bucket() {\n const sec = Math.floor(performance.now() / 1000);\n const slot = ((sec % 60) + 60) % 60;\n let b = this._activity[slot];\n if (!b || b.second !== sec) {\n b = { second: sec, sampledMs: 0, rows: new Map() };\n this._activity[slot] = b;\n }\n return b;\n },\n\n _activityRow(componentId) {\n const rows = this._bucket().rows;\n let r = rows.get(componentId);\n if (!r) {\n r = { changes: 0, renders: 0, renderMs: 0 };\n rows.set(componentId, r);\n }\n return r;\n },\n\n _pollRemove(nodeId) {\n const i = this._pollIndex.get(nodeId);\n if (i === undefined) return;\n this._unseeded.delete(nodeId);\n const ids = this._pollIds;\n const last = ids.pop();\n this._pollIndex.delete(nodeId);\n if (i < ids.length) {\n ids[i] = last;\n this._pollIndex.set(last, i);\n }\n },\n\n _removeChildren(parentId) {\n const parent = this._instances.get(parentId);\n if (!parent) return;\n for (const childId of [...parent.children]) {\n this._removeChildren(childId);\n this._cleanupComponent(childId);\n }\n },\n\n // Full-list pushes are throttled, not debounced: a pending timer is not\n // reset, so continuous churn still produces updates (a debounce would\n // starve), and the interval adapts to the last push's cost so a 10 000+\n // instance app can't spend its main thread serializing (review P3):\n // max(min, 20 x last send ms, 1 s when the last payload exceeded ~1 MB).\n _sendInterval(cost, min) {\n return Math.max(min, 20 * cost.ms, cost.bytes > 1000000 ? 1000 : 0);\n },\n\n _scheduleUpdate() {\n if (!this._active || this._debounceTimer) return;\n this._debounceTimer = setTimeout(() => {\n this._debounceTimer = null;\n this._sendUpdate();\n }, this._sendInterval(this._componentsCost, 100));\n },\n\n // components push (§6.5). Full form { epoch, components } replaces the\n // server's tree for this epoch: sent on activation, when the server does\n // not advertise componentDeltas, as a periodic checkpoint, or when the\n // delta would be larger than the tree. Otherwise the delta form\n // { epoch, added, removed } (removed ids first, ≤ 2 000 entries per\n // message, added in registration order = parents first).\n _sendUpdate(forceFull) {\n // Nobody watching: keep the registry, skip the O(N) payload. A full\n // snapshot is sent on activation (§6.3).\n if (!this._active) return;\n const t0 = performance.now();\n const CHUNK = 2000;\n const CHECKPOINT_MS = 30000;\n const pending = this._pendingAdded.size + this._pendingRemoved.size;\n const full =\n forceFull ||\n !this._componentDeltas ||\n t0 - this._lastFullAt >= CHECKPOINT_MS ||\n pending > this._instances.size;\n const toEntry = (instance) => ({\n id: instance.id,\n file: instance.file,\n name: instance.name,\n parentId: instance.parentId,\n mounted: instance.mounted,\n });\n let bytes = 0;\n const msgs = [];\n if (full) {\n const components = [];\n for (const [, instance] of this._instances) {\n if (instance.mounted) {\n components.push(toEntry(instance));\n bytes += instance.file.length + instance.name.length + 72;\n }\n }\n msgs.push({ epoch: this._epoch, reset: true, components });\n this._lastFullAt = t0;\n } else if (pending > 0) {\n const removed = [...this._pendingRemoved];\n const added = [];\n for (const id of this._pendingAdded) {\n const instance = this._instances.get(id);\n if (instance && instance.mounted) {\n added.push(toEntry(instance));\n bytes += instance.file.length + instance.name.length + 72;\n }\n }\n bytes += removed.length * 8;\n for (let r = 0; r < removed.length; r += CHUNK) {\n msgs.push({ epoch: this._epoch, added: [], removed: removed.slice(r, r + CHUNK) });\n }\n for (let a = 0; a < added.length; a += CHUNK) {\n msgs.push({ epoch: this._epoch, added: added.slice(a, a + CHUNK), removed: [] });\n }\n }\n this._pendingAdded.clear();\n this._pendingRemoved.clear();\n if (import.meta.hot) {\n for (const msg of msgs) import.meta.hot.send('svelte-devtools:components', msg);\n }\n this._componentsCost = { ms: performance.now() - t0, bytes };\n },\n\n getTree() {\n const components = [];\n for (const [, instance] of this._instances) {\n if (instance.mounted) {\n components.push({\n id: instance.id,\n file: instance.file,\n name: instance.name,\n parentId: instance.parentId,\n mounted: instance.mounted,\n });\n }\n }\n return components;\n },\n\n // --- Phase 2: Render Profiling ---\n\n startInit(id) {\n this._initStartTimes.set(id, performance.now());\n },\n\n endInit(id) {\n const start = this._initStartTimes.get(id);\n if (start === undefined) return;\n const initTime = performance.now() - start;\n this._initStartTimes.delete(id);\n const instance = this._instances.get(id);\n if (!instance) return;\n this._profiles.set(id, {\n componentId: id,\n file: instance.file,\n name: instance.name,\n initTime,\n renderCount: 0,\n totalRenderTime: 0,\n lastRenderTime: 0,\n lastRenderAt: Date.now(),\n });\n this._scheduleProfileUpdate();\n },\n\n recordRender(id) {\n const profile = this._profiles.get(id);\n if (!profile) {\n const instance = this._instances.get(id);\n if (!instance) return;\n this._profiles.set(id, {\n componentId: id,\n file: instance.file,\n name: instance.name,\n initTime: 0,\n renderCount: 1,\n totalRenderTime: 0,\n lastRenderTime: 0,\n lastRenderAt: Date.now(),\n });\n } else {\n profile.renderCount++;\n profile.lastRenderAt = Date.now();\n }\n this._activityRow(id).renders++;\n this._scheduleProfileUpdate();\n },\n\n recordRenderTime(id, duration) {\n const profile = this._profiles.get(id);\n if (profile) {\n profile.totalRenderTime += duration;\n profile.lastRenderTime = duration;\n this._activityRow(id).renderMs += duration;\n }\n },\n\n // Profiles push (review P-1 Option A; contract in\n // docs/performance-status.md). Throttled like components, but the floor\n // grows with the payload: every push carries the whole retained map, so\n // a large app coalesces bursts instead of resending ~1 MB twice a second.\n // Hard cap 2 s (contract R6): a slow serialization must not stretch the\n // latency beyond it.\n _profileInterval() {\n const cost = this._profilesCost;\n return Math.min(\n 2000,\n Math.max(500, 20 * cost.ms, cost.bytes > 1000000 ? 2000 : cost.bytes > 250000 ? 1000 : 0),\n );\n },\n\n _scheduleProfileUpdate() {\n if (!this._active || this._profileDebounceTimer) return;\n this._profileDebounceTimer = setTimeout(() => {\n this._profileDebounceTimer = null;\n this._sendProfileUpdate();\n }, this._profileInterval());\n },\n\n // Also the forced path (activation / resync): a pending throttled push is\n // cancelled so the same state is not sent twice.\n _sendProfileUpdate() {\n if (this._profileDebounceTimer) {\n clearTimeout(this._profileDebounceTimer);\n this._profileDebounceTimer = null;\n }\n if (!this._active) return;\n const t0 = performance.now();\n const all = Array.from(this._profiles.values());\n // Exactly what the collector retains: its tail, in the same order.\n const profiles = all.length > this._profileCap ? all.slice(all.length - this._profileCap) : all;\n if (import.meta.hot) {\n // total = all retained profiles; the message carries the newest-mounted\n // tail (§6.7 J IA1 capture policy 'newest-mounted').\n import.meta.hot.send('svelte-devtools:profiles', { epoch: this._epoch, profiles, total: all.length });\n }\n // Payload size from a small sample (entries differ mainly by file path).\n const k = Math.min(8, profiles.length);\n let sampled = 0;\n for (let i = 0; i < k; i++) sampled += JSON.stringify(profiles[i]).length;\n const bytes = k > 0 ? Math.round((sampled / k) * profiles.length) : 0;\n this._profilesCost = { ms: performance.now() - t0, bytes };\n },\n\n getProfiles() {\n return Array.from(this._profiles.values());\n },\n\n resetProfiles() {\n this._profiles.clear();\n this._scheduleProfileUpdate();\n },\n\n // --- Phase 2: Reactive Graph Tracking ---\n\n // `owner` (optional): the effect active when the signal was created; the\n // node dies with it (an {#each} item's {@const}, a class instance created\n // in an effect run).\n trackState(signal, name, componentId, owner) {\n const nodeId = this._nodeIdFor(signal, this._idBySignal, componentId, name);\n this._registerNode(nodeId, componentId, 'state', name, new WeakRef(signal), owner);\n this._idBySignal.set(signal, nodeId);\n this._nameProxyRoot(nodeId, name, componentId);\n this._pollAdd(nodeId);\n },\n\n trackProxy(proxy, name, componentId, owner) {\n const nodeId = this._nodeIdFor(proxy, this._idByProxy, componentId, name);\n this._reactiveProxies.set(nodeId, new WeakRef(proxy));\n // The proxy itself stays weakly held (_reactiveProxies). The marker is\n // held strongly so the node is not GC-dropped while the component lives;\n // _cleanupComponent removes it on unmount.\n const marker = { v: '(proxy)', _isProxy: true };\n this._registerNode(nodeId, componentId, 'state', name, { deref: () => marker }, owner);\n this._idBySignal.set(marker, nodeId);\n this._idByProxy.set(proxy, nodeId);\n this._nameProxyRoot(nodeId, name, componentId);\n this._pollAdd(nodeId);\n },\n\n trackDerived(signal, name, componentId, owner) {\n const nodeId = this._nodeIdFor(signal, this._idBySignal, componentId, name);\n this._registerNode(nodeId, componentId, 'derived', name, new WeakRef(signal), owner);\n this._idBySignal.set(signal, nodeId);\n },\n\n // A user effect object (already created). Kept for callers that have one.\n trackEffect(effect, name, componentId) {\n const nodeId = componentId + ':' + name;\n const target = effect || { v: undefined, _isEffect: true };\n this._registerNode(nodeId, componentId, 'effect', name, effect ? new WeakRef(target) : { deref: () => target });\n this._idBySignal.set(target, nodeId);\n return effect;\n },\n\n // $effect / $effect.pre being created now (the wrapper). The effect object\n // may not exist yet (deferred to mount): the node holds a placeholder\n // until bindEffect() is called on the effect's first run. kind: 'effect'\n // | 'effect_pre'; names are effect_1, effect_pre_1, ... per component.\n trackUserEffect(kind, componentId, owner) {\n let seq = this._effectSeq.get(componentId);\n if (!seq) {\n seq = { effect: 0, effect_pre: 0 };\n this._effectSeq.set(componentId, seq);\n }\n const name = kind + '_' + ++seq[kind];\n const nodeId = componentId + ':' + name;\n const placeholder = { v: undefined, _isEffect: true };\n this._registerNode(nodeId, componentId, 'effect', name, { deref: () => placeholder }, owner);\n return nodeId;\n },\n\n bindEffect(nodeId, effect) {\n const entry = nodeId !== null && this._reactiveNodes.get(nodeId);\n if (!entry || !effect || typeof effect !== 'object') return;\n entry.signal = new WeakRef(effect);\n entry.effect = true;\n this._idBySignal.set(effect, nodeId);\n },\n\n // The component's mount hook effect (the wrapper's, created at pop): its\n // parent is the effect the component's markup effects were created in.\n setComponentEffect(id, effect) {\n const instance = this._instances.get(id);\n if (instance) instance.hook = new WeakRef(effect);\n },\n\n // Markup effects of a component: the effects of its template\n // (template_effect, {#if}/{#each}/... blocks, <svelte:head>), i.e. the\n // effects under the hook's parent created in the component's context\n // (effect.ctx), without descending into other components or the\n // tracked $effect nodes. Svelte links child effects via first/next.\n _markupEffects(id) {\n const instance = this._instances.get(id);\n const hook = instance && instance.hook && instance.hook.deref();\n if (!hook || !hook.ctx || !hook.parent) return [];\n const ctx = hook.ctx;\n const out = [];\n const stack = [];\n for (let e = hook.parent.first; e; e = e.next) stack.push(e);\n while (stack.length) {\n const e = stack.pop();\n if (e === hook || e.ctx !== ctx) continue;\n if (e.deps && !this._idBySignal.has(e)) out.push(e);\n for (let c = e.first; c; c = c.next) stack.push(c);\n }\n return out;\n },\n\n // The tracked node a proxy's property source belongs to. Svelte (dev)\n // labels a proxy's sources after the proxy's path ('todos[0].text',\n // 'cart.items.length', 'todos version'); the root name is a $state node\n // name. Many live nodes may share the name (one per instance), so the\n // candidates are looked up per scope: the reader's component, then its\n // ancestors (props flow down), then module scopes, then all — each\n // narrowed by the source's value at that path (identity for objects).\n // Cost O(label + ancestry depth) for the common cases. null when\n // unresolved.\n _sourceOwner(source, readerCid, liveSignal) {\n const label = source.label;\n if (typeof label !== 'string' || this._proxyNames.size === 0) return null;\n let named = null;\n let rest = '';\n for (let i = label.length; i > 0; i--) {\n const ch = i === label.length ? '' : label[i];\n if (ch !== '' && ch !== '.' && ch !== '[' && ch !== ' ') continue;\n named = this._proxyNames.get(label.slice(0, i));\n if (named) {\n rest = label.slice(i);\n break;\n }\n }\n if (!named) return null;\n let path;\n // live candidates whose value at the source's path is the source's\n // value; without a property path (' version'), all live candidates\n const fits = (ids) => {\n const live = [...ids].filter((id) => liveSignal(id));\n if (live.length === 0) return live;\n if (path === undefined) path = this._labelPath(rest);\n if (!path) return live;\n const v = source.v;\n return live.filter((id) => {\n const at = this._valueAt(this._rootValue(id), path);\n return at !== __NO_VALUE && (Object.is(at, v) || (typeof v === 'symbol' && at === undefined));\n });\n };\n const scopes = [];\n for (let cid = readerCid; cid !== null && cid !== undefined; ) {\n scopes.push(cid);\n const instance = this._instances.get(cid);\n cid = instance ? instance.parentId : null;\n }\n for (const cid of named.keys()) if (this._modules.has(cid)) scopes.push(cid);\n for (const cid of scopes) {\n const ids = named.get(cid);\n const found = ids ? fits(ids) : [];\n if (found.length > 0) return found[0];\n }\n // not in the reader's ancestry or a module: any matching node, else the\n // nearest live one by name\n const all = [];\n for (const ids of named.values()) for (const id of ids) all.push(id);\n const found = fits(all);\n if (found.length > 0) return found[0];\n for (const cid of scopes) for (const id of named.get(cid) || []) if (liveSignal(id)) return id;\n return all.find((id) => liveSignal(id)) || null;\n },\n\n // '.items[0].text' -> ['items', '0', 'text']; null for ' version' and\n // anything else that is not a property path.\n _labelPath(rest) {\n const path = [];\n const re = /\\.([A-Za-z_$][\\w$]*)|\\[(\\d+)\\]|\\['((?:[^'\\\\]|\\\\.)*)'\\]/y;\n let m;\n while (re.lastIndex < rest.length && (m = re.exec(rest))) path.push(m[1] ?? m[2] ?? m[3]);\n return re.lastIndex === rest.length || rest.length === 0 ? path : null;\n },\n\n // Own-property walk (getOwnPropertyDescriptor does not create sources on\n // a Svelte proxy, unlike reading a missing property).\n _valueAt(obj, path) {\n let v = obj;\n for (const key of path) {\n if (v === null || (typeof v !== 'object' && typeof v !== 'function')) return __NO_VALUE;\n let d;\n try { d = Object.getOwnPropertyDescriptor(v, key); } catch { return __NO_VALUE; }\n if (!d) return __NO_VALUE;\n v = 'value' in d ? d.value : __NO_VALUE;\n if (v === __NO_VALUE) return v;\n }\n return v;\n },\n\n _nameProxyRoot(nodeId, name, componentId) {\n let named = this._proxyNames.get(name);\n if (!named) {\n named = new Map();\n this._proxyNames.set(name, named);\n }\n let inScope = named.get(componentId);\n if (!inScope) {\n inScope = new Set();\n named.set(componentId, inScope);\n }\n inScope.add(nodeId);\n },\n\n // The proxy a $state node holds now (tag_proxy: the proxy itself; a\n // state signal: its current value), or undefined.\n _rootValue(nodeId) {\n const proxy = this._reactiveProxies.get(nodeId);\n if (proxy) return proxy.deref();\n const signal = this._reactiveNodes.get(nodeId)?.signal.deref();\n return signal ? signal.v : undefined;\n },\n\n // A component whose init threw (the wrapper unwinds its stack): it never\n // mounted and never will, so it leaves the tree and the init stack.\n abortInit(id) {\n const idx = this._stack.indexOf(id);\n if (idx !== -1) this._stack.splice(idx, 1);\n const instance = this._instances.get(id);\n if (!instance) return;\n if (instance.parentId !== null) {\n const parent = this._instances.get(instance.parentId);\n if (parent) parent.children.delete(id);\n }\n this._removeChildren(id);\n this._cleanupComponent(id);\n this._scheduleUpdate();\n },\n\n _graphNode(nodeId, entry, signal) {\n const node = { ...entry.meta };\n if (signal._isProxy) {\n const proxy = this._reactiveProxies.get(nodeId)?.deref();\n if (proxy) {\n try {\n node.value = Array.isArray(proxy) ? '[' + proxy.length + ']' : '{' + Object.keys(proxy).length + '}';\n } catch { node.value = '(proxy)'; }\n }\n } else if (node.type === 'derived' && typeof signal.v === 'symbol') {\n // Svelte computes a derived lazily: until something reads it, v is\n // the UNINITIALIZED marker (a symbol)\n node.unevaluated = true;\n } else if (node.type !== 'effect' && signal.v !== undefined && typeof signal.v !== 'symbol') {\n try {\n const v = signal.v;\n if (typeof v === 'number' || typeof v === 'string' || typeof v === 'boolean' || v === null) {\n node.value = v;\n } else {\n node.value = '(object)';\n }\n } catch { /* ignore */ }\n }\n return node;\n },\n\n // Reactive graph (§6.7 A/D). Caps are applied while building: past\n // maxNodes no node objects are created and only edges whose endpoints\n // are both included are emitted (the rest are counted in edgesOmitted).\n // - scoped (componentId): that component's nodes plus the tracked nodes\n // directly connected to them (via deps / reactions through untracked\n // intermediates). Dependencies are resolved through _idBySignal, so\n // the cost is O(scope + neighbours), not O(all nodes). The walk is\n // completed even past the cap, so total.nodes/edges are exact.\n // - global: nodes in registration order up to maxNodes ('global-head'),\n // with their incoming dependency edges. Nodes past the cap are not\n // dereferenced; total.nodes is the registered count (may include\n // nodes whose signal was already collected, nodesKind 'registered')\n // and total.edges is null when the walk stopped early (not guessed).\n // Template consumers: the compiler turns every dynamic part of the markup\n // ({expr}, attributes, {#if}/{#each}/{#key}/{#await} conditions,\n // <svelte:head>) into effects that are not tracked one by one. A read\n // from such an effect is reported as an edge to the synthetic node\n // '<componentId>:(template)' (type 'template') of the component the\n // effect belongs to (effect.ctx), so a value only the markup reads is\n // not shown without readers.\n getReactiveGraph(componentId, caps) {\n const scoped = componentId !== undefined && componentId !== null;\n const maxNodes = this._graphCap(caps && caps.maxNodes, 5000);\n const maxEdges = this._graphCap(caps && caps.maxEdges, 20000);\n const live = new Map();\n // Live signal of a tracked node, or null (forgets dead / unmounted nodes).\n const liveSignal = (nodeId) => {\n if (live.has(nodeId)) return live.get(nodeId);\n const signal = this._liveSignal(nodeId);\n live.set(nodeId, signal);\n return signal;\n };\n const idOf = (dep) => {\n const id = this._idBySignal.get(dep);\n return id !== undefined && liveSignal(id) === dep ? id : null;\n };\n // Synthetic template nodes reached in this build: id -> componentId.\n const templates = new Map();\n // Template node of an untracked effect (no reactions: not a derived),\n // or null when its component is unknown / unmounted.\n const templateOf = (effect) => {\n if (!effect || 'reactions' in effect || !effect.ctx) return null;\n const cid = this._idByContext.get(effect.ctx);\n if (cid === undefined || !this._instances.has(cid)) return null;\n const id = cid + ':(template)';\n templates.set(id, cid);\n return id;\n };\n\n const included = new Set();\n const seen = new Set();\n const edges = [];\n const edgeSet = new Set();\n let edgesOmitted = 0;\n let truncated = false;\n const see = (id) => {\n seen.add(id);\n if (!included.has(id) && included.size < maxNodes) included.add(id);\n return included.has(id);\n };\n const addEdge = (from, to) => {\n const key = from + '>' + to;\n if (from === to || edgeSet.has(key)) return;\n edgeSet.add(key);\n const inFrom = see(from);\n const inTo = see(to);\n if (inFrom && inTo && edges.length < maxEdges) edges.push({ from, to });\n else edgesOmitted++;\n };\n // Walk a signal's neighbours (deps or reactions) through untracked\n // intermediates until tracked nodes are reached. onTemplate (reactions\n // only) receives the template node of each untracked effect reached.\n // On the deps side an untracked leaf source (a proxy's property) is\n // resolved to its proxy's node (readerCid: the reading component).\n // nodeId -> dependencies on signals devtools does not track (created\n // in node_modules, e.g. SvelteKit's page state, or outside any\n // component/module body): reported so such a node is not \"isolated\"\n const untracked = new Map();\n const walk = (start, field, onHit, onTemplate, readerCid, onUntracked) => {\n if (!start) return;\n const visited = new Set();\n const queue = [...start];\n // Index cursor instead of queue.shift(): shift is O(queue) per pop.\n for (let qi = 0; qi < queue.length; qi++) {\n const dep = queue[qi];\n if (!dep || visited.has(dep)) continue;\n visited.add(dep);\n const id = idOf(dep);\n if (id) {\n if (onHit) onHit(id);\n } else if (dep[field]) {\n for (const d of dep[field]) queue.push(d);\n } else if (onTemplate) {\n const t = templateOf(dep);\n if (t) onTemplate(t);\n } else if (field === 'deps' && onHit && !('fn' in dep)) {\n const owner = this._sourceOwner(dep, readerCid, liveSignal);\n if (owner) onHit(owner);\n else if (onUntracked) onUntracked();\n }\n }\n };\n const nodeCid = (nodeId) => this._reactiveNodes.get(nodeId).meta.componentId;\n const countUntracked = (nodeId) => () => untracked.set(nodeId, (untracked.get(nodeId) || 0) + 1);\n // Reads of a component's markup effects: edges into its template node\n // (filter: keep only edges from these node ids, for neighbours).\n const markupEdges = (cid, filter) => {\n const effects = this._markupEffects(cid);\n if (effects.length === 0) return;\n const t = cid + ':(template)';\n for (const effect of effects) {\n walk(effect.deps, 'deps', (id) => {\n if (filter && !filter(id)) return;\n templates.set(t, cid);\n addEdge(id, t);\n }, null, cid);\n }\n };\n\n let totalNodes = 0;\n let totalEdges = null;\n if (scoped) {\n for (const nodeId of [...(this._nodesByComponent.get(componentId) || [])]) {\n const signal = liveSignal(nodeId);\n if (!signal) continue;\n see(nodeId);\n walk(signal.deps, 'deps', (depId) => addEdge(depId, nodeId), null, componentId, countUntracked(nodeId));\n const toReader = (rId) => addEdge(nodeId, rId);\n walk(signal.reactions, 'reactions', toReader, toReader);\n }\n if (this._instances.has(componentId)) markupEdges(componentId, null);\n // Readers in direct children (props): a proxy has no reactions list\n // of its own, so its readers are found from their dependencies.\n const own = (id) => nodeCid(id) === componentId;\n const instance = this._instances.get(componentId);\n for (const child of instance ? instance.children : []) {\n for (const nodeId of [...(this._nodesByComponent.get(child) || [])]) {\n const signal = liveSignal(nodeId);\n if (signal) walk(signal.deps, 'deps', (depId) => own(depId) && addEdge(depId, nodeId), null, child);\n }\n markupEdges(child, own);\n }\n totalNodes = seen.size;\n totalEdges = edgeSet.size;\n truncated = included.size < seen.size || edgesOmitted > 0;\n } else {\n let complete = true;\n for (const nodeId of [...this._reactiveNodes.keys()]) {\n if (included.size >= maxNodes && !included.has(nodeId)) {\n complete = false;\n break;\n }\n const signal = liveSignal(nodeId);\n if (!signal) continue;\n see(nodeId);\n walk(signal.deps, 'deps', (depId) => addEdge(depId, nodeId), null, nodeCid(nodeId), countUntracked(nodeId));\n // tracked readers are found from their own deps; only template\n // readers need the reactions side\n walk(signal.reactions, 'reactions', null, (t) => addEdge(nodeId, t));\n }\n if (complete) for (const cid of this._instances.keys()) markupEdges(cid, null);\n // registered count (+ template nodes reached), minus nodes found\n // dead while building\n totalNodes = this._reactiveNodes.size + templates.size;\n totalEdges = complete ? edgeSet.size : null;\n truncated = !complete || edgesOmitted > 0;\n }\n\n const nodes = [];\n for (const nodeId of included) {\n const cid = templates.get(nodeId);\n if (cid !== undefined) {\n const instance = this._instances.get(cid);\n nodes.push({ id: nodeId, type: 'template', name: '(template)', componentId: cid, componentFile: instance ? instance.file : '' });\n continue;\n }\n const signal = liveSignal(nodeId);\n if (!signal) continue;\n const node = this._graphNode(nodeId, this._reactiveNodes.get(nodeId), signal);\n if (untracked.has(nodeId)) node.untrackedDeps = untracked.get(nodeId);\n nodes.push(node);\n }\n return {\n scope: scoped ? componentId : null,\n nodes,\n edges,\n total: { nodes: totalNodes, nodesKind: 'registered', edges: totalEdges },\n truncated,\n edgesOmitted,\n policy: scoped ? 'scoped' : 'global-head',\n };\n },\n\n _graphCap(value, max) {\n const n = Math.floor(Number(value));\n return Number.isFinite(n) && n >= 1 ? Math.min(n, max) : max;\n },\n\n // Request (§6.7 A + review M1): { requestId?, epoch?, componentId?,\n // maxNodes?, maxEdges? }. A request for another page load (epoch) is not\n // answered by this one; the reply echoes requestId and epoch. An old\n // server sends {} → global graph with the default caps (= its LIMITS).\n sendReactiveGraph(request) {\n const req = request || {};\n if (req.epoch !== undefined && req.epoch !== null && req.epoch !== this._epoch) return;\n const cid = typeof req.componentId === 'number' ? req.componentId : null;\n const graph = this.getReactiveGraph(cid, { maxNodes: req.maxNodes, maxEdges: req.maxEdges });\n graph.epoch = this._epoch;\n // freshness (review): when this answer was built; caches keep it\n graph.computedAt = Date.now();\n if (req.requestId !== undefined) graph.requestId = req.requestId;\n if (import.meta.hot) {\n import.meta.hot.send('svelte-devtools:reactive-graph', graph);\n }\n },\n\n // Overview aggregate (§6.7 I/J). Built from the activity buckets of the\n // window (whole seconds: the current partial second plus the previous\n // ones, ceil(windowMs / 1000) buckets) — cost O(components active in the\n // window), no graph, no node scan. Rows rank by sampled changes, then\n // renders, then render ms; rows + other = total (components and nodes).\n getReactiveSummary(opts) {\n const o = opts || {};\n const clampInt = (v, lo, hi, d) => {\n const n = Math.floor(Number(v));\n return Number.isFinite(n) ? Math.min(hi, Math.max(lo, n)) : d;\n };\n const topK = clampInt(o.topK, 1, 200, 50);\n const windowMs = clampInt(o.windowMs, 1000, 60000, 10000);\n const nowSec = Math.floor(performance.now() / 1000);\n const agg = new Map();\n let sampledMs = 0;\n for (let k = 0; k < Math.ceil(windowMs / 1000); k++) {\n const sec = nowSec - k;\n const b = this._activity[((sec % 60) + 60) % 60];\n if (!b || b.second !== sec) continue;\n sampledMs += b.sampledMs;\n for (const [cid, r] of b.rows) {\n if (!this._scopeLive(cid)) continue;\n let a = agg.get(cid);\n if (!a) {\n a = { changes: 0, renders: 0, renderMs: 0 };\n agg.set(cid, a);\n }\n a.changes += r.changes;\n a.renders += r.renders;\n a.renderMs += r.renderMs;\n }\n }\n const ranked = [...agg].sort(\n (x, y) =>\n y[1].changes - x[1].changes || y[1].renders - x[1].renders || y[1].renderMs - x[1].renderMs || x[0] - y[0],\n );\n let rowNodes = 0;\n const rows = ranked.slice(0, topK).map(([cid, a]) => {\n const n = this._nodeCounts.get(cid) || { state: 0, derived: 0, effect: 0 };\n rowNodes += n.state + n.derived + n.effect;\n const row = {\n componentId: cid,\n file: this._scopeFile(cid),\n nodes: { state: n.state, derived: n.derived, effect: n.effect },\n changes: a.changes,\n renders: a.renders,\n renderMs: Math.round(a.renderMs * 1000) / 1000,\n };\n // module scope (shared .svelte.js state), not a component instance\n if (this._modules.has(cid)) row.kind = 'module';\n return row;\n });\n const t = this._nodeTotals;\n // module scopes count as rows like components (rows + other = total)\n const scopes = this._instances.size + this._modules.size;\n const until = Date.now();\n return {\n epoch: this._epoch,\n window: { ms: windowMs, since: until - windowMs, until, sampledActiveMs: Math.min(windowMs, sampledMs) },\n policy: 'sampled-200ms',\n coverage: 'component-init',\n components: { total: scopes, withActivity: agg.size },\n rows,\n other: { components: scopes - rows.length, nodes: t.state + t.derived + t.effect - rowNodes },\n truncated: agg.size > topK,\n capabilities: { valueInspection: false, signalHistory: false, writeCause: false },\n // Seed coverage: changes of pending nodes are not observable yet.\n baseline: this._baselineInfo(),\n };\n },\n\n // Request { requestId?, epoch?, topK?, windowMs? } (same correlation as\n // the graph, review M1).\n sendReactiveSummary(request) {\n const req = request || {};\n if (req.epoch !== undefined && req.epoch !== null && req.epoch !== this._epoch) return;\n const summary = this.getReactiveSummary(req);\n if (req.requestId !== undefined) summary.requestId = req.requestId;\n if (import.meta.hot) {\n import.meta.hot.send('svelte-devtools:reactive-summary', summary);\n }\n },\n\n // --- Phase 3: State Timeline ---\n\n // Current value of a $state node: the signal value, or for tag_proxy\n // nodes (non-reassigned object/array state) the live proxy itself.\n // Returns a sentinel when the value can't be read.\n _readStateValue(nodeId, signal) {\n if (signal._isProxy) {\n const proxy = this._reactiveProxies.get(nodeId)?.deref();\n return proxy === undefined ? __NO_VALUE : proxy;\n }\n const v = signal.v;\n // Svelte's UNINITIALIZED marker is a symbol; functions aren't state data.\n return typeof v === 'symbol' || typeof v === 'function' ? __NO_VALUE : v;\n },\n\n // Runs every 200 ms in the user's app, so it must stay cheap regardless of\n // how much state the app holds (§6.7 D).\n // 1. Hint pass, time-sliced: at most HINT_NODES $state nodes per tick,\n // continuing from a cursor carried across ticks, so a full sweep over\n // n nodes takes ceil(n / HINT_NODES) ticks (_pollSweep.lastMs is the\n // measured sweep time = worst-case detection delay). Per node O(1):\n // primitives are compared directly and recorded at once; objects\n // whose reference or Svelte write version (wv) changed are added to\n // _pollDirty, which persists across ticks until serialized.\n // 2. Serialization work is limited to BUDGET_MS per tick (dirty nodes\n // carry over, nothing is dropped).\n // 3. In-place deep mutations (proxies don't bump an outer wv) are found by\n // time-based re-checks: each object is re-verified no earlier than\n // nextCheckAt = now + max(RECHECK_MS, 50 x its serialization cost),\n // paid from a DEEP_MS-per-tick credit, so the amortized cost is bounded\n // however many large states the app holds.\n // 4. Values serializing above MAX_SNAPSHOT_CHARS are kept as length+hash\n // (not the string) and appear in the timeline as a summary.\n // Writes between two visits of a node are observed as one change (the\n // timeline is a sample, policy 'sampled-200ms'; not counted as dropped).\n _pollStateValues(baseline) {\n const HINT_NODES = 4096;\n const SWEEP_NODES = 2048;\n const BUDGET_MS = 2;\n const DEEP_MS = 0.5;\n const now = performance.now();\n if (!baseline) {\n // nodes of destroyed {#each} items / effect runs leave the counts\n this._sweepNodes(SWEEP_NODES);\n this._deepCredit = Math.min(10 * DEEP_MS, (this._deepCredit || 0) + DEEP_MS);\n // this tick samples 200 ms of wall time (window.sampledActiveMs)\n const bucket = this._bucket();\n bucket.sampledMs = Math.min(1000, bucket.sampledMs + 200);\n }\n const ids = this._pollIds;\n const due = [];\n const limit = Math.min(HINT_NODES, ids.length);\n for (let visited = 0; visited < limit && ids.length > 0; visited++) {\n if (this._pollCursor >= ids.length) {\n this._pollCursor = 0;\n if (!baseline) {\n if (this._pollSweep.startedAt) this._pollSweep.lastMs = now - this._pollSweep.startedAt;\n this._pollSweep.startedAt = now;\n this._pollSweep.nodes = ids.length;\n }\n }\n if (!baseline && !this._pollSweep.startedAt) this._pollSweep.startedAt = now;\n const nodeId = ids[this._pollCursor];\n const entry = this._reactiveNodes.get(nodeId);\n if (!entry || entry.meta.type !== 'state') {\n // re-tracked as another type: drop from the poll list (slot reused)\n this._pollRemove(nodeId);\n continue;\n }\n if (!this._scopeLive(entry.meta.componentId)) {\n this._forgetNode(nodeId); // swaps the last id into this slot\n continue;\n }\n this._pollCursor++;\n const signal = entry.signal.deref();\n const value = signal ? this._readStateValue(nodeId, signal) : __NO_VALUE;\n if (value === __NO_VALUE) {\n // nothing readable (collected, uninitialized, function): not pending\n this._unseeded.delete(nodeId);\n continue;\n }\n if (value === null || typeof value !== 'object') {\n const had = this._stateSnapshots.has(nodeId);\n const prev = this._stateSnapshots.get(nodeId);\n if (had && Object.is(prev, value)) continue;\n // Forget the object key so object -> primitive -> same object again\n // is still recorded as a change.\n if (this._stateSnapshotStrs) this._stateSnapshotStrs.delete(nodeId);\n this._pollDirty.delete(nodeId);\n if (!had) {\n // first observation: seed only\n this._stateSnapshots.set(nodeId, value);\n this._unseeded.delete(nodeId);\n continue;\n }\n this._recordChange(nodeId, entry, prev, value, typeof value === 'string' ? 2 * value.length : 64);\n continue;\n }\n let meta = this._pollMeta.get(nodeId);\n if (!meta) {\n meta = { ref: undefined, wv: undefined, nextCheckAt: 0 };\n this._pollMeta.set(nodeId, meta);\n }\n if (meta.ref !== value || meta.wv !== signal.wv) this._pollDirty.add(nodeId);\n else if (now >= meta.nextCheckAt) due.push(nodeId);\n }\n const start = performance.now();\n for (const nodeId of this._pollDirty) {\n if (performance.now() - start > BUDGET_MS) return;\n this._pollDirty.delete(nodeId);\n this._serializeNode(nodeId, false);\n }\n for (const nodeId of due) {\n if (this._deepCredit <= 0 || performance.now() - start > BUDGET_MS) return;\n this._serializeNode(nodeId, true);\n }\n },\n\n // Activation (late consumer): nodes registered while inactive have no\n // seed yet, so a write before their first visit could not be seen as a\n // change. The first slice is seeded at activation instead of 200 ms later:\n // ONE normal pass (<= HINT_NODES nodes, the per-tick serialization budget;\n // the rest follow on the usual ticks). Seeds record nothing; readiness is\n // disclosed in the summary as baseline { complete, pendingNodes }.\n _pollBaseline() {\n this._baselining = true;\n try {\n this._pollStateValues(true);\n } catch { /* never break activation */ } finally {\n this._baselining = false;\n }\n },\n\n _serializeNode(nodeId, deep) {\n const MAX_SNAPSHOT_CHARS = 32768;\n const RECHECK_MS = 1000;\n const entry = this._reactiveNodes.get(nodeId);\n const meta = this._pollMeta.get(nodeId);\n if (!entry || !meta) return;\n const signal = entry.signal.deref();\n if (!signal) return;\n const value = this._readStateValue(nodeId, signal);\n if (value === __NO_VALUE || value === null || typeof value !== 'object') return;\n try {\n const t0 = performance.now();\n const str = JSON.stringify(value);\n const cost = performance.now() - t0;\n if (deep) this._deepCredit -= cost;\n meta.ref = value;\n meta.wv = signal.wv;\n meta.nextCheckAt = t0 + Math.max(RECHECK_MS, 50 * cost);\n // Small values keep the string (exact compare); large ones only a\n // length+hash so a 5 MB array doesn't pin 5 MB per node.\n const key = str === undefined ? '' : str.length > MAX_SNAPSHOT_CHARS ? str.length + ':' + __hash(str) : str;\n if (!this._stateSnapshotStrs) this._stateSnapshotStrs = new Map();\n const hadKey = this._stateSnapshotStrs.has(nodeId);\n if (hadKey && this._stateSnapshotStrs.get(nodeId) === key) return;\n this._stateSnapshotStrs.set(nodeId, key);\n const tooLarge = str !== undefined && str.length > MAX_SNAPSHOT_CHARS;\n const snapshot =\n str === undefined\n ? null\n : tooLarge\n ? '(object: ' + str.length + ' chars, too large to snapshot)'\n : JSON.parse(str);\n const size = typeof snapshot === 'string' ? 64 : str.length;\n if (!this._stateSnapshots.has(nodeId)) {\n // first observation: seed only (no entry, no counters)\n this._stateSnapshots.set(nodeId, snapshot);\n this._lastSnapshotSize.set(nodeId, size);\n this._unseeded.delete(nodeId);\n return;\n }\n if (tooLarge) this._valueTooLarge++;\n const prev = this._stateSnapshots.get(nodeId);\n this._recordChange(nodeId, entry, prev, snapshot, size + (this._lastSnapshotSize.get(nodeId) || 0));\n this._lastSnapshotSize.set(nodeId, size);\n } catch {\n // non-serializable (cycles, BigInt): never observable, so not pending\n this._unseeded.delete(nodeId);\n }\n },\n\n _recordChange(nodeId, entry, oldValue, newValue, approxBytes) {\n const change = {\n id: nodeId,\n name: entry.meta.name,\n componentFile: entry.meta.componentFile,\n oldValue,\n newValue,\n timestamp: Date.now(),\n };\n const bytes = (approxBytes || 64) + 192;\n // seq stays runtime-internal: the server assigns its own seq (§6.4).\n this._entryInfo.set(change, { seq: ++this._timelineSeq, bytes });\n this._stateTimeline.push(change);\n this._timelineBytes += bytes;\n // sampled change count (§6.7 J IA2): independent of the ring\n this._activityRow(entry.meta.componentId).changes++;\n this._stateSnapshots.set(nodeId, newValue);\n // The ring (_trimTimeline) is applied in bulk, not with one splice per\n // change (a poll tick can record thousands): before every read (each\n // push — with a hot channel and an active consumer, at least every 200\n // changes — the full snapshot, getStateTimeline() and clear), and here\n // once more than __TIMELINE_TRIM_AT entries are held or the byte budget\n // is exceeded, which bounds memory when nothing reads or pushes.\n if (this._stateTimeline.length > __TIMELINE_TRIM_AT || this._timelineBytes > __TIMELINE_BYTES) {\n this._trimTimeline();\n }\n this._scheduleTimelineUpdate();\n },\n\n // Ring: at most 500 entries and __TIMELINE_BYTES (the newest entry is\n // always kept). Removed entries that were never pushed are disclosed\n // as dropped (§6.7 C); pushed ones are already on the server.\n _trimTimeline() {\n const all = this._stateTimeline;\n let cut = 0;\n let bytesLeft = this._timelineBytes;\n while (cut < all.length - 1 && (all.length - cut > 500 || bytesLeft > __TIMELINE_BYTES)) {\n const info = this._entryInfo.get(all[cut]);\n const reason = all.length - cut > 500 ? 'runtime-count' : 'runtime-bytes';\n if (info.seq > this._pushedSeq) this._timelineDropped[reason]++;\n bytesLeft -= info.bytes;\n cut++;\n }\n if (cut > 0) {\n all.splice(0, cut);\n this._timelineBytes = bytesLeft;\n }\n },\n\n // Push (§6.7 C): a throttle, not a debounce — a pending timer is never\n // re-armed, so unsent entries leave within 300 ms of the first one even\n // under continuous change — plus an immediate flush once 200 entries\n // (one message) are unsent, since one poll tick can record thousands.\n _scheduleTimelineUpdate() {\n if (!this._active || this._baselining) return;\n if (this._timelineSeq - this._pushedSeq >= 200) {\n this._flushTimeline();\n return;\n }\n if (this._timelineDebounceTimer) return;\n this._timelineDebounceTimer = setTimeout(() => {\n this._timelineDebounceTimer = null;\n this._flushTimeline();\n }, 300);\n },\n\n _flushTimeline() {\n if (this._timelineDebounceTimer) {\n clearTimeout(this._timelineDebounceTimer);\n this._timelineDebounceTimer = null;\n }\n if (!import.meta.hot) return;\n for (const msg of this._timelineDeltas()) {\n import.meta.hot.send('svelte-devtools:state-timeline', msg);\n }\n },\n\n // Disclosure counters since the previous push, attached to (and reset\n // by) the next message: dropped [{ reason, count }] and valueTooLarge.\n _takeTimelineDisclosure(msg) {\n msg.baseline = this._baselineInfo();\n this._sentPendingNodes = msg.baseline.pendingNodes;\n const dropped = [];\n for (const reason of ['runtime-count', 'runtime-bytes']) {\n const count = this._timelineDropped[reason];\n if (count > 0) dropped.push({ reason, count });\n this._timelineDropped[reason] = 0;\n }\n if (dropped.length) msg.dropped = dropped;\n if (this._valueTooLarge > 0) msg.valueTooLarge = this._valueTooLarge;\n this._valueTooLarge = 0;\n return msg;\n },\n\n // Seed coverage (review B-2), on every state-timeline message and the\n // summary: pendingNodes = polled $state nodes without a first sample;\n // their writes are not observable yet.\n _baselineInfo() {\n return { complete: this._unseeded.size === 0, pendingNodes: this._unseeded.size };\n },\n\n _hasTimelineDisclosure() {\n return (\n this._sentPendingNodes !== this._unseeded.size ||\n this._timelineDropped['runtime-count'] > 0 ||\n this._timelineDropped['runtime-bytes'] > 0 ||\n this._valueTooLarge > 0\n );\n },\n\n // Delta push (docs/devframe-migration.md §6.4): only entries recorded\n // since the previous push, ≤ 200 per message, tagged with a per-page-load\n // epoch; reset after clearStateTimeline(). A message with no changes is\n // still sent when there is something to disclose (all unsent dropped).\n _timelineDeltas() {\n this._trimTimeline();\n const all = this._stateTimeline;\n let start = all.length;\n while (start > 0 && this._entryInfo.get(all[start - 1]).seq > this._pushedSeq) start--;\n const pending = all.slice(start);\n const msgs = [];\n const first = this._timelineReset || this._hasTimelineDisclosure();\n for (let i = 0; i < pending.length || (first && i === 0); i += 200) {\n const msg = { epoch: this._epoch, changes: pending.slice(i, i + 200) };\n if (i === 0 && this._timelineReset) msg.reset = true;\n if (i === 0) this._takeTimelineDisclosure(msg);\n else msg.baseline = this._baselineInfo();\n msgs.push(msg);\n }\n this._timelineReset = false;\n this._pushedSeq = this._timelineSeq;\n return msgs;\n },\n\n // Full buffer (reset: true) — reply to request-state-timeline and the\n // activation snapshot. Newest entries first up to __TIMELINE_BYTES, the\n // server's own buffer budget, so nothing is sent that it would drop.\n _timelineFull() {\n this._trimTimeline();\n const all = this._stateTimeline;\n let start = all.length;\n let bytes = 0;\n while (start > 0) {\n const size = this._entryInfo.get(all[start - 1]).bytes;\n if (bytes + size > __TIMELINE_BYTES && start < all.length) break;\n bytes += size;\n start--;\n }\n this._timelineReset = false;\n this._pushedSeq = this._timelineSeq;\n return this._takeTimelineDisclosure({ epoch: this._epoch, changes: all.slice(start), reset: true });\n },\n\n getStateTimeline() {\n this._trimTimeline();\n return this._stateTimeline;\n },\n\n clearStateTimeline() {\n // Ring first: unsent entries past the cap are disclosed as dropped,\n // exactly as if they had been removed when recorded.\n this._trimTimeline();\n this._stateTimeline = [];\n this._timelineBytes = 0;\n this._pushedSeq = this._timelineSeq;\n this._timelineReset = true;\n this._scheduleTimelineUpdate();\n },\n\n // --- FPS Monitoring ---\n // Uses requestAnimationFrame to measure frame rate with minimal overhead.\n // Only stores timestamps - no allocations per frame beyond a single array push.\n _fpsFrameTimes: [],\n\n _fpsLoop(gen) {\n if (gen !== this._fpsGen) return;\n this._fpsFrameTimes.push(performance.now());\n requestAnimationFrame(() => this._fpsLoop(gen));\n },\n\n _isVisible() {\n return typeof document === 'undefined' || document.visibilityState !== 'hidden';\n },\n\n _setSubscription(data) {\n const wasActive = this._active;\n this._active = !!(data && data.active);\n this._componentDeltas = !!(data && data.componentDeltas);\n if (!this._active) {\n this._pendingAdded.clear();\n this._pendingRemoved.clear();\n }\n // resync: the server holds no full base for this epoch (§6.5, review\n // C1), or we reconnected to a possibly restarted server.\n const resync = this._resync || !!(data && data.resync);\n this._resync = false;\n if (this._active && !wasActive) this._pollBaseline();\n if (this._active && (!wasActive || resync)) {\n // Collector state may be stale or empty (first activation, resync, or a\n // reconnect to a possibly restarted server): resend everything once, in\n // the order components -> profiles -> timeline(reset: true); the\n // reset push marks the activation snapshot complete (D4).\n this._sendUpdate(true);\n this._sendProfileUpdate();\n if (import.meta.hot) {\n import.meta.hot.send('svelte-devtools:state-timeline', this._timelineFull());\n }\n }\n this._syncSampling();\n },\n\n // Sampling (state poll + FPS) runs only while active AND the app tab is\n // visible.\n _syncSampling() {\n const want = this._active && this._isVisible();\n if (want && !this._sampling) {\n this._fpsFrameTimes = [];\n const gen = ++this._fpsGen;\n this._sampling = {\n poll: setInterval(() => {\n this._pollStateValues();\n // seed progress changed: push it even without changes (B-2)\n if (this._sentPendingNodes !== this._unseeded.size) this._scheduleTimelineUpdate();\n }, 200),\n fps: setInterval(() => { this._sampleFps(); }, 500),\n };\n requestAnimationFrame(() => this._fpsLoop(gen));\n } else if (!want && this._sampling) {\n clearInterval(this._sampling.poll);\n clearInterval(this._sampling.fps);\n this._sampling = null;\n this._fpsGen++;\n this._fpsFrameTimes = [];\n }\n },\n\n _sampleFps() {\n const now = performance.now();\n // Remove frame timestamps older than 1 second\n const cutoff = now - 1000;\n let i = 0;\n while (i < this._fpsFrameTimes.length && this._fpsFrameTimes[i] < cutoff) i++;\n if (i > 0) this._fpsFrameTimes.splice(0, i);\n const fps = this._fpsFrameTimes.length;\n if (import.meta.hot) {\n import.meta.hot.send('svelte-devtools:fps', { timestamp: Date.now(), fps });\n }\n }\n };\n\n window.__SVELTE_DEVTOOLS__ = __SVELTE_DT;\n\n // State polling and FPS sampling start on the server's subscription\n // message (§6.3) and pause while the app tab is hidden.\n if (typeof document !== 'undefined' && document.addEventListener) {\n document.addEventListener('visibilitychange', () => __SVELTE_DT._syncSampling());\n }\n\n // Phase 3: Capture runtime errors\n window.addEventListener('error', (event) => {\n if (import.meta.hot) {\n import.meta.hot.send('svelte-devtools:runtime-error', {\n message: event.message,\n file: event.filename,\n line: event.lineno,\n column: event.colno,\n stack: event.error?.stack || '',\n timestamp: Date.now(),\n });\n }\n });\n window.addEventListener('unhandledrejection', (event) => {\n if (import.meta.hot) {\n const reason = event.reason;\n import.meta.hot.send('svelte-devtools:runtime-error', {\n message: reason?.message || String(reason),\n stack: reason?.stack || '',\n timestamp: Date.now(),\n });\n }\n });\n\n // Listen for reactive graph requests from server\n // Pull requests are answered regardless of the subscription state.\n if (import.meta.hot) {\n import.meta.hot.on('svelte-devtools:subscription', (data) => {\n __SVELTE_DT._setSubscription(data);\n });\n import.meta.hot.on('vite:ws:connect', () => {\n // The server may have restarted with an empty collector: the reply to\n // runtime-ready is treated as a fresh activation (full snapshot).\n __SVELTE_DT._resync = true;\n import.meta.hot.send('svelte-devtools:runtime-ready', {});\n });\n import.meta.hot.send('svelte-devtools:runtime-ready', {});\n import.meta.hot.on('svelte-devtools:request-reactive-graph', (data) => {\n __SVELTE_DT.sendReactiveGraph(data);\n });\n import.meta.hot.on('svelte-devtools:request-reactive-summary', (data) => {\n __SVELTE_DT.sendReactiveSummary(data);\n });\n import.meta.hot.on('svelte-devtools:request-state-timeline', () => {\n import.meta.hot.send('svelte-devtools:state-timeline', __SVELTE_DT._timelineFull());\n });\n import.meta.hot.on('svelte-devtools:clear-state-timeline', () => {\n __SVELTE_DT.clearStateTimeline();\n });\n }\n}\n", C = {
|
|
875
|
+
//#region src/server/collector.ts
|
|
876
|
+
var N = {
|
|
214
877
|
liveComponents: 5e4,
|
|
215
878
|
renderProfiles: 5e3,
|
|
216
879
|
stateTimeline: 500,
|
|
@@ -220,7 +883,7 @@ var S = "virtual:svelte-devtools-runtime", ge = "\0virtual:svelte-devtools-runti
|
|
|
220
883
|
runtimeErrors: 200,
|
|
221
884
|
loadProfiles: 200,
|
|
222
885
|
compilerWarnings: 500
|
|
223
|
-
},
|
|
886
|
+
}, Ye = 1e3, Xe = Date.now() * 1e3, P = () => ++Xe, F = "(legacy)", I = {
|
|
224
887
|
components: "svelte-devtools:components",
|
|
225
888
|
profiles: "svelte-devtools:profiles",
|
|
226
889
|
stateTimeline: "svelte-devtools:state-timeline",
|
|
@@ -234,36 +897,56 @@ var S = "virtual:svelte-devtools-runtime", ge = "\0virtual:svelte-devtools-runti
|
|
|
234
897
|
clearStateTimeline: "svelte-devtools:clear-state-timeline",
|
|
235
898
|
runtimeReady: "svelte-devtools:runtime-ready",
|
|
236
899
|
subscription: "svelte-devtools:subscription"
|
|
237
|
-
},
|
|
900
|
+
}, Ze = 32, L = {
|
|
238
901
|
topK: 50,
|
|
239
902
|
maxTopK: 200,
|
|
240
903
|
windowMs: 1e4,
|
|
241
904
|
minWindowMs: 1e3,
|
|
242
905
|
maxWindowMs: 6e4
|
|
243
|
-
},
|
|
906
|
+
}, Qe = [
|
|
244
907
|
"runtime-count",
|
|
245
908
|
"runtime-bytes",
|
|
246
909
|
"server-count",
|
|
247
910
|
"server-bytes"
|
|
248
911
|
];
|
|
249
|
-
function
|
|
912
|
+
function R(e) {
|
|
250
913
|
let t = e;
|
|
251
914
|
return !!t && typeof t.complete == "boolean" && Number.isInteger(t.pendingNodes) && t.pendingNodes >= 0;
|
|
252
915
|
}
|
|
253
|
-
function
|
|
916
|
+
function z(e, t, n, r) {
|
|
254
917
|
return typeof e != "number" || !Number.isFinite(e) ? r : Math.min(n, Math.max(t, Math.trunc(e)));
|
|
255
918
|
}
|
|
256
|
-
|
|
257
|
-
|
|
919
|
+
function B(e) {
|
|
920
|
+
return z(e, 0, 2 ** 53 - 1, 0);
|
|
921
|
+
}
|
|
922
|
+
function V(e) {
|
|
923
|
+
return typeof e == "object" && e && !Array.isArray(e) ? e : void 0;
|
|
924
|
+
}
|
|
925
|
+
function $e(e) {
|
|
926
|
+
let t = V(e);
|
|
927
|
+
return typeof t?.timestamp == "number" && typeof t.fps == "number";
|
|
928
|
+
}
|
|
929
|
+
function et(e) {
|
|
930
|
+
return typeof V(e)?.message == "string";
|
|
931
|
+
}
|
|
932
|
+
function H(e, t) {
|
|
933
|
+
return {
|
|
934
|
+
captured: e,
|
|
935
|
+
total: t,
|
|
936
|
+
truncated: t > e,
|
|
937
|
+
policy: "tail"
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
var tt = 15e3;
|
|
941
|
+
function nt(e, t) {
|
|
258
942
|
let n = Array.isArray(e) ? e : [];
|
|
259
943
|
return n.length > t ? n.slice(-t) : n;
|
|
260
944
|
}
|
|
261
|
-
function
|
|
945
|
+
function U(e, t) {
|
|
262
946
|
let n = Array.isArray(e) ? e : [];
|
|
263
947
|
return n.length > t ? n.slice(0, t) : n;
|
|
264
948
|
}
|
|
265
|
-
var
|
|
266
|
-
hooks;
|
|
949
|
+
var rt = class {
|
|
267
950
|
epochs = /* @__PURE__ */ new Map();
|
|
268
951
|
servedEpoch;
|
|
269
952
|
liveCache;
|
|
@@ -276,22 +959,22 @@ var we = class {
|
|
|
276
959
|
runtimeErrors = [];
|
|
277
960
|
fpsSamples = [];
|
|
278
961
|
versions = {
|
|
279
|
-
components:
|
|
280
|
-
renderProfiles:
|
|
281
|
-
loadProfiles:
|
|
282
|
-
stateTimeline:
|
|
283
|
-
reactiveGraph:
|
|
284
|
-
errors:
|
|
285
|
-
fps:
|
|
962
|
+
components: P(),
|
|
963
|
+
renderProfiles: P(),
|
|
964
|
+
loadProfiles: P(),
|
|
965
|
+
stateTimeline: P(),
|
|
966
|
+
reactiveGraph: P(),
|
|
967
|
+
errors: P(),
|
|
968
|
+
fps: P()
|
|
286
969
|
};
|
|
287
970
|
bump(...e) {
|
|
288
|
-
for (let t of e) this.versions[t] =
|
|
971
|
+
for (let t of e) this.versions[t] = P();
|
|
289
972
|
}
|
|
290
973
|
timeline = [];
|
|
291
974
|
timelineSizes = [];
|
|
292
975
|
timelineEpochs = [];
|
|
293
976
|
timelineBytes = 0;
|
|
294
|
-
timelineSeq =
|
|
977
|
+
timelineSeq = P();
|
|
295
978
|
timelineResetAt = this.timelineSeq;
|
|
296
979
|
timelineTrimmedAt = this.timelineSeq;
|
|
297
980
|
lastPull = { stateTimeline: 0 };
|
|
@@ -317,20 +1000,25 @@ var we = class {
|
|
|
317
1000
|
active = !1;
|
|
318
1001
|
sweepTimer;
|
|
319
1002
|
stateTimelineResolvers = [];
|
|
1003
|
+
hooks;
|
|
320
1004
|
constructor(e = {}) {
|
|
321
1005
|
this.hooks = e;
|
|
322
1006
|
}
|
|
323
1007
|
attach(e) {
|
|
324
1008
|
this.detach(), this.hot = e;
|
|
325
1009
|
let t = [
|
|
326
|
-
[
|
|
327
|
-
[
|
|
328
|
-
[
|
|
329
|
-
[
|
|
330
|
-
[
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
[
|
|
1010
|
+
[I.runtimeReady, (e, t) => t?.send(I.subscription, this.subscription)],
|
|
1011
|
+
[I.components, (e, t) => this.ingestComponents(V(e), t)],
|
|
1012
|
+
[I.profiles, (e) => this.ingestProfiles(V(e))],
|
|
1013
|
+
[I.stateTimeline, (e) => this.ingestStateTimeline(V(e))],
|
|
1014
|
+
[I.fps, (e) => {
|
|
1015
|
+
$e(e) && this.ingestFps(e);
|
|
1016
|
+
}],
|
|
1017
|
+
[I.runtimeError, (e) => {
|
|
1018
|
+
et(e) && this.ingestRuntimeError(e);
|
|
1019
|
+
}],
|
|
1020
|
+
[I.reactiveGraph, (e) => this.ingestReactiveGraph(V(e))],
|
|
1021
|
+
[I.reactiveSummary, (e) => this.ingestReactiveSummary(V(e))]
|
|
334
1022
|
];
|
|
335
1023
|
for (let [n, r] of t) e.on(n, r);
|
|
336
1024
|
this.detachHot = () => {
|
|
@@ -402,7 +1090,7 @@ var we = class {
|
|
|
402
1090
|
serve(e) {
|
|
403
1091
|
this.servedEpoch !== e && (this.servedEpoch !== void 0 && this.bump("components", "renderProfiles"), this.servedEpoch = e, this.liveCache = void 0);
|
|
404
1092
|
}
|
|
405
|
-
lease(e, t =
|
|
1093
|
+
lease(e, t = tt) {
|
|
406
1094
|
let n = this.active;
|
|
407
1095
|
return this.leases.set(e, Date.now() + t), this.updateSubscription(), !n && this.active;
|
|
408
1096
|
}
|
|
@@ -423,12 +1111,12 @@ var we = class {
|
|
|
423
1111
|
let e = Date.now(), t = Infinity;
|
|
424
1112
|
for (let [n, r] of this.leases) r <= e ? this.leases.delete(n) : t = Math.min(t, r);
|
|
425
1113
|
let n = this.leases.size > 0;
|
|
426
|
-
n !== this.active && (this.active = n, this.hot?.send(
|
|
1114
|
+
n !== this.active && (this.active = n, this.hot?.send(I.subscription, this.subscription)), clearTimeout(this.sweepTimer), this.sweepTimer = void 0, t !== Infinity && (this.sweepTimer = setTimeout(() => this.updateSubscription(), t - e + 1), this.sweepTimer.unref?.());
|
|
427
1115
|
}
|
|
428
1116
|
ingestComponents(e, t) {
|
|
429
|
-
let n = typeof e?.epoch == "string" ? e.epoch :
|
|
1117
|
+
let n = typeof e?.epoch == "string" ? e.epoch : F, r = !Array.isArray(e?.components) && (Array.isArray(e?.added) || Array.isArray(e?.removed));
|
|
430
1118
|
if (r && !this.epochs.get(n)?.hasBase) {
|
|
431
|
-
this.resyncRequested.has(n) || (this.resyncRequested.add(n), t?.send(
|
|
1119
|
+
this.resyncRequested.has(n) || (this.resyncRequested.add(n), t?.send(I.subscription, {
|
|
432
1120
|
...this.subscription,
|
|
433
1121
|
resync: !0
|
|
434
1122
|
}));
|
|
@@ -443,33 +1131,29 @@ var we = class {
|
|
|
443
1131
|
i.components.set(t, e);
|
|
444
1132
|
continue;
|
|
445
1133
|
}
|
|
446
|
-
|
|
1134
|
+
let n = e?.parentId;
|
|
1135
|
+
(n == null || i.components.has(n)) && i.components.size < N.liveComponents ? (i.overflow.delete(t), i.components.set(t, e)) : i.overflow.add(t);
|
|
447
1136
|
}
|
|
448
1137
|
this.liveCache = void 0, this.bump("components");
|
|
449
1138
|
}
|
|
450
1139
|
ingestProfiles(e) {
|
|
451
|
-
let t = typeof e?.epoch == "string" ? e.epoch :
|
|
452
|
-
n.profiles =
|
|
1140
|
+
let t = typeof e?.epoch == "string" ? e.epoch : F, n = this.touchEpoch(t);
|
|
1141
|
+
n.profiles = nt(e?.profiles, N.renderProfiles), n.profilesTotal = typeof e?.total == "number" && Number.isFinite(e.total) ? e.total : null, this.bump("renderProfiles");
|
|
453
1142
|
}
|
|
454
1143
|
getCaptureInfo() {
|
|
455
|
-
let e = this.servedState(), t = this.servedEpoch, n =
|
|
456
|
-
captured: e,
|
|
457
|
-
total: t,
|
|
458
|
-
truncated: t > e,
|
|
459
|
-
policy: "tail"
|
|
460
|
-
}), r = {
|
|
1144
|
+
let e = this.servedState(), t = this.servedEpoch, n = {
|
|
461
1145
|
"runtime-count": 0,
|
|
462
1146
|
"runtime-bytes": 0,
|
|
463
1147
|
"server-count": 0,
|
|
464
1148
|
"server-bytes": 0
|
|
465
1149
|
};
|
|
466
|
-
for (let e of this.timelineDrops.values()) for (let t of
|
|
467
|
-
let
|
|
1150
|
+
for (let e of this.timelineDrops.values()) for (let t of Qe) n[t] += e[t];
|
|
1151
|
+
let r = Qe.filter((e) => n[e] > 0).map((e) => ({
|
|
468
1152
|
reason: e,
|
|
469
|
-
count:
|
|
470
|
-
})),
|
|
471
|
-
for (let e of this.timelineTooLarge.values())
|
|
472
|
-
let
|
|
1153
|
+
count: n[e]
|
|
1154
|
+
})), i = 0;
|
|
1155
|
+
for (let e of this.timelineTooLarge.values()) i += e;
|
|
1156
|
+
let a = e?.profiles.length ?? 0, o = e?.profilesTotal ?? null, s = this.lastGraph, c = {
|
|
473
1157
|
liveComponents: {
|
|
474
1158
|
captured: e?.components.size ?? 0,
|
|
475
1159
|
total: this.liveComponentsTotal,
|
|
@@ -478,74 +1162,75 @@ var we = class {
|
|
|
478
1162
|
...t !== void 0 && { epoch: t }
|
|
479
1163
|
},
|
|
480
1164
|
renderProfiles: {
|
|
481
|
-
captured:
|
|
482
|
-
total:
|
|
483
|
-
truncated:
|
|
1165
|
+
captured: a,
|
|
1166
|
+
total: o,
|
|
1167
|
+
truncated: o !== null && o > a,
|
|
484
1168
|
policy: "newest-mounted",
|
|
485
1169
|
...t !== void 0 && { epoch: t }
|
|
486
1170
|
},
|
|
487
1171
|
stateTimeline: {
|
|
488
1172
|
captured: this.timeline.length,
|
|
489
1173
|
total: null,
|
|
490
|
-
truncated:
|
|
1174
|
+
truncated: r.length > 0,
|
|
491
1175
|
policy: "sampled-200ms",
|
|
492
|
-
...
|
|
493
|
-
...
|
|
1176
|
+
...r.length > 0 && { dropped: r },
|
|
1177
|
+
...i > 0 && { valueTooLarge: i },
|
|
494
1178
|
...t !== void 0 && this.timelineBaseline.has(t) && { baseline: this.timelineBaseline.get(t) }
|
|
495
1179
|
},
|
|
496
|
-
fpsSamples:
|
|
497
|
-
runtimeErrors:
|
|
498
|
-
loadProfiles:
|
|
499
|
-
compilerWarnings:
|
|
1180
|
+
fpsSamples: H(this.fpsSamples.length, this.received.fps),
|
|
1181
|
+
runtimeErrors: H(this.runtimeErrors.length, this.received.runtimeErrors),
|
|
1182
|
+
loadProfiles: H(this.loadProfiles.length, this.received.loadProfiles),
|
|
1183
|
+
compilerWarnings: H(this.compilerWarnings.length, this.received.compilerWarnings)
|
|
500
1184
|
};
|
|
501
|
-
if (
|
|
1185
|
+
if (s) {
|
|
502
1186
|
let e = {
|
|
503
|
-
truncated:
|
|
504
|
-
policy:
|
|
505
|
-
...
|
|
1187
|
+
truncated: s.truncated,
|
|
1188
|
+
policy: s.policy,
|
|
1189
|
+
...s.epoch !== null && { epoch: s.epoch }
|
|
506
1190
|
};
|
|
507
|
-
|
|
508
|
-
captured:
|
|
509
|
-
total:
|
|
1191
|
+
c.reactiveNodes = {
|
|
1192
|
+
captured: s.nodes.length,
|
|
1193
|
+
total: s.total?.nodes ?? null,
|
|
510
1194
|
...e
|
|
511
|
-
},
|
|
512
|
-
captured:
|
|
513
|
-
total:
|
|
1195
|
+
}, c.reactiveEdges = {
|
|
1196
|
+
captured: s.edges.length,
|
|
1197
|
+
total: s.total?.edges ?? null,
|
|
514
1198
|
...e
|
|
515
1199
|
};
|
|
516
1200
|
}
|
|
517
|
-
return
|
|
1201
|
+
return c;
|
|
518
1202
|
}
|
|
519
1203
|
get stateTimeline() {
|
|
520
1204
|
return this.timeline;
|
|
521
1205
|
}
|
|
522
1206
|
ingestStateTimeline(e) {
|
|
523
|
-
let t =
|
|
524
|
-
if (typeof e?.epoch == "string") this.touchEpoch(e.epoch), this.countRuntimeLosses(e.epoch, e.dropped, e.valueTooLarge), e.reset === !0 && this.dropEpoch(e.epoch), this.appendTimeline(t, e.epoch),
|
|
1207
|
+
let t = nt(e?.changes, N.stateTimeline);
|
|
1208
|
+
if (typeof e?.epoch == "string") this.touchEpoch(e.epoch), this.countRuntimeLosses(e.epoch, e.dropped, e.valueTooLarge), e.reset === !0 && this.dropEpoch(e.epoch), this.appendTimeline(t, e.epoch), R(e.baseline) && this.timelineBaseline.set(e.epoch, {
|
|
525
1209
|
complete: e.baseline.complete,
|
|
526
1210
|
pendingNodes: e.baseline.pendingNodes
|
|
527
1211
|
});
|
|
528
1212
|
else {
|
|
529
1213
|
let e;
|
|
530
|
-
for (let t = this.timeline.length - 1; t >= 0 && !e; t--) this.timelineEpochs[t] ===
|
|
1214
|
+
for (let t = this.timeline.length - 1; t >= 0 && !e; t--) this.timelineEpochs[t] === F && (e = this.timeline[t]);
|
|
531
1215
|
let n = -1;
|
|
532
|
-
if (e) {
|
|
533
|
-
|
|
1216
|
+
if (e) for (let r = t.length - 1; r >= 0; r--) {
|
|
1217
|
+
let i = t[r];
|
|
1218
|
+
if (i.timestamp === e.timestamp && i.id === e.id) {
|
|
534
1219
|
n = r + 1;
|
|
535
1220
|
break;
|
|
536
1221
|
}
|
|
537
1222
|
}
|
|
538
|
-
n === -1 && (this.dropEpoch(
|
|
1223
|
+
n === -1 && (this.dropEpoch(F), n = 0), this.appendTimeline(t.slice(n), F);
|
|
539
1224
|
}
|
|
540
1225
|
this.lastPull.stateTimeline = Date.now(), this.flushStateTimeline(), e?.reset === !0 && this.resolveSnapshotWaiters();
|
|
541
1226
|
}
|
|
542
1227
|
countRuntimeLosses(e, t, n) {
|
|
543
1228
|
if (Array.isArray(t)) for (let n of t) {
|
|
544
1229
|
if (n?.reason !== "runtime-count" && n?.reason !== "runtime-bytes") continue;
|
|
545
|
-
let t =
|
|
1230
|
+
let t = z(n.count, 0, 2 ** 53 - 1, 0);
|
|
546
1231
|
t > 0 && this.addDrop(e, n.reason, t);
|
|
547
1232
|
}
|
|
548
|
-
let r =
|
|
1233
|
+
let r = z(n, 0, 2 ** 53 - 1, 0);
|
|
549
1234
|
r > 0 && this.timelineTooLarge.set(e, (this.timelineTooLarge.get(e) ?? 0) + r);
|
|
550
1235
|
}
|
|
551
1236
|
addDrop(e, t, n) {
|
|
@@ -573,7 +1258,7 @@ var we = class {
|
|
|
573
1258
|
};
|
|
574
1259
|
}
|
|
575
1260
|
markTimelineReset() {
|
|
576
|
-
this.timelineResetAt = this.timelineSeq =
|
|
1261
|
+
this.timelineResetAt = this.timelineSeq = P(), this.bump("stateTimeline");
|
|
577
1262
|
}
|
|
578
1263
|
dropEpoch(e) {
|
|
579
1264
|
if (!this.timelineEpochs.includes(e)) return;
|
|
@@ -591,10 +1276,10 @@ var we = class {
|
|
|
591
1276
|
}
|
|
592
1277
|
this.timeline.push({
|
|
593
1278
|
...n,
|
|
594
|
-
seq: this.timelineSeq =
|
|
1279
|
+
seq: this.timelineSeq = P()
|
|
595
1280
|
}), this.timelineSizes.push(e), this.timelineEpochs.push(t), this.timelineBytes += e;
|
|
596
1281
|
}
|
|
597
|
-
let n = Math.max(0, this.timeline.length -
|
|
1282
|
+
let n = Math.max(0, this.timeline.length - N.stateTimeline), r = this.timelineBytes;
|
|
598
1283
|
for (let e = 0; e < n; e++) r -= this.timelineSizes[e], this.addDrop(this.timelineEpochs[e], "server-count", 1);
|
|
599
1284
|
for (; r > 4194304 && n < this.timeline.length - 1;) this.addDrop(this.timelineEpochs[n], "server-bytes", 1), r -= this.timelineSizes[n++];
|
|
600
1285
|
n > 0 && (this.timelineTrimmedAt = this.timeline[n - 1].seq, this.timeline = this.timeline.slice(n), this.timelineSizes = this.timelineSizes.slice(n), this.timelineEpochs = this.timelineEpochs.slice(n)), this.timelineBytes = r, this.bump("stateTimeline");
|
|
@@ -604,23 +1289,23 @@ var we = class {
|
|
|
604
1289
|
}
|
|
605
1290
|
ingestReactiveGraph(e) {
|
|
606
1291
|
let t = typeof e?.requestId == "string" ? e.requestId : void 0;
|
|
607
|
-
if (t
|
|
608
|
-
let n = this.pulls.get(t);
|
|
609
|
-
if (!n || n.kind !== "graph" || n.epoch !== null && typeof e?.epoch == "string" && e.epoch !== n.epoch) return;
|
|
610
|
-
this.settle(t, this.normalizeGraph(e, n.scope, n.epoch));
|
|
611
|
-
} else {
|
|
1292
|
+
if (t === void 0) {
|
|
612
1293
|
let t = !1;
|
|
613
1294
|
for (let [n, r] of this.pulls) r.kind === "graph" && (t = !0, this.settle(n, this.normalizeGraph(e, r.scope, r.epoch)));
|
|
614
1295
|
t || this.storeGraph(this.normalizeGraph(e, null, this.servedEpoch ?? null));
|
|
1296
|
+
} else {
|
|
1297
|
+
let n = this.pulls.get(t);
|
|
1298
|
+
if (n?.kind !== "graph" || n.epoch !== null && typeof e?.epoch == "string" && e.epoch !== n.epoch) return;
|
|
1299
|
+
this.settle(t, this.normalizeGraph(e, n.scope, n.epoch));
|
|
615
1300
|
}
|
|
616
1301
|
this.bump("reactiveGraph");
|
|
617
1302
|
}
|
|
618
1303
|
normalizeGraph(e, t, n) {
|
|
619
|
-
let r = typeof e?.requestId != "string", i =
|
|
620
|
-
if (!r &&
|
|
621
|
-
nodes:
|
|
1304
|
+
let r = typeof e?.requestId != "string", i = U(e?.nodes, Infinity), a = U(e?.edges, Infinity), o = e?.policy === "scoped" || e?.policy === "global-head" ? e.policy : t === null ? "global-head" : "scoped", s = null, c = V(e?.total);
|
|
1305
|
+
if (!r && typeof c?.nodes == "number" && (s = {
|
|
1306
|
+
nodes: c.nodes,
|
|
622
1307
|
nodesKind: "registered",
|
|
623
|
-
edges: typeof
|
|
1308
|
+
edges: typeof c.edges == "number" ? c.edges : null
|
|
624
1309
|
}), r && t !== null) {
|
|
625
1310
|
let e = new Set(i.filter((e) => e?.componentId === t).map((e) => e.id)), n = new Set(e);
|
|
626
1311
|
for (let t of a) e.has(t?.from) && n.add(t.to), e.has(t?.to) && n.add(t.from);
|
|
@@ -630,18 +1315,18 @@ var we = class {
|
|
|
630
1315
|
nodesKind: "sent",
|
|
631
1316
|
edges: a.length
|
|
632
1317
|
});
|
|
633
|
-
let
|
|
634
|
-
i.length >
|
|
635
|
-
let
|
|
636
|
-
for (let e of a) !
|
|
1318
|
+
let l = e?.truncated === !0;
|
|
1319
|
+
i.length > N.reactiveNodes && (i = i.slice(0, N.reactiveNodes), l = !0);
|
|
1320
|
+
let u = new Set(i.map((e) => e?.id)), d = z(e?.edgesOmitted, 0, 2 ** 53 - 1, 0), f = [];
|
|
1321
|
+
for (let e of a) !u.has(e?.from) || !u.has(e?.to) ? o !== "server-filter" && d++ : f.length < N.reactiveEdges ? f.push(e) : (d++, l = !0);
|
|
637
1322
|
return {
|
|
638
1323
|
nodes: i,
|
|
639
|
-
edges:
|
|
1324
|
+
edges: f,
|
|
640
1325
|
scope: t,
|
|
641
1326
|
epoch: typeof e?.epoch == "string" ? e.epoch : n,
|
|
642
1327
|
total: s,
|
|
643
|
-
truncated:
|
|
644
|
-
edgesOmitted:
|
|
1328
|
+
truncated: l,
|
|
1329
|
+
edgesOmitted: d,
|
|
645
1330
|
computedAt: typeof e?.computedAt == "number" && Number.isFinite(e.computedAt) ? e.computedAt : null,
|
|
646
1331
|
policy: o
|
|
647
1332
|
};
|
|
@@ -654,61 +1339,63 @@ var we = class {
|
|
|
654
1339
|
}
|
|
655
1340
|
ingestReactiveSummary(e) {
|
|
656
1341
|
let t = typeof e?.requestId == "string" ? e.requestId : void 0, n = t === void 0 ? void 0 : this.pulls.get(t);
|
|
657
|
-
n
|
|
1342
|
+
n?.kind === "summary" && (n.epoch === null || typeof e?.epoch != "string" || e.epoch === n.epoch) && this.settle(t, this.normalizeSummary(e, n.epoch));
|
|
658
1343
|
}
|
|
659
1344
|
normalizeSummary(e, t) {
|
|
660
|
-
let n = (e
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
1345
|
+
let n = U(e?.rows, L.maxTopK).map((e) => {
|
|
1346
|
+
let t = V(e), n = V(t?.nodes), r = {
|
|
1347
|
+
componentId: B(t?.componentId),
|
|
1348
|
+
file: typeof t?.file == "string" ? t.file : "",
|
|
1349
|
+
nodes: {
|
|
1350
|
+
state: B(n?.state),
|
|
1351
|
+
derived: B(n?.derived),
|
|
1352
|
+
effect: B(n?.effect)
|
|
1353
|
+
},
|
|
1354
|
+
changes: B(t?.changes),
|
|
1355
|
+
renders: B(t?.renders),
|
|
1356
|
+
renderMs: typeof t?.renderMs == "number" && t.renderMs >= 0 ? t.renderMs : 0
|
|
1357
|
+
};
|
|
1358
|
+
return t?.kind === "module" && (r.kind = "module"), r;
|
|
1359
|
+
}), r = V(e?.window), i = V(e?.components), a = V(e?.other), o = V(e?.capabilities);
|
|
673
1360
|
return {
|
|
674
1361
|
epoch: typeof e?.epoch == "string" ? e.epoch : t,
|
|
675
1362
|
window: {
|
|
676
|
-
ms:
|
|
677
|
-
since:
|
|
678
|
-
until:
|
|
679
|
-
sampledActiveMs:
|
|
1363
|
+
ms: B(r?.ms),
|
|
1364
|
+
since: B(r?.since),
|
|
1365
|
+
until: B(r?.until),
|
|
1366
|
+
sampledActiveMs: B(r?.sampledActiveMs)
|
|
680
1367
|
},
|
|
681
1368
|
policy: "sampled-200ms",
|
|
682
1369
|
coverage: "component-init",
|
|
683
1370
|
components: {
|
|
684
|
-
total: typeof
|
|
685
|
-
withActivity:
|
|
1371
|
+
total: typeof i?.total == "number" ? i.total : null,
|
|
1372
|
+
withActivity: B(i?.withActivity)
|
|
686
1373
|
},
|
|
687
|
-
rows:
|
|
688
|
-
other:
|
|
689
|
-
components:
|
|
690
|
-
nodes:
|
|
1374
|
+
rows: n,
|
|
1375
|
+
other: typeof a?.components == "number" && typeof a.nodes == "number" ? {
|
|
1376
|
+
components: a.components,
|
|
1377
|
+
nodes: a.nodes
|
|
691
1378
|
} : null,
|
|
692
1379
|
truncated: e?.truncated === !0,
|
|
693
1380
|
capabilities: {
|
|
694
|
-
valueInspection:
|
|
695
|
-
signalHistory:
|
|
696
|
-
writeCause:
|
|
1381
|
+
valueInspection: o?.valueInspection === !0,
|
|
1382
|
+
signalHistory: o?.signalHistory === !0,
|
|
1383
|
+
writeCause: o?.writeCause === !0
|
|
697
1384
|
},
|
|
698
|
-
...
|
|
1385
|
+
...R(e?.baseline) && { baseline: { ...e.baseline } }
|
|
699
1386
|
};
|
|
700
1387
|
}
|
|
701
1388
|
ingestFps(e) {
|
|
702
|
-
this.received.fps++, this.fpsSamples.push(e), this.fpsSamples.length >
|
|
1389
|
+
this.received.fps++, this.fpsSamples.push(e), this.fpsSamples.length > N.fpsSamples && (this.fpsSamples = this.fpsSamples.slice(-N.fpsSamples)), this.bump("fps"), this.hooks.onFpsSample?.(e);
|
|
703
1390
|
}
|
|
704
1391
|
ingestRuntimeError(e) {
|
|
705
|
-
this.received.runtimeErrors++, this.runtimeErrors.push(e), this.runtimeErrors.length >
|
|
1392
|
+
this.received.runtimeErrors++, this.runtimeErrors.push(e), this.runtimeErrors.length > N.runtimeErrors && (this.runtimeErrors = this.runtimeErrors.slice(-N.runtimeErrors)), this.bump("errors");
|
|
706
1393
|
}
|
|
707
1394
|
recordLoadProfile(e) {
|
|
708
|
-
this.received.loadProfiles++, this.loadProfiles.push(e), this.loadProfiles.length >
|
|
1395
|
+
this.received.loadProfiles++, this.loadProfiles.push(e), this.loadProfiles.length > N.loadProfiles && (this.loadProfiles = this.loadProfiles.slice(-N.loadProfiles)), this.bump("loadProfiles"), this.hooks.onLoadProfile?.(e);
|
|
709
1396
|
}
|
|
710
1397
|
recordCompilerWarning(e) {
|
|
711
|
-
this.received.compilerWarnings++, this.compilerWarnings.push(e), this.compilerWarnings.length >
|
|
1398
|
+
this.received.compilerWarnings++, this.compilerWarnings.push(e), this.compilerWarnings.length > N.compilerWarnings && (this.compilerWarnings = this.compilerWarnings.slice(-N.compilerWarnings)), this.bump("errors");
|
|
712
1399
|
}
|
|
713
1400
|
clearLoadProfiles() {
|
|
714
1401
|
this.loadProfiles = [], this.received.loadProfiles = 0, this.bump("loadProfiles");
|
|
@@ -720,10 +1407,10 @@ var we = class {
|
|
|
720
1407
|
this.fpsSamples = [], this.received.fps = 0, this.bump("fps");
|
|
721
1408
|
}
|
|
722
1409
|
clearStateTimeline() {
|
|
723
|
-
this.timeline = [], this.timelineSizes = [], this.timelineEpochs = [], this.timelineBytes = 0, this.timelineDrops.clear(), this.timelineTooLarge.clear(), this.markTimelineReset(), this.hot?.send(
|
|
1410
|
+
this.timeline = [], this.timelineSizes = [], this.timelineEpochs = [], this.timelineBytes = 0, this.timelineDrops.clear(), this.timelineTooLarge.clear(), this.markTimelineReset(), this.hot?.send(I.clearStateTimeline, {});
|
|
724
1411
|
}
|
|
725
1412
|
requestReactiveGraph(e = {}) {
|
|
726
|
-
let t = typeof e.componentId == "number" && Number.isInteger(e.componentId) && e.componentId >= 0 ? e.componentId : null, n =
|
|
1413
|
+
let t = typeof e.componentId == "number" && Number.isInteger(e.componentId) && e.componentId >= 0 ? e.componentId : null, n = z(e.maxNodes, 1, N.reactiveNodes, N.reactiveNodes), r = z(e.maxEdges, 1, N.reactiveEdges, N.reactiveEdges), i = this.servedEpoch ?? null;
|
|
727
1414
|
if (t !== null && i === null) return Promise.resolve({
|
|
728
1415
|
nodes: [],
|
|
729
1416
|
edges: [],
|
|
@@ -751,7 +1438,7 @@ var we = class {
|
|
|
751
1438
|
staleReason: "epoch-changed"
|
|
752
1439
|
});
|
|
753
1440
|
let a = `graph|${i ?? ""}|${t ?? "*"}|${n}|${r}`;
|
|
754
|
-
return this.keyedPull("graph", a, i, t,
|
|
1441
|
+
return this.keyedPull("graph", a, i, t, I.requestReactiveGraph, {
|
|
755
1442
|
...t !== null && { componentId: t },
|
|
756
1443
|
maxNodes: n,
|
|
757
1444
|
maxEdges: r
|
|
@@ -770,8 +1457,8 @@ var we = class {
|
|
|
770
1457
|
}));
|
|
771
1458
|
}
|
|
772
1459
|
requestReactiveSummary(e = {}) {
|
|
773
|
-
let t =
|
|
774
|
-
return this.keyedPull("summary", i, r, null,
|
|
1460
|
+
let t = z(e.topK, 1, L.maxTopK, L.topK), n = z(e.windowMs, L.minWindowMs, L.maxWindowMs, L.windowMs), r = this.servedEpoch ?? null, i = `summary|${r ?? ""}|${t}|${n}`;
|
|
1461
|
+
return this.keyedPull("summary", i, r, null, I.requestReactiveSummary, {
|
|
775
1462
|
topK: t,
|
|
776
1463
|
windowMs: n
|
|
777
1464
|
}, (e) => ({
|
|
@@ -816,7 +1503,7 @@ var we = class {
|
|
|
816
1503
|
let u = this.hot, d = `r${++this.requestSeq}`, f = new Promise((o) => {
|
|
817
1504
|
let c = setTimeout(() => {
|
|
818
1505
|
this.pulls.delete(d), o(s("timeout"));
|
|
819
|
-
},
|
|
1506
|
+
}, Ye);
|
|
820
1507
|
this.pulls.set(d, {
|
|
821
1508
|
kind: e,
|
|
822
1509
|
key: t,
|
|
@@ -840,7 +1527,7 @@ var we = class {
|
|
|
840
1527
|
for (this.pulls.delete(e), clearTimeout(n.timer), this.pullCache.delete(n.key), this.pullCache.set(n.key, {
|
|
841
1528
|
value: t,
|
|
842
1529
|
at: Date.now()
|
|
843
|
-
}); this.pullCache.size >
|
|
1530
|
+
}); this.pullCache.size > Ze;) this.pullCache.delete(this.pullCache.keys().next().value);
|
|
844
1531
|
n.kind === "graph" && this.storeGraph(t), n.resolve(t);
|
|
845
1532
|
}
|
|
846
1533
|
}
|
|
@@ -892,7 +1579,7 @@ var we = class {
|
|
|
892
1579
|
}
|
|
893
1580
|
}
|
|
894
1581
|
requestStateTimeline() {
|
|
895
|
-
return this.pull("stateTimeline",
|
|
1582
|
+
return this.pull("stateTimeline", I.requestStateTimeline, this.stateTimelineResolvers, () => this.stateTimeline);
|
|
896
1583
|
}
|
|
897
1584
|
pull(e, t, n, r) {
|
|
898
1585
|
if (!this.hot || Date.now() - this.lastPull[e] < 1e3) return Promise.resolve(r());
|
|
@@ -904,7 +1591,7 @@ var we = class {
|
|
|
904
1591
|
}, o = setTimeout(() => {
|
|
905
1592
|
let t = n.indexOf(i);
|
|
906
1593
|
t !== -1 && n.splice(t, 1), e(r());
|
|
907
|
-
},
|
|
1594
|
+
}, Ye);
|
|
908
1595
|
n.push(i), a.send(t, {});
|
|
909
1596
|
}).finally(() => {
|
|
910
1597
|
this.inflight[e] === o && (this.inflight[e] = void 0);
|
|
@@ -916,62 +1603,10 @@ var we = class {
|
|
|
916
1603
|
this.stateTimelineResolvers = [];
|
|
917
1604
|
for (let t of e) t(this.stateTimeline);
|
|
918
1605
|
}
|
|
919
|
-
}, Te = {
|
|
920
|
-
".png": "image/png",
|
|
921
|
-
".jpg": "image/jpeg",
|
|
922
|
-
".jpeg": "image/jpeg",
|
|
923
|
-
".gif": "image/gif",
|
|
924
|
-
".svg": "image/svg+xml",
|
|
925
|
-
".webp": "image/webp",
|
|
926
|
-
".avif": "image/avif",
|
|
927
|
-
".ico": "image/x-icon",
|
|
928
|
-
".woff": "font/woff",
|
|
929
|
-
".woff2": "font/woff2",
|
|
930
|
-
".ttf": "font/ttf",
|
|
931
|
-
".eot": "application/vnd.ms-fontobject",
|
|
932
|
-
".otf": "font/otf",
|
|
933
|
-
".mp4": "video/mp4",
|
|
934
|
-
".webm": "video/webm",
|
|
935
|
-
".mp3": "audio/mpeg",
|
|
936
|
-
".wav": "audio/wav",
|
|
937
|
-
".ogg": "audio/ogg",
|
|
938
|
-
".json": "application/json",
|
|
939
|
-
".xml": "application/xml",
|
|
940
|
-
".pdf": "application/pdf",
|
|
941
|
-
".txt": "text/plain",
|
|
942
|
-
".css": "text/css",
|
|
943
|
-
".js": "text/javascript",
|
|
944
|
-
".html": "text/html"
|
|
945
1606
|
};
|
|
946
|
-
function Ee(e, t = "/") {
|
|
947
|
-
if (!r.existsSync(e)) return [];
|
|
948
|
-
let n = [];
|
|
949
|
-
return N(e, e, t.endsWith("/") ? t : `${t}/`, n), n.sort((e, t) => e.relativePath.localeCompare(t.relativePath));
|
|
950
|
-
}
|
|
951
|
-
function N(e, t, n, a) {
|
|
952
|
-
let o = r.readdirSync(e, { withFileTypes: !0 });
|
|
953
|
-
for (let s of o) {
|
|
954
|
-
let o = i.join(e, s.name);
|
|
955
|
-
if (s.isDirectory()) {
|
|
956
|
-
N(o, t, n, a);
|
|
957
|
-
continue;
|
|
958
|
-
}
|
|
959
|
-
if (s.name.startsWith(".")) continue;
|
|
960
|
-
let c = r.statSync(o), l = i.extname(s.name).toLowerCase(), u = i.relative(t, o);
|
|
961
|
-
a.push({
|
|
962
|
-
name: s.name,
|
|
963
|
-
path: o,
|
|
964
|
-
relativePath: u,
|
|
965
|
-
url: n + u.split(i.sep).map(encodeURIComponent).join("/"),
|
|
966
|
-
size: c.size,
|
|
967
|
-
type: Te[l] || "application/octet-stream",
|
|
968
|
-
mtime: c.mtimeMs
|
|
969
|
-
});
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
1607
|
//#endregion
|
|
973
|
-
//#region src/security.ts
|
|
974
|
-
function
|
|
1608
|
+
//#region src/server/security.ts
|
|
1609
|
+
function it(e) {
|
|
975
1610
|
let t;
|
|
976
1611
|
try {
|
|
977
1612
|
t = new URL(e);
|
|
@@ -979,17 +1614,19 @@ function De(e) {
|
|
|
979
1614
|
throw Error(`Invalid URL: ${e}`);
|
|
980
1615
|
}
|
|
981
1616
|
if (t.protocol !== "http:" && t.protocol !== "https:") throw Error(`Blocked URL scheme: ${t.protocol}`);
|
|
982
|
-
let n = t
|
|
983
|
-
if (n === "::1" || n === "[::1]") throw Error("Blocked: loopback address");
|
|
1617
|
+
let n = at(t);
|
|
984
1618
|
if (f.isIP(n)) {
|
|
985
|
-
if (
|
|
986
|
-
} else {
|
|
987
|
-
let e = n.toLowerCase();
|
|
988
|
-
if (e === "localhost" || e.endsWith(".local") || e.endsWith(".internal")) throw Error(`Blocked: internal hostname ${n}`);
|
|
989
|
-
}
|
|
1619
|
+
if (lt(n)) throw Error(`Blocked: private IP address ${n}`);
|
|
1620
|
+
} else if (ot(n)) throw Error(`Blocked: internal hostname ${n}`);
|
|
990
1621
|
return t;
|
|
991
1622
|
}
|
|
992
|
-
|
|
1623
|
+
function at(e) {
|
|
1624
|
+
return e.hostname.replaceAll(/^\[|\]$/g, "").toLowerCase().replace(/\.$/, "");
|
|
1625
|
+
}
|
|
1626
|
+
function ot(e) {
|
|
1627
|
+
return e === "localhost" || e.endsWith(".localhost") || e.endsWith(".local") || e.endsWith(".internal");
|
|
1628
|
+
}
|
|
1629
|
+
async function st(e, t = {}) {
|
|
993
1630
|
let n;
|
|
994
1631
|
try {
|
|
995
1632
|
n = new URL(e);
|
|
@@ -997,122 +1634,297 @@ async function P(e, t = {}) {
|
|
|
997
1634
|
throw Error(`Invalid URL: ${e}`);
|
|
998
1635
|
}
|
|
999
1636
|
if (t.allowedOrigins?.includes(n.origin)) return n;
|
|
1000
|
-
|
|
1001
|
-
let r = n
|
|
1637
|
+
it(e);
|
|
1638
|
+
let r = at(n);
|
|
1002
1639
|
if (f.isIP(r)) return n;
|
|
1003
|
-
let i
|
|
1004
|
-
|
|
1640
|
+
let i;
|
|
1641
|
+
try {
|
|
1642
|
+
i = await d.lookup(r, { all: !0 });
|
|
1643
|
+
} catch {
|
|
1644
|
+
throw Error(`Blocked: ${r} does not resolve`);
|
|
1645
|
+
}
|
|
1646
|
+
for (let { address: e } of i) if (lt(e)) throw Error(`Blocked: ${r} resolves to private address`);
|
|
1005
1647
|
return n;
|
|
1006
1648
|
}
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1649
|
+
var W = new f.BlockList();
|
|
1650
|
+
for (let [e, t] of [
|
|
1651
|
+
["0.0.0.0", 8],
|
|
1652
|
+
["10.0.0.0", 8],
|
|
1653
|
+
["100.64.0.0", 10],
|
|
1654
|
+
["127.0.0.0", 8],
|
|
1655
|
+
["169.254.0.0", 16],
|
|
1656
|
+
["172.16.0.0", 12],
|
|
1657
|
+
["192.0.0.0", 24],
|
|
1658
|
+
["192.0.2.0", 24],
|
|
1659
|
+
["192.168.0.0", 16],
|
|
1660
|
+
["198.18.0.0", 15],
|
|
1661
|
+
["198.51.100.0", 24],
|
|
1662
|
+
["203.0.113.0", 24],
|
|
1663
|
+
["224.0.0.0", 4],
|
|
1664
|
+
["240.0.0.0", 4]
|
|
1665
|
+
]) W.addSubnet(e, t, "ipv4");
|
|
1666
|
+
var ct = new f.BlockList();
|
|
1667
|
+
for (let [e, t] of [
|
|
1668
|
+
["::", 128],
|
|
1669
|
+
["::1", 128],
|
|
1670
|
+
["::", 96],
|
|
1671
|
+
["100::", 64],
|
|
1672
|
+
["2001:db8::", 32],
|
|
1673
|
+
["fc00::", 7],
|
|
1674
|
+
["fe80::", 10],
|
|
1675
|
+
["fec0::", 10],
|
|
1676
|
+
["ff00::", 8]
|
|
1677
|
+
]) ct.addSubnet(e, t, "ipv6");
|
|
1678
|
+
function lt(e) {
|
|
1679
|
+
let t = e.replaceAll(/^\[|\]$/g, "").toLowerCase(), n = f.isIP(t);
|
|
1680
|
+
if (n === 4) return W.check(t, "ipv4");
|
|
1681
|
+
if (n !== 6) return !0;
|
|
1682
|
+
let r = ft(ut(t));
|
|
1683
|
+
return r === null ? ct.check(t, "ipv6") : W.check(r, "ipv4");
|
|
1684
|
+
}
|
|
1685
|
+
function ut(e) {
|
|
1686
|
+
let t = e, n = /(\d+\.\d+\.\d+\.\d+)$/.exec(t);
|
|
1687
|
+
if (n) {
|
|
1688
|
+
let [e, r, i, a] = n[1].split(".").map(Number);
|
|
1689
|
+
t = `${t.slice(0, n.index)}${(e << 8 | r).toString(16)}:${(i << 8 | a).toString(16)}`;
|
|
1690
|
+
}
|
|
1691
|
+
let [r, i] = t.split("::");
|
|
1692
|
+
if (i === void 0) return G(r);
|
|
1693
|
+
let a = G(r), o = G(i);
|
|
1694
|
+
return [
|
|
1695
|
+
...a,
|
|
1696
|
+
...Array.from({ length: 8 - a.length - o.length }, () => 0),
|
|
1697
|
+
...o
|
|
1698
|
+
];
|
|
1699
|
+
}
|
|
1700
|
+
function G(e) {
|
|
1701
|
+
return e === "" ? [] : e.split(":").map((e) => Number.parseInt(e, 16));
|
|
1702
|
+
}
|
|
1703
|
+
function dt(e, t) {
|
|
1704
|
+
return `${e >> 8}.${e & 255}.${t >> 8}.${t & 255}`;
|
|
1011
1705
|
}
|
|
1012
|
-
function
|
|
1013
|
-
let t
|
|
1014
|
-
return n
|
|
1706
|
+
function ft(e) {
|
|
1707
|
+
let [t, n, r, i, a, o, s, c] = e;
|
|
1708
|
+
return t === 0 && n === 0 && r === 0 && i === 0 && a === 0 && (o === 65535 || o === 0) && (s !== 0 || o === 65535) || t === 100 && n === 65435 && r === 0 && i === 0 && a === 0 && o === 0 ? dt(s, c) : t === 8194 ? dt(n, r) : null;
|
|
1015
1709
|
}
|
|
1016
|
-
function
|
|
1710
|
+
function K(e, n) {
|
|
1017
1711
|
if (typeof n != "string" || n.length === 0) throw Error("Invalid file path");
|
|
1018
|
-
let
|
|
1712
|
+
let i = r.realpathSync(e), a = t.isAbsolute(n) ? n : t.resolve(e, n), o;
|
|
1019
1713
|
try {
|
|
1020
|
-
o =
|
|
1714
|
+
o = r.realpathSync(a);
|
|
1021
1715
|
} catch {
|
|
1022
1716
|
throw Error("File not found");
|
|
1023
1717
|
}
|
|
1024
|
-
if (o !==
|
|
1718
|
+
if (o !== i && !o.startsWith(i + t.sep)) throw Error("Forbidden: path outside project root");
|
|
1025
1719
|
return o;
|
|
1026
1720
|
}
|
|
1027
1721
|
//#endregion
|
|
1722
|
+
//#region src/analyzers/http.ts
|
|
1723
|
+
var pt = 3e4;
|
|
1724
|
+
async function mt(e, t, n, r = pt) {
|
|
1725
|
+
let i = new AbortController(), a, o = new Promise((e, t) => {
|
|
1726
|
+
a = setTimeout(() => {
|
|
1727
|
+
let e = /* @__PURE__ */ Error(`Timed out after ${r}ms`);
|
|
1728
|
+
i.abort(e), t(e);
|
|
1729
|
+
}, r);
|
|
1730
|
+
}), s = (async () => n(await fetch(e, {
|
|
1731
|
+
...t,
|
|
1732
|
+
redirect: "manual",
|
|
1733
|
+
signal: i.signal
|
|
1734
|
+
})))();
|
|
1735
|
+
s.catch(() => {});
|
|
1736
|
+
try {
|
|
1737
|
+
return await Promise.race([s, o]);
|
|
1738
|
+
} finally {
|
|
1739
|
+
clearTimeout(a);
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
//#endregion
|
|
1028
1743
|
//#region src/analyzers/api.ts
|
|
1029
|
-
var
|
|
1744
|
+
var ht = [
|
|
1030
1745
|
"GET",
|
|
1031
1746
|
"POST",
|
|
1032
1747
|
"PUT",
|
|
1033
1748
|
"PATCH",
|
|
1034
|
-
"DELETE"
|
|
1749
|
+
"DELETE",
|
|
1750
|
+
"HEAD",
|
|
1751
|
+
"OPTIONS"
|
|
1035
1752
|
];
|
|
1036
|
-
function
|
|
1753
|
+
function gt(e, t) {
|
|
1754
|
+
let { program: n, errors: r } = u(t, e);
|
|
1755
|
+
if (r.length > 0) return null;
|
|
1756
|
+
let i = /* @__PURE__ */ new Set();
|
|
1757
|
+
for (let e of n.body) {
|
|
1758
|
+
if (e.type !== "ExportNamedDeclaration") continue;
|
|
1759
|
+
let t = e.declaration;
|
|
1760
|
+
if (t?.type === "FunctionDeclaration" && t.id) i.add(t.id.name);
|
|
1761
|
+
else if (t?.type === "VariableDeclaration") for (let e of t.declarations) e.id.type === "Identifier" && i.add(e.id.name);
|
|
1762
|
+
for (let t of e.specifiers) {
|
|
1763
|
+
let e = t.exported;
|
|
1764
|
+
i.add(e.type === "Literal" ? e.value : e.name);
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
return i;
|
|
1768
|
+
}
|
|
1769
|
+
function _t(e) {
|
|
1037
1770
|
let t = [];
|
|
1038
1771
|
for (let n of e) {
|
|
1039
1772
|
let e = n.files.find((e) => e.type === "endpoint");
|
|
1040
1773
|
if (!e) continue;
|
|
1041
|
-
let
|
|
1774
|
+
let i = null;
|
|
1042
1775
|
try {
|
|
1043
|
-
|
|
1776
|
+
i = gt(r.readFileSync(e.path, "utf-8"), e.path);
|
|
1044
1777
|
} catch {}
|
|
1045
|
-
let
|
|
1046
|
-
|
|
1778
|
+
let a = i?.has("fallback") ? [...ht] : ht.filter((e) => i?.has(e));
|
|
1779
|
+
t.push({
|
|
1047
1780
|
route: n.id,
|
|
1048
1781
|
path: n.path,
|
|
1049
|
-
methods:
|
|
1782
|
+
methods: a,
|
|
1050
1783
|
file: e.path
|
|
1051
1784
|
});
|
|
1052
1785
|
}
|
|
1053
1786
|
return t;
|
|
1054
1787
|
}
|
|
1055
|
-
|
|
1056
|
-
|
|
1788
|
+
function vt(e) {
|
|
1789
|
+
if (!e.trim()) return {};
|
|
1790
|
+
let t = JSON.parse(e);
|
|
1791
|
+
if (typeof t != "object" || !t || Array.isArray(t) || !Object.values(t).every((e) => typeof e == "string")) throw TypeError("Headers must be a JSON object of string values");
|
|
1792
|
+
return t;
|
|
1793
|
+
}
|
|
1794
|
+
async function yt(e, t = {}, n) {
|
|
1795
|
+
let r = performance.now(), i = () => Math.round((performance.now() - r) * 100) / 100;
|
|
1057
1796
|
try {
|
|
1058
|
-
await
|
|
1059
|
-
let
|
|
1797
|
+
await st(e.url, t);
|
|
1798
|
+
let r = {
|
|
1060
1799
|
method: e.method,
|
|
1061
|
-
headers:
|
|
1062
|
-
redirect: "manual"
|
|
1063
|
-
};
|
|
1064
|
-
e.body && e.method !== "GET" && e.method !== "HEAD" && (i.body = e.body);
|
|
1065
|
-
let a = await fetch(e.url, i), o = await a.text(), s = {};
|
|
1066
|
-
return a.headers.forEach((e, t) => {
|
|
1067
|
-
s[t] = e;
|
|
1068
|
-
}), {
|
|
1069
|
-
status: a.status,
|
|
1070
|
-
statusText: a.statusText,
|
|
1071
|
-
headers: s,
|
|
1072
|
-
body: o,
|
|
1073
|
-
duration: r()
|
|
1800
|
+
headers: vt(e.headers)
|
|
1074
1801
|
};
|
|
1802
|
+
return e.body && e.method !== "GET" && e.method !== "HEAD" && (r.body = e.body), await mt(e.url, r, async (e) => {
|
|
1803
|
+
let t = await e.text(), n = {};
|
|
1804
|
+
return e.headers.forEach((e, t) => {
|
|
1805
|
+
n[t] = e;
|
|
1806
|
+
}), {
|
|
1807
|
+
status: e.status,
|
|
1808
|
+
statusText: e.statusText,
|
|
1809
|
+
headers: n,
|
|
1810
|
+
body: t,
|
|
1811
|
+
duration: i()
|
|
1812
|
+
};
|
|
1813
|
+
}, n);
|
|
1075
1814
|
} catch (e) {
|
|
1076
1815
|
return {
|
|
1077
1816
|
status: 0,
|
|
1078
1817
|
statusText: String(e),
|
|
1079
1818
|
headers: {},
|
|
1080
1819
|
body: "",
|
|
1081
|
-
duration:
|
|
1820
|
+
duration: i()
|
|
1082
1821
|
};
|
|
1083
1822
|
}
|
|
1084
1823
|
}
|
|
1085
1824
|
//#endregion
|
|
1825
|
+
//#region src/analyzers/assets.ts
|
|
1826
|
+
var bt = {
|
|
1827
|
+
".png": "image/png",
|
|
1828
|
+
".jpg": "image/jpeg",
|
|
1829
|
+
".jpeg": "image/jpeg",
|
|
1830
|
+
".gif": "image/gif",
|
|
1831
|
+
".svg": "image/svg+xml",
|
|
1832
|
+
".webp": "image/webp",
|
|
1833
|
+
".avif": "image/avif",
|
|
1834
|
+
".ico": "image/x-icon",
|
|
1835
|
+
".woff": "font/woff",
|
|
1836
|
+
".woff2": "font/woff2",
|
|
1837
|
+
".ttf": "font/ttf",
|
|
1838
|
+
".eot": "application/vnd.ms-fontobject",
|
|
1839
|
+
".otf": "font/otf",
|
|
1840
|
+
".mp4": "video/mp4",
|
|
1841
|
+
".webm": "video/webm",
|
|
1842
|
+
".mp3": "audio/mpeg",
|
|
1843
|
+
".wav": "audio/wav",
|
|
1844
|
+
".ogg": "audio/ogg",
|
|
1845
|
+
".json": "application/json",
|
|
1846
|
+
".xml": "application/xml",
|
|
1847
|
+
".pdf": "application/pdf",
|
|
1848
|
+
".txt": "text/plain",
|
|
1849
|
+
".css": "text/css",
|
|
1850
|
+
".js": "text/javascript",
|
|
1851
|
+
".html": "text/html"
|
|
1852
|
+
};
|
|
1853
|
+
function xt(e, t = "/") {
|
|
1854
|
+
if (!r.existsSync(e)) return [];
|
|
1855
|
+
let n = [];
|
|
1856
|
+
return St(e, e, t.endsWith("/") ? t : `${t}/`, n), n.toSorted((e, t) => e.relativePath.localeCompare(t.relativePath));
|
|
1857
|
+
}
|
|
1858
|
+
function St(e, n, i, a) {
|
|
1859
|
+
let o = r.readdirSync(e, { withFileTypes: !0 });
|
|
1860
|
+
for (let s of o) {
|
|
1861
|
+
let o = t.join(e, s.name);
|
|
1862
|
+
if (s.isDirectory()) {
|
|
1863
|
+
St(o, n, i, a);
|
|
1864
|
+
continue;
|
|
1865
|
+
}
|
|
1866
|
+
if (s.name.startsWith(".")) continue;
|
|
1867
|
+
let c;
|
|
1868
|
+
try {
|
|
1869
|
+
c = r.statSync(o);
|
|
1870
|
+
} catch {
|
|
1871
|
+
continue;
|
|
1872
|
+
}
|
|
1873
|
+
if (!c.isFile()) continue;
|
|
1874
|
+
let l = t.extname(s.name).toLowerCase(), u = t.relative(n, o);
|
|
1875
|
+
a.push({
|
|
1876
|
+
name: s.name,
|
|
1877
|
+
path: o,
|
|
1878
|
+
relativePath: u,
|
|
1879
|
+
url: i + u.split(t.sep).map(encodeURIComponent).join("/"),
|
|
1880
|
+
size: c.size,
|
|
1881
|
+
type: bt[l] ?? "application/octet-stream",
|
|
1882
|
+
mtime: c.mtimeMs
|
|
1883
|
+
});
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
//#endregion
|
|
1086
1887
|
//#region src/analyzers/build.ts
|
|
1087
|
-
var
|
|
1888
|
+
var Ct = /* @__PURE__ */ new Set([
|
|
1088
1889
|
".js",
|
|
1089
1890
|
".css",
|
|
1090
1891
|
".html"
|
|
1091
1892
|
]);
|
|
1092
|
-
function
|
|
1093
|
-
let n = [],
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
],
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1893
|
+
function wt(e) {
|
|
1894
|
+
let n = [], i = /* @__PURE__ */ new Set(), a = [
|
|
1895
|
+
t.resolve(e, ".svelte-kit/output"),
|
|
1896
|
+
t.resolve(e, "build/client"),
|
|
1897
|
+
t.resolve(e, "build")
|
|
1898
|
+
], o = (a) => {
|
|
1899
|
+
let s;
|
|
1900
|
+
try {
|
|
1901
|
+
s = r.readdirSync(a, { withFileTypes: !0 });
|
|
1902
|
+
} catch {
|
|
1903
|
+
return;
|
|
1904
|
+
}
|
|
1905
|
+
for (let c of s) {
|
|
1906
|
+
let s = t.join(a, c.name);
|
|
1907
|
+
if (c.isDirectory()) {
|
|
1908
|
+
o(s);
|
|
1909
|
+
continue;
|
|
1910
|
+
}
|
|
1911
|
+
if (!Ct.has(t.extname(c.name).toLowerCase())) continue;
|
|
1912
|
+
let l, u;
|
|
1913
|
+
try {
|
|
1914
|
+
l = r.realpathSync(s), u = r.statSync(l);
|
|
1915
|
+
} catch {
|
|
1102
1916
|
continue;
|
|
1103
1917
|
}
|
|
1104
|
-
|
|
1105
|
-
name:
|
|
1106
|
-
file:
|
|
1107
|
-
size:
|
|
1918
|
+
u.isFile() && !i.has(l) && (i.add(l), n.push({
|
|
1919
|
+
name: c.name,
|
|
1920
|
+
file: t.relative(e, s),
|
|
1921
|
+
size: u.size,
|
|
1108
1922
|
modules: [],
|
|
1109
|
-
isEntry:
|
|
1110
|
-
});
|
|
1923
|
+
isEntry: c.name.includes("index") || c.name.includes("start")
|
|
1924
|
+
}));
|
|
1111
1925
|
}
|
|
1112
1926
|
};
|
|
1113
|
-
for (let e of
|
|
1114
|
-
i(e);
|
|
1115
|
-
} catch {}
|
|
1927
|
+
for (let e of a) o(e);
|
|
1116
1928
|
return n.sort((e, t) => t.size - e.size), {
|
|
1117
1929
|
chunks: n,
|
|
1118
1930
|
totalSize: n.reduce((e, t) => e + t.size, 0),
|
|
@@ -1121,72 +1933,165 @@ function Ne(t) {
|
|
|
1121
1933
|
}
|
|
1122
1934
|
//#endregion
|
|
1123
1935
|
//#region src/analyzers/module-graph.ts
|
|
1124
|
-
function
|
|
1125
|
-
let n =
|
|
1936
|
+
function Tt(e) {
|
|
1937
|
+
let n = t.extname(e).toLowerCase();
|
|
1126
1938
|
return n === ".svelte" ? "svelte" : n === ".ts" ? "ts" : n === ".js" ? "js" : n === ".css" ? "css" : "other";
|
|
1127
1939
|
}
|
|
1128
|
-
function
|
|
1129
|
-
let r =
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1940
|
+
function Et(e, n) {
|
|
1941
|
+
let r = t.relative(e, n);
|
|
1942
|
+
return r !== "" && r !== ".." && !r.startsWith(`..${t.sep}`) && !t.isAbsolute(r);
|
|
1943
|
+
}
|
|
1944
|
+
function Dt(e, n) {
|
|
1945
|
+
let i = [], a = /* @__PURE__ */ new Map();
|
|
1946
|
+
for (let o of n) {
|
|
1947
|
+
if (!o.file || o.file.split(/[\\/]/).includes("node_modules") || !Et(e, o.file) || a.has(o.file)) continue;
|
|
1948
|
+
let n;
|
|
1135
1949
|
try {
|
|
1136
|
-
|
|
1950
|
+
n = r.statSync(o.file).size;
|
|
1137
1951
|
} catch {}
|
|
1138
|
-
let
|
|
1139
|
-
id:
|
|
1140
|
-
file:
|
|
1141
|
-
type:
|
|
1952
|
+
let s = {
|
|
1953
|
+
id: t.relative(e, o.file),
|
|
1954
|
+
file: o.file,
|
|
1955
|
+
type: Tt(o.file),
|
|
1142
1956
|
importedBy: [],
|
|
1143
1957
|
imports: [],
|
|
1144
|
-
size:
|
|
1958
|
+
size: n
|
|
1145
1959
|
};
|
|
1146
|
-
|
|
1960
|
+
a.set(o.file, s), i.push(s);
|
|
1147
1961
|
}
|
|
1148
|
-
let s = /* @__PURE__ */ new Map();
|
|
1962
|
+
let o = /* @__PURE__ */ new Map(), s = /* @__PURE__ */ new Map();
|
|
1149
1963
|
for (let e of n) {
|
|
1150
|
-
|
|
1151
|
-
let t = i.get(e.file);
|
|
1964
|
+
let t = e.file ? a.get(e.file) : void 0;
|
|
1152
1965
|
if (!t) continue;
|
|
1153
|
-
let n =
|
|
1966
|
+
let n = o.get(t);
|
|
1967
|
+
n || o.set(t, n = /* @__PURE__ */ new Set());
|
|
1154
1968
|
for (let r of e.importedModules) {
|
|
1155
|
-
let e = r.file ?
|
|
1156
|
-
if (!e) continue;
|
|
1969
|
+
let e = r.file ? a.get(r.file) : void 0;
|
|
1970
|
+
if (!e || e === t) continue;
|
|
1157
1971
|
n.has(e.id) || (n.add(e.id), t.imports.push(e.id));
|
|
1158
|
-
let
|
|
1159
|
-
|
|
1972
|
+
let i = s.get(e);
|
|
1973
|
+
i || s.set(e, i = /* @__PURE__ */ new Set()), i.has(t.id) || (i.add(t.id), e.importedBy.push(t.id));
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
let c = new Map(i.map((e) => [e.id, e])), l = Ot(i, c), u = /* @__PURE__ */ new Map();
|
|
1977
|
+
for (let e of l.values()) u.set(e, (u.get(e) ?? 0) + 1);
|
|
1978
|
+
let d = (e) => u.get(l.get(e)) > 1, f = [], p = /* @__PURE__ */ new Set();
|
|
1979
|
+
for (let e of i) {
|
|
1980
|
+
if (!d(e.id) || (e.isCyclic = !0, p.has(e.id))) continue;
|
|
1981
|
+
let t = kt(e.id, c, l);
|
|
1982
|
+
for (let e of t) p.add(e);
|
|
1983
|
+
f.push(t);
|
|
1984
|
+
}
|
|
1985
|
+
return {
|
|
1986
|
+
modules: i,
|
|
1987
|
+
cycles: f
|
|
1988
|
+
};
|
|
1989
|
+
}
|
|
1990
|
+
function Ot(e, t) {
|
|
1991
|
+
let n = /* @__PURE__ */ new Map(), r = /* @__PURE__ */ new Map(), i = [], a = /* @__PURE__ */ new Set(), o = /* @__PURE__ */ new Map(), s = 0, c = 0, l = (e) => {
|
|
1992
|
+
n.set(e, s), r.set(e, s), s++, i.push(e), a.add(e);
|
|
1993
|
+
};
|
|
1994
|
+
for (let s of e) {
|
|
1995
|
+
if (n.has(s.id)) continue;
|
|
1996
|
+
l(s.id);
|
|
1997
|
+
let e = [{
|
|
1998
|
+
id: s.id,
|
|
1999
|
+
i: 0
|
|
2000
|
+
}];
|
|
2001
|
+
for (; e.length > 0;) {
|
|
2002
|
+
let s = e.at(-1), u = t.get(s.id).imports;
|
|
2003
|
+
if (s.i < u.length) {
|
|
2004
|
+
let t = u[s.i++];
|
|
2005
|
+
n.has(t) ? a.has(t) && r.set(s.id, Math.min(r.get(s.id), n.get(t))) : (l(t), e.push({
|
|
2006
|
+
id: t,
|
|
2007
|
+
i: 0
|
|
2008
|
+
}));
|
|
2009
|
+
continue;
|
|
2010
|
+
}
|
|
2011
|
+
e.pop();
|
|
2012
|
+
let d = e.at(-1);
|
|
2013
|
+
if (d && r.set(d.id, Math.min(r.get(d.id), r.get(s.id))), r.get(s.id) === n.get(s.id)) {
|
|
2014
|
+
let e;
|
|
2015
|
+
do
|
|
2016
|
+
e = i.pop(), a.delete(e), o.set(e, c);
|
|
2017
|
+
while (e !== s.id);
|
|
2018
|
+
c++;
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
return o;
|
|
2023
|
+
}
|
|
2024
|
+
function kt(e, t, n) {
|
|
2025
|
+
let r = n.get(e), i = /* @__PURE__ */ new Map(), a = [e];
|
|
2026
|
+
for (let o = 0; o < a.length; o++) {
|
|
2027
|
+
let s = a[o];
|
|
2028
|
+
for (let o of t.get(s).imports) {
|
|
2029
|
+
if (o === e) {
|
|
2030
|
+
let t = [e];
|
|
2031
|
+
for (let n = s; n !== e; n = i.get(n)) t.splice(1, 0, n);
|
|
2032
|
+
return t.push(e), t;
|
|
2033
|
+
}
|
|
2034
|
+
n.get(o) !== r || i.has(o) || (i.set(o, s), a.push(o));
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
/* v8 ignore next -- unreachable: every module of a non-trivial component is on a cycle */
|
|
2038
|
+
return [e, e];
|
|
2039
|
+
}
|
|
2040
|
+
//#endregion
|
|
2041
|
+
//#region src/analyzers/og.ts
|
|
2042
|
+
var At = {
|
|
2043
|
+
amp: "&",
|
|
2044
|
+
lt: "<",
|
|
2045
|
+
gt: ">",
|
|
2046
|
+
quot: "\"",
|
|
2047
|
+
apos: "'",
|
|
2048
|
+
nbsp: "\xA0"
|
|
2049
|
+
};
|
|
2050
|
+
function q(e) {
|
|
2051
|
+
return e.replaceAll(/&(?:#(\d+)|#x([0-9a-f]+)|([a-z]+));/gi, (e, t, n, r) => {
|
|
2052
|
+
if (r) return At[r.toLowerCase()] ?? e;
|
|
2053
|
+
let i = t ? Number(t) : Number.parseInt(n, 16);
|
|
2054
|
+
return i > 0 && i <= 1114111 ? String.fromCodePoint(i) : e;
|
|
2055
|
+
});
|
|
2056
|
+
}
|
|
2057
|
+
var jt = /<meta\b((?:[^>"']|"[^"]*"|'[^']*')*)>/gi, Mt = /([^\s=/>"']+)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>]+))/g;
|
|
2058
|
+
function Nt(e) {
|
|
2059
|
+
let t = [];
|
|
2060
|
+
for (let n of e.matchAll(jt)) {
|
|
2061
|
+
let e = /* @__PURE__ */ new Map();
|
|
2062
|
+
for (let t of n[1].matchAll(Mt)) {
|
|
2063
|
+
let n = t[1].toLowerCase();
|
|
2064
|
+
e.has(n) || e.set(n, t[2] ?? t[3] ?? t[4] ?? "");
|
|
1160
2065
|
}
|
|
2066
|
+
let r = e.get("property") ?? e.get("name"), i = e.get("content");
|
|
2067
|
+
r && i !== void 0 && t.push({
|
|
2068
|
+
property: q(r),
|
|
2069
|
+
content: q(i)
|
|
2070
|
+
});
|
|
2071
|
+
}
|
|
2072
|
+
return t;
|
|
2073
|
+
}
|
|
2074
|
+
function Pt(e) {
|
|
2075
|
+
return e?.match(/charset\s*=\s*["']?\s*([\w.:-]+)/i)?.[1];
|
|
2076
|
+
}
|
|
2077
|
+
function Ft(e, t) {
|
|
2078
|
+
let n = new TextDecoder("latin1").decode(e.subarray(0, 1024)), r = /<meta\b[^>]*charset[^>]*>/i.exec(n)?.[0] ?? null, i = Pt(t) ?? Pt(r) ?? "utf-8", a;
|
|
2079
|
+
try {
|
|
2080
|
+
a = new TextDecoder(i);
|
|
2081
|
+
} catch {
|
|
2082
|
+
a = new TextDecoder("utf-8");
|
|
2083
|
+
}
|
|
2084
|
+
return a.decode(e);
|
|
2085
|
+
}
|
|
2086
|
+
function It(e, t) {
|
|
2087
|
+
try {
|
|
2088
|
+
return new URL(e, t).href;
|
|
2089
|
+
} catch {
|
|
2090
|
+
return e;
|
|
1161
2091
|
}
|
|
1162
|
-
let c = [], l = /* @__PURE__ */ new Set(), u = /* @__PURE__ */ new Set(), d = [];
|
|
1163
|
-
function f(e) {
|
|
1164
|
-
if (u.has(e)) {
|
|
1165
|
-
let t = d.indexOf(e);
|
|
1166
|
-
if (t >= 0) {
|
|
1167
|
-
let n = d.slice(t).concat(e);
|
|
1168
|
-
n.length > 2 && c.push(n);
|
|
1169
|
-
}
|
|
1170
|
-
return;
|
|
1171
|
-
}
|
|
1172
|
-
if (l.has(e)) return;
|
|
1173
|
-
l.add(e), u.add(e), d.push(e);
|
|
1174
|
-
let t = o.get(e);
|
|
1175
|
-
if (t) for (let e of t.imports) f(e);
|
|
1176
|
-
d.pop(), u.delete(e);
|
|
1177
|
-
}
|
|
1178
|
-
for (let e of r) f(e.id);
|
|
1179
|
-
let p = new Set(c.flat());
|
|
1180
|
-
for (let e of r) p.has(e.id) && (e.isCyclic = !0);
|
|
1181
|
-
return {
|
|
1182
|
-
modules: r,
|
|
1183
|
-
cycles: c
|
|
1184
|
-
};
|
|
1185
2092
|
}
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
async function Ie(e, t = {}) {
|
|
1189
|
-
let n = {
|
|
2093
|
+
async function Lt(e, t = {}, n) {
|
|
2094
|
+
let r = {
|
|
1190
2095
|
url: e,
|
|
1191
2096
|
title: "",
|
|
1192
2097
|
description: "",
|
|
@@ -1195,47 +2100,43 @@ async function Ie(e, t = {}) {
|
|
|
1195
2100
|
issues: []
|
|
1196
2101
|
};
|
|
1197
2102
|
try {
|
|
1198
|
-
await
|
|
1199
|
-
let
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
e && (n.title = e[1]);
|
|
2103
|
+
await st(e, t);
|
|
2104
|
+
let i = await mt(e, {}, async (e) => ({
|
|
2105
|
+
status: e.status,
|
|
2106
|
+
location: e.headers.get("location"),
|
|
2107
|
+
html: e.status >= 300 && e.status < 400 ? "" : Ft(new Uint8Array(await e.arrayBuffer()), e.headers.get("content-type"))
|
|
2108
|
+
}), n);
|
|
2109
|
+
if (i.status >= 300 && i.status < 400) return r.issues.push(`Redirects to ${i.location ?? "(no location)"} (not followed)`), r;
|
|
2110
|
+
i.status >= 400 && r.issues.push(`Responded with HTTP ${i.status}`);
|
|
2111
|
+
let a = i.html;
|
|
2112
|
+
r.tags = Nt(a);
|
|
2113
|
+
let o = (e) => r.tags.find((t) => t.property === e)?.content;
|
|
2114
|
+
r.title = o("og:title") ?? "", r.description = o("og:description") ?? "";
|
|
2115
|
+
let s = o("og:image");
|
|
2116
|
+
if (r.image = s ? It(s, e) : "", !r.title) {
|
|
2117
|
+
let e = /<title\b[^>]*>([\s\S]*?)<\/title>/i.exec(a);
|
|
2118
|
+
e && (r.title = q(e[1]).replaceAll(/\s+/g, " ").trim());
|
|
1215
2119
|
}
|
|
1216
|
-
|
|
1217
|
-
let e = n.tags.find((e) => e.property === "description");
|
|
1218
|
-
e && (n.description = e.content);
|
|
1219
|
-
}
|
|
1220
|
-
n.title || n.issues.push("Missing og:title or <title>"), n.description || n.issues.push("Missing og:description or meta description"), n.image || n.issues.push("Missing og:image"), n.tags.some((e) => e.property === "og:url") || n.issues.push("Missing og:url"), n.tags.some((e) => e.property === "og:type") || n.issues.push("Missing og:type");
|
|
2120
|
+
r.description ||= o("description") ?? "", r.title || r.issues.push("Missing og:title or <title>"), r.description || r.issues.push("Missing og:description or meta description"), r.image || r.issues.push("Missing og:image"), r.tags.some((e) => e.property === "og:url") || r.issues.push("Missing og:url"), r.tags.some((e) => e.property === "og:type") || r.issues.push("Missing og:type");
|
|
1221
2121
|
} catch (e) {
|
|
1222
|
-
|
|
2122
|
+
r.issues.push(`Failed to fetch: ${String(e)}`);
|
|
1223
2123
|
}
|
|
1224
|
-
return
|
|
2124
|
+
return r;
|
|
1225
2125
|
}
|
|
1226
2126
|
//#endregion
|
|
1227
2127
|
//#region src/analyzers/source.ts
|
|
1228
|
-
function
|
|
2128
|
+
function Rt(e, t, n) {
|
|
1229
2129
|
let r = e.split("\n");
|
|
1230
2130
|
if (n === "effect") return r.findIndex((e) => e.includes("$effect(") || e.includes("$effect.pre(")) + 1;
|
|
1231
|
-
|
|
2131
|
+
if (!t) return 0;
|
|
2132
|
+
let i = t.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&"), a = RegExp(`\\b(?:let|const|var)\\s+${i}(?![\\w$])`);
|
|
1232
2133
|
return r.findIndex((e) => a.test(e)) + 1;
|
|
1233
2134
|
}
|
|
1234
2135
|
//#endregion
|
|
1235
|
-
//#region src/devframe.ts
|
|
1236
|
-
var
|
|
1237
|
-
function
|
|
1238
|
-
let { collector: t } = e, n = () =>
|
|
2136
|
+
//#region src/server/devframe.ts
|
|
2137
|
+
var J = "svelte-devtools", Y = "/.svelte-devtools/", zt = s(import.meta.url), Bt = zt.resolve("vite-devtools-svelte/package.json"), X = zt(Bt), Vt = t.join(t.dirname(Bt), "dist/client"), Z = o.string().min(1).max(4096);
|
|
2138
|
+
function Ht(e) {
|
|
2139
|
+
let { collector: t } = e, n = () => y(e.root()), i = () => de(n().routesDir);
|
|
1239
2140
|
return [
|
|
1240
2141
|
l({
|
|
1241
2142
|
name: "get-project",
|
|
@@ -1247,25 +2148,25 @@ function Be(e) {
|
|
|
1247
2148
|
name: "get-routes",
|
|
1248
2149
|
type: "query",
|
|
1249
2150
|
jsonSerializable: !0,
|
|
1250
|
-
handler: () =>
|
|
2151
|
+
handler: () => i()
|
|
1251
2152
|
}),
|
|
1252
2153
|
l({
|
|
1253
2154
|
name: "get-assets",
|
|
1254
2155
|
type: "query",
|
|
1255
2156
|
jsonSerializable: !0,
|
|
1256
|
-
handler: () =>
|
|
2157
|
+
handler: () => xt(n().staticDir, e.publicBase())
|
|
1257
2158
|
}),
|
|
1258
2159
|
l({
|
|
1259
2160
|
name: "get-component-relations",
|
|
1260
2161
|
type: "query",
|
|
1261
2162
|
jsonSerializable: !0,
|
|
1262
|
-
handler: () =>
|
|
2163
|
+
handler: () => g(e.root())
|
|
1263
2164
|
}),
|
|
1264
2165
|
l({
|
|
1265
2166
|
name: "get-svelte-files",
|
|
1266
2167
|
type: "query",
|
|
1267
2168
|
jsonSerializable: !0,
|
|
1268
|
-
handler: () =>
|
|
2169
|
+
handler: () => g(e.root()).map((e) => ({
|
|
1269
2170
|
file: e.file,
|
|
1270
2171
|
name: e.name
|
|
1271
2172
|
}))
|
|
@@ -1291,11 +2192,11 @@ function Be(e) {
|
|
|
1291
2192
|
name: "set-active",
|
|
1292
2193
|
type: "action",
|
|
1293
2194
|
jsonSerializable: !0,
|
|
1294
|
-
args: [
|
|
1295
|
-
client:
|
|
1296
|
-
active:
|
|
2195
|
+
args: [o.object({
|
|
2196
|
+
client: o.string().min(1).max(64),
|
|
2197
|
+
active: o.boolean()
|
|
1297
2198
|
})],
|
|
1298
|
-
returns:
|
|
2199
|
+
returns: o.void(),
|
|
1299
2200
|
handler: ({ client: e, active: n }) => {
|
|
1300
2201
|
n ? t.lease(`ui:${e}`) : t.release(`ui:${e}`);
|
|
1301
2202
|
}
|
|
@@ -1304,13 +2205,13 @@ function Be(e) {
|
|
|
1304
2205
|
name: "open-in-editor",
|
|
1305
2206
|
type: "action",
|
|
1306
2207
|
jsonSerializable: !0,
|
|
1307
|
-
args: [
|
|
1308
|
-
file:
|
|
1309
|
-
line:
|
|
2208
|
+
args: [o.object({
|
|
2209
|
+
file: Z,
|
|
2210
|
+
line: o.number().int().nonnegative().optional()
|
|
1310
2211
|
})],
|
|
1311
|
-
returns:
|
|
2212
|
+
returns: o.void(),
|
|
1312
2213
|
handler: ({ file: t, line: n }) => {
|
|
1313
|
-
let r =
|
|
2214
|
+
let r = K(e.root(), t);
|
|
1314
2215
|
e.openInEditor(n ? `${r}:${n}` : r);
|
|
1315
2216
|
}
|
|
1316
2217
|
}),
|
|
@@ -1318,18 +2219,18 @@ function Be(e) {
|
|
|
1318
2219
|
name: "open-reactive-in-editor",
|
|
1319
2220
|
type: "action",
|
|
1320
2221
|
jsonSerializable: !0,
|
|
1321
|
-
args: [
|
|
1322
|
-
file:
|
|
1323
|
-
name:
|
|
1324
|
-
type:
|
|
2222
|
+
args: [o.object({
|
|
2223
|
+
file: Z,
|
|
2224
|
+
name: o.string().max(256),
|
|
2225
|
+
type: o.string().max(32)
|
|
1325
2226
|
})],
|
|
1326
|
-
returns:
|
|
1327
|
-
handler: ({ file: t, name: n, type:
|
|
1328
|
-
let
|
|
2227
|
+
returns: o.void(),
|
|
2228
|
+
handler: ({ file: t, name: n, type: i }) => {
|
|
2229
|
+
let a = K(e.root(), t), o = 0;
|
|
1329
2230
|
try {
|
|
1330
|
-
o =
|
|
2231
|
+
o = Rt(r.readFileSync(a, "utf-8"), n, i);
|
|
1331
2232
|
} catch {}
|
|
1332
|
-
e.openInEditor(o > 0 ? `${
|
|
2233
|
+
e.openInEditor(o > 0 ? `${a}:${o}` : a);
|
|
1333
2234
|
}
|
|
1334
2235
|
}),
|
|
1335
2236
|
l({
|
|
@@ -1342,24 +2243,24 @@ function Be(e) {
|
|
|
1342
2243
|
name: "get-reactive-graph",
|
|
1343
2244
|
type: "query",
|
|
1344
2245
|
jsonSerializable: !0,
|
|
1345
|
-
args: [
|
|
1346
|
-
componentId:
|
|
1347
|
-
epoch:
|
|
1348
|
-
maxNodes:
|
|
1349
|
-
maxEdges:
|
|
2246
|
+
args: [o.object({
|
|
2247
|
+
componentId: o.number().int().nonnegative().optional(),
|
|
2248
|
+
epoch: o.string().max(200).optional(),
|
|
2249
|
+
maxNodes: o.number().int().positive().optional(),
|
|
2250
|
+
maxEdges: o.number().int().positive().optional()
|
|
1350
2251
|
}).optional()],
|
|
1351
|
-
returns:
|
|
2252
|
+
returns: o.custom(),
|
|
1352
2253
|
handler: (e) => t.requestReactiveGraph(e ?? {})
|
|
1353
2254
|
}),
|
|
1354
2255
|
l({
|
|
1355
2256
|
name: "get-reactive-summary",
|
|
1356
2257
|
type: "query",
|
|
1357
2258
|
jsonSerializable: !0,
|
|
1358
|
-
args: [
|
|
1359
|
-
topK:
|
|
1360
|
-
windowMs:
|
|
2259
|
+
args: [o.object({
|
|
2260
|
+
topK: o.number().int().positive().optional(),
|
|
2261
|
+
windowMs: o.number().int().positive().optional()
|
|
1361
2262
|
}).optional()],
|
|
1362
|
-
returns:
|
|
2263
|
+
returns: o.custom(),
|
|
1363
2264
|
handler: (e) => t.requestReactiveSummary(e ?? {})
|
|
1364
2265
|
}),
|
|
1365
2266
|
l({
|
|
@@ -1390,8 +2291,8 @@ function Be(e) {
|
|
|
1390
2291
|
name: "get-state-timeline-delta",
|
|
1391
2292
|
type: "query",
|
|
1392
2293
|
jsonSerializable: !0,
|
|
1393
|
-
args: [
|
|
1394
|
-
returns:
|
|
2294
|
+
args: [o.object({ since: o.number().int().nonnegative().optional() })],
|
|
2295
|
+
returns: o.custom(),
|
|
1395
2296
|
handler: ({ since: e }) => t.getStateTimelineDelta(e)
|
|
1396
2297
|
}),
|
|
1397
2298
|
l({
|
|
@@ -1410,15 +2311,15 @@ function Be(e) {
|
|
|
1410
2311
|
name: "get-api-endpoints",
|
|
1411
2312
|
type: "query",
|
|
1412
2313
|
jsonSerializable: !0,
|
|
1413
|
-
handler: () =>
|
|
2314
|
+
handler: () => _t(i())
|
|
1414
2315
|
}),
|
|
1415
2316
|
l({
|
|
1416
2317
|
name: "send-api-request",
|
|
1417
2318
|
type: "action",
|
|
1418
2319
|
jsonSerializable: !0,
|
|
1419
|
-
args: [
|
|
1420
|
-
url:
|
|
1421
|
-
method:
|
|
2320
|
+
args: [o.object({
|
|
2321
|
+
url: o.string().max(8192),
|
|
2322
|
+
method: o.enum([
|
|
1422
2323
|
"GET",
|
|
1423
2324
|
"POST",
|
|
1424
2325
|
"PUT",
|
|
@@ -1427,11 +2328,11 @@ function Be(e) {
|
|
|
1427
2328
|
"HEAD",
|
|
1428
2329
|
"OPTIONS"
|
|
1429
2330
|
]),
|
|
1430
|
-
headers:
|
|
1431
|
-
body:
|
|
2331
|
+
headers: o.string().max(65536),
|
|
2332
|
+
body: o.string().max(1e6)
|
|
1432
2333
|
})],
|
|
1433
|
-
returns:
|
|
1434
|
-
handler: (t) =>
|
|
2334
|
+
returns: o.custom(),
|
|
2335
|
+
handler: (t) => yt(t, { allowedOrigins: e.serverOrigins() })
|
|
1435
2336
|
}),
|
|
1436
2337
|
l({
|
|
1437
2338
|
name: "get-compiler-warnings",
|
|
@@ -1455,35 +2356,35 @@ function Be(e) {
|
|
|
1455
2356
|
name: "inspect-file",
|
|
1456
2357
|
type: "query",
|
|
1457
2358
|
jsonSerializable: !0,
|
|
1458
|
-
args: [
|
|
1459
|
-
returns:
|
|
2359
|
+
args: [o.object({ file: Z })],
|
|
2360
|
+
returns: o.custom(),
|
|
1460
2361
|
handler: async ({ file: t }) => {
|
|
1461
2362
|
let n = {
|
|
1462
2363
|
source: "",
|
|
1463
2364
|
compiled: "",
|
|
1464
2365
|
file: t
|
|
1465
|
-
},
|
|
2366
|
+
}, i, a;
|
|
1466
2367
|
try {
|
|
1467
|
-
|
|
2368
|
+
i = K(e.root(), t), a = r.readFileSync(i, "utf-8");
|
|
1468
2369
|
} catch {
|
|
1469
2370
|
return n;
|
|
1470
2371
|
}
|
|
1471
2372
|
if (!e.transformRequest) return {
|
|
1472
2373
|
...n,
|
|
1473
|
-
source:
|
|
2374
|
+
source: a
|
|
1474
2375
|
};
|
|
1475
2376
|
try {
|
|
1476
|
-
let n = await e.transformRequest(
|
|
2377
|
+
let n = await e.transformRequest(i), r = Wt(n?.map);
|
|
1477
2378
|
return {
|
|
1478
|
-
source:
|
|
1479
|
-
compiled: n?.code
|
|
2379
|
+
source: a,
|
|
2380
|
+
compiled: n?.code ?? "",
|
|
1480
2381
|
file: t,
|
|
1481
|
-
mappings:
|
|
1482
|
-
sources:
|
|
2382
|
+
mappings: r?.mappings,
|
|
2383
|
+
sources: r?.sources
|
|
1483
2384
|
};
|
|
1484
2385
|
} catch {
|
|
1485
2386
|
return {
|
|
1486
|
-
source:
|
|
2387
|
+
source: a,
|
|
1487
2388
|
compiled: "// Transform failed",
|
|
1488
2389
|
file: t
|
|
1489
2390
|
};
|
|
@@ -1494,21 +2395,21 @@ function Be(e) {
|
|
|
1494
2395
|
name: "get-module-graph",
|
|
1495
2396
|
type: "query",
|
|
1496
2397
|
jsonSerializable: !0,
|
|
1497
|
-
handler: () =>
|
|
2398
|
+
handler: () => Dt(e.root(), e.modules())
|
|
1498
2399
|
}),
|
|
1499
2400
|
l({
|
|
1500
2401
|
name: "get-og-preview",
|
|
1501
2402
|
type: "action",
|
|
1502
2403
|
jsonSerializable: !0,
|
|
1503
|
-
args: [
|
|
1504
|
-
returns:
|
|
1505
|
-
handler: ({ url: t }) =>
|
|
2404
|
+
args: [o.object({ url: o.string().max(8192) })],
|
|
2405
|
+
returns: o.custom(),
|
|
2406
|
+
handler: ({ url: t }) => Lt(t, { allowedOrigins: e.serverOrigins() })
|
|
1506
2407
|
}),
|
|
1507
2408
|
l({
|
|
1508
2409
|
name: "get-build-analysis",
|
|
1509
2410
|
type: "query",
|
|
1510
2411
|
jsonSerializable: !0,
|
|
1511
|
-
handler: () =>
|
|
2412
|
+
handler: () => wt(e.root())
|
|
1512
2413
|
}),
|
|
1513
2414
|
l({
|
|
1514
2415
|
name: "get-fps",
|
|
@@ -1524,701 +2425,106 @@ function Be(e) {
|
|
|
1524
2425
|
})
|
|
1525
2426
|
];
|
|
1526
2427
|
}
|
|
1527
|
-
function
|
|
2428
|
+
function Ut(e) {
|
|
1528
2429
|
return c({
|
|
1529
|
-
id:
|
|
2430
|
+
id: J,
|
|
1530
2431
|
name: "Svelte",
|
|
1531
|
-
version:
|
|
1532
|
-
packageName:
|
|
2432
|
+
version: X.version,
|
|
2433
|
+
packageName: X.name,
|
|
1533
2434
|
importMetaUrl: import.meta.url,
|
|
1534
|
-
homepage:
|
|
1535
|
-
description:
|
|
2435
|
+
homepage: X.homepage,
|
|
2436
|
+
description: X.description,
|
|
1536
2437
|
icon: "simple-icons:svelte",
|
|
1537
|
-
basePath:
|
|
2438
|
+
basePath: Y,
|
|
1538
2439
|
capabilities: {
|
|
1539
2440
|
dev: !0,
|
|
1540
|
-
build: !1
|
|
1541
|
-
},
|
|
1542
|
-
dock: { category: "framework" },
|
|
1543
|
-
clientAssets:
|
|
1544
|
-
setup(t) {
|
|
1545
|
-
let n = t.scope(
|
|
1546
|
-
for (let t of
|
|
1547
|
-
}
|
|
1548
|
-
});
|
|
1549
|
-
}
|
|
1550
|
-
//#endregion
|
|
1551
|
-
//#region src/mount.ts
|
|
1552
|
-
var He = /* @__PURE__ */ new Set(["0.0.0.0", "::"]), Ue = /* @__PURE__ */ new Set([
|
|
1553
|
-
"127.0.0.1",
|
|
1554
|
-
"::1",
|
|
1555
|
-
"localhost"
|
|
1556
|
-
]);
|
|
1557
|
-
function We(e) {
|
|
1558
|
-
return e.includes(":") ? `[${e}]` : e;
|
|
1559
|
-
}
|
|
1560
|
-
function Ge(e, t) {
|
|
1561
|
-
return Ue.has(e.address) ? [] : (He.has(e.address) ? Object.values(p.networkInterfaces()).flat().filter((e) => e && !e.internal).map((e) => e.address) : [e.address]).map((n) => `${t}://${We(n)}:${e.port}`);
|
|
1562
|
-
}
|
|
1563
|
-
function Ke(e, t) {
|
|
1564
|
-
let n, r = !1, i = e.httpServer;
|
|
1565
|
-
if (e.middlewares.use((e, t, r) => {
|
|
1566
|
-
if (n) return n.nodeMiddleware(e, t, r);
|
|
1567
|
-
if (e.url?.startsWith("/.svelte-devtools/")) {
|
|
1568
|
-
t.statusCode = 503, t.end("Svelte DevTools is starting");
|
|
1569
|
-
return;
|
|
1570
|
-
}
|
|
1571
|
-
r();
|
|
1572
|
-
}), !i) {
|
|
1573
|
-
let e = m(t, {
|
|
1574
|
-
base: L,
|
|
1575
|
-
ws: !1,
|
|
1576
|
-
sse: !0,
|
|
1577
|
-
mcp: !1
|
|
1578
|
-
});
|
|
1579
|
-
return n = e, async () => {
|
|
1580
|
-
n = void 0, await e.close().catch(() => {});
|
|
1581
|
-
};
|
|
1582
|
-
}
|
|
1583
|
-
i.once("listening", () => {
|
|
1584
|
-
let a = i.address();
|
|
1585
|
-
if (r || !a || typeof a == "string") return;
|
|
1586
|
-
let o = e.config.server.https ? "https" : "http", s = `${o}://localhost:${a.port}`;
|
|
1587
|
-
n = m(t, {
|
|
1588
|
-
base: L,
|
|
1589
|
-
server: i,
|
|
1590
|
-
mcp: !1,
|
|
1591
|
-
origin: `${s}${L}`,
|
|
1592
|
-
allowedOrigins: Ge(a, o)
|
|
1593
|
-
}), e.config.logger.info(` ➜ Svelte DevTools: ${s}${L}`);
|
|
1594
|
-
}), i.once("close", () => {
|
|
1595
|
-
r = !0;
|
|
1596
|
-
let e = n;
|
|
1597
|
-
n = void 0, e?.close().catch(() => {});
|
|
1598
|
-
});
|
|
1599
|
-
}
|
|
1600
|
-
//#endregion
|
|
1601
|
-
//#region src/mcp/sessions.ts
|
|
1602
|
-
var qe = 1, Je = 5, Ye = 1, Xe = 30;
|
|
1603
|
-
function B(e) {
|
|
1604
|
-
return {
|
|
1605
|
-
renderProfiles: e.getRenderProfiles().map((e) => ({
|
|
1606
|
-
componentId: e.componentId,
|
|
1607
|
-
file: e.file,
|
|
1608
|
-
name: e.name,
|
|
1609
|
-
renderCount: e.renderCount,
|
|
1610
|
-
totalRenderTime: e.totalRenderTime
|
|
1611
|
-
})),
|
|
1612
|
-
loadProfilesCount: e.getLoadProfiles().length,
|
|
1613
|
-
fpsCount: e.getFpsSamples().length,
|
|
1614
|
-
takenAt: Date.now()
|
|
1615
|
-
};
|
|
1616
|
-
}
|
|
1617
|
-
function V(e, t, n) {
|
|
1618
|
-
return Math.abs(e) < t ? "unchanged" : n ? e < 0 ? "improved" : "regressed" : e > 0 ? "improved" : "regressed";
|
|
1619
|
-
}
|
|
1620
|
-
var H = /^s_[0-9a-z]{1,16}_[0-9a-f]{6}$/;
|
|
1621
|
-
function U(e) {
|
|
1622
|
-
return typeof e == "string" && H.test(e);
|
|
1623
|
-
}
|
|
1624
|
-
var Ze = class {
|
|
1625
|
-
sessions = /* @__PURE__ */ new Map();
|
|
1626
|
-
active = null;
|
|
1627
|
-
persistDir;
|
|
1628
|
-
getters;
|
|
1629
|
-
constructor(t) {
|
|
1630
|
-
this.persistDir = e.resolve(t.persistDir), this.getters = t.getters;
|
|
1631
|
-
}
|
|
1632
|
-
start(e, n) {
|
|
1633
|
-
if (this.active) throw Error(`A session is already active: ${this.active}. Call end_session before starting a new one.`);
|
|
1634
|
-
let r = `s_${Date.now().toString(36)}_${t.randomBytes(3).toString("hex")}`, i = {
|
|
1635
|
-
id: r,
|
|
1636
|
-
label: e,
|
|
1637
|
-
startedAt: Date.now(),
|
|
1638
|
-
persist: n,
|
|
1639
|
-
startSnapshot: B(this.getters),
|
|
1640
|
-
loadProfiles: [],
|
|
1641
|
-
fpsSamples: []
|
|
1642
|
-
};
|
|
1643
|
-
return this.sessions.set(r, i), this.active = r, i;
|
|
1644
|
-
}
|
|
1645
|
-
recordLoadProfile(e) {
|
|
1646
|
-
if (!this.active) return;
|
|
1647
|
-
let t = this.sessions.get(this.active);
|
|
1648
|
-
t && t.loadProfiles.push(e);
|
|
1649
|
-
}
|
|
1650
|
-
recordFpsSample(e) {
|
|
1651
|
-
if (!this.active) return;
|
|
1652
|
-
let t = this.sessions.get(this.active);
|
|
1653
|
-
t && t.fpsSamples.push(e);
|
|
1654
|
-
}
|
|
1655
|
-
end(e) {
|
|
1656
|
-
if (!this.active) throw Error("No active session");
|
|
1657
|
-
let t = this.sessions.get(this.active);
|
|
1658
|
-
if (!t) throw Error("Active session missing from store");
|
|
1659
|
-
return t.endedAt = Date.now(), t.endSnapshot = B(this.getters), this.active = null, e === "discard" ? this.sessions.delete(t.id) : (e === "disk" || t.persist) && this.persistToDisk(t), t;
|
|
1660
|
-
}
|
|
1661
|
-
get(e) {
|
|
1662
|
-
return U(e) ? this.sessions.get(e) || this.loadFromDisk(e) : void 0;
|
|
1663
|
-
}
|
|
1664
|
-
list() {
|
|
1665
|
-
let e = /* @__PURE__ */ new Set(), t = [];
|
|
1666
|
-
for (let n of this.sessions.values()) e.add(n.id), t.push({
|
|
1667
|
-
id: n.id,
|
|
1668
|
-
label: n.label,
|
|
1669
|
-
startedAt: n.startedAt,
|
|
1670
|
-
endedAt: n.endedAt,
|
|
1671
|
-
persisted: a.existsSync(this.pathFor(n.id)),
|
|
1672
|
-
active: this.active === n.id
|
|
1673
|
-
});
|
|
1674
|
-
try {
|
|
1675
|
-
if (a.existsSync(this.persistDir)) for (let n of a.readdirSync(this.persistDir)) {
|
|
1676
|
-
if (!n.endsWith(".json")) continue;
|
|
1677
|
-
let r = n.slice(0, -5);
|
|
1678
|
-
if (U(r) && !e.has(r)) try {
|
|
1679
|
-
let e = JSON.parse(a.readFileSync(this.pathFor(r), "utf-8"));
|
|
1680
|
-
if (e?.id !== r) continue;
|
|
1681
|
-
t.push({
|
|
1682
|
-
id: e.id,
|
|
1683
|
-
label: e.label,
|
|
1684
|
-
startedAt: e.startedAt,
|
|
1685
|
-
endedAt: e.endedAt,
|
|
1686
|
-
persisted: !0,
|
|
1687
|
-
active: !1
|
|
1688
|
-
});
|
|
1689
|
-
} catch {}
|
|
1690
|
-
}
|
|
1691
|
-
} catch {}
|
|
1692
|
-
return t.sort((e, t) => t.startedAt - e.startedAt);
|
|
1693
|
-
}
|
|
1694
|
-
delete(e) {
|
|
1695
|
-
if (!U(e)) return !1;
|
|
1696
|
-
let t = this.sessions.delete(e), n = !1;
|
|
1697
|
-
try {
|
|
1698
|
-
let t = this.pathFor(e);
|
|
1699
|
-
a.existsSync(t) && (a.unlinkSync(t), n = !0);
|
|
1700
|
-
} catch {}
|
|
1701
|
-
return this.active === e && (this.active = null), t || n;
|
|
1702
|
-
}
|
|
1703
|
-
delta(e) {
|
|
1704
|
-
let t = this.get(e);
|
|
1705
|
-
if (!t) throw Error(`Session not found: ${e}`);
|
|
1706
|
-
if (!t.endSnapshot || t.endedAt === void 0) throw Error(`Session ${e} has not been ended yet`);
|
|
1707
|
-
let n = new Map(t.startSnapshot.renderProfiles.map((e) => [e.componentId, e])), r = t.endSnapshot.renderProfiles.map((e) => {
|
|
1708
|
-
let t = n.get(e.componentId), r = t?.renderCount ?? 0, i = t?.totalRenderTime ?? 0, a = e.renderCount - r, o = e.totalRenderTime - i;
|
|
1709
|
-
return {
|
|
1710
|
-
componentId: e.componentId,
|
|
1711
|
-
file: e.file,
|
|
1712
|
-
name: e.name,
|
|
1713
|
-
renderCountDelta: a,
|
|
1714
|
-
totalRenderTimeDelta: o,
|
|
1715
|
-
avgRenderTimeDelta: a > 0 ? o / a : 0
|
|
1716
|
-
};
|
|
1717
|
-
}).filter((e) => e.renderCountDelta > 0).sort((e, t) => t.totalRenderTimeDelta - e.totalRenderTimeDelta), i = t.loadProfiles.map((e) => e.duration), a = W(i), o = Qe(i, 95), s = t.fpsSamples.map((e) => e.fps), c = W(s), l = s.length ? Math.min(...s) : 0, u = s.filter((e) => e < Xe).length;
|
|
1718
|
-
return {
|
|
1719
|
-
durationMs: t.endedAt - t.startedAt,
|
|
1720
|
-
components: r,
|
|
1721
|
-
loadProfiles: {
|
|
1722
|
-
count: t.loadProfiles.length,
|
|
1723
|
-
avgDuration: a,
|
|
1724
|
-
p95Duration: o
|
|
1725
|
-
},
|
|
1726
|
-
fps: {
|
|
1727
|
-
samples: s.length,
|
|
1728
|
-
avg: c,
|
|
1729
|
-
min: l,
|
|
1730
|
-
drops: u
|
|
1731
|
-
}
|
|
1732
|
-
};
|
|
1733
|
-
}
|
|
1734
|
-
compare(e, t) {
|
|
1735
|
-
let n = this.delta(e), r = this.delta(t), i = this.get(e), a = this.get(t), o = n.components.reduce((e, t) => e + t.totalRenderTimeDelta, 0), s = r.components.reduce((e, t) => e + t.totalRenderTimeDelta, 0), c = s - o, l = r.loadProfiles.avgDuration - n.loadProfiles.avgDuration, u = r.fps.avg - n.fps.avg;
|
|
1736
|
-
return {
|
|
1737
|
-
a: {
|
|
1738
|
-
id: i.id,
|
|
1739
|
-
label: i.label
|
|
1740
|
-
},
|
|
1741
|
-
b: {
|
|
1742
|
-
id: a.id,
|
|
1743
|
-
label: a.label
|
|
1744
|
-
},
|
|
1745
|
-
render: {
|
|
1746
|
-
totalRenderTimeDeltaA: o,
|
|
1747
|
-
totalRenderTimeDeltaB: s,
|
|
1748
|
-
diff: c,
|
|
1749
|
-
verdict: V(c, qe, !0)
|
|
1750
|
-
},
|
|
1751
|
-
load: {
|
|
1752
|
-
avgA: n.loadProfiles.avgDuration,
|
|
1753
|
-
avgB: r.loadProfiles.avgDuration,
|
|
1754
|
-
diff: l,
|
|
1755
|
-
verdict: V(l, Je, !0)
|
|
1756
|
-
},
|
|
1757
|
-
fps: {
|
|
1758
|
-
avgA: n.fps.avg,
|
|
1759
|
-
avgB: r.fps.avg,
|
|
1760
|
-
diff: u,
|
|
1761
|
-
verdict: V(u, Ye, !1)
|
|
1762
|
-
}
|
|
1763
|
-
};
|
|
1764
|
-
}
|
|
1765
|
-
pathFor(t) {
|
|
1766
|
-
if (!U(t)) throw Error("Invalid session id");
|
|
1767
|
-
let n = e.resolve(this.persistDir, `${t}.json`);
|
|
1768
|
-
if (e.dirname(n) !== this.persistDir) throw Error("Invalid session id");
|
|
1769
|
-
return n;
|
|
1770
|
-
}
|
|
1771
|
-
persistToDisk(e) {
|
|
1772
|
-
try {
|
|
1773
|
-
a.mkdirSync(this.persistDir, { recursive: !0 }), a.writeFileSync(this.pathFor(e.id), JSON.stringify(e, null, 2), { mode: 384 });
|
|
1774
|
-
} catch {}
|
|
1775
|
-
}
|
|
1776
|
-
loadFromDisk(e) {
|
|
1777
|
-
try {
|
|
1778
|
-
let t = a.readFileSync(this.pathFor(e), "utf-8"), n = JSON.parse(t);
|
|
1779
|
-
return n?.id === e ? n : void 0;
|
|
1780
|
-
} catch {
|
|
1781
|
-
return;
|
|
1782
|
-
}
|
|
1783
|
-
}
|
|
1784
|
-
};
|
|
1785
|
-
function W(e) {
|
|
1786
|
-
return e.length ? e.reduce((e, t) => e + t, 0) / e.length : 0;
|
|
1787
|
-
}
|
|
1788
|
-
function Qe(e, t) {
|
|
1789
|
-
if (!e.length) return 0;
|
|
1790
|
-
let n = [...e].sort((e, t) => e - t);
|
|
1791
|
-
return n[Math.min(n.length - 1, Math.floor(t / 100 * n.length))];
|
|
1792
|
-
}
|
|
1793
|
-
//#endregion
|
|
1794
|
-
//#region src/mcp/issues.ts
|
|
1795
|
-
var G = {
|
|
1796
|
-
avgRenderTimeMs: 4,
|
|
1797
|
-
renderCount: 30,
|
|
1798
|
-
loadDurationMs: 200,
|
|
1799
|
-
fpsDropThreshold: 40,
|
|
1800
|
-
effectMaxDeps: 8
|
|
1801
|
-
};
|
|
1802
|
-
function $e(e, t = {}) {
|
|
1803
|
-
let n = {
|
|
1804
|
-
...G,
|
|
1805
|
-
...t
|
|
1806
|
-
}, r = [];
|
|
1807
|
-
for (let t of e.renderProfiles) {
|
|
1808
|
-
let e = t.renderCount > 0 ? t.totalRenderTime / t.renderCount : 0;
|
|
1809
|
-
e >= n.avgRenderTimeMs && r.push({
|
|
1810
|
-
id: `slow-render:${t.file}:${t.componentId}`,
|
|
1811
|
-
kind: "slow-component-render",
|
|
1812
|
-
severity: e >= n.avgRenderTimeMs * 4 ? "high" : e >= n.avgRenderTimeMs * 2 ? "medium" : "low",
|
|
1813
|
-
summary: `${t.name} averages ${e.toFixed(2)}ms per render (${t.renderCount} renders)`,
|
|
1814
|
-
file: t.file,
|
|
1815
|
-
metric: {
|
|
1816
|
-
avgRenderTimeMs: q(e),
|
|
1817
|
-
renderCount: t.renderCount,
|
|
1818
|
-
totalRenderTimeMs: q(t.totalRenderTime)
|
|
1819
|
-
},
|
|
1820
|
-
suggestedTool: "get_render_profile"
|
|
1821
|
-
}), t.renderCount >= n.renderCount && r.push({
|
|
1822
|
-
id: `over-render:${t.file}:${t.componentId}`,
|
|
1823
|
-
kind: "over-rendered-component",
|
|
1824
|
-
severity: t.renderCount >= n.renderCount * 8 ? "high" : t.renderCount >= n.renderCount * 3 ? "medium" : "low",
|
|
1825
|
-
summary: `${t.name} rendered ${t.renderCount} times`,
|
|
1826
|
-
file: t.file,
|
|
1827
|
-
metric: {
|
|
1828
|
-
renderCount: t.renderCount,
|
|
1829
|
-
avgRenderTimeMs: q(e)
|
|
1830
|
-
},
|
|
1831
|
-
suggestedTool: "get_component_hotspots"
|
|
1832
|
-
});
|
|
1833
|
-
}
|
|
1834
|
-
for (let t of e.loadProfiles) t.duration >= n.loadDurationMs && r.push({
|
|
1835
|
-
id: `slow-load:${t.route}:${t.timestamp}`,
|
|
1836
|
-
kind: "slow-load",
|
|
1837
|
-
severity: t.duration >= n.loadDurationMs * 5 ? "high" : t.duration >= n.loadDurationMs * 2 ? "medium" : "low",
|
|
1838
|
-
summary: `${t.type} load for ${t.route} took ${t.duration.toFixed(0)}ms`,
|
|
1839
|
-
file: t.file,
|
|
1840
|
-
metric: {
|
|
1841
|
-
durationMs: q(t.duration),
|
|
1842
|
-
dataSizeBytes: t.dataSize
|
|
1843
|
-
},
|
|
1844
|
-
suggestedTool: "get_load_waterfall"
|
|
1845
|
-
});
|
|
1846
|
-
let i = e.fpsSamples.filter((e) => e.fps < n.fpsDropThreshold);
|
|
1847
|
-
if (i.length > 0) {
|
|
1848
|
-
let t = Math.min(...i.map((e) => e.fps));
|
|
1849
|
-
r.push({
|
|
1850
|
-
id: `fps-drops:${e.fpsSamples[0]?.timestamp ?? 0}`,
|
|
1851
|
-
kind: "fps-drop",
|
|
1852
|
-
severity: t < 15 ? "high" : t < 30 ? "medium" : "low",
|
|
1853
|
-
summary: `${i.length} FPS samples below ${n.fpsDropThreshold} (min ${t})`,
|
|
1854
|
-
metric: {
|
|
1855
|
-
dropCount: i.length,
|
|
1856
|
-
minFps: t,
|
|
1857
|
-
threshold: n.fpsDropThreshold
|
|
1858
|
-
},
|
|
1859
|
-
suggestedTool: "get_fps_drops"
|
|
1860
|
-
});
|
|
1861
|
-
}
|
|
1862
|
-
let a = /* @__PURE__ */ new Map();
|
|
1863
|
-
for (let t of e.reactiveGraph.edges) a.set(t.to, (a.get(t.to) ?? 0) + 1);
|
|
1864
|
-
for (let t of e.reactiveGraph.nodes) if (t.type === "effect") {
|
|
1865
|
-
let e = a.get(t.id) ?? 0;
|
|
1866
|
-
e >= n.effectMaxDeps && r.push({
|
|
1867
|
-
id: `effect-deps:${t.id}`,
|
|
1868
|
-
kind: "effect-overconnected",
|
|
1869
|
-
severity: e >= n.effectMaxDeps * 3 ? "high" : e >= n.effectMaxDeps * 2 ? "medium" : "low",
|
|
1870
|
-
summary: `effect "${t.name}" depends on ${e} reactive values`,
|
|
1871
|
-
file: t.componentFile,
|
|
1872
|
-
metric: { depCount: e },
|
|
1873
|
-
suggestedTool: "get_reactive_graph_problems"
|
|
1874
|
-
});
|
|
1875
|
-
}
|
|
1876
|
-
return r.sort(et);
|
|
1877
|
-
}
|
|
1878
|
-
var K = {
|
|
1879
|
-
high: 0,
|
|
1880
|
-
medium: 1,
|
|
1881
|
-
low: 2
|
|
1882
|
-
};
|
|
1883
|
-
function et(e, t) {
|
|
1884
|
-
return K[e.severity] - K[t.severity];
|
|
1885
|
-
}
|
|
1886
|
-
function q(e) {
|
|
1887
|
-
return Math.round(e * 100) / 100;
|
|
2441
|
+
build: !1
|
|
2442
|
+
},
|
|
2443
|
+
dock: { category: "framework" },
|
|
2444
|
+
clientAssets: Vt,
|
|
2445
|
+
setup(t) {
|
|
2446
|
+
let n = t.scope(J);
|
|
2447
|
+
for (let t of Ht(e)) n.rpc.register(t);
|
|
2448
|
+
}
|
|
2449
|
+
});
|
|
1888
2450
|
}
|
|
1889
|
-
function
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
for (let t of e.edges) r.set(t.to, (r.get(t.to) ?? 0) + 1), i.set(t.from, (i.get(t.from) ?? 0) + 1);
|
|
1895
|
-
let a = [], o = [], s = [];
|
|
1896
|
-
for (let t of e.nodes) {
|
|
1897
|
-
let e = r.get(t.id) ?? 0, c = i.get(t.id) ?? 0;
|
|
1898
|
-
t.type === "effect" && e >= n.effectMaxDeps && a.push({
|
|
1899
|
-
id: t.id,
|
|
1900
|
-
name: t.name,
|
|
1901
|
-
file: t.componentFile,
|
|
1902
|
-
depCount: e
|
|
1903
|
-
}), t.type !== "template" && (t.type === "derived" && t.unevaluated ? o.push({
|
|
1904
|
-
id: t.id,
|
|
1905
|
-
name: t.name,
|
|
1906
|
-
file: t.componentFile
|
|
1907
|
-
}) : e === 0 && c === 0 && !t.untrackedDeps && s.push({
|
|
1908
|
-
id: t.id,
|
|
1909
|
-
name: t.name,
|
|
1910
|
-
type: t.type,
|
|
1911
|
-
file: t.componentFile
|
|
1912
|
-
}));
|
|
1913
|
-
}
|
|
2451
|
+
function Wt(e) {
|
|
2452
|
+
if (typeof e != "string") return e ?? void 0;
|
|
2453
|
+
let t = JSON.parse(e);
|
|
2454
|
+
if (typeof t != "object" || !t) return;
|
|
2455
|
+
let { mappings: n, sources: r } = t;
|
|
1914
2456
|
return {
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
isolatedNodes: s
|
|
2457
|
+
mappings: typeof n == "string" ? n : void 0,
|
|
2458
|
+
sources: Array.isArray(r) && r.every((e) => typeof e == "string") ? r : void 0
|
|
1918
2459
|
};
|
|
1919
2460
|
}
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
}
|
|
1929
|
-
return { omitted: "not serializable" };
|
|
1930
|
-
}
|
|
1931
|
-
return n === void 0 || n.length <= t ? e : {
|
|
1932
|
-
omitted: "too large",
|
|
1933
|
-
chars: n.length,
|
|
1934
|
-
preview: n.slice(0, Math.min(200, t))
|
|
1935
|
-
};
|
|
2461
|
+
//#endregion
|
|
2462
|
+
//#region src/server/mount.ts
|
|
2463
|
+
var Gt = /* @__PURE__ */ new Set(["0.0.0.0", "::"]), Kt = /* @__PURE__ */ new Set([
|
|
2464
|
+
"127.0.0.1",
|
|
2465
|
+
"::1",
|
|
2466
|
+
"localhost"
|
|
2467
|
+
]);
|
|
2468
|
+
function qt(e) {
|
|
2469
|
+
return e.includes(":") ? `[${e}]` : e;
|
|
1936
2470
|
}
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
}
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
title: "List performance issues",
|
|
1948
|
-
description: "Cross-cuts render/reactive/load/fps metrics and returns ranked issues. The entry point for AI-driven performance audits. Each issue carries `suggestedTool` naming the detail tool to call next.",
|
|
1949
|
-
inputSchema: {
|
|
1950
|
-
avgRenderTimeMs: u.number().optional(),
|
|
1951
|
-
renderCount: u.number().optional(),
|
|
1952
|
-
loadDurationMs: u.number().optional(),
|
|
1953
|
-
fpsDropThreshold: u.number().optional(),
|
|
1954
|
-
effectMaxDeps: u.number().optional()
|
|
1955
|
-
}
|
|
1956
|
-
}, async (t) => {
|
|
1957
|
-
let n = t, r = await e.getReactiveGraph(), i = $e({
|
|
1958
|
-
renderProfiles: e.getRenderProfiles(),
|
|
1959
|
-
reactiveGraph: r,
|
|
1960
|
-
loadProfiles: e.getLoadProfiles(),
|
|
1961
|
-
fpsSamples: e.getFpsSamples()
|
|
1962
|
-
}, n);
|
|
1963
|
-
return Z({
|
|
1964
|
-
count: i.length,
|
|
1965
|
-
issues: i
|
|
1966
|
-
});
|
|
1967
|
-
}), t.registerTool("get_component_hotspots", {
|
|
1968
|
-
title: "Component render hotspots",
|
|
1969
|
-
description: "Top components by total render time, with render count and per-render average.",
|
|
1970
|
-
inputSchema: { topN: u.number().int().min(1).max(200).optional() }
|
|
1971
|
-
}, async ({ topN: t = 20 }) => Z([...e.getRenderProfiles()].map((e) => ({
|
|
1972
|
-
file: e.file,
|
|
1973
|
-
name: e.name,
|
|
1974
|
-
componentId: e.componentId,
|
|
1975
|
-
renderCount: e.renderCount,
|
|
1976
|
-
totalRenderTimeMs: Q(e.totalRenderTime),
|
|
1977
|
-
avgRenderTimeMs: Q(e.renderCount > 0 ? e.totalRenderTime / e.renderCount : 0),
|
|
1978
|
-
lastRenderTimeMs: Q(e.lastRenderTime),
|
|
1979
|
-
lastRenderAt: e.lastRenderAt
|
|
1980
|
-
})).sort((e, t) => t.totalRenderTimeMs - e.totalRenderTimeMs).slice(0, t))), t.registerTool("get_reactive_graph_problems", {
|
|
1981
|
-
title: "Reactive graph problems",
|
|
1982
|
-
description: "Classified reactive graph issues: over-connected effects, orphan deriveds (declared but never evaluated so far — may still be read later, e.g. in a branch not shown yet), isolated nodes (no tracked dependency or reader; reads from the markup count as readers). Returns categories instead of the full graph.",
|
|
1983
|
-
inputSchema: { effectMaxDeps: u.number().int().min(1).optional() }
|
|
1984
|
-
}, async ({ effectMaxDeps: t }) => Z(tt(await e.getReactiveGraph(), { effectMaxDeps: t }))), t.registerTool("get_load_waterfall", {
|
|
1985
|
-
title: "SvelteKit load waterfall",
|
|
1986
|
-
description: "Load profiles grouped by route, with timing and data size. Optionally filtered by route.",
|
|
1987
|
-
inputSchema: { route: u.string().optional() }
|
|
1988
|
-
}, async ({ route: t }) => {
|
|
1989
|
-
let n = e.getLoadProfiles();
|
|
1990
|
-
t && (n = n.filter((e) => e.route === t));
|
|
1991
|
-
let r = /* @__PURE__ */ new Map();
|
|
1992
|
-
for (let e of n) {
|
|
1993
|
-
let t = r.get(e.route) ?? [];
|
|
1994
|
-
t.push(e), r.set(e.route, t);
|
|
1995
|
-
}
|
|
1996
|
-
return Z([...r.entries()].map(([e, t]) => {
|
|
1997
|
-
let n = t.map((e) => e.duration);
|
|
1998
|
-
return {
|
|
1999
|
-
route: e,
|
|
2000
|
-
file: t[0]?.file,
|
|
2001
|
-
count: t.length,
|
|
2002
|
-
avgDuration: Q(rt(n)),
|
|
2003
|
-
maxDuration: Q(Math.max(...n)),
|
|
2004
|
-
totalDataBytes: t.reduce((e, t) => e + t.dataSize, 0),
|
|
2005
|
-
samples: t
|
|
2006
|
-
};
|
|
2007
|
-
}).sort((e, t) => t.avgDuration - e.avgDuration));
|
|
2008
|
-
}), t.registerTool("get_fps_drops", {
|
|
2009
|
-
title: "FPS drops",
|
|
2010
|
-
description: "Samples whose FPS fell below `threshold`. Returns timestamp + fps for each.",
|
|
2011
|
-
inputSchema: {
|
|
2012
|
-
threshold: u.number().min(1).max(120).optional(),
|
|
2013
|
-
sinceMs: u.number().optional()
|
|
2014
|
-
}
|
|
2015
|
-
}, async ({ threshold: t = 40, sinceMs: n }) => {
|
|
2016
|
-
let r = e.getFpsSamples();
|
|
2017
|
-
if (n !== void 0) {
|
|
2018
|
-
let e = Date.now() - n;
|
|
2019
|
-
r = r.filter((t) => t.timestamp >= e);
|
|
2020
|
-
}
|
|
2021
|
-
let i = r.filter((e) => e.fps < t);
|
|
2022
|
-
return Z({
|
|
2023
|
-
threshold: t,
|
|
2024
|
-
sampleCount: r.length,
|
|
2025
|
-
dropCount: i.length,
|
|
2026
|
-
minFps: i.length ? Math.min(...i.map((e) => e.fps)) : null,
|
|
2027
|
-
drops: i
|
|
2028
|
-
});
|
|
2029
|
-
}), t.registerTool("get_render_profile", {
|
|
2030
|
-
title: "Render profile for a specific file",
|
|
2031
|
-
description: "Returns render profile entries matching the given component file (substring match).",
|
|
2032
|
-
inputSchema: { file: u.string() }
|
|
2033
|
-
}, async ({ file: t }) => Z(e.getRenderProfiles().filter((e) => e.file.includes(t)).map((e) => ({
|
|
2034
|
-
...e,
|
|
2035
|
-
totalRenderTimeMs: Q(e.totalRenderTime),
|
|
2036
|
-
avgRenderTimeMs: Q(e.renderCount > 0 ? e.totalRenderTime / e.renderCount : 0)
|
|
2037
|
-
})))), t.registerTool("get_project_info", {
|
|
2038
|
-
title: "Project info",
|
|
2039
|
-
description: "Package name/version, Svelte / SvelteKit / Vite versions, dependency lists.",
|
|
2040
|
-
inputSchema: {}
|
|
2041
|
-
}, async () => Z(e.getProject())), t.registerTool("get_routes", {
|
|
2042
|
-
title: "SvelteKit routes",
|
|
2043
|
-
description: "Static analysis of the SvelteKit routes tree.",
|
|
2044
|
-
inputSchema: {}
|
|
2045
|
-
}, async () => Z(e.getRoutes())), t.registerTool("get_live_components", {
|
|
2046
|
-
title: "Currently mounted components",
|
|
2047
|
-
description: "Component instances with file, parent, mounted status as currently mounted in the browser (an array, parents first). With `includeMeta: true` the answer is `{ epoch, total, captured, truncated, components }` instead, limited to `limit` (default 1000): component ids are only valid within that `epoch` (one page load), so pass both to get_reactive_scope.",
|
|
2048
|
-
inputSchema: {
|
|
2049
|
-
includeMeta: u.boolean().optional(),
|
|
2050
|
-
limit: u.number().int().min(1).max(5e4).optional()
|
|
2051
|
-
}
|
|
2052
|
-
}, async ({ includeMeta: t, limit: n }) => {
|
|
2053
|
-
if (!t || !e.getLiveSnapshot) {
|
|
2054
|
-
let t = e.getLiveComponents();
|
|
2055
|
-
return Z(n === void 0 ? t : t.slice(0, n));
|
|
2471
|
+
function Jt(e, t) {
|
|
2472
|
+
return Kt.has(e.address) ? [] : (Gt.has(e.address) ? Object.values(p.networkInterfaces()).flat().filter((e) => e && !e.internal).map((e) => e.address) : [e.address]).map((n) => `${t}://${qt(n)}:${e.port}`);
|
|
2473
|
+
}
|
|
2474
|
+
function Yt(e, t, { auth: n = !0 } = {}) {
|
|
2475
|
+
let r, i = !1, a = e.httpServer;
|
|
2476
|
+
if (e.middlewares.use((e, t, n) => {
|
|
2477
|
+
if (r) return r.nodeMiddleware(e, t, n);
|
|
2478
|
+
if (e.url?.startsWith("/.svelte-devtools/")) {
|
|
2479
|
+
t.statusCode = 503, t.end("Svelte DevTools is starting");
|
|
2480
|
+
return;
|
|
2056
2481
|
}
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
}), t.registerTool("get_component_relations", {
|
|
2066
|
-
title: "Component import graph",
|
|
2067
|
-
description: "Static import relations between .svelte components.",
|
|
2068
|
-
inputSchema: {}
|
|
2069
|
-
}, async () => Z(e.getComponentRelations())), e.getReactiveSummary) {
|
|
2070
|
-
let n = e.getReactiveSummary;
|
|
2071
|
-
t.registerTool("get_reactive_summary", {
|
|
2072
|
-
title: "Reactive overview (top components)",
|
|
2073
|
-
description: "Busiest component instances from runtime counters over all instances, without capturing the graph. Counts are sampled state changes (at most one per state per 200 ms) and renders within the window, not rates. Tracked: state created during component init and shared state of .svelte.js/.ts modules (rows with kind \"module\"; their componentId is a module scope id, usable with get_reactive_scope). `rows` + `other` add up to the totals; null means unknown. Use get_reactive_scope with a componentId to look at one instance. The same request is answered from a cache for up to 1 s; `window.until` says when the answer was computed.",
|
|
2074
|
-
inputSchema: {
|
|
2075
|
-
topK: u.number().int().min(1).max(200).optional(),
|
|
2076
|
-
windowMs: u.number().int().min(1e3).max(6e4).optional()
|
|
2077
|
-
}
|
|
2078
|
-
}, async ({ topK: e, windowMs: t }) => Z(await n({
|
|
2079
|
-
topK: e,
|
|
2080
|
-
windowMs: t
|
|
2081
|
-
})));
|
|
2082
|
-
}
|
|
2083
|
-
if (e.getReactiveScope) {
|
|
2084
|
-
let n = e.getReactiveScope;
|
|
2085
|
-
t.registerTool("get_reactive_scope", {
|
|
2086
|
-
title: "Reactive graph of one component",
|
|
2087
|
-
description: "$state/$derived/$effect nodes of one component instance and their direct neighbours, built in the app within the caps. Edges mean 'can affect' (current dependencies), not a recorded cause. `componentId` requires the `epoch` it came from (get_live_components with includeMeta): after a reload the answer is empty with staleReason 'epoch-changed' instead of another instance. Omit componentId only for the whole-app graph (capped; see total/truncated). The same request may be answered from a cache for up to 1 s; `computedAt` says when the app built the graph (null when unknown or for an empty fallback). `stale: true` only marks a fallback after the app did not answer (an earlier answer with its own computedAt, or empty).",
|
|
2088
|
-
inputSchema: {
|
|
2089
|
-
componentId: u.number().int().nonnegative().optional(),
|
|
2090
|
-
epoch: u.string().max(200).optional(),
|
|
2091
|
-
maxNodes: u.number().int().min(1).max(5e3).optional(),
|
|
2092
|
-
maxEdges: u.number().int().min(1).max(2e4).optional()
|
|
2093
|
-
}
|
|
2094
|
-
}, async ({ componentId: e, epoch: t, maxNodes: r, maxEdges: i }) => e !== void 0 && t === void 0 ? {
|
|
2095
|
-
content: [{
|
|
2096
|
-
type: "text",
|
|
2097
|
-
text: "componentId requires epoch (from get_live_components with includeMeta: true)"
|
|
2098
|
-
}],
|
|
2099
|
-
isError: !0
|
|
2100
|
-
} : Z(await n({
|
|
2101
|
-
componentId: e,
|
|
2102
|
-
epoch: t,
|
|
2103
|
-
maxNodes: r,
|
|
2104
|
-
maxEdges: i
|
|
2105
|
-
})));
|
|
2106
|
-
}
|
|
2107
|
-
if (e.getStateTimelineDelta) {
|
|
2108
|
-
let n = e.getStateTimelineDelta;
|
|
2109
|
-
t.registerTool("get_state_timeline", {
|
|
2110
|
-
title: "State changes since a cursor",
|
|
2111
|
-
description: "Sampled $state changes (200 ms) after `since` (the cursor from the previous call). Contains values of state in the running app. `reset: true` means the cursor was stale and this is the whole buffer. At most `limit` newest entries are returned; `omitted` counts older ones in the range that were left out. Values larger than `maxValueChars` (JSON) are replaced by a size summary. Timestamps are detection times, so order within about a second is not causal.",
|
|
2112
|
-
inputSchema: {
|
|
2113
|
-
since: u.number().int().nonnegative().optional(),
|
|
2114
|
-
limit: u.number().int().min(1).max(500).optional(),
|
|
2115
|
-
maxValueChars: u.number().int().min(16).max(J.max).optional()
|
|
2116
|
-
}
|
|
2117
|
-
}, async ({ since: e, limit: t, maxValueChars: r }) => {
|
|
2118
|
-
let i = n(e), a = t ?? 100, o = r ?? J.default, s = Math.max(0, i.changes.length - a), c = i.changes.slice(s).map((e) => ({
|
|
2119
|
-
...e,
|
|
2120
|
-
oldValue: Y(e.oldValue, o),
|
|
2121
|
-
newValue: Y(e.newValue, o)
|
|
2122
|
-
}));
|
|
2123
|
-
return Z({
|
|
2124
|
-
...i,
|
|
2125
|
-
changes: c,
|
|
2126
|
-
omitted: s,
|
|
2127
|
-
maxValueChars: o
|
|
2128
|
-
});
|
|
2482
|
+
n();
|
|
2483
|
+
}), !a) {
|
|
2484
|
+
let e = m(t, {
|
|
2485
|
+
base: Y,
|
|
2486
|
+
ws: !1,
|
|
2487
|
+
sse: !0,
|
|
2488
|
+
mcp: !1,
|
|
2489
|
+
auth: n
|
|
2129
2490
|
});
|
|
2491
|
+
return r = e, async () => {
|
|
2492
|
+
r = void 0, await e.close().catch(() => {});
|
|
2493
|
+
};
|
|
2130
2494
|
}
|
|
2131
|
-
|
|
2132
|
-
let
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
return Z({
|
|
2149
|
-
id: r.id,
|
|
2150
|
-
label: r.label,
|
|
2151
|
-
startedAt: r.startedAt,
|
|
2152
|
-
persist: r.persist
|
|
2153
|
-
});
|
|
2154
|
-
}), t.registerTool("end_session", {
|
|
2155
|
-
title: "End current measurement session",
|
|
2156
|
-
description: "Closes the active session and returns its delta. `keep` decides disposal: \"memory\" keeps it for this dev-server lifetime, \"disk\" writes to .vite-devtools-svelte/sessions/, \"discard\" deletes it.",
|
|
2157
|
-
inputSchema: { keep: u.enum([
|
|
2158
|
-
"memory",
|
|
2159
|
-
"disk",
|
|
2160
|
-
"discard"
|
|
2161
|
-
]).optional() }
|
|
2162
|
-
}, async ({ keep: t = "memory" }) => {
|
|
2163
|
-
let n = e.sessions.end(t), r = n.endedAt ? e.sessions.delta(n.id) : null;
|
|
2164
|
-
return Z({
|
|
2165
|
-
id: n.id,
|
|
2166
|
-
label: n.label,
|
|
2167
|
-
startedAt: n.startedAt,
|
|
2168
|
-
endedAt: n.endedAt,
|
|
2169
|
-
keep: t,
|
|
2170
|
-
delta: r
|
|
2171
|
-
});
|
|
2172
|
-
}), t.registerTool("compare_sessions", {
|
|
2173
|
-
title: "Compare two sessions",
|
|
2174
|
-
description: "Diff render / load / fps metrics between two ended sessions. Each section carries `verdict`: improved | regressed | unchanged.",
|
|
2175
|
-
inputSchema: {
|
|
2176
|
-
a: X,
|
|
2177
|
-
b: X
|
|
2178
|
-
}
|
|
2179
|
-
}, async ({ a: t, b: n }) => Z(e.sessions.compare(t, n))), t.registerTool("list_sessions", {
|
|
2180
|
-
title: "List sessions",
|
|
2181
|
-
description: "In-memory + on-disk sessions, most recent first.",
|
|
2182
|
-
inputSchema: {}
|
|
2183
|
-
}, async () => Z(e.sessions.list())), t.registerTool("load_session", {
|
|
2184
|
-
title: "Load a session by id",
|
|
2185
|
-
description: "Returns the full session record (including delta if ended) by id.",
|
|
2186
|
-
inputSchema: { id: X }
|
|
2187
|
-
}, async ({ id: t }) => {
|
|
2188
|
-
let n = e.sessions.get(t);
|
|
2189
|
-
if (!n) return Z({ error: `Session not found: ${t}` });
|
|
2190
|
-
let r = n.endedAt ? e.sessions.delta(t) : null;
|
|
2191
|
-
return Z({
|
|
2192
|
-
...n,
|
|
2193
|
-
delta: r
|
|
2194
|
-
});
|
|
2195
|
-
}), t.registerTool("delete_session", {
|
|
2196
|
-
title: "Delete a session",
|
|
2197
|
-
description: "Removes a session from memory and disk.",
|
|
2198
|
-
inputSchema: { id: X }
|
|
2199
|
-
}, async ({ id: t }) => Z({ deleted: e.sessions.delete(t) })), t;
|
|
2200
|
-
}
|
|
2201
|
-
function rt(e) {
|
|
2202
|
-
return e.length ? e.reduce((e, t) => e + t, 0) / e.length : 0;
|
|
2203
|
-
}
|
|
2204
|
-
function Q(e) {
|
|
2205
|
-
return Math.round(e * 100) / 100;
|
|
2495
|
+
a.once("listening", () => {
|
|
2496
|
+
let o = a.address();
|
|
2497
|
+
if (i || !o || typeof o == "string") return;
|
|
2498
|
+
let s = e.config.server.https ? "https" : "http", c = `${s}://localhost:${o.port}`;
|
|
2499
|
+
r = m(t, {
|
|
2500
|
+
base: Y,
|
|
2501
|
+
server: a,
|
|
2502
|
+
mcp: !1,
|
|
2503
|
+
auth: n,
|
|
2504
|
+
origin: `${c}${Y}`,
|
|
2505
|
+
allowedOrigins: Jt(o, s)
|
|
2506
|
+
}), e.config.logger.info(` ➜ Svelte DevTools: ${c}${Y}`);
|
|
2507
|
+
}), a.once("close", () => {
|
|
2508
|
+
i = !0;
|
|
2509
|
+
let e = r;
|
|
2510
|
+
r = void 0, e?.close().catch(() => {});
|
|
2511
|
+
});
|
|
2206
2512
|
}
|
|
2207
|
-
var
|
|
2208
|
-
function
|
|
2209
|
-
if (!e.includes("templates: {") || !e.includes(
|
|
2210
|
-
let t = e.indexOf(
|
|
2211
|
-
return
|
|
2513
|
+
var Xt = [".svelte-kit/generated/server/internal.js", ".svelte-kit/generated/dev/server.js"], Zt = "templates: {", Qt = `${h}embedded.js`, $t = `<script type=\\"module\\" src=\\"${Qt}\\"><\/script>`, Q = "</body>";
|
|
2514
|
+
function en(e) {
|
|
2515
|
+
if (!e.includes("templates: {") || !e.includes(Q) || e.includes(Qt)) return null;
|
|
2516
|
+
let t = e.indexOf(Zt), n = e.indexOf("app:", t), r = n === -1 ? -1 : e.indexOf("\n", n), i = n === -1 ? -1 : e.lastIndexOf(Q, r === -1 ? e.length : r), a = i > n ? i : e.indexOf(Q, t);
|
|
2517
|
+
return a === -1 ? null : e.slice(0, a) + $t + e.slice(a);
|
|
2212
2518
|
}
|
|
2213
|
-
function
|
|
2519
|
+
function tn(e) {
|
|
2214
2520
|
return {
|
|
2215
2521
|
name: "vite-devtools-svelte:sveltekit-template-injector",
|
|
2216
2522
|
enforce: "post",
|
|
2217
2523
|
apply: (e, t) => t.command === "serve" && !t.isSsrBuild,
|
|
2218
2524
|
transform(t, n) {
|
|
2219
|
-
if (!
|
|
2220
|
-
let r =
|
|
2221
|
-
|
|
2525
|
+
if (!Xt.some((e) => n.endsWith(e)) || !e()) return null;
|
|
2526
|
+
let r = en(t);
|
|
2527
|
+
return r === null ? null : {
|
|
2222
2528
|
code: r,
|
|
2223
2529
|
map: null
|
|
2224
2530
|
};
|
|
@@ -2227,8 +2533,13 @@ function ut(e) {
|
|
|
2227
2533
|
}
|
|
2228
2534
|
//#endregion
|
|
2229
2535
|
//#region src/plugin.ts
|
|
2230
|
-
var
|
|
2231
|
-
function
|
|
2536
|
+
var nn = "vite:devtools", $ = Symbol.for("vite-devtools-svelte:warn-sink"), rn = 0;
|
|
2537
|
+
function an(t, n) {
|
|
2538
|
+
if (typeof t != "string") return !1;
|
|
2539
|
+
let r = Buffer.from(t), i = Buffer.from(n);
|
|
2540
|
+
return r.length === i.length && e.timingSafeEqual(r, i);
|
|
2541
|
+
}
|
|
2542
|
+
function on(e) {
|
|
2232
2543
|
let t = [];
|
|
2233
2544
|
for (let n of Object.values(e.environments ?? {})) {
|
|
2234
2545
|
let e = n.moduleGraph;
|
|
@@ -2240,135 +2551,123 @@ function pt(e) {
|
|
|
2240
2551
|
}
|
|
2241
2552
|
return t;
|
|
2242
2553
|
}
|
|
2243
|
-
function
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
var ht = /\.svelte\.[cm]?[jt]s$/;
|
|
2249
|
-
function gt(e, t) {
|
|
2250
|
-
if (e.includes("svelte/internal/server")) return null;
|
|
2251
|
-
let n = JSON.stringify(t), r = "(typeof window !== 'undefined' && window.__SVELTE_DEVTOOLS__)";
|
|
2252
|
-
return `import '${S}';if (${r}) { window.__SVELTE_DEVTOOLS__._enterModule(${n}); }` + e + `\n;if (${r}) { window.__SVELTE_DEVTOOLS__._leaveModule(); }\n`;
|
|
2253
|
-
}
|
|
2254
|
-
function _t(r = {}) {
|
|
2255
|
-
let { componentTracking: i = !0 } = r, a, o, s, c = !1, l = t.randomUUID(), u = null;
|
|
2256
|
-
function d() {
|
|
2257
|
-
return u ||= new Ze({
|
|
2258
|
-
persistDir: e.join(s, "node_modules", ".vite-devtools-svelte", "sessions"),
|
|
2554
|
+
function sn(r = {}) {
|
|
2555
|
+
let { componentTracking: i = !0, clientAuth: o = !0 } = r, s, c, l, u = !1, d = e.randomUUID(), f = null;
|
|
2556
|
+
function p() {
|
|
2557
|
+
return f ??= new je({
|
|
2558
|
+
persistDir: t.join(l, "node_modules", ".vite-devtools-svelte", "sessions"),
|
|
2259
2559
|
getters: {
|
|
2260
|
-
getRenderProfiles: () =>
|
|
2261
|
-
getLoadProfiles: () =>
|
|
2262
|
-
getFpsSamples: () =>
|
|
2560
|
+
getRenderProfiles: () => m.renderProfiles,
|
|
2561
|
+
getLoadProfiles: () => m.loadProfiles,
|
|
2562
|
+
getFpsSamples: () => m.fpsSamples
|
|
2263
2563
|
}
|
|
2264
|
-
}),
|
|
2265
|
-
}
|
|
2266
|
-
let
|
|
2267
|
-
onFpsSample: (e) =>
|
|
2268
|
-
onLoadProfile: (e) =>
|
|
2269
|
-
}),
|
|
2270
|
-
collector:
|
|
2271
|
-
root: () =>
|
|
2272
|
-
publicBase: () =>
|
|
2273
|
-
modules: () =>
|
|
2274
|
-
transformRequest: (e) =>
|
|
2564
|
+
}), f;
|
|
2565
|
+
}
|
|
2566
|
+
let m = new rt({
|
|
2567
|
+
onFpsSample: (e) => p().recordFpsSample(e),
|
|
2568
|
+
onLoadProfile: (e) => p().recordLoadProfile(e)
|
|
2569
|
+
}), h = Ut({
|
|
2570
|
+
collector: m,
|
|
2571
|
+
root: () => l,
|
|
2572
|
+
publicBase: () => s?.base ?? "/",
|
|
2573
|
+
modules: () => c ? on(c) : [],
|
|
2574
|
+
transformRequest: (e) => c ? c.transformRequest(e) : Promise.resolve(null),
|
|
2275
2575
|
serverOrigins: () => {
|
|
2276
|
-
let e =
|
|
2576
|
+
let e = c?.resolvedUrls;
|
|
2277
2577
|
return e ? [...e.local, ...e.network].map((e) => new URL(e).origin) : [];
|
|
2278
2578
|
},
|
|
2279
2579
|
openInEditor: (e) => n(e)
|
|
2280
|
-
}),
|
|
2580
|
+
}), _ = [];
|
|
2281
2581
|
return [
|
|
2282
2582
|
{
|
|
2283
2583
|
name: "vite-devtools-svelte",
|
|
2284
2584
|
enforce: "pre",
|
|
2285
2585
|
apply: "serve",
|
|
2286
|
-
api: { getDevtoolsToken: () =>
|
|
2586
|
+
api: { getDevtoolsToken: () => d },
|
|
2287
2587
|
configResolved(e) {
|
|
2288
|
-
|
|
2588
|
+
s = e, l = s.root, u = s.plugins.some((e) => e.name === nn);
|
|
2289
2589
|
},
|
|
2290
2590
|
configureServer(e) {
|
|
2291
|
-
|
|
2292
|
-
let t = ++
|
|
2293
|
-
if (
|
|
2294
|
-
|
|
2295
|
-
}), !
|
|
2296
|
-
let n =
|
|
2297
|
-
n &&
|
|
2591
|
+
c = e;
|
|
2592
|
+
let t = ++rn;
|
|
2593
|
+
if (m.attach(e.hot), e.httpServer?.once("close", () => {
|
|
2594
|
+
c === e && (m.detach(), c = void 0);
|
|
2595
|
+
}), !u) {
|
|
2596
|
+
let n = Yt(e, h, { auth: o });
|
|
2597
|
+
n && _.push({
|
|
2298
2598
|
generation: t,
|
|
2299
2599
|
dispose: n
|
|
2300
2600
|
});
|
|
2301
2601
|
}
|
|
2302
|
-
let n = () =>
|
|
2303
|
-
getProject: () =>
|
|
2304
|
-
getRoutes: () =>
|
|
2305
|
-
getLiveComponents: () =>
|
|
2306
|
-
getLiveSnapshot: () =>
|
|
2307
|
-
getComponentRelations: () =>
|
|
2308
|
-
getRenderProfiles: () =>
|
|
2309
|
-
getReactiveGraph: () =>
|
|
2310
|
-
getLoadProfiles: () =>
|
|
2311
|
-
getFpsSamples: () =>
|
|
2312
|
-
sessions:
|
|
2313
|
-
getReactiveSummary: (e) =>
|
|
2314
|
-
getReactiveScope: (e) =>
|
|
2315
|
-
getStateTimelineDelta: (e) =>
|
|
2316
|
-
getCaptureInfo: () =>
|
|
2602
|
+
let n = () => Re({
|
|
2603
|
+
getProject: () => y(l),
|
|
2604
|
+
getRoutes: () => de(y(l).routesDir),
|
|
2605
|
+
getLiveComponents: () => m.liveComponents,
|
|
2606
|
+
getLiveSnapshot: () => m.liveSnapshot,
|
|
2607
|
+
getComponentRelations: () => g(l),
|
|
2608
|
+
getRenderProfiles: () => m.renderProfiles,
|
|
2609
|
+
getReactiveGraph: () => m.requestReactiveGraph(),
|
|
2610
|
+
getLoadProfiles: () => m.loadProfiles,
|
|
2611
|
+
getFpsSamples: () => m.fpsSamples,
|
|
2612
|
+
sessions: p(),
|
|
2613
|
+
getReactiveSummary: (e) => m.requestReactiveSummary(e),
|
|
2614
|
+
getReactiveScope: (e) => m.requestReactiveGraph(e),
|
|
2615
|
+
getStateTimelineDelta: (e) => m.getStateTimelineDelta(e),
|
|
2616
|
+
getCaptureInfo: () => m.getCaptureInfo()
|
|
2317
2617
|
});
|
|
2318
2618
|
e.httpServer?.once("listening", () => {
|
|
2319
2619
|
try {
|
|
2320
2620
|
let t = e.httpServer?.address();
|
|
2321
2621
|
if (!t || typeof t == "string") return;
|
|
2322
|
-
let n = `claude mcp add --transport http svelte ${`${e.config.server?.https ? "https" : "http"}://${t.address === "::" || t.address === "0.0.0.0" || t.address === "::1" || t.address === "127.0.0.1" ? "localhost" : t.family === "IPv6" ? `[${t.address}]` : t.address}:${t.port}/__svelte-devtools/mcp`} --header x-svelte-devtools-token:${
|
|
2622
|
+
let n = `claude mcp add --transport http svelte ${`${e.config.server?.https ? "https" : "http"}://${t.address === "::" || t.address === "0.0.0.0" || t.address === "::1" || t.address === "127.0.0.1" ? "localhost" : t.family === "IPv6" ? `[${t.address}]` : t.address}:${t.port}/__svelte-devtools/mcp`} --header x-svelte-devtools-token:${d}`;
|
|
2323
2623
|
e.config.logger.info(""), e.config.logger.info(" svelte-devtools MCP ready — register with Claude Code:"), e.config.logger.info(` ${n}`), e.config.logger.info("");
|
|
2324
2624
|
} catch {}
|
|
2325
|
-
})
|
|
2326
|
-
|
|
2327
|
-
if ((
|
|
2625
|
+
});
|
|
2626
|
+
let r = async (t, r) => {
|
|
2627
|
+
if (!an(t.headers["x-svelte-devtools-token"], d)) {
|
|
2328
2628
|
r.statusCode = 403, r.end("Forbidden");
|
|
2329
2629
|
return;
|
|
2330
2630
|
}
|
|
2331
|
-
|
|
2332
|
-
let
|
|
2631
|
+
m.lease("mcp", 6e4) && await m.waitForSnapshot(1e3);
|
|
2632
|
+
let i = n(), o = new a({
|
|
2333
2633
|
sessionIdGenerator: void 0,
|
|
2334
2634
|
enableJsonResponse: !0
|
|
2335
2635
|
});
|
|
2336
2636
|
o.onerror = (t) => {
|
|
2337
|
-
e.config.logger.error(`[svelte-devtools] MCP transport error: ${t.stack
|
|
2637
|
+
e.config.logger.error(`[svelte-devtools] MCP transport error: ${t.stack ?? t.message}`);
|
|
2338
2638
|
};
|
|
2339
2639
|
try {
|
|
2340
|
-
await
|
|
2640
|
+
await i.connect(o), await o.handleRequest(t, r);
|
|
2341
2641
|
} catch (t) {
|
|
2342
|
-
e.config.logger.error(`[svelte-devtools] MCP handler error: ${t instanceof Error ? t.stack
|
|
2642
|
+
e.config.logger.error(`[svelte-devtools] MCP handler error: ${t instanceof Error ? t.stack ?? t.message : String(t)}`), r.headersSent || (r.statusCode = 500, r.setHeader("Content-Type", "application/json"), r.end(JSON.stringify({ error: String(t) })));
|
|
2343
2643
|
} finally {
|
|
2344
|
-
await o.close().catch(() => {}), await
|
|
2644
|
+
await o.close().catch(() => {}), await i.close().catch(() => {});
|
|
2345
2645
|
}
|
|
2646
|
+
};
|
|
2647
|
+
e.middlewares.use("/__svelte-devtools/mcp", (t, n) => {
|
|
2648
|
+
r(t, n).catch((t) => {
|
|
2649
|
+
e.config.logger.error(`[svelte-devtools] MCP handler error: ${String(t)}`), n.headersSent || (n.statusCode = 500, n.setHeader("Content-Type", "application/json"), n.end(JSON.stringify({ error: String(t) })));
|
|
2650
|
+
});
|
|
2346
2651
|
});
|
|
2347
2652
|
},
|
|
2348
2653
|
async closeServer({ reason: e }) {
|
|
2349
|
-
let t =
|
|
2350
|
-
for (let e of t)
|
|
2654
|
+
let t = _.filter((t) => e === "close" || t.generation < rn);
|
|
2655
|
+
for (let e of t) _.splice(_.indexOf(e), 1);
|
|
2351
2656
|
await Promise.all(t.map((e) => e.dispose()));
|
|
2352
2657
|
},
|
|
2353
2658
|
resolveId(e, t) {
|
|
2354
|
-
|
|
2355
|
-
if (e === "virtual:svelte-devtools-runtime") return ge;
|
|
2356
|
-
if (i && e === "svelte/internal/client" && t && !t.includes("node_modules") && !t.startsWith("\0")) return _e;
|
|
2357
|
-
}
|
|
2659
|
+
return s?.command === "serve" ? e === "virtual:svelte-devtools-runtime" ? He : i && e === "svelte/internal/client" && t && !t.includes("node_modules") && !t.startsWith("\0") ? Ue : null : null;
|
|
2358
2660
|
},
|
|
2359
2661
|
load(e) {
|
|
2360
|
-
|
|
2361
|
-
if (e === "\0virtual:svelte-devtools-runtime") return ye;
|
|
2362
|
-
if (e === "\0svelte-devtools:wrapped-client") return ve;
|
|
2363
|
-
}
|
|
2662
|
+
return s?.command === "serve" ? e === "\0virtual:svelte-devtools-runtime" ? Ge : e === "\0svelte-devtools:wrapped-client" ? We : null : null;
|
|
2364
2663
|
},
|
|
2365
2664
|
transformIndexHtml: {
|
|
2366
2665
|
order: "pre",
|
|
2367
2666
|
handler() {
|
|
2368
|
-
return
|
|
2667
|
+
return s.command === "serve" ? [{
|
|
2369
2668
|
tag: "script",
|
|
2370
2669
|
attrs: { type: "module" },
|
|
2371
|
-
children: `import '${
|
|
2670
|
+
children: `import '${M}'`,
|
|
2372
2671
|
injectTo: "head-prepend"
|
|
2373
2672
|
}] : [];
|
|
2374
2673
|
}
|
|
@@ -2380,7 +2679,7 @@ function _t(r = {}) {
|
|
|
2380
2679
|
},
|
|
2381
2680
|
async setup(e) {
|
|
2382
2681
|
let { createPluginFromDevframe: t } = await import("@vitejs/devtools-kit/node");
|
|
2383
|
-
await t(
|
|
2682
|
+
await t(h, { base: Y }).devtools?.setup?.(e);
|
|
2384
2683
|
}
|
|
2385
2684
|
}
|
|
2386
2685
|
},
|
|
@@ -2389,10 +2688,10 @@ function _t(r = {}) {
|
|
|
2389
2688
|
enforce: "post",
|
|
2390
2689
|
apply: "serve",
|
|
2391
2690
|
transform(e, t, n) {
|
|
2392
|
-
if (!i || t.includes("node_modules") ||
|
|
2393
|
-
let r = t.split("?")
|
|
2394
|
-
return
|
|
2395
|
-
code:
|
|
2691
|
+
if (!i || t.includes("node_modules") || s?.command !== "serve" || n?.ssr) return null;
|
|
2692
|
+
let [r = t] = t.split("?"), a = r.endsWith(".svelte") ? Ke(e, r) : qe.test(r) ? Je(e, r) : null;
|
|
2693
|
+
return a === null ? null : {
|
|
2694
|
+
code: a,
|
|
2396
2695
|
map: null
|
|
2397
2696
|
};
|
|
2398
2697
|
}
|
|
@@ -2402,25 +2701,28 @@ function _t(r = {}) {
|
|
|
2402
2701
|
enforce: "post",
|
|
2403
2702
|
apply: "serve",
|
|
2404
2703
|
transform(e, t) {
|
|
2405
|
-
if (
|
|
2704
|
+
if (s?.command !== "serve") return null;
|
|
2406
2705
|
let n = /\+page\.server\.[tj]s$/.test(t) || /\+layout\.server\.[tj]s$/.test(t), r = /\+(page|layout)\.[tj]s$/.test(t) && !t.includes(".server.");
|
|
2407
2706
|
if (!n && !r || t.includes("node_modules") || !e.includes("export") || !e.includes("load")) return null;
|
|
2408
|
-
let i = n ? "server" : "universal",
|
|
2409
|
-
if (
|
|
2410
|
-
let
|
|
2707
|
+
let i = n ? "server" : "universal", a = (t.match(/routes(.*)\/\+/)?.[1] ?? "") || "/", o = JSON.stringify(a), c = JSON.stringify(t), l = JSON.stringify(i), u = e.replace(/export\s+(const|let|var)\s+load\s*=\s*/, "$1 __original_load = ");
|
|
2708
|
+
if (u === e && (u = e.replace(/export\s+(async\s+)?function\s+load\s*\(/, "const __original_load = $1function __load_impl(")), u === e) return null;
|
|
2709
|
+
let d = `
|
|
2411
2710
|
export const load = async (event) => {
|
|
2412
2711
|
const __start = performance.now();
|
|
2413
2712
|
const __result = await __original_load(event);
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2713
|
+
try {
|
|
2714
|
+
const __duration = performance.now() - __start;
|
|
2715
|
+
let __dataSize = 0;
|
|
2716
|
+
try { __dataSize = JSON.stringify(__result || {}).length; } catch {}
|
|
2717
|
+
if (typeof globalThis.__svelte_devtools_record_load === 'function') {
|
|
2718
|
+
globalThis.__svelte_devtools_record_load(${o}, ${c}, ${l}, __duration, __dataSize);
|
|
2719
|
+
}
|
|
2720
|
+
} catch {}
|
|
2419
2721
|
return __result;
|
|
2420
2722
|
};
|
|
2421
2723
|
`;
|
|
2422
2724
|
return {
|
|
2423
|
-
code:
|
|
2725
|
+
code: u + d,
|
|
2424
2726
|
map: null
|
|
2425
2727
|
};
|
|
2426
2728
|
}
|
|
@@ -2438,7 +2740,7 @@ export const load = async (event) => {
|
|
|
2438
2740
|
dataSize: i,
|
|
2439
2741
|
timestamp: Date.now()
|
|
2440
2742
|
};
|
|
2441
|
-
|
|
2743
|
+
m.recordLoadProfile(a);
|
|
2442
2744
|
};
|
|
2443
2745
|
}
|
|
2444
2746
|
},
|
|
@@ -2449,26 +2751,27 @@ export const load = async (event) => {
|
|
|
2449
2751
|
configResolved(e) {
|
|
2450
2752
|
let t = e.logger, n = $ in t;
|
|
2451
2753
|
if (t[$] = (e) => {
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2754
|
+
let t = e.replaceAll(/\u001B\[[0-9;]*m/g, "").trim();
|
|
2755
|
+
if (!t.includes(".svelte") || s?.command !== "serve") return;
|
|
2756
|
+
let n = t.match(/(?:^|\s)((?:\/|\.\/|\w:)[^\s:]+\.svelte)(?::(\d+):(\d+))?/), r = t.match(/\(([a-z0-9_-]+)\)/);
|
|
2757
|
+
m.recordCompilerWarning({
|
|
2758
|
+
code: r?.[1] ?? "unknown",
|
|
2759
|
+
message: t,
|
|
2760
|
+
file: n?.[1] ?? "",
|
|
2761
|
+
line: n?.[2] ? Number(n[2]) : void 0,
|
|
2762
|
+
column: n?.[3] ? Number(n[3]) : void 0
|
|
2460
2763
|
});
|
|
2461
2764
|
}, n) return;
|
|
2462
|
-
let r = t.warn;
|
|
2765
|
+
let r = t.warn.bind(t);
|
|
2463
2766
|
t.warn = (e, n) => {
|
|
2464
2767
|
t[$]?.(e), r(e, n);
|
|
2465
2768
|
};
|
|
2466
2769
|
}
|
|
2467
2770
|
},
|
|
2468
|
-
|
|
2771
|
+
tn(() => u)
|
|
2469
2772
|
];
|
|
2470
2773
|
}
|
|
2471
2774
|
//#endregion
|
|
2472
|
-
export {
|
|
2775
|
+
export { sn as svelteDevtools };
|
|
2473
2776
|
|
|
2474
2777
|
//# sourceMappingURL=index.mjs.map
|