@tracepanel/js 0.0.0-stage → 0.3.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 (87) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +271 -2
  3. package/dist/agent.d.ts +42 -0
  4. package/dist/agent.js +298 -0
  5. package/dist/agent.js.map +1 -0
  6. package/dist/api.d.ts +11 -0
  7. package/dist/api.js +34 -0
  8. package/dist/api.js.map +1 -0
  9. package/dist/browser.d.ts +25 -0
  10. package/dist/browser.js +438 -0
  11. package/dist/browser.js.map +1 -0
  12. package/dist/bullmq.d.ts +7 -0
  13. package/dist/bullmq.js +86 -0
  14. package/dist/bullmq.js.map +1 -0
  15. package/dist/bun.d.ts +15 -0
  16. package/dist/bun.js +114 -0
  17. package/dist/bun.js.map +1 -0
  18. package/dist/calls.d.ts +20 -0
  19. package/dist/calls.js +145 -0
  20. package/dist/calls.js.map +1 -0
  21. package/dist/cli.d.ts +2 -0
  22. package/dist/cli.js +39 -0
  23. package/dist/cli.js.map +1 -0
  24. package/dist/config.d.ts +29 -0
  25. package/dist/config.js +48 -0
  26. package/dist/config.js.map +1 -0
  27. package/dist/console.d.ts +6 -0
  28. package/dist/console.js +44 -0
  29. package/dist/console.js.map +1 -0
  30. package/dist/explain.d.ts +25 -0
  31. package/dist/explain.js +145 -0
  32. package/dist/explain.js.map +1 -0
  33. package/dist/express.d.ts +2 -0
  34. package/dist/express.js +77 -0
  35. package/dist/express.js.map +1 -0
  36. package/dist/frameworks.d.ts +2 -0
  37. package/dist/frameworks.js +39 -0
  38. package/dist/frameworks.js.map +1 -0
  39. package/dist/headers.d.ts +2 -0
  40. package/dist/headers.js +42 -0
  41. package/dist/headers.js.map +1 -0
  42. package/dist/index.d.ts +5 -0
  43. package/dist/index.js +3 -0
  44. package/dist/index.js.map +1 -0
  45. package/dist/logs.d.ts +8 -0
  46. package/dist/logs.js +54 -0
  47. package/dist/logs.js.map +1 -0
  48. package/dist/model.d.ts +68 -0
  49. package/dist/model.js +41 -0
  50. package/dist/model.js.map +1 -0
  51. package/dist/next-widget.d.ts +3 -0
  52. package/dist/next-widget.js +17 -0
  53. package/dist/next-widget.js.map +1 -0
  54. package/dist/next.d.ts +2 -0
  55. package/dist/next.js +16 -0
  56. package/dist/next.js.map +1 -0
  57. package/dist/nuxt-plugin.d.ts +24 -0
  58. package/dist/nuxt-plugin.js +39 -0
  59. package/dist/nuxt-plugin.js.map +1 -0
  60. package/dist/nuxt.d.ts +10 -0
  61. package/dist/nuxt.js +16 -0
  62. package/dist/nuxt.js.map +1 -0
  63. package/dist/processor.d.ts +105 -0
  64. package/dist/processor.js +491 -0
  65. package/dist/processor.js.map +1 -0
  66. package/dist/register.d.ts +1 -0
  67. package/dist/register.js +5 -0
  68. package/dist/register.js.map +1 -0
  69. package/dist/runtime.d.ts +17 -0
  70. package/dist/runtime.js +27 -0
  71. package/dist/runtime.js.map +1 -0
  72. package/dist/setup.d.ts +10 -0
  73. package/dist/setup.js +102 -0
  74. package/dist/setup.js.map +1 -0
  75. package/dist/sql.d.ts +3 -0
  76. package/dist/sql.js +18 -0
  77. package/dist/sql.js.map +1 -0
  78. package/dist/stack.d.ts +13 -0
  79. package/dist/stack.js +66 -0
  80. package/dist/stack.js.map +1 -0
  81. package/dist/vite.d.ts +36 -0
  82. package/dist/vite.js +139 -0
  83. package/dist/vite.js.map +1 -0
  84. package/dist/widget.d.ts +26 -0
  85. package/dist/widget.js +70 -0
  86. package/dist/widget.js.map +1 -0
  87. package/package.json +125 -4
