@ogygia/devtools 0.11.4 → 0.13.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.
Files changed (81) hide show
  1. package/README.md +1 -1
  2. package/dist/app.d.ts +3 -0
  3. package/dist/app.js +9 -0
  4. package/dist/devtools/BoundaryOverlay.svelte +10 -6
  5. package/dist/devtools/Devtools.svelte +24 -18
  6. package/dist/devtools/IslandDetail.svelte +7 -5
  7. package/dist/devtools/PageTab.svelte +4 -2
  8. package/dist/devtools/ProfilerTab.svelte +8 -2
  9. package/dist/devtools/SessionTab.svelte +21 -8
  10. package/dist/devtools/highlight.js +14 -7
  11. package/dist/devtools/ledger-dom.js +6 -9
  12. package/dist/devtools/names.js +18 -17
  13. package/dist/devtools/page.d.ts +2 -0
  14. package/dist/devtools/page.js +37 -17
  15. package/dist/devtools/profile-store.d.ts +4 -9
  16. package/dist/devtools/profile-store.js +26 -21
  17. package/dist/devtools/session.d.ts +2 -0
  18. package/dist/devtools/session.js +48 -42
  19. package/dist/devtools/testing-engine.js +10 -1
  20. package/dist/devtools/ui-app.d.ts +3 -1
  21. package/dist/devtools/ui-app.js +9 -3
  22. package/dist/devtools/ui.js +83 -40
  23. package/dist/messages/build.d.ts +21 -0
  24. package/dist/messages/build.js +56 -0
  25. package/dist/messages/index.d.ts +3 -0
  26. package/dist/messages/index.js +3 -0
  27. package/dist/messages/runtime.d.ts +63 -0
  28. package/dist/messages/runtime.js +122 -0
  29. package/dist/profiler/async-io.d.ts +12 -7
  30. package/dist/profiler/async-io.js +94 -82
  31. package/dist/profiler/beacon-standalone.js +15 -6
  32. package/dist/profiler/busy.d.ts +13 -0
  33. package/dist/profiler/busy.js +45 -0
  34. package/dist/profiler/client.js +5 -24
  35. package/dist/profiler/embedded-maps.js +9 -5
  36. package/dist/profiler/gc.d.ts +5 -0
  37. package/dist/profiler/gc.js +75 -11
  38. package/dist/profiler/index.d.ts +23 -2
  39. package/dist/profiler/index.js +648 -373
  40. package/dist/profiler/net.d.ts +14 -6
  41. package/dist/profiler/net.js +61 -45
  42. package/dist/profiler/patterns.js +29 -1
  43. package/dist/profiler/source-scan.js +24 -12
  44. package/dist/profiler/standalone.js +12 -4
  45. package/dist/profiler/storage/index.d.ts +14 -3
  46. package/dist/profiler/storage/index.js +41 -41
  47. package/dist/profiler/storage/postgres.d.ts +18 -5
  48. package/dist/profiler/storage/postgres.js +124 -85
  49. package/dist/profiler/storage/redis.d.ts +18 -5
  50. package/dist/profiler/storage/redis.js +160 -131
  51. package/dist/profiler/storage/sqlite.d.ts +15 -2
  52. package/dist/profiler/storage/sqlite.js +54 -48
  53. package/dist/profiler/ui/ComponentsTable.svelte +2 -2
  54. package/dist/profiler/ui/Dashboard.svelte +2 -3
  55. package/dist/profiler/ui/DownloadHtml.svelte +1 -0
  56. package/dist/profiler/ui/ExportButton.svelte +1 -0
  57. package/dist/profiler/ui/Flame.svelte +10 -4
  58. package/dist/profiler/ui/Flame.svelte.d.ts +0 -5
  59. package/dist/profiler/ui/FrameDetails.svelte +13 -2
  60. package/dist/profiler/ui/FrameDetails.svelte.d.ts +0 -6
  61. package/dist/profiler/ui/FunctionsTable.svelte +2 -2
  62. package/dist/profiler/ui/IslandsTable.svelte +2 -2
  63. package/dist/profiler/ui/OnThisPage.svelte +12 -4
  64. package/dist/profiler/ui/RunView.svelte +31 -27
  65. package/dist/profiler/ui/ShareLink.svelte +6 -1
  66. package/dist/profiler/ui/Sidebar.svelte +14 -9
  67. package/dist/profiler/ui/Treemap.svelte +11 -4
  68. package/dist/profiler/ui/Treemap.svelte.d.ts +0 -6
  69. package/dist/profiler/ui/WaitingTable.svelte +2 -2
  70. package/dist/profiler/ui/fake-progress.svelte.d.ts +7 -12
  71. package/dist/profiler/ui/fake-progress.svelte.js +57 -36
  72. package/dist/profiler/ui/press.js +4 -2
  73. package/dist/profiler/ui/row-anchor.svelte.d.ts +0 -6
  74. package/dist/profiler/ui/row-anchor.svelte.js +13 -8
  75. package/dist/profiler/ui/sort.svelte.d.ts +9 -6
  76. package/dist/profiler/ui/sort.svelte.js +34 -28
  77. package/dist/profiler/visit.d.ts +0 -1
  78. package/dist/profiler/visit.js +1 -2
  79. package/dist/server.js +27 -10
  80. package/dist/vite.js +3 -2
  81. package/package.json +18 -9
