autotel-devtools 21.0.1 → 23.0.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 +183 -28
- package/dist/cli.cjs +80 -13
- package/dist/cli.js +80 -13
- package/dist/compile-JDFHUCo5.d.cts +177 -0
- package/dist/compile-JDFHUCo5.d.ts +177 -0
- package/dist/{error-aggregator-D8VKciLY.d.ts → error-aggregator-B52JI8jL.d.ts} +1 -1
- package/dist/{error-aggregator-DCEKMOm3.d.cts → error-aggregator-DSwUvgFF.d.cts} +1 -1
- package/dist/exporter-BrEcnzoT.d.ts +546 -0
- package/dist/exporter-C4uKgo7l.d.cts +546 -0
- package/dist/fullpage.global.js +39 -0
- package/dist/grpc-CSWjMRiB.cjs +82 -0
- package/dist/grpc-CZEDUYCM.js +77 -0
- package/dist/http-Cqm_MAtc.cjs +3942 -0
- package/dist/http-Dbd9TIYU.js +3776 -0
- package/dist/index.cjs +25 -7
- package/dist/index.d.cts +30 -3
- package/dist/index.d.ts +30 -3
- package/dist/index.js +25 -7
- package/dist/{listen-DBfsfcdd.js → listen-D-lLgfro.js} +5 -2
- package/dist/{listen-CEJ3nYJf.cjs → listen-l09RRHht.cjs} +5 -2
- package/dist/parse-BRlosZft.cjs +642 -0
- package/dist/parse-D_RmPPQs.js +612 -0
- package/dist/query/index.cjs +8 -0
- package/dist/query/index.d.cts +16 -0
- package/dist/query/index.d.ts +16 -0
- package/dist/query/index.js +3 -0
- package/dist/server/exporter.d.cts +1 -1
- package/dist/server/exporter.d.ts +1 -1
- package/dist/server/index.cjs +6 -2
- package/dist/server/index.d.cts +19 -6
- package/dist/server/index.d.ts +18 -5
- package/dist/server/index.js +3 -2
- package/dist/types-B0tjwFqj.d.cts +107 -0
- package/dist/types-DM8y4A9Z.d.ts +107 -0
- package/dist/widget.global.js +15 -23
- package/dist/wire/index.cjs +7 -0
- package/dist/wire/index.d.cts +26 -0
- package/dist/wire/index.d.ts +26 -0
- package/dist/wire/index.js +3 -0
- package/dist/wire-2Rmfg6IT.js +51 -0
- package/dist/wire-CHU1PkMo.cjs +75 -0
- package/package.json +22 -6
- package/dist/exporter-Dt4kx128.d.cts +0 -207
- package/dist/exporter-Due9Rd4s.d.ts +0 -207
- package/dist/http-CNZMrnzv.js +0 -1453
- package/dist/http-CXSzX4ee.cjs +0 -1607
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as probePortHolder,
|
|
3
|
-
import { t as listenLoopbackDualStack } from "./listen-
|
|
2
|
+
import { a as probePortHolder, g as hostHeaderIsLoopback, l as DevtoolsServer, r as resolveSourceRoot, t as attachDevtoolsRoutes } from "./http-Dbd9TIYU.js";
|
|
3
|
+
import { t as listenLoopbackDualStack } from "./listen-D-lLgfro.js";
|
|
4
|
+
import { t as startOtlpGrpcReceiver } from "./grpc-CZEDUYCM.js";
|
|
4
5
|
import { createServer } from "node:http";
|
|
5
6
|
import { readFileSync } from "node:fs";
|
|
6
7
|
import { dirname, resolve } from "node:path";
|
|
@@ -9,7 +10,7 @@ import { spawn } from "node:child_process";
|
|
|
9
10
|
|
|
10
11
|
//#region src/cli.ts
|
|
11
12
|
function printHelp() {
|
|
12
|
-
process.stdout.write("autotel-devtools - Standalone OTLP receiver with web devtools UI\n\nUsage: autotel-devtools [port] [options]\n autotel-devtools claude [claude args] [--print-env] [--log-prompts]\n\nSubcommands:\n claude Start the receiver AND launch Claude Code wired to it\n (HTTP/protobuf → this receiver, 1s intervals, session id on).\n Open the \"Agents\" tab to watch sessions, tokens, cost,\n tool / MCP / sub-agent / skill usage live.\n --print-env Print the telemetry env block and exit (don't launch)\n --log-prompts Capture prompt text (default: length only / private)\n\nArguments:\n port Port to listen on (shorthand for --port; must be a positive integer)\n\nOptions:\n -p, --port <port> Port to listen on (default: 4318, env: AUTOTEL_DEVTOOLS_PORT).\n If the port is taken, the next free port is used and a warning is shown.\n -H, --host <host> Host to bind to (default: 127.0.0.1, env: AUTOTEL_DEVTOOLS_HOST)\n -t, --title <title> Dashboard title, shown in the banner and browser tab\n (env: AUTOTEL_DEVTOOLS_TITLE)\n Env limits: AUTOTEL_MAX_TRACE_COUNT, AUTOTEL_MAX_LOG_COUNT
|
|
13
|
+
process.stdout.write("autotel-devtools - Standalone OTLP receiver with web devtools UI\n\nUsage: autotel-devtools [port] [options]\n autotel-devtools claude [claude args] [--print-env] [--log-prompts]\n\nSubcommands:\n claude Start the receiver AND launch Claude Code wired to it\n (HTTP/protobuf → this receiver, 1s intervals, session id on).\n Open the \"Agents\" tab to watch sessions, tokens, cost,\n tool / MCP / sub-agent / skill usage live.\n --print-env Print the telemetry env block and exit (don't launch)\n --log-prompts Capture prompt text (default: length only / private)\n\nArguments:\n port Port to listen on (shorthand for --port; must be a positive integer)\n\nOptions:\n -p, --port <port> Port to listen on (default: 4318, env: AUTOTEL_DEVTOOLS_PORT).\n If the port is taken, the next free port is used and a warning is shown.\n -H, --host <host> Host to bind to (default: 127.0.0.1, env: AUTOTEL_DEVTOOLS_HOST)\n -t, --title <title> Dashboard title, shown in the banner and browser tab\n -d, --db <path> Keep telemetry in a sqlite file so it survives restarts\n (default: in-memory, env: AUTOTEL_DEVTOOLS_DB)\n --max-traces <n> Traces retained before the oldest are pruned\n (default: 100000, env: AUTOTEL_DEVTOOLS_MAX_TRACES)\n --grpc-port <n> OTLP/gRPC port (default: 4317, env: AUTOTEL_DEVTOOLS_GRPC_PORT)\n --db-max-size <size> Logical store cap, e.g. 512mb or 2gb\n (default: 512mb memory / 2gb disk, env: AUTOTEL_DEVTOOLS_DB_MAX_SIZE)\n (env: AUTOTEL_DEVTOOLS_TITLE)\n Env limits: AUTOTEL_MAX_TRACE_COUNT, AUTOTEL_MAX_LOG_COUNT\n -h, --help Show this help message\n -v, --version Show version number\n\nEndpoints:\n GET / Web devtools UI (fullpage)\n GET /widget.js Widget bundle (embed in your app)\n POST /v1/traces Receive OTLP JSON trace data\n GET /v1/traces Read back received traces (verify ingestion in tests)\n DELETE /v1/traces Clear captured telemetry (test reset)\n POST /v1/logs Receive OTLP JSON log data\n POST /v1/metrics Receive OTLP JSON metric data\n gRPC :4317 Receive OTLP protobuf traces, logs and metrics\n WS /ws WebSocket stream for real-time updates\n GET /healthz Health check\n\nExamples:\n npx autotel-devtools\n npx autotel-devtools 4319\n npx autotel-devtools -p 4319 -H 0.0.0.0\n npx autotel-devtools claude # watch Claude Code in the Agents tab\n npx autotel-devtools claude --print-env # just print the env (for MDM / VS Code)\n\nThen point your app:\n OTEL_EXPORTER_OTLP_PROTOCOL=http/json OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 node app.js\n\nView in browser:\n http://localhost:4318\n\nOr embed widget in your app:\n <script src=\"http://localhost:4318/widget.js\"><\/script>\n\n");
|
|
13
14
|
}
|
|
14
15
|
function printVersion() {
|
|
15
16
|
try {
|
|
@@ -24,7 +25,11 @@ function parseArgs(argv) {
|
|
|
24
25
|
const options = {
|
|
25
26
|
port: parsePort(process.env.AUTOTEL_DEVTOOLS_PORT || "4318"),
|
|
26
27
|
host: process.env.AUTOTEL_DEVTOOLS_HOST || "127.0.0.1",
|
|
27
|
-
title: process.env.AUTOTEL_DEVTOOLS_TITLE
|
|
28
|
+
title: process.env.AUTOTEL_DEVTOOLS_TITLE,
|
|
29
|
+
dbPath: process.env.AUTOTEL_DEVTOOLS_DB,
|
|
30
|
+
maxTraces: parseCount(process.env.AUTOTEL_DEVTOOLS_MAX_TRACES),
|
|
31
|
+
maxDbBytes: parseBytes(process.env.AUTOTEL_DEVTOOLS_DB_MAX_SIZE),
|
|
32
|
+
grpcPort: parsePort(process.env.AUTOTEL_DEVTOOLS_GRPC_PORT || "4317")
|
|
28
33
|
};
|
|
29
34
|
let portWasExplicit = false;
|
|
30
35
|
let positionalPortConsumed = false;
|
|
@@ -55,6 +60,26 @@ function parseArgs(argv) {
|
|
|
55
60
|
i++;
|
|
56
61
|
continue;
|
|
57
62
|
}
|
|
63
|
+
if ((arg === "--db" || arg === "-d") && next) {
|
|
64
|
+
options.dbPath = next;
|
|
65
|
+
i++;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (arg === "--max-traces" && next) {
|
|
69
|
+
options.maxTraces = parseCount(next);
|
|
70
|
+
i++;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (arg === "--grpc-port" && next) {
|
|
74
|
+
options.grpcPort = parsePort(next);
|
|
75
|
+
i++;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (arg === "--db-max-size" && next) {
|
|
79
|
+
options.maxDbBytes = parseBytes(next);
|
|
80
|
+
i++;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
58
83
|
if (/^\d+$/.test(arg) && !positionalPortConsumed) {
|
|
59
84
|
if (!portWasExplicit) options.port = parsePort(arg);
|
|
60
85
|
positionalPortConsumed = true;
|
|
@@ -63,6 +88,28 @@ function parseArgs(argv) {
|
|
|
63
88
|
}
|
|
64
89
|
return options;
|
|
65
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* A positive integer option, or undefined when unset.
|
|
93
|
+
*
|
|
94
|
+
* Unlike `parsePort` this does not exit on a bad value: a nonsense retention
|
|
95
|
+
* cap should fall back to the default rather than refuse to start a viewer.
|
|
96
|
+
*/
|
|
97
|
+
function parseCount(value) {
|
|
98
|
+
if (!value) return void 0;
|
|
99
|
+
const n = Number(value);
|
|
100
|
+
return Number.isInteger(n) && n > 0 ? n : void 0;
|
|
101
|
+
}
|
|
102
|
+
function parseBytes(value) {
|
|
103
|
+
if (!value) return void 0;
|
|
104
|
+
const match = value.trim().toLowerCase().match(/^(\d+(?:\.\d+)?)\s*(b|kb|mb|gb)?$/);
|
|
105
|
+
if (!match) return void 0;
|
|
106
|
+
return Math.floor(Number(match[1]) * {
|
|
107
|
+
b: 1,
|
|
108
|
+
kb: 1024,
|
|
109
|
+
mb: 1024 ** 2,
|
|
110
|
+
gb: 1024 ** 3
|
|
111
|
+
}[match[2] || "b"]);
|
|
112
|
+
}
|
|
66
113
|
function parsePort(value) {
|
|
67
114
|
const n = Number(value);
|
|
68
115
|
if (!Number.isInteger(n) || n < 0 || n > 65535) {
|
|
@@ -77,7 +124,10 @@ async function startReceiver(options) {
|
|
|
77
124
|
const wsServer = new DevtoolsServer({
|
|
78
125
|
server: httpServer,
|
|
79
126
|
host: options.host,
|
|
80
|
-
verbose: true
|
|
127
|
+
verbose: true,
|
|
128
|
+
dbPath: options.dbPath,
|
|
129
|
+
maxTraces: options.maxTraces,
|
|
130
|
+
maxDbBytes: options.maxDbBytes
|
|
81
131
|
});
|
|
82
132
|
const sourceRoot = resolveSourceRoot(process.env.AUTOTEL_DEVTOOLS_SOURCE_ROOT, process.cwd(), loopbackOnly);
|
|
83
133
|
attachDevtoolsRoutes(httpServer, wsServer, {
|
|
@@ -89,22 +139,36 @@ async function startReceiver(options) {
|
|
|
89
139
|
primary: httpServer,
|
|
90
140
|
port: options.port,
|
|
91
141
|
host: options.host,
|
|
92
|
-
attachSecondary: (s) =>
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
142
|
+
attachSecondary: (s) => {
|
|
143
|
+
attachDevtoolsRoutes(s, wsServer, {
|
|
144
|
+
loopbackOnly,
|
|
145
|
+
title: options.title,
|
|
146
|
+
sourceRoot
|
|
147
|
+
});
|
|
148
|
+
wsServer.attachWebSocket(s);
|
|
149
|
+
}
|
|
97
150
|
});
|
|
98
151
|
const { addresses, warnings, port: boundPort } = await listeners.ready;
|
|
152
|
+
const grpc = await startOtlpGrpcReceiver({
|
|
153
|
+
devtools: wsServer,
|
|
154
|
+
host: options.host,
|
|
155
|
+
port: options.grpcPort
|
|
156
|
+
});
|
|
157
|
+
if (grpc.port !== options.grpcPort) warnings.push(`OTLP/gRPC port ${options.grpcPort} was busy; using ${grpc.port}.`);
|
|
99
158
|
if (boundPort !== options.port) if (await probePortHolder(options.host, options.port) === "autotel-devtools") warnings.push(`another autotel-devtools is already running on port ${options.port}; this instance is on ${boundPort}. Use the existing one, or stop it and restart here.`);
|
|
100
159
|
else warnings.push(`port ${options.port} is held by another process that is NOT autotel-devtools. Anything exporting OTLP to :${options.port} is reaching that process, not this devtools. Point your exporter at :${boundPort}, or free :${options.port} and restart.`);
|
|
101
160
|
return {
|
|
102
161
|
wsServer,
|
|
103
|
-
closeAll: () => Promise.all([
|
|
162
|
+
closeAll: () => Promise.all([
|
|
163
|
+
wsServer.close(),
|
|
164
|
+
listeners.closeSibling(),
|
|
165
|
+
grpc.close()
|
|
166
|
+
]).then(() => void 0),
|
|
104
167
|
addresses,
|
|
105
168
|
warnings,
|
|
106
169
|
boundPort,
|
|
107
|
-
uiBase: `http://${options.host === "localhost" ? "127.0.0.1" : options.host}:${boundPort}
|
|
170
|
+
uiBase: `http://${options.host === "localhost" ? "127.0.0.1" : options.host}:${boundPort}`,
|
|
171
|
+
grpcAddress: grpc.address
|
|
108
172
|
};
|
|
109
173
|
}
|
|
110
174
|
function buildAgentEnv(uiBase, logPrompts) {
|
|
@@ -169,7 +233,8 @@ async function runClaudeSubcommand(argv) {
|
|
|
169
233
|
}
|
|
170
234
|
const receiver = await startReceiver({
|
|
171
235
|
port: opts.port,
|
|
172
|
-
host: opts.host
|
|
236
|
+
host: opts.host,
|
|
237
|
+
grpcPort: 4317
|
|
173
238
|
});
|
|
174
239
|
const env = buildAgentEnv(receiver.uiBase, opts.logPrompts);
|
|
175
240
|
process.stdout.write(`\n autotel-devtools — Claude Code\n\n`);
|
|
@@ -214,6 +279,7 @@ async function main() {
|
|
|
214
279
|
process.stdout.write(` Listening: ${addresses.join(" + ")}\n`);
|
|
215
280
|
process.stdout.write(` UI: ${uiBase} (open in a browser)\n`);
|
|
216
281
|
process.stdout.write(` OTLP: ${uiBase}/v1/traces\n`);
|
|
282
|
+
process.stdout.write(` OTLP/gRPC: ${receiver.grpcAddress}\n`);
|
|
217
283
|
process.stdout.write(` WebSocket: ${uiBase.replace("http", "ws")}/ws\n\n`);
|
|
218
284
|
process.stdout.write(` Embed in your app — paste into your HTML; a floating panel appears automatically:\n`);
|
|
219
285
|
process.stdout.write(` <script src="${uiBase}/widget.js"><\/script>\n\n`);
|
|
@@ -222,6 +288,7 @@ async function main() {
|
|
|
222
288
|
process.stdout.write(` Or point any OTLP exporter at this receiver:\n`);
|
|
223
289
|
process.stdout.write(` OTEL_EXPORTER_OTLP_PROTOCOL=http/json\n`);
|
|
224
290
|
process.stdout.write(` OTEL_EXPORTER_OTLP_ENDPOINT=${uiBase}\n\n`);
|
|
291
|
+
process.stdout.write(` Or use gRPC: OTEL_EXPORTER_OTLP_ENDPOINT=http://${receiver.grpcAddress}\n\n`);
|
|
225
292
|
process.stdout.write(` Verify ingestion: curl -s ${uiBase}/v1/traces\n\n`);
|
|
226
293
|
for (const w of warnings) process.stdout.write(` ⚠ ${w}\n`);
|
|
227
294
|
if (warnings.length > 0) process.stdout.write("\n");
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
//#region src/query/ast.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* AST and vocabulary for the telemetry query language.
|
|
4
|
+
*
|
|
5
|
+
* Shared by everything that touches a query: the parser produces these nodes,
|
|
6
|
+
* the SQL compiler consumes them, and the editor reads the same operator table
|
|
7
|
+
* to offer completions. One definition, so a new operator cannot be added to
|
|
8
|
+
* the language without the compiler and the editor both seeing it.
|
|
9
|
+
*/
|
|
10
|
+
/** Half-open source range `[from, to)`, used for editor squiggles. */
|
|
11
|
+
interface Range {
|
|
12
|
+
from: number;
|
|
13
|
+
to: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Every operator the language accepts.
|
|
17
|
+
*
|
|
18
|
+
* Sigils and keywords are two spellings of one set — `=~` and `REGEXP` produce
|
|
19
|
+
* the same node — so downstream code branches on one canonical value.
|
|
20
|
+
*/
|
|
21
|
+
declare const OPERATORS: {
|
|
22
|
+
readonly '=': {
|
|
23
|
+
readonly label: "equals";
|
|
24
|
+
readonly kind: "comparison";
|
|
25
|
+
};
|
|
26
|
+
readonly '!=': {
|
|
27
|
+
readonly label: "does not equal";
|
|
28
|
+
readonly kind: "comparison";
|
|
29
|
+
};
|
|
30
|
+
readonly '>': {
|
|
31
|
+
readonly label: "greater than";
|
|
32
|
+
readonly kind: "ordered";
|
|
33
|
+
};
|
|
34
|
+
readonly '<': {
|
|
35
|
+
readonly label: "less than";
|
|
36
|
+
readonly kind: "ordered";
|
|
37
|
+
};
|
|
38
|
+
readonly '>=': {
|
|
39
|
+
readonly label: "greater than or equal";
|
|
40
|
+
readonly kind: "ordered";
|
|
41
|
+
};
|
|
42
|
+
readonly '<=': {
|
|
43
|
+
readonly label: "less than or equal";
|
|
44
|
+
readonly kind: "ordered";
|
|
45
|
+
};
|
|
46
|
+
readonly '^': {
|
|
47
|
+
readonly label: "starts with";
|
|
48
|
+
readonly kind: "text";
|
|
49
|
+
};
|
|
50
|
+
readonly $: {
|
|
51
|
+
readonly label: "ends with";
|
|
52
|
+
readonly kind: "text";
|
|
53
|
+
};
|
|
54
|
+
readonly CONTAINS: {
|
|
55
|
+
readonly label: "contains";
|
|
56
|
+
readonly kind: "text";
|
|
57
|
+
};
|
|
58
|
+
readonly 'NOT CONTAINS': {
|
|
59
|
+
readonly label: "does not contain";
|
|
60
|
+
readonly kind: "text";
|
|
61
|
+
};
|
|
62
|
+
readonly REGEXP: {
|
|
63
|
+
readonly label: "matches regex";
|
|
64
|
+
readonly kind: "text";
|
|
65
|
+
};
|
|
66
|
+
readonly 'NOT REGEXP': {
|
|
67
|
+
readonly label: "does not match regex";
|
|
68
|
+
readonly kind: "text";
|
|
69
|
+
};
|
|
70
|
+
readonly IN: {
|
|
71
|
+
readonly label: "is one of";
|
|
72
|
+
readonly kind: "set";
|
|
73
|
+
};
|
|
74
|
+
readonly 'NOT IN': {
|
|
75
|
+
readonly label: "is not one of";
|
|
76
|
+
readonly kind: "set";
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
type Operator = keyof typeof OPERATORS;
|
|
80
|
+
/** Sigil spellings that map onto a keyword operator. */
|
|
81
|
+
declare const SIGIL_ALIASES: Readonly<Record<string, Operator>>;
|
|
82
|
+
type QueryValue = {
|
|
83
|
+
type: 'string';
|
|
84
|
+
value: string;
|
|
85
|
+
} | {
|
|
86
|
+
type: 'number';
|
|
87
|
+
value: number;
|
|
88
|
+
} | {
|
|
89
|
+
type: 'boolean';
|
|
90
|
+
value: boolean;
|
|
91
|
+
} | {
|
|
92
|
+
type: 'null';
|
|
93
|
+
} | {
|
|
94
|
+
type: 'array';
|
|
95
|
+
values: QueryValue[];
|
|
96
|
+
};
|
|
97
|
+
type QueryNode =
|
|
98
|
+
/** Empty query: matches everything. Distinct from an error. */
|
|
99
|
+
{
|
|
100
|
+
type: 'all';
|
|
101
|
+
} | {
|
|
102
|
+
type: 'and';
|
|
103
|
+
left: QueryNode;
|
|
104
|
+
right: QueryNode;
|
|
105
|
+
range: Range;
|
|
106
|
+
} | {
|
|
107
|
+
type: 'or';
|
|
108
|
+
left: QueryNode;
|
|
109
|
+
right: QueryNode;
|
|
110
|
+
range: Range;
|
|
111
|
+
} | {
|
|
112
|
+
type: 'comparison';
|
|
113
|
+
field: string;
|
|
114
|
+
op: Operator;
|
|
115
|
+
value: QueryValue;
|
|
116
|
+
range: Range;
|
|
117
|
+
} |
|
|
118
|
+
/** A bare word or phrase, matched across a signal's default text fields. */
|
|
119
|
+
{
|
|
120
|
+
type: 'freeText';
|
|
121
|
+
text: string;
|
|
122
|
+
range: Range;
|
|
123
|
+
};
|
|
124
|
+
interface QueryError {
|
|
125
|
+
message: string;
|
|
126
|
+
range: Range;
|
|
127
|
+
}
|
|
128
|
+
type ParseResult = {
|
|
129
|
+
ok: true;
|
|
130
|
+
node: QueryNode;
|
|
131
|
+
} | {
|
|
132
|
+
ok: false;
|
|
133
|
+
errors: QueryError[];
|
|
134
|
+
};
|
|
135
|
+
//#endregion
|
|
136
|
+
//#region src/query/compile.d.ts
|
|
137
|
+
interface ColumnSchema {
|
|
138
|
+
/** Physical column name. May differ from the name used in queries. */
|
|
139
|
+
column: string;
|
|
140
|
+
type: 'string' | 'number' | 'boolean';
|
|
141
|
+
}
|
|
142
|
+
interface SignalSchema {
|
|
143
|
+
/** Query field name → physical column. Anything absent is an attribute. */
|
|
144
|
+
columns: Record<string, ColumnSchema>;
|
|
145
|
+
/** Column holding the JSON attribute bag. */
|
|
146
|
+
attributesColumn: string;
|
|
147
|
+
/** Query fields a bare word is matched against. Must be keys of `columns`. */
|
|
148
|
+
freeTextColumns: string[];
|
|
149
|
+
/** Optional normalized attribute index used for exact equality probes. */
|
|
150
|
+
attributeIndex?: {
|
|
151
|
+
table: string;
|
|
152
|
+
signal: string;
|
|
153
|
+
/** Trusted SQL expression identifying the current row. */
|
|
154
|
+
entitySql: string;
|
|
155
|
+
};
|
|
156
|
+
/**
|
|
157
|
+
* Query fields that live in a child table rather than on the row itself, so
|
|
158
|
+
* `event.name = timeout` asks whether *any* event on the span matches.
|
|
159
|
+
*
|
|
160
|
+
* Every part is a trusted SQL fragment from the schema, never from the query
|
|
161
|
+
* text: `table` and `column` are identifiers, `joinSql` is the predicate
|
|
162
|
+
* tying a child row to the current one.
|
|
163
|
+
*/
|
|
164
|
+
related?: Record<string, {
|
|
165
|
+
table: string;
|
|
166
|
+
column: string;
|
|
167
|
+
joinSql: string;
|
|
168
|
+
}>;
|
|
169
|
+
}
|
|
170
|
+
interface CompiledQuery {
|
|
171
|
+
/** A WHERE-clause fragment. `1` matches everything, `0` matches nothing. */
|
|
172
|
+
sql: string;
|
|
173
|
+
params: unknown[];
|
|
174
|
+
}
|
|
175
|
+
declare function compileWhere(node: QueryNode, schema: SignalSchema): CompiledQuery;
|
|
176
|
+
//#endregion
|
|
177
|
+
export { OPERATORS as a, QueryError as c, Range as d, SIGIL_ALIASES as f, compileWhere as i, QueryNode as l, CompiledQuery as n, Operator as o, SignalSchema as r, ParseResult as s, ColumnSchema as t, QueryValue as u };
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
//#region src/query/ast.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* AST and vocabulary for the telemetry query language.
|
|
4
|
+
*
|
|
5
|
+
* Shared by everything that touches a query: the parser produces these nodes,
|
|
6
|
+
* the SQL compiler consumes them, and the editor reads the same operator table
|
|
7
|
+
* to offer completions. One definition, so a new operator cannot be added to
|
|
8
|
+
* the language without the compiler and the editor both seeing it.
|
|
9
|
+
*/
|
|
10
|
+
/** Half-open source range `[from, to)`, used for editor squiggles. */
|
|
11
|
+
interface Range {
|
|
12
|
+
from: number;
|
|
13
|
+
to: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Every operator the language accepts.
|
|
17
|
+
*
|
|
18
|
+
* Sigils and keywords are two spellings of one set — `=~` and `REGEXP` produce
|
|
19
|
+
* the same node — so downstream code branches on one canonical value.
|
|
20
|
+
*/
|
|
21
|
+
declare const OPERATORS: {
|
|
22
|
+
readonly '=': {
|
|
23
|
+
readonly label: "equals";
|
|
24
|
+
readonly kind: "comparison";
|
|
25
|
+
};
|
|
26
|
+
readonly '!=': {
|
|
27
|
+
readonly label: "does not equal";
|
|
28
|
+
readonly kind: "comparison";
|
|
29
|
+
};
|
|
30
|
+
readonly '>': {
|
|
31
|
+
readonly label: "greater than";
|
|
32
|
+
readonly kind: "ordered";
|
|
33
|
+
};
|
|
34
|
+
readonly '<': {
|
|
35
|
+
readonly label: "less than";
|
|
36
|
+
readonly kind: "ordered";
|
|
37
|
+
};
|
|
38
|
+
readonly '>=': {
|
|
39
|
+
readonly label: "greater than or equal";
|
|
40
|
+
readonly kind: "ordered";
|
|
41
|
+
};
|
|
42
|
+
readonly '<=': {
|
|
43
|
+
readonly label: "less than or equal";
|
|
44
|
+
readonly kind: "ordered";
|
|
45
|
+
};
|
|
46
|
+
readonly '^': {
|
|
47
|
+
readonly label: "starts with";
|
|
48
|
+
readonly kind: "text";
|
|
49
|
+
};
|
|
50
|
+
readonly $: {
|
|
51
|
+
readonly label: "ends with";
|
|
52
|
+
readonly kind: "text";
|
|
53
|
+
};
|
|
54
|
+
readonly CONTAINS: {
|
|
55
|
+
readonly label: "contains";
|
|
56
|
+
readonly kind: "text";
|
|
57
|
+
};
|
|
58
|
+
readonly 'NOT CONTAINS': {
|
|
59
|
+
readonly label: "does not contain";
|
|
60
|
+
readonly kind: "text";
|
|
61
|
+
};
|
|
62
|
+
readonly REGEXP: {
|
|
63
|
+
readonly label: "matches regex";
|
|
64
|
+
readonly kind: "text";
|
|
65
|
+
};
|
|
66
|
+
readonly 'NOT REGEXP': {
|
|
67
|
+
readonly label: "does not match regex";
|
|
68
|
+
readonly kind: "text";
|
|
69
|
+
};
|
|
70
|
+
readonly IN: {
|
|
71
|
+
readonly label: "is one of";
|
|
72
|
+
readonly kind: "set";
|
|
73
|
+
};
|
|
74
|
+
readonly 'NOT IN': {
|
|
75
|
+
readonly label: "is not one of";
|
|
76
|
+
readonly kind: "set";
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
type Operator = keyof typeof OPERATORS;
|
|
80
|
+
/** Sigil spellings that map onto a keyword operator. */
|
|
81
|
+
declare const SIGIL_ALIASES: Readonly<Record<string, Operator>>;
|
|
82
|
+
type QueryValue = {
|
|
83
|
+
type: 'string';
|
|
84
|
+
value: string;
|
|
85
|
+
} | {
|
|
86
|
+
type: 'number';
|
|
87
|
+
value: number;
|
|
88
|
+
} | {
|
|
89
|
+
type: 'boolean';
|
|
90
|
+
value: boolean;
|
|
91
|
+
} | {
|
|
92
|
+
type: 'null';
|
|
93
|
+
} | {
|
|
94
|
+
type: 'array';
|
|
95
|
+
values: QueryValue[];
|
|
96
|
+
};
|
|
97
|
+
type QueryNode =
|
|
98
|
+
/** Empty query: matches everything. Distinct from an error. */
|
|
99
|
+
{
|
|
100
|
+
type: 'all';
|
|
101
|
+
} | {
|
|
102
|
+
type: 'and';
|
|
103
|
+
left: QueryNode;
|
|
104
|
+
right: QueryNode;
|
|
105
|
+
range: Range;
|
|
106
|
+
} | {
|
|
107
|
+
type: 'or';
|
|
108
|
+
left: QueryNode;
|
|
109
|
+
right: QueryNode;
|
|
110
|
+
range: Range;
|
|
111
|
+
} | {
|
|
112
|
+
type: 'comparison';
|
|
113
|
+
field: string;
|
|
114
|
+
op: Operator;
|
|
115
|
+
value: QueryValue;
|
|
116
|
+
range: Range;
|
|
117
|
+
} |
|
|
118
|
+
/** A bare word or phrase, matched across a signal's default text fields. */
|
|
119
|
+
{
|
|
120
|
+
type: 'freeText';
|
|
121
|
+
text: string;
|
|
122
|
+
range: Range;
|
|
123
|
+
};
|
|
124
|
+
interface QueryError {
|
|
125
|
+
message: string;
|
|
126
|
+
range: Range;
|
|
127
|
+
}
|
|
128
|
+
type ParseResult = {
|
|
129
|
+
ok: true;
|
|
130
|
+
node: QueryNode;
|
|
131
|
+
} | {
|
|
132
|
+
ok: false;
|
|
133
|
+
errors: QueryError[];
|
|
134
|
+
};
|
|
135
|
+
//#endregion
|
|
136
|
+
//#region src/query/compile.d.ts
|
|
137
|
+
interface ColumnSchema {
|
|
138
|
+
/** Physical column name. May differ from the name used in queries. */
|
|
139
|
+
column: string;
|
|
140
|
+
type: 'string' | 'number' | 'boolean';
|
|
141
|
+
}
|
|
142
|
+
interface SignalSchema {
|
|
143
|
+
/** Query field name → physical column. Anything absent is an attribute. */
|
|
144
|
+
columns: Record<string, ColumnSchema>;
|
|
145
|
+
/** Column holding the JSON attribute bag. */
|
|
146
|
+
attributesColumn: string;
|
|
147
|
+
/** Query fields a bare word is matched against. Must be keys of `columns`. */
|
|
148
|
+
freeTextColumns: string[];
|
|
149
|
+
/** Optional normalized attribute index used for exact equality probes. */
|
|
150
|
+
attributeIndex?: {
|
|
151
|
+
table: string;
|
|
152
|
+
signal: string;
|
|
153
|
+
/** Trusted SQL expression identifying the current row. */
|
|
154
|
+
entitySql: string;
|
|
155
|
+
};
|
|
156
|
+
/**
|
|
157
|
+
* Query fields that live in a child table rather than on the row itself, so
|
|
158
|
+
* `event.name = timeout` asks whether *any* event on the span matches.
|
|
159
|
+
*
|
|
160
|
+
* Every part is a trusted SQL fragment from the schema, never from the query
|
|
161
|
+
* text: `table` and `column` are identifiers, `joinSql` is the predicate
|
|
162
|
+
* tying a child row to the current one.
|
|
163
|
+
*/
|
|
164
|
+
related?: Record<string, {
|
|
165
|
+
table: string;
|
|
166
|
+
column: string;
|
|
167
|
+
joinSql: string;
|
|
168
|
+
}>;
|
|
169
|
+
}
|
|
170
|
+
interface CompiledQuery {
|
|
171
|
+
/** A WHERE-clause fragment. `1` matches everything, `0` matches nothing. */
|
|
172
|
+
sql: string;
|
|
173
|
+
params: unknown[];
|
|
174
|
+
}
|
|
175
|
+
declare function compileWhere(node: QueryNode, schema: SignalSchema): CompiledQuery;
|
|
176
|
+
//#endregion
|
|
177
|
+
export { OPERATORS as a, QueryError as c, Range as d, SIGIL_ALIASES as f, compileWhere as i, QueryNode as l, CompiledQuery as n, Operator as o, SignalSchema as r, ParseResult as s, ColumnSchema as t, QueryValue as u };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as ErrorGroup, o as TraceData, r as ErrorOccurrence } from "./types-DM8y4A9Z.js";
|
|
2
2
|
//#region src/server/error-aggregator.d.ts
|
|
3
3
|
/** How many errors the aggregator is holding, and what they are. */
|
|
4
4
|
interface ErrorStats {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as ErrorGroup, o as TraceData, r as ErrorOccurrence } from "./types-B0tjwFqj.cjs";
|
|
2
2
|
//#region src/server/error-aggregator.d.ts
|
|
3
3
|
/** How many errors the aggregator is holding, and what they are. */
|
|
4
4
|
interface ErrorStats {
|