@mastra/duckdb 1.0.1 → 1.1.0-alpha.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/CHANGELOG.md +49 -0
- package/LICENSE.md +15 -0
- package/dist/chunk-37GBWD4M.js +195 -0
- package/dist/chunk-37GBWD4M.js.map +1 -0
- package/dist/chunk-S2AWBPTS.cjs +198 -0
- package/dist/chunk-S2AWBPTS.cjs.map +1 -0
- package/dist/docs/SKILL.md +2 -2
- package/dist/docs/assets/SOURCE_MAP.json +8 -2
- package/dist/docs/references/reference-vectors-duckdb.md +46 -46
- package/dist/index.cjs +241 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +238 -6
- package/dist/index.js.map +1 -1
- package/dist/observability-PW6J27KS.js +1675 -0
- package/dist/observability-PW6J27KS.js.map +1 -0
- package/dist/observability-W2QRBK56.cjs +1677 -0
- package/dist/observability-W2QRBK56.cjs.map +1 -0
- package/dist/storage/db/index.d.ts +46 -0
- package/dist/storage/db/index.d.ts.map +1 -0
- package/dist/storage/domains/observability/ddl.d.ts +25 -0
- package/dist/storage/domains/observability/ddl.d.ts.map +1 -0
- package/dist/storage/domains/observability/discovery.d.ts +13 -0
- package/dist/storage/domains/observability/discovery.d.ts.map +1 -0
- package/dist/storage/domains/observability/feedback.d.ts +9 -0
- package/dist/storage/domains/observability/feedback.d.ts.map +1 -0
- package/dist/storage/domains/observability/filters.d.ts +27 -0
- package/dist/storage/domains/observability/filters.d.ts.map +1 -0
- package/dist/storage/domains/observability/helpers.d.ts +14 -0
- package/dist/storage/domains/observability/helpers.d.ts.map +1 -0
- package/dist/storage/domains/observability/index.d.ts +54 -0
- package/dist/storage/domains/observability/index.d.ts.map +1 -0
- package/dist/storage/domains/observability/logs.d.ts +7 -0
- package/dist/storage/domains/observability/logs.d.ts.map +1 -0
- package/dist/storage/domains/observability/metrics.d.ts +21 -0
- package/dist/storage/domains/observability/metrics.d.ts.map +1 -0
- package/dist/storage/domains/observability/scores.d.ts +9 -0
- package/dist/storage/domains/observability/scores.d.ts.map +1 -0
- package/dist/storage/domains/observability/tracing.d.ts +17 -0
- package/dist/storage/domains/observability/tracing.d.ts.map +1 -0
- package/dist/storage/index.d.ts +101 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/vector/filter-builder.d.ts +1 -0
- package/dist/vector/filter-builder.d.ts.map +1 -1
- package/dist/vector/index.d.ts +9 -0
- package/dist/vector/index.d.ts.map +1 -1
- package/package.json +7 -7
|
@@ -0,0 +1,1677 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkS2AWBPTS_cjs = require('./chunk-S2AWBPTS.cjs');
|
|
4
|
+
var storage = require('@mastra/core/storage');
|
|
5
|
+
var observability = require('@mastra/core/observability');
|
|
6
|
+
var utils = require('@mastra/core/utils');
|
|
7
|
+
|
|
8
|
+
// src/storage/domains/observability/ddl.ts
|
|
9
|
+
var SPAN_EVENTS_DDL = `
|
|
10
|
+
CREATE TABLE IF NOT EXISTS span_events (
|
|
11
|
+
-- Event metadata
|
|
12
|
+
eventType VARCHAR NOT NULL,
|
|
13
|
+
timestamp TIMESTAMP NOT NULL,
|
|
14
|
+
|
|
15
|
+
-- IDs
|
|
16
|
+
traceId VARCHAR NOT NULL,
|
|
17
|
+
spanId VARCHAR NOT NULL,
|
|
18
|
+
parentSpanId VARCHAR,
|
|
19
|
+
experimentId VARCHAR,
|
|
20
|
+
|
|
21
|
+
-- Entity
|
|
22
|
+
entityType VARCHAR,
|
|
23
|
+
entityId VARCHAR,
|
|
24
|
+
entityName VARCHAR,
|
|
25
|
+
|
|
26
|
+
-- Context
|
|
27
|
+
userId VARCHAR,
|
|
28
|
+
organizationId VARCHAR,
|
|
29
|
+
resourceId VARCHAR,
|
|
30
|
+
runId VARCHAR,
|
|
31
|
+
sessionId VARCHAR,
|
|
32
|
+
threadId VARCHAR,
|
|
33
|
+
requestId VARCHAR,
|
|
34
|
+
environment VARCHAR,
|
|
35
|
+
source VARCHAR,
|
|
36
|
+
serviceName VARCHAR,
|
|
37
|
+
requestContext JSON,
|
|
38
|
+
|
|
39
|
+
-- Span-specific scalars
|
|
40
|
+
name VARCHAR,
|
|
41
|
+
spanType VARCHAR,
|
|
42
|
+
isEvent BOOLEAN,
|
|
43
|
+
endedAt TIMESTAMP,
|
|
44
|
+
|
|
45
|
+
-- JSON fields
|
|
46
|
+
attributes JSON,
|
|
47
|
+
metadata JSON,
|
|
48
|
+
tags JSON,
|
|
49
|
+
scope JSON,
|
|
50
|
+
links JSON,
|
|
51
|
+
input JSON,
|
|
52
|
+
output JSON,
|
|
53
|
+
error JSON
|
|
54
|
+
)`;
|
|
55
|
+
var METRIC_EVENTS_DDL = `
|
|
56
|
+
CREATE TABLE IF NOT EXISTS metric_events (
|
|
57
|
+
-- Event metadata
|
|
58
|
+
timestamp TIMESTAMP NOT NULL,
|
|
59
|
+
|
|
60
|
+
-- IDs
|
|
61
|
+
traceId VARCHAR,
|
|
62
|
+
spanId VARCHAR,
|
|
63
|
+
experimentId VARCHAR,
|
|
64
|
+
|
|
65
|
+
-- Entity hierarchy
|
|
66
|
+
entityType VARCHAR,
|
|
67
|
+
entityId VARCHAR,
|
|
68
|
+
entityName VARCHAR,
|
|
69
|
+
parentEntityType VARCHAR,
|
|
70
|
+
parentEntityId VARCHAR,
|
|
71
|
+
parentEntityName VARCHAR,
|
|
72
|
+
rootEntityType VARCHAR,
|
|
73
|
+
rootEntityId VARCHAR,
|
|
74
|
+
rootEntityName VARCHAR,
|
|
75
|
+
|
|
76
|
+
-- Context
|
|
77
|
+
userId VARCHAR,
|
|
78
|
+
organizationId VARCHAR,
|
|
79
|
+
resourceId VARCHAR,
|
|
80
|
+
runId VARCHAR,
|
|
81
|
+
sessionId VARCHAR,
|
|
82
|
+
threadId VARCHAR,
|
|
83
|
+
requestId VARCHAR,
|
|
84
|
+
environment VARCHAR,
|
|
85
|
+
source VARCHAR,
|
|
86
|
+
serviceName VARCHAR,
|
|
87
|
+
|
|
88
|
+
-- Metric-specific scalars
|
|
89
|
+
name VARCHAR NOT NULL,
|
|
90
|
+
value DOUBLE NOT NULL,
|
|
91
|
+
provider VARCHAR,
|
|
92
|
+
model VARCHAR,
|
|
93
|
+
estimatedCost DOUBLE,
|
|
94
|
+
costUnit VARCHAR,
|
|
95
|
+
|
|
96
|
+
-- JSON fields
|
|
97
|
+
tags JSON,
|
|
98
|
+
labels JSON,
|
|
99
|
+
costMetadata JSON,
|
|
100
|
+
metadata JSON,
|
|
101
|
+
scope JSON
|
|
102
|
+
)`;
|
|
103
|
+
var LOG_EVENTS_DDL = `
|
|
104
|
+
CREATE TABLE IF NOT EXISTS log_events (
|
|
105
|
+
-- Event metadata
|
|
106
|
+
timestamp TIMESTAMP NOT NULL,
|
|
107
|
+
|
|
108
|
+
-- IDs
|
|
109
|
+
traceId VARCHAR,
|
|
110
|
+
spanId VARCHAR,
|
|
111
|
+
experimentId VARCHAR,
|
|
112
|
+
|
|
113
|
+
-- Entity hierarchy
|
|
114
|
+
entityType VARCHAR,
|
|
115
|
+
entityId VARCHAR,
|
|
116
|
+
entityName VARCHAR,
|
|
117
|
+
parentEntityType VARCHAR,
|
|
118
|
+
parentEntityId VARCHAR,
|
|
119
|
+
parentEntityName VARCHAR,
|
|
120
|
+
rootEntityType VARCHAR,
|
|
121
|
+
rootEntityId VARCHAR,
|
|
122
|
+
rootEntityName VARCHAR,
|
|
123
|
+
|
|
124
|
+
-- Context
|
|
125
|
+
userId VARCHAR,
|
|
126
|
+
organizationId VARCHAR,
|
|
127
|
+
resourceId VARCHAR,
|
|
128
|
+
runId VARCHAR,
|
|
129
|
+
sessionId VARCHAR,
|
|
130
|
+
threadId VARCHAR,
|
|
131
|
+
requestId VARCHAR,
|
|
132
|
+
environment VARCHAR,
|
|
133
|
+
source VARCHAR,
|
|
134
|
+
serviceName VARCHAR,
|
|
135
|
+
|
|
136
|
+
-- Log-specific scalars
|
|
137
|
+
level VARCHAR NOT NULL,
|
|
138
|
+
message VARCHAR NOT NULL,
|
|
139
|
+
|
|
140
|
+
-- JSON fields
|
|
141
|
+
data JSON,
|
|
142
|
+
tags JSON,
|
|
143
|
+
metadata JSON,
|
|
144
|
+
scope JSON
|
|
145
|
+
)`;
|
|
146
|
+
var SCORE_EVENTS_DDL = `
|
|
147
|
+
CREATE TABLE IF NOT EXISTS score_events (
|
|
148
|
+
-- Event metadata
|
|
149
|
+
timestamp TIMESTAMP NOT NULL,
|
|
150
|
+
|
|
151
|
+
-- IDs
|
|
152
|
+
traceId VARCHAR NOT NULL,
|
|
153
|
+
spanId VARCHAR,
|
|
154
|
+
experimentId VARCHAR,
|
|
155
|
+
scoreTraceId VARCHAR,
|
|
156
|
+
|
|
157
|
+
-- Score-specific scalars
|
|
158
|
+
scorerId VARCHAR NOT NULL,
|
|
159
|
+
scorerVersion VARCHAR,
|
|
160
|
+
source VARCHAR,
|
|
161
|
+
score DOUBLE NOT NULL,
|
|
162
|
+
reason VARCHAR,
|
|
163
|
+
|
|
164
|
+
-- JSON fields
|
|
165
|
+
metadata JSON
|
|
166
|
+
)`;
|
|
167
|
+
var FEEDBACK_EVENTS_DDL = `
|
|
168
|
+
CREATE TABLE IF NOT EXISTS feedback_events (
|
|
169
|
+
-- Event metadata
|
|
170
|
+
timestamp TIMESTAMP NOT NULL,
|
|
171
|
+
|
|
172
|
+
-- IDs
|
|
173
|
+
traceId VARCHAR NOT NULL,
|
|
174
|
+
spanId VARCHAR,
|
|
175
|
+
experimentId VARCHAR,
|
|
176
|
+
userId VARCHAR,
|
|
177
|
+
sourceId VARCHAR,
|
|
178
|
+
|
|
179
|
+
-- Feedback-specific scalars
|
|
180
|
+
source VARCHAR NOT NULL,
|
|
181
|
+
feedbackType VARCHAR NOT NULL,
|
|
182
|
+
value VARCHAR NOT NULL,
|
|
183
|
+
comment VARCHAR,
|
|
184
|
+
|
|
185
|
+
-- JSON fields
|
|
186
|
+
metadata JSON
|
|
187
|
+
)`;
|
|
188
|
+
var ALL_DDL = [SPAN_EVENTS_DDL, METRIC_EVENTS_DDL, LOG_EVENTS_DDL, SCORE_EVENTS_DDL, FEEDBACK_EVENTS_DDL];
|
|
189
|
+
function unionDistinctQueries(selects, orderBy) {
|
|
190
|
+
return `${selects.join("\nUNION\n")}
|
|
191
|
+
ORDER BY ${orderBy}`;
|
|
192
|
+
}
|
|
193
|
+
async function getEntityTypes(db, _args) {
|
|
194
|
+
const rows = await db.query(
|
|
195
|
+
unionDistinctQueries(
|
|
196
|
+
[
|
|
197
|
+
`SELECT entityType FROM span_events WHERE entityType IS NOT NULL`,
|
|
198
|
+
`SELECT entityType FROM metric_events WHERE entityType IS NOT NULL`,
|
|
199
|
+
`SELECT entityType FROM log_events WHERE entityType IS NOT NULL`
|
|
200
|
+
],
|
|
201
|
+
"entityType"
|
|
202
|
+
)
|
|
203
|
+
);
|
|
204
|
+
const validTypes = new Set(Object.values(observability.EntityType));
|
|
205
|
+
const typeSet = /* @__PURE__ */ new Set();
|
|
206
|
+
for (const row of rows) {
|
|
207
|
+
if (row.entityType && validTypes.has(row.entityType)) {
|
|
208
|
+
typeSet.add(row.entityType);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return { entityTypes: Array.from(typeSet).sort() };
|
|
212
|
+
}
|
|
213
|
+
async function getEntityNames(db, args) {
|
|
214
|
+
const buildSelect = (table) => {
|
|
215
|
+
const conditions = [`entityName IS NOT NULL`];
|
|
216
|
+
if (args.entityType) {
|
|
217
|
+
conditions.push(`entityType = ?`);
|
|
218
|
+
}
|
|
219
|
+
return `SELECT entityName FROM ${table} WHERE ${conditions.join(" AND ")}`;
|
|
220
|
+
};
|
|
221
|
+
const params = args.entityType ? [args.entityType, args.entityType, args.entityType] : [];
|
|
222
|
+
const rows = await db.query(
|
|
223
|
+
unionDistinctQueries(
|
|
224
|
+
[buildSelect("span_events"), buildSelect("metric_events"), buildSelect("log_events")],
|
|
225
|
+
"entityName"
|
|
226
|
+
),
|
|
227
|
+
params
|
|
228
|
+
);
|
|
229
|
+
return { names: rows.map((r) => r.entityName) };
|
|
230
|
+
}
|
|
231
|
+
async function getServiceNames(db, _args) {
|
|
232
|
+
const rows = await db.query(
|
|
233
|
+
unionDistinctQueries(
|
|
234
|
+
[
|
|
235
|
+
`SELECT serviceName FROM span_events WHERE serviceName IS NOT NULL`,
|
|
236
|
+
`SELECT serviceName FROM metric_events WHERE serviceName IS NOT NULL`,
|
|
237
|
+
`SELECT serviceName FROM log_events WHERE serviceName IS NOT NULL`
|
|
238
|
+
],
|
|
239
|
+
"serviceName"
|
|
240
|
+
)
|
|
241
|
+
);
|
|
242
|
+
return { serviceNames: rows.map((r) => r.serviceName) };
|
|
243
|
+
}
|
|
244
|
+
async function getEnvironments(db, _args) {
|
|
245
|
+
const rows = await db.query(
|
|
246
|
+
unionDistinctQueries(
|
|
247
|
+
[
|
|
248
|
+
`SELECT environment FROM span_events WHERE environment IS NOT NULL`,
|
|
249
|
+
`SELECT environment FROM metric_events WHERE environment IS NOT NULL`,
|
|
250
|
+
`SELECT environment FROM log_events WHERE environment IS NOT NULL`
|
|
251
|
+
],
|
|
252
|
+
"environment"
|
|
253
|
+
)
|
|
254
|
+
);
|
|
255
|
+
return { environments: rows.map((r) => r.environment) };
|
|
256
|
+
}
|
|
257
|
+
async function getTags(db, args) {
|
|
258
|
+
const buildSelect = (table) => {
|
|
259
|
+
const conditions = [`tags IS NOT NULL`];
|
|
260
|
+
if (args.entityType) {
|
|
261
|
+
conditions.push(`entityType = ?`);
|
|
262
|
+
}
|
|
263
|
+
return `SELECT unnest(CAST(tags AS VARCHAR[])) AS tag FROM ${table} WHERE ${conditions.join(" AND ")}`;
|
|
264
|
+
};
|
|
265
|
+
const params = args.entityType ? [args.entityType, args.entityType, args.entityType] : [];
|
|
266
|
+
const rows = await db.query(
|
|
267
|
+
unionDistinctQueries([buildSelect("span_events"), buildSelect("metric_events"), buildSelect("log_events")], "tag"),
|
|
268
|
+
params
|
|
269
|
+
);
|
|
270
|
+
return { tags: rows.map((r) => r.tag) };
|
|
271
|
+
}
|
|
272
|
+
function buildJsonPath(key) {
|
|
273
|
+
try {
|
|
274
|
+
return `$.${utils.parseFieldKey(key)}`;
|
|
275
|
+
} catch {
|
|
276
|
+
const escaped = key.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
277
|
+
return `$."${escaped}"`;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
function normalizeJsonFilterValue(value) {
|
|
281
|
+
if (value === void 0) return null;
|
|
282
|
+
if (typeof value === "string") return value;
|
|
283
|
+
const json = JSON.stringify(value);
|
|
284
|
+
return json ?? null;
|
|
285
|
+
}
|
|
286
|
+
function sanitizeColumn(column) {
|
|
287
|
+
return utils.parseFieldKey(column);
|
|
288
|
+
}
|
|
289
|
+
function buildWhereClause(filters, fieldMappings) {
|
|
290
|
+
if (!filters) return { clause: "", params: [] };
|
|
291
|
+
const conditions = [];
|
|
292
|
+
const params = [];
|
|
293
|
+
for (const [key, value] of Object.entries(filters)) {
|
|
294
|
+
if (value === void 0 || value === null) continue;
|
|
295
|
+
const column = sanitizeColumn(key);
|
|
296
|
+
if (key === "timestamp" || key === "startedAt" || key === "endedAt") {
|
|
297
|
+
const dateRange = value;
|
|
298
|
+
if (dateRange.start) {
|
|
299
|
+
const op = dateRange.startExclusive ? ">" : ">=";
|
|
300
|
+
conditions.push(`${column} ${op} ?`);
|
|
301
|
+
params.push(dateRange.start);
|
|
302
|
+
}
|
|
303
|
+
if (dateRange.end) {
|
|
304
|
+
const op = dateRange.endExclusive ? "<" : "<=";
|
|
305
|
+
conditions.push(`${column} ${op} ?`);
|
|
306
|
+
params.push(dateRange.end);
|
|
307
|
+
}
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
if (key === "labels") {
|
|
311
|
+
const labelsObj = value;
|
|
312
|
+
for (const [labelKey, labelValue] of Object.entries(labelsObj)) {
|
|
313
|
+
conditions.push(`json_extract_string(${column}, ?) = ?`);
|
|
314
|
+
params.push(buildJsonPath(labelKey), labelValue);
|
|
315
|
+
}
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
if (key === "tags") {
|
|
319
|
+
const tags = value;
|
|
320
|
+
for (const tag of tags) {
|
|
321
|
+
conditions.push(`list_contains(CAST(${column} AS VARCHAR[]), ?)`);
|
|
322
|
+
params.push(tag);
|
|
323
|
+
}
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
if (key === "status") {
|
|
327
|
+
const status = value;
|
|
328
|
+
if (status === "error") {
|
|
329
|
+
conditions.push(`error IS NOT NULL`);
|
|
330
|
+
} else if (status === "running") {
|
|
331
|
+
conditions.push(`endedAt IS NULL AND error IS NULL`);
|
|
332
|
+
} else if (status === "success") {
|
|
333
|
+
conditions.push(`endedAt IS NOT NULL AND error IS NULL`);
|
|
334
|
+
}
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
if (key === "hasChildError") {
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
if (key === "metadata" || key === "scope") {
|
|
341
|
+
const jsonObj = value;
|
|
342
|
+
for (const [jsonKey, jsonValue] of Object.entries(jsonObj)) {
|
|
343
|
+
const normalized = normalizeJsonFilterValue(jsonValue);
|
|
344
|
+
if (normalized === null) continue;
|
|
345
|
+
conditions.push(`json_extract_string(${column}, ?) = ?`);
|
|
346
|
+
params.push(buildJsonPath(jsonKey), normalized);
|
|
347
|
+
}
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
if (Array.isArray(value)) {
|
|
351
|
+
if (value.length === 0) continue;
|
|
352
|
+
const placeholders = value.map(() => "?").join(", ");
|
|
353
|
+
conditions.push(`${column} IN (${placeholders})`);
|
|
354
|
+
params.push(...value);
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
conditions.push(`${column} = ?`);
|
|
358
|
+
params.push(value);
|
|
359
|
+
}
|
|
360
|
+
const clause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
361
|
+
return { clause, params };
|
|
362
|
+
}
|
|
363
|
+
function buildOrderByClause(orderBy) {
|
|
364
|
+
if (!orderBy) return "";
|
|
365
|
+
const dir = orderBy.direction.toUpperCase();
|
|
366
|
+
if (dir !== "ASC" && dir !== "DESC") {
|
|
367
|
+
throw new Error(`Invalid sort direction: ${orderBy.direction}`);
|
|
368
|
+
}
|
|
369
|
+
const field = utils.parseFieldKey(orderBy.field);
|
|
370
|
+
return `ORDER BY ${field} ${dir}`;
|
|
371
|
+
}
|
|
372
|
+
function buildPaginationClause(pagination) {
|
|
373
|
+
if (!pagination) return { clause: "", params: [] };
|
|
374
|
+
if (!Number.isInteger(pagination.page) || pagination.page < 0) {
|
|
375
|
+
throw new Error(`Invalid page: ${pagination.page}`);
|
|
376
|
+
}
|
|
377
|
+
if (!Number.isInteger(pagination.perPage) || pagination.perPage <= 0) {
|
|
378
|
+
throw new Error(`Invalid perPage: ${pagination.perPage}`);
|
|
379
|
+
}
|
|
380
|
+
const offset = pagination.page * pagination.perPage;
|
|
381
|
+
return {
|
|
382
|
+
clause: `LIMIT ? OFFSET ?`,
|
|
383
|
+
params: [pagination.perPage, offset]
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// src/storage/domains/observability/helpers.ts
|
|
388
|
+
var v = chunkS2AWBPTS_cjs.DuckDBConnection.sqlValue;
|
|
389
|
+
function jsonV(val) {
|
|
390
|
+
if (val === null || val === void 0) return "NULL";
|
|
391
|
+
return chunkS2AWBPTS_cjs.DuckDBConnection.sqlValue(JSON.stringify(val));
|
|
392
|
+
}
|
|
393
|
+
function toDate(val) {
|
|
394
|
+
if (val === null || val === void 0) {
|
|
395
|
+
throw new Error("Expected date value but received null/undefined");
|
|
396
|
+
}
|
|
397
|
+
const date = val instanceof Date ? val : new Date(String(val));
|
|
398
|
+
if (Number.isNaN(date.getTime())) {
|
|
399
|
+
throw new Error("Expected valid date but received invalid date");
|
|
400
|
+
}
|
|
401
|
+
return date;
|
|
402
|
+
}
|
|
403
|
+
function toDateOrNull(val) {
|
|
404
|
+
if (val === null || val === void 0) return null;
|
|
405
|
+
return val instanceof Date ? val : new Date(String(val));
|
|
406
|
+
}
|
|
407
|
+
function parseJson(value) {
|
|
408
|
+
if (value === null || value === void 0) return null;
|
|
409
|
+
if (typeof value === "string") {
|
|
410
|
+
try {
|
|
411
|
+
return JSON.parse(value);
|
|
412
|
+
} catch {
|
|
413
|
+
return value;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return value;
|
|
417
|
+
}
|
|
418
|
+
function parseJsonArray(value) {
|
|
419
|
+
if (value === null || value === void 0) return null;
|
|
420
|
+
const parsed = parseJson(value);
|
|
421
|
+
return Array.isArray(parsed) ? parsed : null;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// src/storage/domains/observability/feedback.ts
|
|
425
|
+
async function createFeedback(db, args) {
|
|
426
|
+
const f = args.feedback;
|
|
427
|
+
await db.execute(
|
|
428
|
+
`INSERT INTO feedback_events (timestamp, traceId, spanId, experimentId, userId, sourceId, source, feedbackType, value, comment, metadata)
|
|
429
|
+
VALUES (${[
|
|
430
|
+
v(f.timestamp),
|
|
431
|
+
v(f.traceId),
|
|
432
|
+
v(f.spanId ?? null),
|
|
433
|
+
v(f.experimentId ?? null),
|
|
434
|
+
v(f.userId ?? null),
|
|
435
|
+
v(f.sourceId ?? null),
|
|
436
|
+
v(f.source),
|
|
437
|
+
v(f.feedbackType),
|
|
438
|
+
v(String(f.value)),
|
|
439
|
+
v(f.comment ?? null),
|
|
440
|
+
jsonV(f.metadata)
|
|
441
|
+
].join(", ")})`
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
async function batchCreateFeedback(db, args) {
|
|
445
|
+
if (args.feedbacks.length === 0) return;
|
|
446
|
+
const tuples = args.feedbacks.map(
|
|
447
|
+
(f) => `(${[
|
|
448
|
+
v(f.timestamp),
|
|
449
|
+
v(f.traceId),
|
|
450
|
+
v(f.spanId ?? null),
|
|
451
|
+
v(f.experimentId ?? null),
|
|
452
|
+
v(f.userId ?? null),
|
|
453
|
+
v(f.sourceId ?? null),
|
|
454
|
+
v(f.source),
|
|
455
|
+
v(f.feedbackType),
|
|
456
|
+
v(String(f.value)),
|
|
457
|
+
v(f.comment ?? null),
|
|
458
|
+
jsonV(f.metadata)
|
|
459
|
+
].join(", ")})`
|
|
460
|
+
);
|
|
461
|
+
await db.execute(
|
|
462
|
+
`INSERT INTO feedback_events (timestamp, traceId, spanId, experimentId, userId, sourceId, source, feedbackType, value, comment, metadata)
|
|
463
|
+
VALUES ${tuples.join(",\n ")}`
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
async function listFeedback(db, args) {
|
|
467
|
+
const filters = args.filters ?? {};
|
|
468
|
+
const page = Number(args.pagination?.page ?? 0);
|
|
469
|
+
const perPage = Number(args.pagination?.perPage ?? 10);
|
|
470
|
+
const orderBy = { field: args.orderBy?.field ?? "timestamp", direction: args.orderBy?.direction ?? "DESC" };
|
|
471
|
+
const { clause: filterClause, params: filterParams } = buildWhereClause(filters);
|
|
472
|
+
const orderByClause = buildOrderByClause(orderBy);
|
|
473
|
+
const { clause: paginationClause, params: paginationParams } = buildPaginationClause({ page, perPage });
|
|
474
|
+
const countResult = await db.query(
|
|
475
|
+
`SELECT COUNT(*) as total FROM feedback_events ${filterClause}`,
|
|
476
|
+
filterParams
|
|
477
|
+
);
|
|
478
|
+
const total = Number(countResult[0]?.total ?? 0);
|
|
479
|
+
const rows = await db.query(`SELECT * FROM feedback_events ${filterClause} ${orderByClause} ${paginationClause}`, [
|
|
480
|
+
...filterParams,
|
|
481
|
+
...paginationParams
|
|
482
|
+
]);
|
|
483
|
+
const feedback = rows.map((row) => {
|
|
484
|
+
const r = row;
|
|
485
|
+
const rawValue = r.value;
|
|
486
|
+
let value = rawValue;
|
|
487
|
+
const numValue = Number(rawValue);
|
|
488
|
+
if (!isNaN(numValue)) value = numValue;
|
|
489
|
+
return {
|
|
490
|
+
timestamp: toDate(r.timestamp),
|
|
491
|
+
traceId: r.traceId,
|
|
492
|
+
spanId: r.spanId ?? null,
|
|
493
|
+
experimentId: r.experimentId ?? null,
|
|
494
|
+
userId: r.userId ?? null,
|
|
495
|
+
sourceId: r.sourceId ?? null,
|
|
496
|
+
source: r.source,
|
|
497
|
+
feedbackType: r.feedbackType,
|
|
498
|
+
value,
|
|
499
|
+
comment: r.comment ?? null,
|
|
500
|
+
metadata: parseJson(r.metadata)
|
|
501
|
+
};
|
|
502
|
+
});
|
|
503
|
+
return {
|
|
504
|
+
pagination: { total, page, perPage, hasMore: (page + 1) * perPage < total },
|
|
505
|
+
feedback
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// src/storage/domains/observability/logs.ts
|
|
510
|
+
var COLUMNS = [
|
|
511
|
+
"timestamp",
|
|
512
|
+
"level",
|
|
513
|
+
"message",
|
|
514
|
+
"data",
|
|
515
|
+
"traceId",
|
|
516
|
+
"spanId",
|
|
517
|
+
"entityType",
|
|
518
|
+
"entityId",
|
|
519
|
+
"entityName",
|
|
520
|
+
"parentEntityType",
|
|
521
|
+
"parentEntityId",
|
|
522
|
+
"parentEntityName",
|
|
523
|
+
"rootEntityType",
|
|
524
|
+
"rootEntityId",
|
|
525
|
+
"rootEntityName",
|
|
526
|
+
"userId",
|
|
527
|
+
"organizationId",
|
|
528
|
+
"resourceId",
|
|
529
|
+
"runId",
|
|
530
|
+
"sessionId",
|
|
531
|
+
"threadId",
|
|
532
|
+
"requestId",
|
|
533
|
+
"environment",
|
|
534
|
+
"source",
|
|
535
|
+
"serviceName",
|
|
536
|
+
"experimentId",
|
|
537
|
+
"tags",
|
|
538
|
+
"metadata",
|
|
539
|
+
"scope"
|
|
540
|
+
];
|
|
541
|
+
var COLUMNS_SQL = COLUMNS.join(", ");
|
|
542
|
+
function rowToLogRecord(row) {
|
|
543
|
+
return {
|
|
544
|
+
timestamp: toDate(row.timestamp),
|
|
545
|
+
level: row.level,
|
|
546
|
+
message: row.message,
|
|
547
|
+
data: parseJson(row.data),
|
|
548
|
+
traceId: row.traceId ?? null,
|
|
549
|
+
spanId: row.spanId ?? null,
|
|
550
|
+
entityType: row.entityType ?? null,
|
|
551
|
+
entityId: row.entityId ?? null,
|
|
552
|
+
entityName: row.entityName ?? null,
|
|
553
|
+
parentEntityType: row.parentEntityType ?? null,
|
|
554
|
+
parentEntityId: row.parentEntityId ?? null,
|
|
555
|
+
parentEntityName: row.parentEntityName ?? null,
|
|
556
|
+
rootEntityType: row.rootEntityType ?? null,
|
|
557
|
+
rootEntityId: row.rootEntityId ?? null,
|
|
558
|
+
rootEntityName: row.rootEntityName ?? null,
|
|
559
|
+
userId: row.userId ?? null,
|
|
560
|
+
organizationId: row.organizationId ?? null,
|
|
561
|
+
resourceId: row.resourceId ?? null,
|
|
562
|
+
runId: row.runId ?? null,
|
|
563
|
+
sessionId: row.sessionId ?? null,
|
|
564
|
+
threadId: row.threadId ?? null,
|
|
565
|
+
requestId: row.requestId ?? null,
|
|
566
|
+
environment: row.environment ?? null,
|
|
567
|
+
source: row.source ?? null,
|
|
568
|
+
serviceName: row.serviceName ?? null,
|
|
569
|
+
experimentId: row.experimentId ?? null,
|
|
570
|
+
tags: parseJsonArray(row.tags),
|
|
571
|
+
metadata: parseJson(row.metadata),
|
|
572
|
+
scope: parseJson(row.scope)
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
async function batchCreateLogs(db, args) {
|
|
576
|
+
if (args.logs.length === 0) return;
|
|
577
|
+
const tuples = args.logs.map((log) => {
|
|
578
|
+
return `(${[
|
|
579
|
+
v(log.timestamp),
|
|
580
|
+
v(log.level),
|
|
581
|
+
v(log.message),
|
|
582
|
+
jsonV(log.data),
|
|
583
|
+
v(log.traceId ?? null),
|
|
584
|
+
v(log.spanId ?? null),
|
|
585
|
+
v(log.entityType ?? null),
|
|
586
|
+
v(log.entityId ?? null),
|
|
587
|
+
v(log.entityName ?? null),
|
|
588
|
+
v(log.parentEntityType ?? null),
|
|
589
|
+
v(log.parentEntityId ?? null),
|
|
590
|
+
v(log.parentEntityName ?? null),
|
|
591
|
+
v(log.rootEntityType ?? null),
|
|
592
|
+
v(log.rootEntityId ?? null),
|
|
593
|
+
v(log.rootEntityName ?? null),
|
|
594
|
+
v(log.userId ?? null),
|
|
595
|
+
v(log.organizationId ?? null),
|
|
596
|
+
v(log.resourceId ?? null),
|
|
597
|
+
v(log.runId ?? null),
|
|
598
|
+
v(log.sessionId ?? null),
|
|
599
|
+
v(log.threadId ?? null),
|
|
600
|
+
v(log.requestId ?? null),
|
|
601
|
+
v(log.environment ?? null),
|
|
602
|
+
v(log.source ?? null),
|
|
603
|
+
v(log.serviceName ?? null),
|
|
604
|
+
v(log.experimentId ?? null),
|
|
605
|
+
jsonV(log.tags),
|
|
606
|
+
jsonV(log.metadata),
|
|
607
|
+
jsonV(log.scope)
|
|
608
|
+
].join(", ")})`;
|
|
609
|
+
});
|
|
610
|
+
await db.execute(`INSERT INTO log_events (${COLUMNS_SQL}) VALUES ${tuples.join(",\n")}`);
|
|
611
|
+
}
|
|
612
|
+
async function listLogs(db, args) {
|
|
613
|
+
const filters = args.filters ?? {};
|
|
614
|
+
const page = Number(args.pagination?.page ?? 0);
|
|
615
|
+
const perPage = Number(args.pagination?.perPage ?? 10);
|
|
616
|
+
const orderBy = { field: args.orderBy?.field ?? "timestamp", direction: args.orderBy?.direction ?? "DESC" };
|
|
617
|
+
const { clause: filterClause, params: filterParams } = buildWhereClause(filters);
|
|
618
|
+
const orderByClause = buildOrderByClause(orderBy);
|
|
619
|
+
const { clause: paginationClause, params: paginationParams } = buildPaginationClause({ page, perPage });
|
|
620
|
+
const countResult = await db.query(
|
|
621
|
+
`SELECT COUNT(*) as total FROM log_events ${filterClause}`,
|
|
622
|
+
filterParams
|
|
623
|
+
);
|
|
624
|
+
const total = Number(countResult[0]?.total ?? 0);
|
|
625
|
+
const rows = await db.query(`SELECT * FROM log_events ${filterClause} ${orderByClause} ${paginationClause}`, [
|
|
626
|
+
...filterParams,
|
|
627
|
+
...paginationParams
|
|
628
|
+
]);
|
|
629
|
+
const logs = rows.map((row) => rowToLogRecord(row));
|
|
630
|
+
return {
|
|
631
|
+
pagination: { total, page, perPage, hasMore: (page + 1) * perPage < total },
|
|
632
|
+
logs
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
function getAggregationSql(aggregation, measure = "value") {
|
|
636
|
+
switch (aggregation) {
|
|
637
|
+
case "sum":
|
|
638
|
+
return `SUM(${measure})`;
|
|
639
|
+
case "avg":
|
|
640
|
+
return `AVG(${measure})`;
|
|
641
|
+
case "min":
|
|
642
|
+
return `MIN(${measure})`;
|
|
643
|
+
case "max":
|
|
644
|
+
return `MAX(${measure})`;
|
|
645
|
+
case "count":
|
|
646
|
+
return `CAST(COUNT(${measure}) AS DOUBLE)`;
|
|
647
|
+
case "last":
|
|
648
|
+
return `arg_max(${measure}, timestamp)`;
|
|
649
|
+
default:
|
|
650
|
+
return `SUM(${measure})`;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
function getIntervalSql(interval) {
|
|
654
|
+
switch (interval) {
|
|
655
|
+
case "1m":
|
|
656
|
+
return "1 minute";
|
|
657
|
+
case "5m":
|
|
658
|
+
return "5 minutes";
|
|
659
|
+
case "15m":
|
|
660
|
+
return "15 minutes";
|
|
661
|
+
case "1h":
|
|
662
|
+
return "1 hour";
|
|
663
|
+
case "1d":
|
|
664
|
+
return "1 day";
|
|
665
|
+
default:
|
|
666
|
+
return "1 hour";
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
function buildMetricNameFilter(name) {
|
|
670
|
+
if (Array.isArray(name)) {
|
|
671
|
+
const placeholders = name.map(() => "?").join(", ");
|
|
672
|
+
return { clause: `name IN (${placeholders})`, params: name };
|
|
673
|
+
}
|
|
674
|
+
return { clause: `name = ?`, params: [name] };
|
|
675
|
+
}
|
|
676
|
+
var METRIC_COLUMNS = [
|
|
677
|
+
"timestamp",
|
|
678
|
+
"name",
|
|
679
|
+
"value",
|
|
680
|
+
"traceId",
|
|
681
|
+
"spanId",
|
|
682
|
+
"entityType",
|
|
683
|
+
"entityId",
|
|
684
|
+
"entityName",
|
|
685
|
+
"parentEntityType",
|
|
686
|
+
"parentEntityId",
|
|
687
|
+
"parentEntityName",
|
|
688
|
+
"rootEntityType",
|
|
689
|
+
"rootEntityId",
|
|
690
|
+
"rootEntityName",
|
|
691
|
+
"userId",
|
|
692
|
+
"organizationId",
|
|
693
|
+
"resourceId",
|
|
694
|
+
"runId",
|
|
695
|
+
"sessionId",
|
|
696
|
+
"threadId",
|
|
697
|
+
"requestId",
|
|
698
|
+
"environment",
|
|
699
|
+
"source",
|
|
700
|
+
"serviceName",
|
|
701
|
+
"experimentId",
|
|
702
|
+
"provider",
|
|
703
|
+
"model",
|
|
704
|
+
"estimatedCost",
|
|
705
|
+
"costUnit",
|
|
706
|
+
"tags",
|
|
707
|
+
"labels",
|
|
708
|
+
"costMetadata",
|
|
709
|
+
"metadata",
|
|
710
|
+
"scope"
|
|
711
|
+
];
|
|
712
|
+
var METRIC_COLUMNS_SQL = METRIC_COLUMNS.join(", ");
|
|
713
|
+
var METRIC_COLUMN_SET = new Set(METRIC_COLUMNS);
|
|
714
|
+
var METRIC_LABEL_ONLY_GROUP_BY_EXCLUDED = /* @__PURE__ */ new Set(["metadata", "scope", "costMetadata", "tags"]);
|
|
715
|
+
function buildGroupByAlias(index) {
|
|
716
|
+
return `group_by_${index}`;
|
|
717
|
+
}
|
|
718
|
+
function toSeriesDisplayValue(value) {
|
|
719
|
+
return value === null || value === void 0 ? "" : String(value);
|
|
720
|
+
}
|
|
721
|
+
function getCostSummarySelect(prefix = "") {
|
|
722
|
+
const ref = (column) => `${prefix}${column}`;
|
|
723
|
+
return [
|
|
724
|
+
`SUM(${ref("estimatedCost")}) FILTER (WHERE ${ref("estimatedCost")} IS NOT NULL) AS estimatedCost`,
|
|
725
|
+
`,`,
|
|
726
|
+
`CASE`,
|
|
727
|
+
` WHEN COUNT(DISTINCT ${ref("costUnit")}) FILTER (WHERE ${ref("costUnit")} IS NOT NULL) = 1`,
|
|
728
|
+
` THEN MIN(${ref("costUnit")}) FILTER (WHERE ${ref("costUnit")} IS NOT NULL)`,
|
|
729
|
+
` ELSE NULL`,
|
|
730
|
+
`END AS costUnit`
|
|
731
|
+
].join(" ");
|
|
732
|
+
}
|
|
733
|
+
function normalizeCostSummaryRow(row) {
|
|
734
|
+
return {
|
|
735
|
+
estimatedCost: row.estimatedCost === null || row.estimatedCost === void 0 ? null : Number(row.estimatedCost),
|
|
736
|
+
costUnit: row.costUnit === null || row.costUnit === void 0 ? null : String(row.costUnit)
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
function buildCombinedWhereClause(nameClause, nameParams, filterClause, filterParams) {
|
|
740
|
+
const conditions = [nameClause];
|
|
741
|
+
const params = [...nameParams];
|
|
742
|
+
if (filterClause) {
|
|
743
|
+
conditions.push(filterClause.replace("WHERE ", ""));
|
|
744
|
+
params.push(...filterParams);
|
|
745
|
+
}
|
|
746
|
+
return { clause: `WHERE ${conditions.join(" AND ")}`, params };
|
|
747
|
+
}
|
|
748
|
+
function resolveGroupBy(groupBy) {
|
|
749
|
+
return groupBy.map((key, index) => {
|
|
750
|
+
if (METRIC_COLUMN_SET.has(key)) {
|
|
751
|
+
const parsed = utils.parseFieldKey(key);
|
|
752
|
+
if (METRIC_LABEL_ONLY_GROUP_BY_EXCLUDED.has(parsed)) {
|
|
753
|
+
throw new Error(`Invalid groupBy column(s): ${key}`);
|
|
754
|
+
}
|
|
755
|
+
return {
|
|
756
|
+
kind: "column",
|
|
757
|
+
key,
|
|
758
|
+
selectSql: `${parsed} AS "${key}"`,
|
|
759
|
+
groupSql: parsed,
|
|
760
|
+
resultKey: key
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
const labelPath = buildJsonPath(key).replace(/'/g, "''");
|
|
764
|
+
const labelExpr = `json_extract_string(labels, '${labelPath}')`;
|
|
765
|
+
const alias = buildGroupByAlias(index);
|
|
766
|
+
return {
|
|
767
|
+
kind: "label",
|
|
768
|
+
key,
|
|
769
|
+
selectSql: `${labelExpr} AS ${alias}`,
|
|
770
|
+
groupSql: alias,
|
|
771
|
+
resultKey: alias
|
|
772
|
+
};
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
function rowToMetricRecord(row) {
|
|
776
|
+
return {
|
|
777
|
+
timestamp: toDate(row.timestamp),
|
|
778
|
+
name: row.name,
|
|
779
|
+
value: Number(row.value),
|
|
780
|
+
traceId: row.traceId ?? null,
|
|
781
|
+
spanId: row.spanId ?? null,
|
|
782
|
+
entityType: row.entityType ?? null,
|
|
783
|
+
entityId: row.entityId ?? null,
|
|
784
|
+
entityName: row.entityName ?? null,
|
|
785
|
+
parentEntityType: row.parentEntityType ?? null,
|
|
786
|
+
parentEntityId: row.parentEntityId ?? null,
|
|
787
|
+
parentEntityName: row.parentEntityName ?? null,
|
|
788
|
+
rootEntityType: row.rootEntityType ?? null,
|
|
789
|
+
rootEntityId: row.rootEntityId ?? null,
|
|
790
|
+
rootEntityName: row.rootEntityName ?? null,
|
|
791
|
+
userId: row.userId ?? null,
|
|
792
|
+
organizationId: row.organizationId ?? null,
|
|
793
|
+
resourceId: row.resourceId ?? null,
|
|
794
|
+
runId: row.runId ?? null,
|
|
795
|
+
sessionId: row.sessionId ?? null,
|
|
796
|
+
threadId: row.threadId ?? null,
|
|
797
|
+
requestId: row.requestId ?? null,
|
|
798
|
+
environment: row.environment ?? null,
|
|
799
|
+
source: row.source ?? null,
|
|
800
|
+
serviceName: row.serviceName ?? null,
|
|
801
|
+
experimentId: row.experimentId ?? null,
|
|
802
|
+
provider: row.provider ?? null,
|
|
803
|
+
model: row.model ?? null,
|
|
804
|
+
estimatedCost: row.estimatedCost === null || row.estimatedCost === void 0 ? null : Number(row.estimatedCost),
|
|
805
|
+
costUnit: row.costUnit ?? null,
|
|
806
|
+
costMetadata: parseJson(row.costMetadata),
|
|
807
|
+
tags: parseJsonArray(row.tags),
|
|
808
|
+
labels: parseJson(row.labels) ?? {},
|
|
809
|
+
metadata: parseJson(row.metadata),
|
|
810
|
+
scope: parseJson(row.scope)
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
async function batchCreateMetrics(db, args) {
|
|
814
|
+
if (args.metrics.length === 0) return;
|
|
815
|
+
const tuples = args.metrics.map((m) => {
|
|
816
|
+
return `(${[
|
|
817
|
+
v(m.timestamp),
|
|
818
|
+
v(m.name),
|
|
819
|
+
v(m.value),
|
|
820
|
+
v(m.traceId ?? null),
|
|
821
|
+
v(m.spanId ?? null),
|
|
822
|
+
v(m.entityType ?? null),
|
|
823
|
+
v(m.entityId ?? null),
|
|
824
|
+
v(m.entityName ?? null),
|
|
825
|
+
v(m.parentEntityType ?? null),
|
|
826
|
+
v(m.parentEntityId ?? null),
|
|
827
|
+
v(m.parentEntityName ?? null),
|
|
828
|
+
v(m.rootEntityType ?? null),
|
|
829
|
+
v(m.rootEntityId ?? null),
|
|
830
|
+
v(m.rootEntityName ?? null),
|
|
831
|
+
v(m.userId ?? null),
|
|
832
|
+
v(m.organizationId ?? null),
|
|
833
|
+
v(m.resourceId ?? null),
|
|
834
|
+
v(m.runId ?? null),
|
|
835
|
+
v(m.sessionId ?? null),
|
|
836
|
+
v(m.threadId ?? null),
|
|
837
|
+
v(m.requestId ?? null),
|
|
838
|
+
v(m.environment ?? null),
|
|
839
|
+
v(m.source ?? null),
|
|
840
|
+
v(m.serviceName ?? null),
|
|
841
|
+
v(m.experimentId ?? null),
|
|
842
|
+
v(m.provider ?? null),
|
|
843
|
+
v(m.model ?? null),
|
|
844
|
+
v(m.estimatedCost ?? null),
|
|
845
|
+
v(m.costUnit ?? null),
|
|
846
|
+
jsonV(m.tags ?? null),
|
|
847
|
+
v(JSON.stringify(m.labels ?? {})),
|
|
848
|
+
jsonV(m.costMetadata ?? null),
|
|
849
|
+
jsonV(m.metadata ?? null),
|
|
850
|
+
jsonV(m.scope ?? null)
|
|
851
|
+
].join(", ")})`;
|
|
852
|
+
});
|
|
853
|
+
await db.execute(`INSERT INTO metric_events (${METRIC_COLUMNS_SQL}) VALUES ${tuples.join(",\n")}`);
|
|
854
|
+
}
|
|
855
|
+
async function listMetrics(db, args) {
|
|
856
|
+
const filters = args.filters ?? {};
|
|
857
|
+
const page = Number(args.pagination?.page ?? 0);
|
|
858
|
+
const perPage = Number(args.pagination?.perPage ?? 10);
|
|
859
|
+
const orderBy = { field: args.orderBy?.field ?? "timestamp", direction: args.orderBy?.direction ?? "DESC" };
|
|
860
|
+
const { clause: filterClause, params: filterParams } = buildWhereClause(filters);
|
|
861
|
+
const orderByClause = buildOrderByClause(orderBy);
|
|
862
|
+
const { clause: paginationClause, params: paginationParams } = buildPaginationClause({ page, perPage });
|
|
863
|
+
const countResult = await db.query(
|
|
864
|
+
`SELECT COUNT(*) AS total FROM metric_events ${filterClause}`,
|
|
865
|
+
filterParams
|
|
866
|
+
);
|
|
867
|
+
const total = Number(countResult[0]?.total ?? 0);
|
|
868
|
+
const rows = await db.query(
|
|
869
|
+
`SELECT * FROM metric_events ${filterClause} ${orderByClause} ${paginationClause}`,
|
|
870
|
+
[...filterParams, ...paginationParams]
|
|
871
|
+
);
|
|
872
|
+
return {
|
|
873
|
+
pagination: { total, page, perPage, hasMore: (page + 1) * perPage < total },
|
|
874
|
+
metrics: rows.map((row) => rowToMetricRecord(row))
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
async function getMetricAggregate(db, args) {
|
|
878
|
+
const aggSql = getAggregationSql(args.aggregation);
|
|
879
|
+
const { clause: nameClause, params: nameParams } = buildMetricNameFilter(args.name);
|
|
880
|
+
const { clause: filterClause, params: filterParams } = buildWhereClause(
|
|
881
|
+
args.filters
|
|
882
|
+
);
|
|
883
|
+
const { clause: whereClause, params: allParams } = buildCombinedWhereClause(
|
|
884
|
+
nameClause,
|
|
885
|
+
nameParams,
|
|
886
|
+
filterClause,
|
|
887
|
+
filterParams
|
|
888
|
+
);
|
|
889
|
+
const sql = `SELECT ${aggSql} AS value, ${getCostSummarySelect()} FROM metric_events ${whereClause}`;
|
|
890
|
+
const result = await db.query(sql, allParams);
|
|
891
|
+
const row = result[0] ?? {};
|
|
892
|
+
const value = row.value === null || row.value === void 0 ? null : Number(row.value);
|
|
893
|
+
const costSummary = normalizeCostSummaryRow(row);
|
|
894
|
+
if (args.comparePeriod && args.filters?.timestamp) {
|
|
895
|
+
const ts = args.filters.timestamp;
|
|
896
|
+
if (ts.start && ts.end) {
|
|
897
|
+
const duration = ts.end.getTime() - ts.start.getTime();
|
|
898
|
+
let prevStart;
|
|
899
|
+
let prevEnd;
|
|
900
|
+
switch (args.comparePeriod) {
|
|
901
|
+
case "previous_period":
|
|
902
|
+
prevStart = new Date(ts.start.getTime() - duration);
|
|
903
|
+
prevEnd = new Date(ts.end.getTime() - duration);
|
|
904
|
+
break;
|
|
905
|
+
case "previous_day":
|
|
906
|
+
prevStart = new Date(ts.start.getTime() - 864e5);
|
|
907
|
+
prevEnd = new Date(ts.end.getTime() - 864e5);
|
|
908
|
+
break;
|
|
909
|
+
case "previous_week":
|
|
910
|
+
prevStart = new Date(ts.start.getTime() - 6048e5);
|
|
911
|
+
prevEnd = new Date(ts.end.getTime() - 6048e5);
|
|
912
|
+
break;
|
|
913
|
+
default:
|
|
914
|
+
prevStart = new Date(ts.start.getTime() - duration);
|
|
915
|
+
prevEnd = new Date(ts.end.getTime() - duration);
|
|
916
|
+
}
|
|
917
|
+
const prevFilters = {
|
|
918
|
+
...args.filters ?? {},
|
|
919
|
+
timestamp: {
|
|
920
|
+
start: prevStart,
|
|
921
|
+
end: prevEnd,
|
|
922
|
+
startExclusive: ts.startExclusive,
|
|
923
|
+
endExclusive: ts.endExclusive
|
|
924
|
+
}
|
|
925
|
+
};
|
|
926
|
+
const { clause: prevFilterClause, params: prevFilterParams } = buildWhereClause(
|
|
927
|
+
prevFilters
|
|
928
|
+
);
|
|
929
|
+
const { clause: prevWhereClause, params: prevParams } = buildCombinedWhereClause(
|
|
930
|
+
nameClause,
|
|
931
|
+
nameParams,
|
|
932
|
+
prevFilterClause,
|
|
933
|
+
prevFilterParams
|
|
934
|
+
);
|
|
935
|
+
const prevSql = `SELECT ${aggSql} AS value, ${getCostSummarySelect()} FROM metric_events ${prevWhereClause}`;
|
|
936
|
+
const prevResult = await db.query(prevSql, prevParams);
|
|
937
|
+
const prevRow = prevResult[0] ?? {};
|
|
938
|
+
const previousValue = prevRow.value === null || prevRow.value === void 0 ? null : Number(prevRow.value);
|
|
939
|
+
const previousCostSummary = normalizeCostSummaryRow(prevRow);
|
|
940
|
+
let changePercent = null;
|
|
941
|
+
if (previousValue !== null && previousValue !== 0 && value !== null) {
|
|
942
|
+
changePercent = (value - previousValue) / Math.abs(previousValue) * 100;
|
|
943
|
+
}
|
|
944
|
+
let costChangePercent = null;
|
|
945
|
+
if (previousCostSummary.estimatedCost !== null && previousCostSummary.estimatedCost !== 0 && costSummary.estimatedCost !== null) {
|
|
946
|
+
costChangePercent = (costSummary.estimatedCost - previousCostSummary.estimatedCost) / Math.abs(previousCostSummary.estimatedCost) * 100;
|
|
947
|
+
}
|
|
948
|
+
return {
|
|
949
|
+
value,
|
|
950
|
+
estimatedCost: costSummary.estimatedCost,
|
|
951
|
+
costUnit: costSummary.costUnit,
|
|
952
|
+
previousValue,
|
|
953
|
+
previousEstimatedCost: previousCostSummary.estimatedCost,
|
|
954
|
+
changePercent,
|
|
955
|
+
costChangePercent
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
return { value, estimatedCost: costSummary.estimatedCost, costUnit: costSummary.costUnit };
|
|
960
|
+
}
|
|
961
|
+
async function getMetricBreakdown(db, args) {
|
|
962
|
+
const aggSql = getAggregationSql(args.aggregation);
|
|
963
|
+
const { clause: nameClause, params: nameParams } = buildMetricNameFilter(args.name);
|
|
964
|
+
const { clause: filterClause, params: filterParams } = buildWhereClause(
|
|
965
|
+
args.filters
|
|
966
|
+
);
|
|
967
|
+
const { clause: whereClause, params: allParams } = buildCombinedWhereClause(
|
|
968
|
+
nameClause,
|
|
969
|
+
nameParams,
|
|
970
|
+
filterClause,
|
|
971
|
+
filterParams
|
|
972
|
+
);
|
|
973
|
+
const resolvedGroupBy = resolveGroupBy(args.groupBy);
|
|
974
|
+
const selectGroupBy = resolvedGroupBy.map((entry) => entry.selectSql).join(", ");
|
|
975
|
+
const groupByCols = resolvedGroupBy.map((entry) => entry.groupSql).join(", ");
|
|
976
|
+
const sql = `SELECT ${selectGroupBy}, ${aggSql} AS value, ${getCostSummarySelect()} FROM metric_events ${whereClause} GROUP BY ${groupByCols} ORDER BY value DESC`;
|
|
977
|
+
const rows = await db.query(sql, allParams);
|
|
978
|
+
const groups = rows.map((row) => {
|
|
979
|
+
const dimensions = {};
|
|
980
|
+
for (const entry of resolvedGroupBy) {
|
|
981
|
+
const value = row[entry.resultKey];
|
|
982
|
+
dimensions[entry.key] = value === null || value === void 0 ? null : String(value);
|
|
983
|
+
}
|
|
984
|
+
const costSummary = normalizeCostSummaryRow(row);
|
|
985
|
+
return {
|
|
986
|
+
dimensions,
|
|
987
|
+
value: Number(row.value ?? 0),
|
|
988
|
+
estimatedCost: costSummary.estimatedCost,
|
|
989
|
+
costUnit: costSummary.costUnit
|
|
990
|
+
};
|
|
991
|
+
});
|
|
992
|
+
return { groups };
|
|
993
|
+
}
|
|
994
|
+
async function getMetricTimeSeries(db, args) {
|
|
995
|
+
const aggSql = getAggregationSql(args.aggregation);
|
|
996
|
+
const intervalSql = getIntervalSql(args.interval);
|
|
997
|
+
const { clause: nameClause, params: nameParams } = buildMetricNameFilter(args.name);
|
|
998
|
+
const { clause: filterClause, params: filterParams } = buildWhereClause(
|
|
999
|
+
args.filters
|
|
1000
|
+
);
|
|
1001
|
+
const { clause: whereClause, params: allParams } = buildCombinedWhereClause(
|
|
1002
|
+
nameClause,
|
|
1003
|
+
nameParams,
|
|
1004
|
+
filterClause,
|
|
1005
|
+
filterParams
|
|
1006
|
+
);
|
|
1007
|
+
if (args.groupBy && args.groupBy.length > 0) {
|
|
1008
|
+
const resolvedGroupBy = resolveGroupBy(args.groupBy);
|
|
1009
|
+
const selectGroupBy = resolvedGroupBy.map((entry) => entry.selectSql).join(", ");
|
|
1010
|
+
const groupByCols = resolvedGroupBy.map((entry) => entry.groupSql).join(", ");
|
|
1011
|
+
const sql2 = `
|
|
1012
|
+
SELECT time_bucket(INTERVAL '${intervalSql}', timestamp) AS bucket,
|
|
1013
|
+
${selectGroupBy},
|
|
1014
|
+
${aggSql} AS value,
|
|
1015
|
+
${getCostSummarySelect()}
|
|
1016
|
+
FROM metric_events ${whereClause}
|
|
1017
|
+
GROUP BY bucket, ${groupByCols}
|
|
1018
|
+
ORDER BY bucket
|
|
1019
|
+
`;
|
|
1020
|
+
const rows2 = await db.query(sql2, allParams);
|
|
1021
|
+
const seriesMap = /* @__PURE__ */ new Map();
|
|
1022
|
+
for (const row of rows2) {
|
|
1023
|
+
const dimensionValues = resolvedGroupBy.map((entry) => row[entry.resultKey]);
|
|
1024
|
+
const seriesKey = JSON.stringify(dimensionValues);
|
|
1025
|
+
const name = dimensionValues.map(toSeriesDisplayValue).join("|");
|
|
1026
|
+
const costSummary = normalizeCostSummaryRow(row);
|
|
1027
|
+
if (!seriesMap.has(seriesKey)) {
|
|
1028
|
+
seriesMap.set(seriesKey, {
|
|
1029
|
+
name,
|
|
1030
|
+
costUnits: /* @__PURE__ */ new Set(),
|
|
1031
|
+
points: []
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
if (costSummary.costUnit) {
|
|
1035
|
+
seriesMap.get(seriesKey).costUnits.add(costSummary.costUnit);
|
|
1036
|
+
}
|
|
1037
|
+
seriesMap.get(seriesKey).points.push({
|
|
1038
|
+
timestamp: row.bucket instanceof Date ? row.bucket : new Date(String(row.bucket)),
|
|
1039
|
+
value: Number(row.value ?? 0),
|
|
1040
|
+
estimatedCost: costSummary.estimatedCost
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
return {
|
|
1044
|
+
series: Array.from(seriesMap.values()).map((series) => ({
|
|
1045
|
+
name: series.name,
|
|
1046
|
+
costUnit: series.costUnits.size === 1 ? Array.from(series.costUnits)[0] : null,
|
|
1047
|
+
points: series.points
|
|
1048
|
+
}))
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
const sql = `
|
|
1052
|
+
SELECT time_bucket(INTERVAL '${intervalSql}', timestamp) AS bucket,
|
|
1053
|
+
${aggSql} AS value,
|
|
1054
|
+
${getCostSummarySelect()}
|
|
1055
|
+
FROM metric_events ${whereClause}
|
|
1056
|
+
GROUP BY bucket
|
|
1057
|
+
ORDER BY bucket
|
|
1058
|
+
`;
|
|
1059
|
+
const rows = await db.query(sql, allParams);
|
|
1060
|
+
const metricName = Array.isArray(args.name) ? args.name.join(",") : args.name;
|
|
1061
|
+
const overallCostUnits = new Set(
|
|
1062
|
+
rows.map((row) => row.costUnit).filter((value) => typeof value === "string")
|
|
1063
|
+
);
|
|
1064
|
+
return {
|
|
1065
|
+
series: [
|
|
1066
|
+
{
|
|
1067
|
+
name: metricName,
|
|
1068
|
+
costUnit: overallCostUnits.size === 1 ? Array.from(overallCostUnits)[0] : null,
|
|
1069
|
+
points: rows.map((row) => {
|
|
1070
|
+
const costSummary = normalizeCostSummaryRow(row);
|
|
1071
|
+
return {
|
|
1072
|
+
timestamp: row.bucket instanceof Date ? row.bucket : new Date(String(row.bucket)),
|
|
1073
|
+
value: Number(row.value ?? 0),
|
|
1074
|
+
estimatedCost: costSummary.estimatedCost
|
|
1075
|
+
};
|
|
1076
|
+
})
|
|
1077
|
+
}
|
|
1078
|
+
]
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
async function getMetricPercentiles(db, args) {
|
|
1082
|
+
const intervalSql = getIntervalSql(args.interval);
|
|
1083
|
+
const { clause: filterClause, params: filterParams } = buildWhereClause(
|
|
1084
|
+
args.filters
|
|
1085
|
+
);
|
|
1086
|
+
const allConditions = [`name = ?`];
|
|
1087
|
+
const allParams = [args.name];
|
|
1088
|
+
if (filterClause) {
|
|
1089
|
+
allConditions.push(filterClause.replace("WHERE ", ""));
|
|
1090
|
+
allParams.push(...filterParams);
|
|
1091
|
+
}
|
|
1092
|
+
const whereClause = `WHERE ${allConditions.join(" AND ")}`;
|
|
1093
|
+
const series = [];
|
|
1094
|
+
for (const p of args.percentiles) {
|
|
1095
|
+
const sql = `
|
|
1096
|
+
SELECT time_bucket(INTERVAL '${intervalSql}', timestamp) AS bucket,
|
|
1097
|
+
percentile_cont(${p}) WITHIN GROUP (ORDER BY value) AS pvalue
|
|
1098
|
+
FROM metric_events ${whereClause}
|
|
1099
|
+
GROUP BY bucket
|
|
1100
|
+
ORDER BY bucket
|
|
1101
|
+
`;
|
|
1102
|
+
const rows = await db.query(sql, allParams);
|
|
1103
|
+
series.push({
|
|
1104
|
+
percentile: p,
|
|
1105
|
+
points: rows.map((row) => ({
|
|
1106
|
+
timestamp: row.bucket instanceof Date ? row.bucket : new Date(String(row.bucket)),
|
|
1107
|
+
value: Number(row.pvalue ?? 0)
|
|
1108
|
+
}))
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
return { series };
|
|
1112
|
+
}
|
|
1113
|
+
async function getMetricNames(db, args) {
|
|
1114
|
+
const conditions = [];
|
|
1115
|
+
const params = [];
|
|
1116
|
+
if (args.prefix) {
|
|
1117
|
+
conditions.push(`name LIKE ?`);
|
|
1118
|
+
params.push(`${args.prefix}%`);
|
|
1119
|
+
}
|
|
1120
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
1121
|
+
const limitClause = args.limit ? `LIMIT ?` : "";
|
|
1122
|
+
if (args.limit) params.push(args.limit);
|
|
1123
|
+
const rows = await db.query(
|
|
1124
|
+
`SELECT DISTINCT name FROM metric_events ${whereClause} ORDER BY name ${limitClause}`,
|
|
1125
|
+
params
|
|
1126
|
+
);
|
|
1127
|
+
return { names: rows.map((r) => r.name) };
|
|
1128
|
+
}
|
|
1129
|
+
async function getMetricLabelKeys(db, args) {
|
|
1130
|
+
const rows = await db.query(
|
|
1131
|
+
`SELECT DISTINCT unnest(json_keys(labels)) AS key FROM metric_events WHERE name = ? AND labels IS NOT NULL`,
|
|
1132
|
+
[args.metricName]
|
|
1133
|
+
);
|
|
1134
|
+
return { keys: rows.map((r) => r.key) };
|
|
1135
|
+
}
|
|
1136
|
+
async function getMetricLabelValues(db, args) {
|
|
1137
|
+
const labelPath = buildJsonPath(args.labelKey);
|
|
1138
|
+
const conditions = [`name = ?`, `json_extract_string(labels, ?) IS NOT NULL`];
|
|
1139
|
+
const params = [args.metricName, labelPath];
|
|
1140
|
+
if (args.prefix) {
|
|
1141
|
+
conditions.push(`json_extract_string(labels, ?) LIKE ?`);
|
|
1142
|
+
params.push(labelPath, `${args.prefix}%`);
|
|
1143
|
+
}
|
|
1144
|
+
const limitClause = args.limit ? `LIMIT ?` : "";
|
|
1145
|
+
if (args.limit) params.push(args.limit);
|
|
1146
|
+
const rows = await db.query(
|
|
1147
|
+
`SELECT DISTINCT json_extract_string(labels, ?) AS val FROM metric_events WHERE ${conditions.join(" AND ")} ORDER BY val ${limitClause}`,
|
|
1148
|
+
[labelPath, ...params]
|
|
1149
|
+
);
|
|
1150
|
+
return { values: rows.map((r) => r.val) };
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
// src/storage/domains/observability/scores.ts
|
|
1154
|
+
async function createScore(db, args) {
|
|
1155
|
+
const s = args.score;
|
|
1156
|
+
await db.execute(
|
|
1157
|
+
`INSERT INTO score_events (timestamp, traceId, spanId, scorerId, scorerVersion, source, score, reason, experimentId, scoreTraceId, metadata)
|
|
1158
|
+
VALUES (${[
|
|
1159
|
+
v(s.timestamp),
|
|
1160
|
+
v(s.traceId),
|
|
1161
|
+
v(s.spanId ?? null),
|
|
1162
|
+
v(s.scorerId),
|
|
1163
|
+
v(s.scorerVersion ?? null),
|
|
1164
|
+
v(s.source ?? null),
|
|
1165
|
+
v(s.score),
|
|
1166
|
+
v(s.reason ?? null),
|
|
1167
|
+
v(s.experimentId ?? null),
|
|
1168
|
+
v(s.scoreTraceId ?? null),
|
|
1169
|
+
jsonV(s.metadata)
|
|
1170
|
+
].join(", ")})`
|
|
1171
|
+
);
|
|
1172
|
+
}
|
|
1173
|
+
async function batchCreateScores(db, args) {
|
|
1174
|
+
if (args.scores.length === 0) return;
|
|
1175
|
+
const tuples = args.scores.map(
|
|
1176
|
+
(s) => `(${[
|
|
1177
|
+
v(s.timestamp),
|
|
1178
|
+
v(s.traceId),
|
|
1179
|
+
v(s.spanId ?? null),
|
|
1180
|
+
v(s.scorerId),
|
|
1181
|
+
v(s.scorerVersion ?? null),
|
|
1182
|
+
v(s.source ?? null),
|
|
1183
|
+
v(s.score),
|
|
1184
|
+
v(s.reason ?? null),
|
|
1185
|
+
v(s.experimentId ?? null),
|
|
1186
|
+
v(s.scoreTraceId ?? null),
|
|
1187
|
+
jsonV(s.metadata)
|
|
1188
|
+
].join(", ")})`
|
|
1189
|
+
);
|
|
1190
|
+
await db.execute(
|
|
1191
|
+
`INSERT INTO score_events (timestamp, traceId, spanId, scorerId, scorerVersion, source, score, reason, experimentId, scoreTraceId, metadata)
|
|
1192
|
+
VALUES ${tuples.join(",\n ")}`
|
|
1193
|
+
);
|
|
1194
|
+
}
|
|
1195
|
+
async function listScores(db, args) {
|
|
1196
|
+
const filters = args.filters ?? {};
|
|
1197
|
+
const page = Number(args.pagination?.page ?? 0);
|
|
1198
|
+
const perPage = Number(args.pagination?.perPage ?? 10);
|
|
1199
|
+
const orderBy = { field: args.orderBy?.field ?? "timestamp", direction: args.orderBy?.direction ?? "DESC" };
|
|
1200
|
+
const { clause: filterClause, params: filterParams } = buildWhereClause(filters);
|
|
1201
|
+
const orderByClause = buildOrderByClause(orderBy);
|
|
1202
|
+
const { clause: paginationClause, params: paginationParams } = buildPaginationClause({ page, perPage });
|
|
1203
|
+
const countResult = await db.query(
|
|
1204
|
+
`SELECT COUNT(*) as total FROM score_events ${filterClause}`,
|
|
1205
|
+
filterParams
|
|
1206
|
+
);
|
|
1207
|
+
const total = Number(countResult[0]?.total ?? 0);
|
|
1208
|
+
const rows = await db.query(`SELECT * FROM score_events ${filterClause} ${orderByClause} ${paginationClause}`, [
|
|
1209
|
+
...filterParams,
|
|
1210
|
+
...paginationParams
|
|
1211
|
+
]);
|
|
1212
|
+
const scores = rows.map((row) => {
|
|
1213
|
+
const r = row;
|
|
1214
|
+
return {
|
|
1215
|
+
timestamp: toDate(r.timestamp),
|
|
1216
|
+
traceId: r.traceId,
|
|
1217
|
+
spanId: r.spanId ?? null,
|
|
1218
|
+
scorerId: r.scorerId,
|
|
1219
|
+
scorerVersion: r.scorerVersion ?? null,
|
|
1220
|
+
source: r.source ?? null,
|
|
1221
|
+
score: Number(r.score),
|
|
1222
|
+
reason: r.reason ?? null,
|
|
1223
|
+
experimentId: r.experimentId ?? null,
|
|
1224
|
+
scoreTraceId: r.scoreTraceId ?? null,
|
|
1225
|
+
metadata: parseJson(r.metadata)
|
|
1226
|
+
};
|
|
1227
|
+
});
|
|
1228
|
+
return {
|
|
1229
|
+
pagination: { total, page, perPage, hasMore: (page + 1) * perPage < total },
|
|
1230
|
+
scores
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
var COLUMNS2 = [
|
|
1234
|
+
"eventType",
|
|
1235
|
+
"timestamp",
|
|
1236
|
+
"traceId",
|
|
1237
|
+
"spanId",
|
|
1238
|
+
"parentSpanId",
|
|
1239
|
+
"name",
|
|
1240
|
+
"spanType",
|
|
1241
|
+
"isEvent",
|
|
1242
|
+
"endedAt",
|
|
1243
|
+
"experimentId",
|
|
1244
|
+
"entityType",
|
|
1245
|
+
"entityId",
|
|
1246
|
+
"entityName",
|
|
1247
|
+
"userId",
|
|
1248
|
+
"organizationId",
|
|
1249
|
+
"resourceId",
|
|
1250
|
+
"runId",
|
|
1251
|
+
"sessionId",
|
|
1252
|
+
"threadId",
|
|
1253
|
+
"requestId",
|
|
1254
|
+
"environment",
|
|
1255
|
+
"source",
|
|
1256
|
+
"serviceName",
|
|
1257
|
+
"attributes",
|
|
1258
|
+
"metadata",
|
|
1259
|
+
"tags",
|
|
1260
|
+
"scope",
|
|
1261
|
+
"links",
|
|
1262
|
+
"input",
|
|
1263
|
+
"output",
|
|
1264
|
+
"error",
|
|
1265
|
+
"requestContext"
|
|
1266
|
+
];
|
|
1267
|
+
var COLUMNS_SQL2 = COLUMNS2.join(", ");
|
|
1268
|
+
function argMaxNonNull(col) {
|
|
1269
|
+
return `arg_max(${col}, timestamp) FILTER (WHERE ${col} IS NOT NULL) as ${col}`;
|
|
1270
|
+
}
|
|
1271
|
+
var SPAN_RECONSTRUCT_SELECT = `
|
|
1272
|
+
SELECT
|
|
1273
|
+
traceId, spanId,
|
|
1274
|
+
${argMaxNonNull("name")},
|
|
1275
|
+
${argMaxNonNull("spanType")},
|
|
1276
|
+
${argMaxNonNull("parentSpanId")},
|
|
1277
|
+
${argMaxNonNull("isEvent")},
|
|
1278
|
+
coalesce(min(timestamp) FILTER (WHERE eventType = 'start'), min(timestamp)) as startedAt,
|
|
1279
|
+
${argMaxNonNull("endedAt")},
|
|
1280
|
+
${argMaxNonNull("experimentId")},
|
|
1281
|
+
${argMaxNonNull("entityType")},
|
|
1282
|
+
${argMaxNonNull("entityId")},
|
|
1283
|
+
${argMaxNonNull("entityName")},
|
|
1284
|
+
${argMaxNonNull("userId")},
|
|
1285
|
+
${argMaxNonNull("organizationId")},
|
|
1286
|
+
${argMaxNonNull("resourceId")},
|
|
1287
|
+
${argMaxNonNull("runId")},
|
|
1288
|
+
${argMaxNonNull("sessionId")},
|
|
1289
|
+
${argMaxNonNull("threadId")},
|
|
1290
|
+
${argMaxNonNull("requestId")},
|
|
1291
|
+
${argMaxNonNull("environment")},
|
|
1292
|
+
${argMaxNonNull("source")},
|
|
1293
|
+
${argMaxNonNull("serviceName")},
|
|
1294
|
+
${argMaxNonNull("attributes")},
|
|
1295
|
+
${argMaxNonNull("metadata")},
|
|
1296
|
+
${argMaxNonNull("tags")},
|
|
1297
|
+
${argMaxNonNull("scope")},
|
|
1298
|
+
${argMaxNonNull("links")},
|
|
1299
|
+
${argMaxNonNull("input")},
|
|
1300
|
+
${argMaxNonNull("output")},
|
|
1301
|
+
${argMaxNonNull("error")},
|
|
1302
|
+
${argMaxNonNull("requestContext")}
|
|
1303
|
+
FROM span_events
|
|
1304
|
+
`;
|
|
1305
|
+
function rowToSpanRecord(row) {
|
|
1306
|
+
return {
|
|
1307
|
+
traceId: row.traceId,
|
|
1308
|
+
spanId: row.spanId,
|
|
1309
|
+
name: row.name,
|
|
1310
|
+
spanType: row.spanType,
|
|
1311
|
+
parentSpanId: row.parentSpanId ?? null,
|
|
1312
|
+
isEvent: row.isEvent,
|
|
1313
|
+
startedAt: toDate(row.startedAt),
|
|
1314
|
+
endedAt: toDateOrNull(row.endedAt),
|
|
1315
|
+
experimentId: row.experimentId ?? null,
|
|
1316
|
+
entityType: row.entityType ?? null,
|
|
1317
|
+
entityId: row.entityId ?? null,
|
|
1318
|
+
entityName: row.entityName ?? null,
|
|
1319
|
+
userId: row.userId ?? null,
|
|
1320
|
+
organizationId: row.organizationId ?? null,
|
|
1321
|
+
resourceId: row.resourceId ?? null,
|
|
1322
|
+
runId: row.runId ?? null,
|
|
1323
|
+
sessionId: row.sessionId ?? null,
|
|
1324
|
+
threadId: row.threadId ?? null,
|
|
1325
|
+
requestId: row.requestId ?? null,
|
|
1326
|
+
environment: row.environment ?? null,
|
|
1327
|
+
source: row.source ?? null,
|
|
1328
|
+
serviceName: row.serviceName ?? null,
|
|
1329
|
+
attributes: parseJson(row.attributes),
|
|
1330
|
+
metadata: parseJson(row.metadata),
|
|
1331
|
+
tags: parseJsonArray(row.tags),
|
|
1332
|
+
scope: parseJson(row.scope),
|
|
1333
|
+
links: parseJsonArray(row.links),
|
|
1334
|
+
input: parseJson(row.input),
|
|
1335
|
+
output: parseJson(row.output),
|
|
1336
|
+
error: parseJson(row.error),
|
|
1337
|
+
requestContext: parseJson(row.requestContext),
|
|
1338
|
+
createdAt: toDate(row.startedAt),
|
|
1339
|
+
updatedAt: null
|
|
1340
|
+
};
|
|
1341
|
+
}
|
|
1342
|
+
function buildHasChildErrorClause(hasChildError) {
|
|
1343
|
+
if (hasChildError === void 0) return "";
|
|
1344
|
+
const base = `SELECT 1 FROM reconstructed_spans c WHERE c.traceId = root_spans.traceId AND c.spanId != root_spans.spanId AND c.error IS NOT NULL`;
|
|
1345
|
+
return hasChildError ? `EXISTS (${base})` : `NOT EXISTS (${base})`;
|
|
1346
|
+
}
|
|
1347
|
+
function toValuesTuple(row) {
|
|
1348
|
+
return [
|
|
1349
|
+
v(row.eventType),
|
|
1350
|
+
v(row.timestamp),
|
|
1351
|
+
v(row.traceId),
|
|
1352
|
+
v(row.spanId),
|
|
1353
|
+
v(row.parentSpanId),
|
|
1354
|
+
v(row.name),
|
|
1355
|
+
v(row.spanType),
|
|
1356
|
+
v(row.isEvent),
|
|
1357
|
+
v(row.endedAt),
|
|
1358
|
+
v(row.experimentId),
|
|
1359
|
+
v(row.entityType),
|
|
1360
|
+
v(row.entityId),
|
|
1361
|
+
v(row.entityName),
|
|
1362
|
+
v(row.userId),
|
|
1363
|
+
v(row.organizationId),
|
|
1364
|
+
v(row.resourceId),
|
|
1365
|
+
v(row.runId),
|
|
1366
|
+
v(row.sessionId),
|
|
1367
|
+
v(row.threadId),
|
|
1368
|
+
v(row.requestId),
|
|
1369
|
+
v(row.environment),
|
|
1370
|
+
v(row.source),
|
|
1371
|
+
v(row.serviceName),
|
|
1372
|
+
jsonV(row.attributes),
|
|
1373
|
+
jsonV(row.metadata),
|
|
1374
|
+
jsonV(row.tags),
|
|
1375
|
+
jsonV(row.scope),
|
|
1376
|
+
jsonV(row.links),
|
|
1377
|
+
jsonV(row.input),
|
|
1378
|
+
jsonV(row.output),
|
|
1379
|
+
jsonV(row.error),
|
|
1380
|
+
jsonV(row.requestContext)
|
|
1381
|
+
].join(", ");
|
|
1382
|
+
}
|
|
1383
|
+
async function insertSpanEvents(db, rows) {
|
|
1384
|
+
if (rows.length === 0) return;
|
|
1385
|
+
const tuples = rows.map((row) => `(${toValuesTuple(row)})`).join(",\n");
|
|
1386
|
+
await db.execute(`INSERT INTO span_events (${COLUMNS_SQL2}) VALUES ${tuples}`);
|
|
1387
|
+
}
|
|
1388
|
+
function createStartSpanRow(s) {
|
|
1389
|
+
return {
|
|
1390
|
+
eventType: "start",
|
|
1391
|
+
timestamp: s.startedAt,
|
|
1392
|
+
traceId: s.traceId,
|
|
1393
|
+
spanId: s.spanId,
|
|
1394
|
+
parentSpanId: s.parentSpanId ?? null,
|
|
1395
|
+
name: s.name,
|
|
1396
|
+
spanType: s.spanType,
|
|
1397
|
+
isEvent: s.isEvent,
|
|
1398
|
+
endedAt: null,
|
|
1399
|
+
experimentId: s.experimentId ?? null,
|
|
1400
|
+
entityType: s.entityType ?? null,
|
|
1401
|
+
entityId: s.entityId ?? null,
|
|
1402
|
+
entityName: s.entityName ?? null,
|
|
1403
|
+
userId: s.userId ?? null,
|
|
1404
|
+
organizationId: s.organizationId ?? null,
|
|
1405
|
+
resourceId: s.resourceId ?? null,
|
|
1406
|
+
runId: s.runId ?? null,
|
|
1407
|
+
sessionId: s.sessionId ?? null,
|
|
1408
|
+
threadId: s.threadId ?? null,
|
|
1409
|
+
requestId: s.requestId ?? null,
|
|
1410
|
+
environment: s.environment ?? null,
|
|
1411
|
+
source: s.source ?? null,
|
|
1412
|
+
serviceName: s.serviceName ?? null,
|
|
1413
|
+
attributes: s.attributes ?? null,
|
|
1414
|
+
metadata: s.metadata ?? null,
|
|
1415
|
+
tags: s.tags ?? null,
|
|
1416
|
+
scope: s.scope ?? null,
|
|
1417
|
+
links: null,
|
|
1418
|
+
input: s.input ?? null,
|
|
1419
|
+
output: null,
|
|
1420
|
+
error: null,
|
|
1421
|
+
requestContext: s.requestContext ?? null
|
|
1422
|
+
};
|
|
1423
|
+
}
|
|
1424
|
+
function createEndSpanRow(s) {
|
|
1425
|
+
return {
|
|
1426
|
+
eventType: "end",
|
|
1427
|
+
timestamp: s.endedAt,
|
|
1428
|
+
traceId: s.traceId,
|
|
1429
|
+
spanId: s.spanId,
|
|
1430
|
+
parentSpanId: s.parentSpanId ?? null,
|
|
1431
|
+
name: s.name,
|
|
1432
|
+
spanType: s.spanType,
|
|
1433
|
+
isEvent: s.isEvent,
|
|
1434
|
+
endedAt: s.endedAt ?? null,
|
|
1435
|
+
experimentId: s.experimentId ?? null,
|
|
1436
|
+
entityType: s.entityType ?? null,
|
|
1437
|
+
entityId: s.entityId ?? null,
|
|
1438
|
+
entityName: s.entityName ?? null,
|
|
1439
|
+
userId: s.userId ?? null,
|
|
1440
|
+
organizationId: s.organizationId ?? null,
|
|
1441
|
+
resourceId: s.resourceId ?? null,
|
|
1442
|
+
runId: s.runId ?? null,
|
|
1443
|
+
sessionId: s.sessionId ?? null,
|
|
1444
|
+
threadId: s.threadId ?? null,
|
|
1445
|
+
requestId: s.requestId ?? null,
|
|
1446
|
+
environment: s.environment ?? null,
|
|
1447
|
+
source: s.source ?? null,
|
|
1448
|
+
serviceName: s.serviceName ?? null,
|
|
1449
|
+
attributes: s.attributes ?? null,
|
|
1450
|
+
metadata: s.metadata ?? null,
|
|
1451
|
+
tags: s.tags ?? null,
|
|
1452
|
+
scope: s.scope ?? null,
|
|
1453
|
+
links: s.links ?? null,
|
|
1454
|
+
input: s.input ?? null,
|
|
1455
|
+
output: s.output ?? null,
|
|
1456
|
+
error: s.error ?? null,
|
|
1457
|
+
requestContext: s.requestContext ?? null
|
|
1458
|
+
};
|
|
1459
|
+
}
|
|
1460
|
+
async function createSpan(db, args) {
|
|
1461
|
+
const rows = [createStartSpanRow(args.span)];
|
|
1462
|
+
if (args.span.endedAt) {
|
|
1463
|
+
rows.push(createEndSpanRow(args.span));
|
|
1464
|
+
}
|
|
1465
|
+
await insertSpanEvents(db, rows);
|
|
1466
|
+
}
|
|
1467
|
+
async function batchCreateSpans(db, args) {
|
|
1468
|
+
if (args.records.length === 0) return;
|
|
1469
|
+
const rows = args.records.flatMap((record) => {
|
|
1470
|
+
const events = [createStartSpanRow(record)];
|
|
1471
|
+
if (record.endedAt) {
|
|
1472
|
+
events.push(createEndSpanRow(record));
|
|
1473
|
+
}
|
|
1474
|
+
return events;
|
|
1475
|
+
});
|
|
1476
|
+
await insertSpanEvents(db, rows);
|
|
1477
|
+
}
|
|
1478
|
+
async function batchDeleteTraces(db, args) {
|
|
1479
|
+
if (args.traceIds.length === 0) return;
|
|
1480
|
+
const placeholders = args.traceIds.map(() => "?").join(", ");
|
|
1481
|
+
await db.execute(`DELETE FROM span_events WHERE traceId IN (${placeholders})`, args.traceIds);
|
|
1482
|
+
}
|
|
1483
|
+
async function getSpan(db, args) {
|
|
1484
|
+
const rows = await db.query(`${SPAN_RECONSTRUCT_SELECT} WHERE traceId = ? AND spanId = ? GROUP BY traceId, spanId`, [
|
|
1485
|
+
args.traceId,
|
|
1486
|
+
args.spanId
|
|
1487
|
+
]);
|
|
1488
|
+
if (rows.length === 0) return null;
|
|
1489
|
+
return { span: rowToSpanRecord(rows[0]) };
|
|
1490
|
+
}
|
|
1491
|
+
async function getRootSpan(db, args) {
|
|
1492
|
+
const rows = await db.query(
|
|
1493
|
+
`${SPAN_RECONSTRUCT_SELECT} WHERE traceId = ? GROUP BY traceId, spanId HAVING arg_max(parentSpanId, timestamp) IS NULL LIMIT 1`,
|
|
1494
|
+
[args.traceId]
|
|
1495
|
+
);
|
|
1496
|
+
if (rows.length === 0) return null;
|
|
1497
|
+
return { span: rowToSpanRecord(rows[0]) };
|
|
1498
|
+
}
|
|
1499
|
+
async function getTrace(db, args) {
|
|
1500
|
+
const rows = await db.query(`${SPAN_RECONSTRUCT_SELECT} WHERE traceId = ? GROUP BY traceId, spanId`, [args.traceId]);
|
|
1501
|
+
if (rows.length === 0) return null;
|
|
1502
|
+
return {
|
|
1503
|
+
traceId: args.traceId,
|
|
1504
|
+
spans: rows.map((row) => rowToSpanRecord(row))
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1507
|
+
async function listTraces(db, args) {
|
|
1508
|
+
const filters = args.filters ?? {};
|
|
1509
|
+
const page = Number(args.pagination?.page ?? 0);
|
|
1510
|
+
const perPage = Number(args.pagination?.perPage ?? 10);
|
|
1511
|
+
const orderBy = { field: args.orderBy?.field ?? "startedAt", direction: args.orderBy?.direction ?? "DESC" };
|
|
1512
|
+
const { clause: filterClause, params: filterParams } = buildWhereClause(filters);
|
|
1513
|
+
const orderByClause = buildOrderByClause(orderBy);
|
|
1514
|
+
const { clause: paginationClause, params: paginationParams } = buildPaginationClause({ page, perPage });
|
|
1515
|
+
const filterParts = [];
|
|
1516
|
+
if (filterClause) filterParts.push(filterClause.replace(/^WHERE\s+/i, ""));
|
|
1517
|
+
const hasChildError = typeof filters.hasChildError === "boolean" ? filters.hasChildError : void 0;
|
|
1518
|
+
const childErrorClause = buildHasChildErrorClause(hasChildError);
|
|
1519
|
+
if (childErrorClause) filterParts.push(childErrorClause);
|
|
1520
|
+
const combinedFilterClause = filterParts.length > 0 ? `WHERE ${filterParts.join(" AND ")}` : "";
|
|
1521
|
+
const cteSql = `
|
|
1522
|
+
WITH reconstructed_spans AS (
|
|
1523
|
+
${SPAN_RECONSTRUCT_SELECT}
|
|
1524
|
+
GROUP BY traceId, spanId
|
|
1525
|
+
),
|
|
1526
|
+
root_spans AS (
|
|
1527
|
+
SELECT * FROM reconstructed_spans
|
|
1528
|
+
WHERE parentSpanId IS NULL
|
|
1529
|
+
)
|
|
1530
|
+
`;
|
|
1531
|
+
const countSql = `
|
|
1532
|
+
${cteSql}
|
|
1533
|
+
SELECT COUNT(*) as total FROM root_spans ${combinedFilterClause}
|
|
1534
|
+
`;
|
|
1535
|
+
const countResult = await db.query(countSql, filterParams);
|
|
1536
|
+
const total = Number(countResult[0]?.total ?? 0);
|
|
1537
|
+
const dataSql = `
|
|
1538
|
+
${cteSql}
|
|
1539
|
+
SELECT * FROM root_spans ${combinedFilterClause} ${orderByClause} ${paginationClause}
|
|
1540
|
+
`;
|
|
1541
|
+
const rows = await db.query(dataSql, [...filterParams, ...paginationParams]);
|
|
1542
|
+
const spans = rows.map((row) => rowToSpanRecord(row));
|
|
1543
|
+
return {
|
|
1544
|
+
pagination: {
|
|
1545
|
+
total,
|
|
1546
|
+
page,
|
|
1547
|
+
perPage,
|
|
1548
|
+
hasMore: (page + 1) * perPage < total
|
|
1549
|
+
},
|
|
1550
|
+
spans: storage.toTraceSpans(spans)
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
// src/storage/domains/observability/index.ts
|
|
1555
|
+
var ObservabilityStorageDuckDB = class extends storage.ObservabilityStorage {
|
|
1556
|
+
db;
|
|
1557
|
+
constructor(config) {
|
|
1558
|
+
super();
|
|
1559
|
+
this.db = config.db;
|
|
1560
|
+
}
|
|
1561
|
+
/** Create all observability tables if they don't exist. */
|
|
1562
|
+
async init() {
|
|
1563
|
+
for (const ddl of ALL_DDL) {
|
|
1564
|
+
await this.db.execute(ddl);
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
/** Delete all rows from every observability table. Use with caution. */
|
|
1568
|
+
async dangerouslyClearAll() {
|
|
1569
|
+
for (const table of ["span_events", "metric_events", "log_events", "score_events", "feedback_events"]) {
|
|
1570
|
+
await this.db.execute(`TRUNCATE TABLE ${table}`);
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
get observabilityStrategy() {
|
|
1574
|
+
return {
|
|
1575
|
+
preferred: "event-sourced",
|
|
1576
|
+
supported: ["event-sourced"]
|
|
1577
|
+
};
|
|
1578
|
+
}
|
|
1579
|
+
// Tracing
|
|
1580
|
+
async createSpan(args) {
|
|
1581
|
+
return createSpan(this.db, args);
|
|
1582
|
+
}
|
|
1583
|
+
async batchCreateSpans(args) {
|
|
1584
|
+
return batchCreateSpans(this.db, args);
|
|
1585
|
+
}
|
|
1586
|
+
async batchDeleteTraces(args) {
|
|
1587
|
+
return batchDeleteTraces(this.db, args);
|
|
1588
|
+
}
|
|
1589
|
+
async getSpan(args) {
|
|
1590
|
+
return getSpan(this.db, args);
|
|
1591
|
+
}
|
|
1592
|
+
async getRootSpan(args) {
|
|
1593
|
+
return getRootSpan(this.db, args);
|
|
1594
|
+
}
|
|
1595
|
+
async getTrace(args) {
|
|
1596
|
+
return getTrace(this.db, args);
|
|
1597
|
+
}
|
|
1598
|
+
async listTraces(args) {
|
|
1599
|
+
return listTraces(this.db, args);
|
|
1600
|
+
}
|
|
1601
|
+
// Logs
|
|
1602
|
+
async batchCreateLogs(args) {
|
|
1603
|
+
return batchCreateLogs(this.db, args);
|
|
1604
|
+
}
|
|
1605
|
+
async listLogs(args) {
|
|
1606
|
+
return listLogs(this.db, args);
|
|
1607
|
+
}
|
|
1608
|
+
// Metrics
|
|
1609
|
+
async batchCreateMetrics(args) {
|
|
1610
|
+
return batchCreateMetrics(this.db, args);
|
|
1611
|
+
}
|
|
1612
|
+
async listMetrics(args) {
|
|
1613
|
+
return listMetrics(this.db, args);
|
|
1614
|
+
}
|
|
1615
|
+
async getMetricAggregate(args) {
|
|
1616
|
+
return getMetricAggregate(this.db, args);
|
|
1617
|
+
}
|
|
1618
|
+
async getMetricBreakdown(args) {
|
|
1619
|
+
return getMetricBreakdown(this.db, args);
|
|
1620
|
+
}
|
|
1621
|
+
async getMetricTimeSeries(args) {
|
|
1622
|
+
return getMetricTimeSeries(this.db, args);
|
|
1623
|
+
}
|
|
1624
|
+
async getMetricPercentiles(args) {
|
|
1625
|
+
return getMetricPercentiles(this.db, args);
|
|
1626
|
+
}
|
|
1627
|
+
// Metric Discovery
|
|
1628
|
+
async getMetricNames(args) {
|
|
1629
|
+
return getMetricNames(this.db, args);
|
|
1630
|
+
}
|
|
1631
|
+
async getMetricLabelKeys(args) {
|
|
1632
|
+
return getMetricLabelKeys(this.db, args);
|
|
1633
|
+
}
|
|
1634
|
+
async getMetricLabelValues(args) {
|
|
1635
|
+
return getMetricLabelValues(this.db, args);
|
|
1636
|
+
}
|
|
1637
|
+
// Span Discovery
|
|
1638
|
+
async getEntityTypes(args) {
|
|
1639
|
+
return getEntityTypes(this.db);
|
|
1640
|
+
}
|
|
1641
|
+
async getEntityNames(args) {
|
|
1642
|
+
return getEntityNames(this.db, args);
|
|
1643
|
+
}
|
|
1644
|
+
async getServiceNames(args) {
|
|
1645
|
+
return getServiceNames(this.db);
|
|
1646
|
+
}
|
|
1647
|
+
async getEnvironments(args) {
|
|
1648
|
+
return getEnvironments(this.db);
|
|
1649
|
+
}
|
|
1650
|
+
async getTags(args) {
|
|
1651
|
+
return getTags(this.db, args);
|
|
1652
|
+
}
|
|
1653
|
+
// Scores
|
|
1654
|
+
async createScore(args) {
|
|
1655
|
+
return createScore(this.db, args);
|
|
1656
|
+
}
|
|
1657
|
+
async batchCreateScores(args) {
|
|
1658
|
+
return batchCreateScores(this.db, args);
|
|
1659
|
+
}
|
|
1660
|
+
async listScores(args) {
|
|
1661
|
+
return listScores(this.db, args);
|
|
1662
|
+
}
|
|
1663
|
+
// Feedback
|
|
1664
|
+
async createFeedback(args) {
|
|
1665
|
+
return createFeedback(this.db, args);
|
|
1666
|
+
}
|
|
1667
|
+
async batchCreateFeedback(args) {
|
|
1668
|
+
return batchCreateFeedback(this.db, args);
|
|
1669
|
+
}
|
|
1670
|
+
async listFeedback(args) {
|
|
1671
|
+
return listFeedback(this.db, args);
|
|
1672
|
+
}
|
|
1673
|
+
};
|
|
1674
|
+
|
|
1675
|
+
exports.ObservabilityStorageDuckDB = ObservabilityStorageDuckDB;
|
|
1676
|
+
//# sourceMappingURL=observability-W2QRBK56.cjs.map
|
|
1677
|
+
//# sourceMappingURL=observability-W2QRBK56.cjs.map
|