@@ -1,3 +1,4 @@
1
+ import { scope } from "@ogygia/machine";
1
2
  // src/profiler/ui/press.ts
2
3
  /** The keys alone, as an `onkeydown` handler: for an element whose markup already gives it its
3
4
  * role and `tabindex="0"`, so the compiler sees the keyboard path next to the click. */
@@ -13,7 +14,8 @@ const press_key = (e) => {
13
14
  };
14
15
  const pressable = (node) => {
15
16
  if (node.tabIndex < 0) node.tabIndex = 0;
16
- node.addEventListener("keydown", press_key);
17
- return () => node.removeEventListener("keydown", press_key);
17
+ const s = scope();
18
+ s.on(node, "keydown", press_key);
19
+ return () => s.close();
18
20
  };
19
21
  export { press_key, pressable };
@@ -1,9 +1,3 @@
1
- /**
2
- * ROW ANCHORS — a finding links to the row it is about (`#comp=<name>`, `#fn=<key>`); the table
3
- * island opens that row and scrolls to it, on load and on every hash change. Shared by the two
4
- * tables: give it the anchor kind and a getter for the open state, get back the row id maker and
5
- * the effect to install.
6
- */
7
1
  export type RowKind = "comp" | "fn" | "island" | "seed";
8
2
  export declare function row_id(kind: RowKind, key: string): string;
9
3
  /** The `href` a finding uses to reach a row. */
@@ -1,4 +1,11 @@
1
+ import { scope } from "@ogygia/machine";
1
2
  // src/profiler/ui/row-anchor.svelte.ts