@@ -0,0 +1,145 @@
1
+ // EXPLAIN for the agent's console: the agent writes the statement and sends it down the connection the traces go up
2
+ // (agent.ts); this runs it on the app's database and answers with the rows, which the agent reads as a plan.
3
+ //
4
+ // The app's databases are the ones its queries ran on: each pg or mysql2 connection that queries is remembered by
5
+ // its settings (the password among them), and EXPLAIN opens one of its own with them, so it never runs inside the
6
+ // app's transaction. Whatever EXPLAIN ANALYZE does is rolled back.
7
+ import { InstrumentationBase, InstrumentationNodeModuleDefinition } from "@opentelemetry/instrumentation";
8
+ const DATABASES = new Map();
9
+ function remember(database) {
10
+ const key = `${database.vendor} ${database.server}:${database.port}/${database.database}`;
11
+ if (!DATABASES.has(key))
12
+ DATABASES.set(key, database);
13
+ }
14
+ /** The database a span's query ran on: by vendor, server, port and name; the only one of that vendor otherwise. */
15
+ function databaseFor(request) {
16
+ const vendor = request.vendor === "mariadb" ? "mysql" : request.vendor;
17
+ const candidates = [...DATABASES.values()].filter((database) => database.vendor === vendor);
18
+ const exact = candidates.find((database) => (request.server == null || database.server === request.server) &&
19
+ (request.port == null || database.port === Number(request.port)) &&
20
+ (request.database == null || database.database === request.database));
21
+ return exact ?? (candidates.length === 1 ? candidates[0] : undefined);
22
+ }
23
+ export async function explain(request) {
24
+ const database = databaseFor(request);
25
+ if (!database)
26
+ throw new Error(`The app has queried no ${request.vendor} database it can run EXPLAIN on.`);
27
+ const session = await database.open();
28
+ try {
29
+ await session.run("BEGIN", []);
30
+ return await session.run(request.sql, request.params);
31
+ }
32
+ finally {
33
+ await session.run("ROLLBACK", []).catch(() => undefined);
34
+ await session.close();
35
+ }
36
+ }
37
+ const MARK = Symbol.for("tracepanel.explain");
38
+ /** Records pg's and mysql2's connections as they query: where they are, and their settings. */
39
+ export class DatabasesInstrumentation extends InstrumentationBase {
40
+ constructor() {
41
+ super("tracepanel-databases", "1.0.0", {});
42
+ }
43
+ init() {
44
+ return [
45
+ new InstrumentationNodeModuleDefinition("pg", [">=8"], (exports) => {
46
+ this.watch(exports.Client?.prototype, (client) => rememberPg(client));
47
+ return exports;
48
+ }),
49
+ new InstrumentationNodeModuleDefinition("mysql2", [">=3"], (exports) => {
50
+ this.watch(exports.Connection?.prototype, (client) => rememberMysql(client, exports));
51
+ return exports;
52
+ }),
53
+ ];
54
+ }
55
+ /**
56
+ * `query` on `proto` notes the connection first. The OpenTelemetry instrumentation wraps it too, so a wrapper of
57
+ * its own is told by a mark, not by isWrapped.
58
+ */
59
+ watch(proto, note) {
60
+ if (!proto || typeof proto.query !== "function" || proto.query[MARK])
61
+ return;
62
+ const original = proto.query;
63
+ const watched = function (...args) {
64
+ try {
65
+ note(this);
66
+ }
67
+ catch {
68
+ // Never in the way of the app's query.
69
+ }
70
+ return original.apply(this, args);
71
+ };
72
+ Object.defineProperty(watched, MARK, { value: true });
73
+ proto.query = watched;
74
+ }
75
+ }
76
+ function rememberPg(client) {
77
+ const params = client?.connectionParameters;
78
+ const Client = client?.constructor;
79
+ if (!params || typeof Client !== "function")
80
+ return;
81
+ remember({
82
+ vendor: "postgresql",
83
+ server: String(params.host ?? "localhost"),
84
+ port: Number(params.port ?? 5432),
85
+ database: String(params.database ?? ""),
86
+ async open() {
87
+ // Field by field: pg keeps the password a property the spread leaves out.
88
+ const session = new Client({
89
+ user: params.user,
90
+ password: params.password,
91
+ host: params.host,
92
+ port: params.port,
93
+ database: params.database,
94
+ ssl: params.ssl,
95
+ });
96
+ await session.connect();
97
+ return {
98
+ async run(sql, values) {
99
+ const result = await session.query({ text: sql, values, rowMode: "array" });
100
+ return { columns: (result.fields ?? []).map((field) => String(field.name)), rows: result.rows ?? [] };
101
+ },
102
+ close: () => session.end(),
103
+ };
104
+ },
105
+ });
106
+ }
107
+ function rememberMysql(connection, mysql2) {
108
+ const config = connection?.config;
109
+ if (!config || typeof mysql2.createConnection !== "function")
110
+ return;
111
+ const settings = {
112
+ host: config.host,
113
+ port: config.port,
114
+ user: config.user,
115
+ password: config.password,
116
+ database: config.database,
117
+ socketPath: config.socketPath,
118
+ ssl: config.ssl,
119
+ };
120
+ remember({
121
+ vendor: "mysql",
122
+ server: String(config.host ?? "localhost"),
123
+ port: Number(config.port ?? 3306),
124
+ database: String(config.database ?? ""),
125
+ async open() {
126
+ const session = mysql2.createConnection(settings);
127
+ const query = (sql, values) => new Promise((resolve, reject) => {
128
+ session.query({ sql, rowsAsArray: true }, values, (error, rows, fields) => {
129
+ if (error)
130
+ reject(error);
131
+ else
132
+ resolve({
133
+ columns: (fields ?? []).map((field) => String(field.name)),
134
+ rows: Array.isArray(rows) ? rows : [],
135
+ });
136
+ });
137
+ });
138
+ return {
139
+ run: query,
140
+ close: () => new Promise((resolve) => session.end(() => resolve())),
141
+ };
142
+ },
143
+ });
144
+ }
145
+ //# sourceMappingURL=explain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"explain.js","sourceRoot":"","sources":["../src/explain.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { NextFunction, Request, Response } from "express";
2
+ export declare function tracepanel(): (req: Request, res: Response, next: NextFunction) => void;
@@ -0,0 +1,77 @@
1
+ // `app.use(tracepanel())`: every response gets what the badge reads (Tracepanel-Id, Server-Timing), and HTML pages
2
+ // get widget.min.js. The response is held until the handler ends it: a page or a JSON answer, not a stream.
3
+ import { runtime } from "./runtime.js";
4
+ import { badge, exposedHeaders, inject } from "./widget.js";
5
+ function toBuffer(chunk, encoding) {
6
+ if (Buffer.isBuffer(chunk))
7
+ return chunk;
8
+ if (chunk instanceof Uint8Array)
9
+ return Buffer.from(chunk);
10
+ return Buffer.from(String(chunk), encoding);
11
+ }
12
+ export function tracepanel() {
13
+ const tp = runtime();
14
+ return function tracepanelMiddleware(req, res, next) {
15
+ if (!tp) {
16
+ next();
17
+ return;
18
+ }
19
+ tp.processor.setFramework("Express");
20
+ const startedMs = performance.now();
21
+ const chunks = [];
22
+ const write = res.write.bind(res);
23
+ const end = res.end.bind(res);
24
+ let streaming = false;
25
+ const flushHeld = () => {
26
+ if (chunks.length > 0)
27
+ write(Buffer.concat(chunks.splice(0)));
28
+ };
29
+ res.write = ((chunk, encoding, callback) => {
30
+ if (streaming || String(res.getHeader("content-type")).includes("text/event-stream")) {
31
+ streaming = true;
32
+ flushHeld();
33
+ return write(chunk, encoding, callback);
34
+ }
35
+ chunks.push(toBuffer(chunk, typeof encoding === "string" ? encoding : undefined));
36
+ if (typeof encoding === "function")
37
+ encoding();
38
+ else if (typeof callback === "function")
39
+ callback();
40
+ return true;
41
+ });
42
+ res.end = ((chunk, encoding, callback) => {
43
+ res.write = write;
44
+ res.end = end;
45
+ if (typeof chunk === "function")
46
+ return end(chunk);
47
+ if (chunk !== undefined && chunk !== null && !streaming) {
48
+ chunks.push(toBuffer(chunk, typeof encoding === "string" ? encoding : undefined));
49
+ }
50
+ else if (streaming && chunk !== undefined && chunk !== null) {
51
+ return end(chunk, encoding, callback);
52
+ }
53
+ if (streaming || res.headersSent) {
54
+ flushHeld();
55
+ return end(callback);
56
+ }
57
+ let body = Buffer.concat(chunks);
58
+ const page = String(res.getHeader("content-type") ?? "").includes("text/html");
59
+ const framework = tp.processor.framework() ?? "Express";
60
+ const marks = badge(tp, { method: req.method, status: res.statusCode, startedMs, page, framework });
61
+ if (marks) {
62
+ for (const [name, value] of Object.entries(marks.headers))
63
+ res.setHeader(name, value);
64
+ const expose = exposedHeaders(res.getHeader("access-control-allow-origin"), res.getHeader("access-control-expose-headers"));
65
+ if (expose)
66
+ res.setHeader("Access-Control-Expose-Headers", expose);
67
+ if (marks.script)
68
+ body = Buffer.from(inject(body.toString("utf8"), marks.script));
69
+ }
70
+ if (!res.getHeader("transfer-encoding"))
71
+ res.setHeader("Content-Length", body.length);
72
+ return end(body, callback);
73
+ });
74
+ next();
75
+ };
76
+ }
77
+ //# sourceMappingURL=express.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"express.js","sourceRoot":"","sources":["../src/express.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ /** "Express" as "Express 5.3.0", or with `parts` of the version only ("Express 5.3"); as it is when unknown. */
2
+ export declare function withVersion(framework: string, parts?: number): string;
@@ -0,0 +1,39 @@
1
+ // A framework's version, from its package.json in the project's node_modules: "Next.js 16.4", as the Python SDK says
2
+ // "Django 6.1". Read once per framework.
3
+ import { readFileSync } from "node:fs";
4
+ import { dirname, join } from "node:path";
5
+ const PACKAGES = {
6
+ Express: "express",
7
+ NestJS: "@nestjs/core",
8
+ "Next.js": "next",
9
+ Nuxt: "nuxt",
10
+ };
11
+ const versions = new Map();
12
+ function installed(name) {
13
+ let folder = process.cwd();
14
+ for (;;) {
15
+ try {
16
+ const version = JSON.parse(readFileSync(join(folder, "node_modules", name, "package.json"), "utf8")).version;
17
+ return typeof version === "string" ? version : null;
18
+ }
19
+ catch {
20
+ const parent = dirname(folder);
21
+ if (parent === folder)
22
+ return null;
23
+ folder = parent;
24
+ }
25
+ }
26
+ }
27
+ /** "Express" as "Express 5.3.0", or with `parts` of the version only ("Express 5.3"); as it is when unknown. */
28
+ export function withVersion(framework, parts) {
29
+ const name = PACKAGES[framework];
30
+ if (!name)
31
+ return framework;
32
+ if (!versions.has(name))
33
+ versions.set(name, installed(name));
34
+ const version = versions.get(name);
35
+ if (!version)
36
+ return framework;
37
+ return `${framework} ${parts ? version.split(".").slice(0, parts).join(".") : version}`;
38
+ }
39
+ //# sourceMappingURL=frameworks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frameworks.js","sourceRoot":"","sources":["../src/frameworks.ts"],"names":[],"mappings":"AAAA,qHAAqH;AACrH,yCAAyC;AAEzC,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE1C,MAAM,QAAQ,GAA2B;IACvC,OAAO,EAAE,SAAS;IAClB,MAAM,EAAE,cAAc;IACtB,SAAS,EAAE,MAAM;IACjB,IAAI,EAAE,MAAM;CACb,CAAC;AAEF,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAyB,CAAC;AAElD,SAAS,SAAS,CAAC,IAAY;IAC7B,IAAI,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAC3B,SAAS,CAAC;QACR,IAAI,CAAC;YACH,MAAM,OAAO,GAAY,IAAI,CAAC,KAAK,CACjC,YAAY,CAAC,IAAI,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CACzE,CAAC,OAAO,CAAC;YACV,OAAO,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;QACtD,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;YAC/B,IAAI,MAAM,KAAK,MAAM;gBAAE,OAAO,IAAI,CAAC;YACnC,MAAM,GAAG,MAAM,CAAC;QAClB,CAAC;IACH,CAAC;AACH,CAAC;AAED,gHAAgH;AAChH,MAAM,UAAU,WAAW,CAAC,SAAiB,EAAE,KAAc;IAC3D,MAAM,IAAI,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC;IACjC,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAC5B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;IAC7D,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACnC,IAAI,CAAC,OAAO;QAAE,OAAO,SAAS,CAAC;IAC/B,OAAO,GAAG,SAAS,IAAI,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;AAC1F,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { Runtime } from "./runtime.js";
2
+ export declare function headersOnResponses(tracepanel: Runtime, framework: string): void;
@@ -0,0 +1,42 @@
1
+ // For frameworks that answer from their own server (Next.js, Nuxt): every response of a traced request gets what the
2
+ // widget and the badge read, Tracepanel-Id, Tracepanel-Url and Server-Timing, as its headers go out.
3
+ import { ServerResponse } from "node:http";
4
+ import { exposedHeaders } from "./widget.js";
5
+ const PATCHED = Symbol.for("tracepanel.headers");
6
+ export function headersOnResponses(tracepanel, framework) {
7
+ const prototype = ServerResponse.prototype;
8
+ if (prototype[PATCHED])
9
+ return;
10
+ prototype[PATCHED] = true;
11
+ const writeHead = prototype.writeHead;
12
+ prototype.writeHead = function (...args) {
13
+ if (!this.headersSent) {
14
+ const marks = tracepanel.badge({
15
+ method: this.req?.method ?? "GET",
16
+ url: this.req?.url ?? "/",
17
+ status: typeof args[0] === "number" ? args[0] : this.statusCode,
18
+ startedMs: performance.now(),
19
+ page: false,
20
+ framework: tracepanel.processor.framework() ?? framework,
21
+ });
22
+ for (const [name, value] of Object.entries(marks?.headers ?? {})) {
23
+ if (!this.hasHeader(name))
24
+ this.setHeader(name, value);
25
+ }
26
+ // Headers passed to writeHead itself (an object) count too: CORS middleware often sets them there.
27
+ const given = [args[1], args[2]].find((arg) => arg !== null && typeof arg === "object" && !Array.isArray(arg));
28
+ const passed = (given ?? {});
29
+ const lower = Object.fromEntries(Object.entries(passed ?? {}).map(([name, value]) => [name.toLowerCase(), value]));
30
+ const expose = marks
31
+ ? exposedHeaders(this.getHeader("access-control-allow-origin") ?? lower["access-control-allow-origin"], this.getHeader("access-control-expose-headers") ?? lower["access-control-expose-headers"])
32
+ : undefined;
33
+ if (expose) {
34
+ this.setHeader("Access-Control-Expose-Headers", expose);
35
+ delete passed["access-control-expose-headers"];
36
+ delete passed["Access-Control-Expose-Headers"];
37
+ }
38
+ }
39
+ return writeHead.apply(this, args);
40
+ };
41
+ }
42
+ //# sourceMappingURL=headers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headers.js","sourceRoot":"","sources":["../src/headers.ts"],"names":[],"mappings":"AAAA,qHAAqH;AACrH,qGAAqG;AAErG,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAE3C,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;AAEjD,MAAM,UAAU,kBAAkB,CAAC,UAAmB,EAAE,SAAiB;IACvE,MAAM,SAAS,GAAG,cAAc,CAAC,SAAqD,CAAC;IACvF,IAAI,SAAS,CAAC,OAAO,CAAC;QAAE,OAAO;IAC/B,SAAS,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;IAC1B,MAAM,SAAS,GAAG,SAAS,CAAC,SAAS,CAAC;IACtC,SAAS,CAAC,SAAS,GAAG,UAAgC,GAAG,IAAe;QACtE,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACtB,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC;gBAC7B,MAAM,EAAE,IAAI,CAAC,GAAG,EAAE,MAAM,IAAI,KAAK;gBACjC,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,GAAG;gBACzB,MAAM,EAAE,OAAO,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU;gBAC/D,SAAS,EAAE,WAAW,CAAC,GAAG,EAAE;gBAC5B,IAAI,EAAE,KAAK;gBACX,SAAS,EAAE,UAAU,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,SAAS;aACzD,CAAC,CAAC;YACH,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,IAAI,EAAE,CAAC,EAAE,CAAC;gBACjE,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;oBAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YACzD,CAAC;YACD,mGAAmG;YACnG,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,KAAK,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;YAC/G,MAAM,MAAM,GAAG,CAAC,KAAK,IAAI,EAAE,CAA4B,CAAC;YACxD,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,CAC9B,MAAM,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,KAAK,CAAC,CAAC,CACjF,CAAC;YACF,MAAM,MAAM,GAAG,KAAK;gBAClB,CAAC,CAAC,cAAc,CACZ,IAAI,CAAC,SAAS,CAAC,6BAA6B,CAAC,IAAI,KAAK,CAAC,6BAA6B,CAAC,EACrF,IAAI,CAAC,SAAS,CAAC,+BAA+B,CAAC,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAC1F;gBACH,CAAC,CAAC,SAAS,CAAC;YACd,IAAI,MAAM,EAAE,CAAC;gBACX,IAAI,CAAC,SAAS,CAAC,+BAA+B,EAAE,MAAM,CAAC,CAAC;gBACxD,OAAQ,MAAkC,CAAC,+BAA+B,CAAC,CAAC;gBAC5E,OAAQ,MAAkC,CAAC,+BAA+B,CAAC,CAAC;YAC9E,CAAC;QACH,CAAC;QACD,OAAQ,SAAsD,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACnF,CAAqB,CAAC;AACxB,CAAC"}
@@ -0,0 +1,5 @@
1
+ export type { TraceOptions } from "./api.js";
2
+ export { span, trace } from "./api.js";
3
+ export type { Config, Thresholds } from "./config.js";
4
+ export { loadConfig } from "./config.js";
5
+ export type { Trace } from "./model.js";
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { span, trace } from "./api.js";
2
+ export { loadConfig } from "./config.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAEvC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC"}
package/dist/logs.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ import { InstrumentationBase, InstrumentationNodeModuleDefinition } from "@opentelemetry/instrumentation";
2
+ import type { TracepanelProcessor } from "./processor.js";
3
+ /** Nest's ConsoleLogger, which Logger and the app's own loggers use unless replaced. */
4
+ export declare class NestLogs extends InstrumentationBase {
5
+ private readonly processor;
6
+ constructor(processor: TracepanelProcessor);
7
+ protected init(): InstrumentationNodeModuleDefinition;
8
+ }
package/dist/logs.js ADDED
@@ -0,0 +1,54 @@
1
+ // Nest's Logger calls go to the request's trace, as console's do (console.ts): Nest writes to the terminal itself.
2
+ import { format } from "node:util";
3
+ import { InstrumentationBase, InstrumentationNodeModuleDefinition, isWrapped } from "@opentelemetry/instrumentation";
4
+ const NEST_LEVELS = {
5
+ verbose: "debug",
6
+ debug: "debug",
7
+ log: "info",
8
+ warn: "warning",
9
+ error: "error",
10
+ fatal: "critical",
11
+ };
12
+ /** `logger.log("created", "BooksService")`: the context is the last string argument, a stack the one before it. */
13
+ function nestRecord(level, message, params) {
14
+ const context = params.length > 0 && typeof params.at(-1) === "string" ? String(params.at(-1)) : "nest";
15
+ const stack = level === "error" && params.length > 1 && typeof params[0] === "string" ? params[0] : null;
16
+ return {
17
+ time: Date.now() * 1000,
18
+ level,
19
+ logger: context,
20
+ message: message instanceof Error ? message.message : format(message),
21
+ exception: message instanceof Error ? (message.stack ?? null) : stack,
22
+ attrs: {},
23
+ };
24
+ }
25
+ /** Nest's ConsoleLogger, which Logger and the app's own loggers use unless replaced. */
26
+ export class NestLogs extends InstrumentationBase {
27
+ processor;
28
+ constructor(processor) {
29
+ super("tracepanel-nest-logger", "1.0.0", {});
30
+ this.processor = processor;
31
+ }
32
+ init() {
33
+ return new InstrumentationNodeModuleDefinition("@nestjs/common", [">=8"], (exports) => {
34
+ const prototype = exports.ConsoleLogger
35
+ ?.prototype;
36
+ if (!prototype)
37
+ return exports;
38
+ for (const [method, level] of Object.entries(NEST_LEVELS)) {
39
+ const original = prototype[method];
40
+ if (typeof original !== "function" || isWrapped(original))
41
+ continue;
42
+ this._wrap(prototype, method, () => {
43
+ const processor = this.processor;
44
+ return function (message, ...params) {
45
+ processor.addLog(nestRecord(level, message, params));
46
+ return original.call(this, message, ...params);
47
+ };
48
+ });
49
+ }
50
+ return exports;
51
+ });
52
+ }
53
+ }
54
+ //# sourceMappingURL=logs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logs.js","sourceRoot":"","sources":["../src/logs.ts"],"names":[],"mappings":"AAAA,mHAAmH;AAEnH,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACnC,OAAO,EAAE,mBAAmB,EAAE,mCAAmC,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAIrH,MAAM,WAAW,GAAiC;IAChD,OAAO,EAAE,OAAO;IAChB,KAAK,EAAE,OAAO;IACd,GAAG,EAAE,MAAM;IACX,IAAI,EAAE,SAAS;IACf,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,UAAU;CAClB,CAAC;AAIF,mHAAmH;AACnH,SAAS,UAAU,CAAC,KAAmB,EAAE,OAAgB,EAAE,MAAiB;IAC1E,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IACxG,MAAM,KAAK,GAAG,KAAK,KAAK,OAAO,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,MAAM,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACzG,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI;QACvB,KAAK;QACL,MAAM,EAAE,OAAO;QACf,OAAO,EAAE,OAAO,YAAY,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;QACrE,SAAS,EAAE,OAAO,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK;QACrE,KAAK,EAAE,EAAE;KACV,CAAC;AACJ,CAAC;AAED,wFAAwF;AACxF,MAAM,OAAO,QAAS,SAAQ,mBAAmB;IAClB,SAAS;IAAtC,YAA6B,SAA8B;QACzD,KAAK,CAAC,wBAAwB,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;yBADlB,SAAS;IAEtC,CAAC;IAES,IAAI;QACZ,OAAO,IAAI,mCAAmC,CAAC,gBAAgB,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE;YACpF,MAAM,SAAS,GAAI,OAAyE,CAAC,aAAa;gBACxG,EAAE,SAAS,CAAC;YACd,IAAI,CAAC,SAAS;gBAAE,OAAO,OAAO,CAAC;YAC/B,KAAK,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;gBAC1D,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;gBACnC,IAAI,OAAO,QAAQ,KAAK,UAAU,IAAI,SAAS,CAAC,QAAQ,CAAC;oBAAE,SAAS;gBACpE,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE;oBACjC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;oBACjC,OAAO,UAAyB,OAAgB,EAAE,GAAG,MAAiB;wBACpE,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;wBACrD,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC,CAAC;oBACjD,CAAC,CAAC;gBACJ,CAAC,CAAC,CAAC;YACL,CAAC;YACD,OAAO,OAAO,CAAC;QACjB,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}
@@ -0,0 +1,68 @@
1
+ export type Attrs = Record<string, unknown>;
2
+ export interface Span {
3
+ id: string;
4
+ parent_id: string | null;
5
+ kind: string;
6
+ name: string;
7
+ start: number;
8
+ duration: number;
9
+ status: "ok" | "error";
10
+ attrs: Attrs;
11
+ }
12
+ export interface Log {
13
+ span_id: string | null;
14
+ time: number;
15
+ level: "debug" | "info" | "warning" | "error" | "critical";
16
+ logger: string;
17
+ message: string;
18
+ exception: string | null;
19
+ attrs: Attrs;
20
+ }
21
+ export interface Frame {
22
+ filename: string;
23
+ module: string;
24
+ function: string;
25
+ lineno: number;
26
+ in_app: boolean;
27
+ context_start: number | null;
28
+ context: string[];
29
+ locals: Record<string, string>;
30
+ }
31
+ export interface TraceError {
32
+ span_id: string | null;
33
+ time: number;
34
+ type: string;
35
+ message: string;
36
+ handled: boolean;
37
+ frames: Frame[];
38
+ chain: unknown[];
39
+ }
40
+ export interface Trace {
41
+ id: string;
42
+ kind: "http" | "task" | "command";
43
+ name: string;
44
+ start: number;
45
+ duration: number;
46
+ status: "ok" | "error";
47
+ attrs: Attrs;
48
+ spans: Span[];
49
+ logs: Log[];
50
+ errors: TraceError[];
51
+ links: unknown[];
52
+ issues: unknown[];
53
+ }
54
+ /** A ULID: 48 bits of milliseconds, 80 random bits, 26 characters of Crockford base32. */
55
+ export declare function newId(now?: number): string;
56
+ /** Microseconds since the epoch, as the agent stores times. */
57
+ export declare function micros(ms: number): number;
58
+ /** The most spans a trace is sent with, as the agent stores it; the rest are counted (`tracepanel.dropped_spans`). */
59
+ export declare const MAX_SPANS = 2000;
60
+ /** The most log records a trace keeps; the rest are counted (`tracepanel.dropped_logs`). */
61
+ export declare const MAX_LOGS = 1000;
62
+ /** Adds `count` to a trace's tally of what went unrecorded. */
63
+ export declare function countDropped(trace: Trace, key: "tracepanel.dropped_spans" | "tracepanel.dropped_logs", count?: number): void;
64
+ /**
65
+ * The trace cut to MAX_SPANS: its earliest spans, its roots always. Spans end children first, so a cut as they end
66
+ * would keep children whose parent went: by start, a parent always comes before its children.
67
+ */
68
+ export declare function limited(trace: Trace): Trace;
package/dist/model.js ADDED
@@ -0,0 +1,41 @@
1
+ // The trace as every Tracepanel SDK sends it to the agent: schema/trace.schema.json in the tracepanel repository.
2
+ import { randomBytes } from "node:crypto";
3
+ const CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
4
+ /** A ULID: 48 bits of milliseconds, 80 random bits, 26 characters of Crockford base32. */
5
+ export function newId(now = Date.now()) {
6
+ let value = (BigInt(now) << 80n) | BigInt(`0x${randomBytes(10).toString("hex")}`);
7
+ let out = "";
8
+ for (let i = 0; i < 26; i++) {
9
+ out = CROCKFORD[Number(value & 31n)] + out;
10
+ value >>= 5n;
11
+ }
12
+ return out;
13
+ }
14
+ /** Microseconds since the epoch, as the agent stores times. */
15
+ export function micros(ms) {
16
+ return Math.round(ms * 1000);
17
+ }
18
+ /** The most spans a trace is sent with, as the agent stores it; the rest are counted (`tracepanel.dropped_spans`). */
19
+ export const MAX_SPANS = 2000;
20
+ /** The most log records a trace keeps; the rest are counted (`tracepanel.dropped_logs`). */
21
+ export const MAX_LOGS = 1000;
22
+ /** Adds `count` to a trace's tally of what went unrecorded. */
23
+ export function countDropped(trace, key, count = 1) {
24
+ trace.attrs[key] = Number(trace.attrs[key] ?? 0) + count;
25
+ }
26
+ /**
27
+ * The trace cut to MAX_SPANS: its earliest spans, its roots always. Spans end children first, so a cut as they end
28
+ * would keep children whose parent went: by start, a parent always comes before its children.
29
+ */
30
+ export function limited(trace) {
31
+ if (trace.spans.length <= MAX_SPANS)
32
+ return trace;
33
+ const ranked = trace.spans
34
+ .map((span, index) => ({ span, index }))
35
+ .sort((a, b) => Number(a.span.parent_id !== null) - Number(b.span.parent_id !== null) || a.span.start - b.span.start);
36
+ const kept = new Set(ranked.slice(0, MAX_SPANS).map(({ index }) => index));
37
+ countDropped(trace, "tracepanel.dropped_spans", trace.spans.length - kept.size);
38
+ trace.spans = trace.spans.filter((_, index) => kept.has(index));
39
+ return trace;
40
+ }
41
+ //# sourceMappingURL=model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.js","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAAA,kHAAkH;AAElH,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AA6D1C,MAAM,SAAS,GAAG,kCAAkC,CAAC;AAErD,0FAA0F;AAC1F,MAAM,UAAU,KAAK,CAAC,GAAG,GAAW,IAAI,CAAC,GAAG,EAAE;IAC5C,IAAI,KAAK,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,GAAG,MAAM,CAAC,KAAK,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAClF,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5B,GAAG,GAAG,SAAS,CAAC,MAAM,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC;QAC3C,KAAK,KAAK,EAAE,CAAC;IACf,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,MAAM,CAAC,EAAU;IAC/B,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC;AAC/B,CAAC;AAED,sHAAsH;AACtH,MAAM,CAAC,MAAM,SAAS,GAAG,IAAI,CAAC;AAC9B,4FAA4F;AAC5F,MAAM,CAAC,MAAM,QAAQ,GAAG,IAAI,CAAC;AAE7B,+DAA+D;AAC/D,MAAM,UAAU,YAAY,CAC1B,KAAY,EACZ,GAA2D,EAC3D,KAAK,GAAG,CAAC;IAET,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,KAAK,CAAC;AAC3D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,KAAY;IAClC,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,IAAI,SAAS;QAAE,OAAO,KAAK,CAAC;IAClD,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK;SACvB,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;SACvC,IAAI,CACH,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,KAAK,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,KAAK,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,CAC/G,CAAC;IACJ,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IAC3E,YAAY,CAAC,KAAK,EAAE,0BAA0B,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;IAChF,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;IAChE,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { type ReactElement } from "react";
2
+ /** The widget's script tag for this request's page, or nothing outside development. */
3
+ export declare function Tracepanel(): ReactElement | null;
@@ -0,0 +1,17 @@
1
+ // The badge on a Next.js app's pages: `<Tracepanel />` in the root layout. A server component that reads the active
2
+ // request from the process's Tracepanel; no OpenTelemetry in it, so the layout's bundle stays as it was.
3
+ import { createElement } from "react";
4
+ const KEY = Symbol.for("tracepanel.runtime");
5
+ /** The widget's script tag for this request's page, or nothing outside development. */
6
+ export function Tracepanel() {
7
+ const tracepanel = globalThis[KEY];
8
+ const marks = tracepanel?.badge({
9
+ method: "GET",
10
+ status: 200,
11
+ startedMs: performance.now(),
12
+ page: true,
13
+ framework: tracepanel.processor.framework() ?? "Next.js",
14
+ });
15
+ return marks?.scriptAttrs ? createElement("script", { ...marks.scriptAttrs, async: true }) : null;
16
+ }
17
+ //# sourceMappingURL=next-widget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"next-widget.js","sourceRoot":"","sources":["../src/next-widget.ts"],"names":[],"mappings":"AAAA,oHAAoH;AACpH,yGAAyG;AAEzG,OAAO,EAAE,aAAa,EAAqB,MAAM,OAAO,CAAC;AAGzD,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;AAE7C,uFAAuF;AACvF,MAAM,UAAU,UAAU;IACxB,MAAM,UAAU,GAAI,UAAkC,CAAC,GAAG,CAAC,CAAC;IAC5D,MAAM,KAAK,GAAG,UAAU,EAAE,KAAK,CAAC;QAC9B,MAAM,EAAE,KAAK;QACb,MAAM,EAAE,GAAG;QACX,SAAS,EAAE,WAAW,CAAC,GAAG,EAAE;QAC5B,IAAI,EAAE,IAAI;QACV,SAAS,EAAE,UAAU,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,SAAS;KACzD,CAAC,CAAC;IACH,OAAO,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,aAAa,CAAC,QAAQ,EAAE,EAAE,GAAG,KAAK,CAAC,WAAW,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACpG,CAAC"}
package/dist/next.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ /** OpenTelemetry for the app, without Node's HTTP server spans: Next's root span is the request. */
2
+ export declare function register(): void;
package/dist/next.js ADDED
@@ -0,0 +1,16 @@
1
+ // Next.js starts its own server, before any `--import` could reach it, and makes the request's root span itself.
2
+ // Tracepanel goes in from the app's instrumentation hook:
3
+ //
4
+ // // instrumentation.ts
5
+ // export async function register() {
6
+ // if (process.env.NEXT_RUNTIME === "nodejs") (await import("@tracepanel/js/next")).register();
7
+ // }
8
+ import { headersOnResponses } from "./headers.js";
9
+ import { start } from "./setup.js";
10
+ /** OpenTelemetry for the app, without Node's HTTP server spans: Next's root span is the request. */
11
+ export function register() {
12
+ const tracepanel = start({ httpServer: false });
13
+ if (tracepanel)
14
+ headersOnResponses(tracepanel, "Next.js");
15
+ }
16
+ //# sourceMappingURL=next.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"next.js","sourceRoot":"","sources":["../src/next.ts"],"names":[],"mappings":"AAAA,iHAAiH;AACjH,0DAA0D;AAC1D,EAAE;AACF,0BAA0B;AAC1B,uCAAuC;AACvC,mGAAmG;AACnG,MAAM;AAEN,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,oGAAoG;AACpG,MAAM,UAAU,QAAQ;IACtB,MAAM,UAAU,GAAG,KAAK,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC;IAChD,IAAI,UAAU;QAAE,kBAAkB,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;AAC5D,CAAC"}
@@ -0,0 +1,24 @@
1
+ interface Event {
2
+ method?: string;
3
+ path?: string;
4
+ context: {
5
+ matchedRoute?: {
6
+ path?: string;
7
+ };
8
+ };
9
+ }
10
+ interface NitroApp {
11
+ hooks: {
12
+ hook(name: "request" | "beforeResponse", handler: (event: Event) => void): void;
13
+ hook(name: "render:html", handler: (html: {
14
+ head: string[];
15
+ }, context: {
16
+ event: Event;
17
+ }) => void): void;
18
+ hook(name: "error", handler: (error: unknown, context: {
19
+ event?: Event;
20
+ }) => void): void;
21
+ };
22
+ }
23
+ export default function tracepanelPlugin(nitroApp: NitroApp): void;
24
+ export {};