3
+ /**
4
+ * ROW ANCHORS — a finding links to the row it is about (`#comp=<name>`, `#fn=<key>`); the table
5
+ * island opens that row and scrolls to it, on load and on every hash change. Shared by the two
6
+ * tables: give it the anchor kind and a getter for the open state, get back the row id maker and
7
+ * the effect to install.
8
+ */
2
9
  function row_id(kind, key) {
3
10
  let h = 5381;
4
11
  for (let i = 0; i < key.length; i++) h = (Math.imul(h, 33) ^ key.charCodeAt(i)) >>> 0;
@@ -11,6 +18,7 @@ function row_href(anchor) {
11
18
  }
12
19
  /** Install in a table island: opens the row named by the URL hash (if it is one of `keys`). */
13
20
  function follow_hash(kind, keys, open) {
21
+ const s = scope();
14
22
  const apply = () => {
15
23
  const m = /^#(comp|fn|island|seed)=(.*)$/.exec(location.hash);
16
24
  if (!m || m[1] !== kind) return;
@@ -22,7 +30,7 @@ function follow_hash(kind, keys, open) {
22
30
  }
23
31
  if (!keys().includes(key)) return;
24
32
  open(key);
25
- setTimeout(() => {
33
+ s.timeout(() => {
26
34
  document.getElementById(row_id(kind, key))?.scrollIntoView({
27
35
  block: "center",
28
36
  behavior: "smooth"
@@ -30,15 +38,12 @@ function follow_hash(kind, keys, open) {
30
38
  }, 30);
31
39
  };
32
40
  apply();
33
- window.addEventListener("hashchange", apply);
41
+ s.on(window, "hashchange", apply);
34
42
  const again = (e) => {
35
43
  const href = (e.target?.closest?.("a"))?.getAttribute("href");
36
- if (href && href === location.hash) setTimeout(apply, 0);
37
- };
38
- document.addEventListener("click", again);
39
- return () => {
40
- window.removeEventListener("hashchange", apply);
41
- document.removeEventListener("click", again);
44
+ if (href && href === location.hash) s.timeout(apply, 0);
42
45
  };
46
+ s.on(document, "click", again);
47
+ return () => s.close();
43
48
  }
44
49
  export { follow_hash, row_href, row_id };
@@ -1,14 +1,17 @@
1
1
  /**
2
2
  * Reactive column sort shared by the profiler's tables (was report.ts's TABLE_SORT_JS DOM hack).
3
- * Give it a getter for the rows and the initial numeric key; it owns the sort state and returns the
3
+ * Give it a getter for the rows and the initial numeric key; it owns the sort state and holds the
4
4
  * sorted view + a click handler. Svelte runes in a `.svelte.ts` module — imported by the table
5
5
  * islands, one instance each.
6
6
  */
7
- export interface Sortable<T> {
8
- readonly key: string;
9
- readonly sorted: T[];
7
+ export declare class Sortable<T> {
8
+ #private;
9
+ constructor(rows: () => T[], initial_key: string);
10
+ get key(): string;
11
+ get sorted(): T[];
10
12
  click(k: string): void;
13
+ /** The little arrow for a header: ▲/▼ when it's the active column, else empty. */
11
14
  arrow(k: string): string;
15
+ /** The header's `aria-sort`: what a screen reader says the arrow means. */
12
16
  aria(k: string): "ascending" | "descending" | "none";
13
- }
14
- export declare function sortable<T>(rows: () => T[], initial_key: string): Sortable<T>;
17
+ }
@@ -1,37 +1,43 @@
1
1
  // src/profiler/ui/sort.svelte.ts
2
- function sortable(rows, initial_key) {
3
- let key = $state(initial_key);
4
- let dir = $state("desc");
5
- const sorted = $derived([...rows()].sort((a, b) => {
6
- const x = Number(a[key]) || 0;
7
- const y = Number(b[key]) || 0;
8
- return dir === "asc" ? x - y : y - x;
2
+ /**
3
+ * Reactive column sort shared by the profiler's tables (was report.ts's TABLE_SORT_JS DOM hack).
4
+ * Give it a getter for the rows and the initial numeric key; it owns the sort state and holds the
5
+ * sorted view + a click handler. Svelte runes in a `.svelte.ts` module — imported by the table
6
+ * islands, one instance each.
7
+ */
8
+ var Sortable = class {
9
+ #rows;
10
+ #key = $state("");
11
+ #dir = $state("desc");
12
+ #sorted = $derived.by(() => [...this.#rows()].sort((a, b) => {
13
+ const x = Number(a[this.#key]) || 0;
14
+ const y = Number(b[this.#key]) || 0;
15
+ return this.#dir === "asc" ? x - y : y - x;
9
16
  }));
10
- function click(k) {
11
- if (key === k) dir = dir === "asc" ? "desc" : "asc";
17
+ constructor(rows, initial_key) {
18
+ this.#rows = rows;
19
+ this.#key = initial_key;
20
+ }
21
+ get key() {
22
+ return this.#key;
23
+ }
24
+ get sorted() {
25
+ return this.#sorted;
26
+ }
27
+ click(k) {
28
+ if (this.#key === k) this.#dir = this.#dir === "asc" ? "desc" : "asc";
12
29
  else {
13
- key = k;
14
- dir = "desc";
30
+ this.#key = k;
31
+ this.#dir = "desc";
15
32
  }
16
33
  }
17
34
  /** The little arrow for a header: ▲/▼ when it's the active column, else empty. */
18
- function arrow(k) {
19
- return key === k ? dir === "asc" ? " ▲" : " ▼" : "";
35
+ arrow(k) {
36
+ return this.#key === k ? this.#dir === "asc" ? " ▲" : " ▼" : "";
20
37
  }
21
38
  /** The header's `aria-sort`: what a screen reader says the arrow means. */
22
- function aria(k) {
23
- return key === k ? dir === "asc" ? "ascending" : "descending" : "none";
39
+ aria(k) {
40
+ return this.#key === k ? this.#dir === "asc" ? "ascending" : "descending" : "none";
24
41
  }
25
- return {
26
- get key() {
27
- return key;
28
- },
29
- get sorted() {
30
- return sorted;
31
- },
32
- click,
33
- arrow,
34
- aria
35
- };
36
- }
37
- export { sortable };
42
+ };
43
+ export { Sortable };
@@ -195,7 +195,6 @@ export interface Visit {
195
195
  as: string;
196
196
  crossorigin: string | null;
197
197
  vary?: string;
198
- hole?: true;
199
198
  }[];
200
199
  /** the `@font-face` rules behind the fonts it fetched: family, font-display, the fetched files */
201
200
  font_faces?: {
@@ -311,8 +311,7 @@ function parse_visit(page, raw) {
311
311
  type,
312
312
  bytes,
313
313
  as: str(m.as, 20) ?? "",
314
- crossorigin: typeof m.crossorigin === "string" ? m.crossorigin.slice(0, 20) : null,
315
- ...m.hole === true ? { hole: true } : {}
314
+ crossorigin: typeof m.crossorigin === "string" ? m.crossorigin.slice(0, 20) : null
316
315
  });
317
316
  }
318
317
  if (preload_misses.length) visit.preload_misses = preload_misses;
package/dist/server.js CHANGED
@@ -1,4 +1,7 @@
1
+ import { set_devtools_app } from "./app.js";
1
2
  import { profiler_demand_skip } from "./profiler/on-demand.js";
3
+ import { OgygiaError } from "./messages/runtime.js";
4
+ import { STALE, flights } from "@ogygia/machine";
2
5
  import { defineServer } from "@ogygia/core/plugin";
3
6
  import { want_request_stats } from "@ogygia/core/internal/server";
4
7
  // src/server.ts
@@ -9,31 +12,45 @@ import { want_request_stats } from "@ogygia/core/internal/server";
9
12
  * path — loads on the first request through a dynamic import, so an app pays for it only when it is
10
13
  * on; with `onDemand`, only once someone opens the profiler.
11
14
  */
12
- /** the mounted profiler; `undefined` until the first request resolves it, `null` when unavailable */
13
- let mounted;
14
- async function mount(options) {
15
- if (mounted !== void 0) return mounted;
16
- try {
15
+ /** the mounted profiler, once a mount succeeded */
16
+ let mounted = null;
17
+ /** The process's one profiler mount: the first requests that arrive together share it. A mount
18
+ * that fails (an edge runtime without node:inspector, a dev server mid-edit) is not kept, so the
19
+ * next request tries again. */
20
+ const mounting = flights.keyed()({
21
+ name: "profiler mount",
22
+ key: [],
23
+ ignore: { options: "one mount for the process: the first request's options are the ones it mounts with" },
24
+ run: async ({ options }) => {
17
25
  const { profiler } = await import("./profiler/index.js");
18
26
  const { getProfilerStore } = await import("./profiler/storage/index.js").catch(() => ({ getProfilerStore: () => void 0 }));
19
27
  const store = getProfilerStore();
20
- mounted = profiler({
28
+ return mounted = profiler({
21
29
  ...options,
22
30
  ...store ? { store } : {}
23
31
  });
24
- } catch {
25
- mounted = null;
26
32
  }
27
- return mounted;
33
+ });
34
+ /** a failed mount was said (once for the process: an edge runtime fails every request the same way) */
35
+ let mount_failure_said = false;
36
+ async function mount(options) {
37
+ return mounting.get({ options }).then((m) => m === STALE ? null : m, (error) => {
38
+ if (!mount_failure_said) {
39
+ mount_failure_said = true;
40
+ console.error(new OgygiaError("devtools_profiler_mount_failed"), error);
41
+ }
42
+ return null;
43
+ });
28
44
  }
29
45
  const server = defineServer({
30
46
  setup(app) {
47
+ set_devtools_app(app);
31
48
  if (app.options.profiler) want_request_stats();
32
49
  },
33
50
  async handle({ event, options }, next) {
34
51
  const config = options.profiler;
35
52
  if (!config) return next();
36
- if (profiler_demand_skip(!!config.onDemand, mounted !== void 0, event.url.pathname, config.path || "/__profiler")) return next();
53
+ if (profiler_demand_skip(!!config.onDemand, mounted !== null, event.url.pathname, config.path || "/__profiler")) return next();
37
54
  const prof = await mount(config);
38
55
  if (!prof) return next();
39
56
  return prof({
package/dist/vite.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { warn } from "./messages/build.js";
1
2
  import fs from "node:fs";
2
3
  import path from "node:path";
3
4
  import { fileURLToPath } from "node:url";
@@ -58,8 +59,8 @@ function devtools_vite(dock, profiler) {
58
59
  config(_config, env) {
59
60
  dev = env.command === "serve";
60
61
  if (env.command === "build" && isMainThread) {
61
- if (dock) console.warn("[ogygia] devtools is ON for this build: its pages carry the devtools launcher (the dock loads only when opened). Keep it off for production: list `devtools()` only when `mode !== 'production'`.");
62
- if (profiler && "secret" in profiler && !profiler.secret) console.warn("[ogygia] profiler: `profiler.secret` is EMPTY in this build — the variable it reads is not set in the BUILD environment. The UI will be DISABLED in production unless OGYGIA_PROFILER_SECRET exists at runtime (on Amplify / Lambda it does not): /__profiler will answer \"installed but disabled\". Set the variable where the build runs, and keep the `profiler` option unconditional.");
62
+ if (dock) warn("devtools_on_in_build");
63
+ if (profiler && "secret" in profiler && !profiler.secret) warn("devtools_profiler_secret_empty");
63
64
  }
64
65
  const out = {};
65
66
  if (dock && env.command === "serve") out.optimizeDeps = { include: ["@ogygia/devtools > @neodrag/svelte", "@ogygia/devtools > @neodrag/svelte/resize"] };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ogygia/devtools",
3
- "version": "0.11.4",
3
+ "version": "0.13.0",
4
4
  "description": "Devtools for ogygia: the in-page dock, and the SSR profiler with its report, client marks and storage adapters",
5
5
  "keywords": [
6
6
  "devtools",
@@ -22,6 +22,9 @@
22
22
  "README.md"
23
23
  ],
24
24
  "type": "module",
25
+ "sideEffects": [
26
+ "**/*.css"
27
+ ],
25
28
  "exports": {
26
29
  ".": {
27
30
  "types": "./dist/devtools/index.d.ts",
@@ -59,6 +62,10 @@
59
62
  "./profiler/storage/postgres": {
60
63
  "types": "./dist/profiler/storage/postgres.d.ts",
61
64
  "default": "./dist/profiler/storage/postgres.js"
65
+ },
66
+ "./internal/messages": {
67
+ "types": "./dist/messages/index.d.ts",
68
+ "default": "./dist/messages/index.js"
62
69
  }
63
70
  },
64
71
  "publishConfig": {
@@ -66,10 +73,15 @@
66
73
  },
67
74
  "dependencies": {
68
75
  "@neodrag/svelte": "3.0.0-next.12",
69
- "devalue": "^5.9.3",
70
- "@ogygia/router": "^0.11.0"
76
+ "@ogygia/machine": "^0.13.0",
77
+ "@ogygia/router": "^0.13.0",
78
+ "devalue": "^5.9.3"
71
79
  },
72
80
  "devDependencies": {
81
+ "@ogygia/config": "0.0.0",
82
+ "@ogygia/core": "0.13.0",
83
+ "@ogygia/tooling": "0.0.0",
84
+ "@ogygia/tsdown-plugin-svelte": "1.1.1",
73
85
  "@playwright/test": "1.62.1",
74
86
  "@sveltejs/kit": "^2.70.2",
75
87
  "@sveltejs/vite-plugin-svelte": "^7.2.0",
@@ -80,16 +92,13 @@
80
92
  "tsdown": "^0.23.4",
81
93
  "typescript": "^6.0.3",
82
94
  "vite": "^8.3.5",
83
- "vitest": "^4.1.12",
84
- "@ogygia/core": "0.11.4",
85
- "@ogygia/tooling": "0.0.0",
86
- "@ogygia/tsdown-plugin-svelte": "1.1.0"
95
+ "vitest": "^4.1.12"
87
96
  },
88
97
  "peerDependencies": {
98
+ "@ogygia/core": "^0.13.0",
89
99
  "@sveltejs/kit": ">=2.70.2 <4",
90
100
  "svelte": "^5.56.8",
91
- "vite": "^8.2.4",
92
- "@ogygia/core": "^0.11.4"
101
+ "vite": "^8.2.4"
93
102
  },
94
103
  "scripts": {
95
104
  "build": "tsdown",