@mastra/duckdb 1.5.1 → 1.5.2

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.
@@ -0,0 +1,3732 @@
1
+ const require_db = require("./db-TBEcMD49.cjs");
2
+ let _mastra_core_error = require("@mastra/core/error");
3
+ let _mastra_core_storage = require("@mastra/core/storage");
4
+ let _mastra_core_features = require("@mastra/core/features");
5
+ let _mastra_core_observability = require("@mastra/core/observability");
6
+ let _mastra_core_utils = require("@mastra/core/utils");
7
+ //#region src/storage/domains/observability/ddl.ts
8
+ /**
9
+ * DDL statements for DuckDB observability tables.
10
+ * All tables use append-only patterns with a single `timestamp` column.
11
+ *
12
+ * Column ordering convention:
13
+ * 1. Event metadata (eventType, timestamp)
14
+ * 2. IDs (trace, span, experiment, resource, run, session, etc.)
15
+ * 3. Entity hierarchy (entity, parent, root)
16
+ * 4. Context (user, org, environment, service, executionSource)
17
+ * 5. Domain-specific scalar fields
18
+ * 6. JSON fields (attributes, metadata, tags, input/output, etc.)
19
+ */
20
+ const SPAN_EVENTS_CURSOR_SEQUENCE_DDL = `
21
+ CREATE SEQUENCE IF NOT EXISTS span_events_cursor_id_seq START 1
22
+ `;
23
+ const METRIC_EVENTS_CURSOR_SEQUENCE_DDL = `
24
+ CREATE SEQUENCE IF NOT EXISTS metric_events_cursor_id_seq START 1
25
+ `;
26
+ const LOG_EVENTS_CURSOR_SEQUENCE_DDL = `
27
+ CREATE SEQUENCE IF NOT EXISTS log_events_cursor_id_seq START 1
28
+ `;
29
+ const SCORE_EVENTS_CURSOR_SEQUENCE_DDL = `
30
+ CREATE SEQUENCE IF NOT EXISTS score_events_cursor_id_seq START 1
31
+ `;
32
+ const FEEDBACK_EVENTS_CURSOR_SEQUENCE_DDL = `
33
+ CREATE SEQUENCE IF NOT EXISTS feedback_events_cursor_id_seq START 1
34
+ `;
35
+ /** DDL for the span_events append-only table. */
36
+ const SPAN_EVENTS_DDL = `
37
+ CREATE TABLE IF NOT EXISTS span_events (
38
+ -- Event metadata
39
+ eventType VARCHAR NOT NULL,
40
+ timestamp TIMESTAMP NOT NULL,
41
+ cursorId BIGINT,
42
+
43
+ -- IDs
44
+ traceId VARCHAR NOT NULL,
45
+ spanId VARCHAR NOT NULL,
46
+ parentSpanId VARCHAR,
47
+ experimentId VARCHAR,
48
+
49
+ -- Entity
50
+ entityType VARCHAR,
51
+ entityId VARCHAR,
52
+ entityName VARCHAR,
53
+ entityVersionId VARCHAR,
54
+
55
+ -- Context
56
+ userId VARCHAR,
57
+ organizationId VARCHAR,
58
+ resourceId VARCHAR,
59
+ runId VARCHAR,
60
+ sessionId VARCHAR,
61
+ threadId VARCHAR,
62
+ requestId VARCHAR,
63
+ environment VARCHAR,
64
+ source VARCHAR,
65
+ serviceName VARCHAR,
66
+ requestContext JSON,
67
+
68
+ -- Span-specific scalars
69
+ name VARCHAR,
70
+ spanType VARCHAR,
71
+ isEvent BOOLEAN,
72
+ endedAt TIMESTAMP,
73
+
74
+ -- JSON fields
75
+ attributes JSON,
76
+ metadata JSON,
77
+ tags JSON,
78
+ scope JSON,
79
+ links JSON,
80
+ input JSON,
81
+ output JSON,
82
+ error JSON
83
+ )`;
84
+ /** DDL for the metric_events append-only table. */
85
+ const METRIC_EVENTS_DDL = `
86
+ CREATE TABLE IF NOT EXISTS metric_events (
87
+ -- Event metadata
88
+ timestamp TIMESTAMP NOT NULL,
89
+ cursorId BIGINT,
90
+
91
+ -- IDs
92
+ metricId VARCHAR NOT NULL PRIMARY KEY,
93
+ traceId VARCHAR,
94
+ spanId VARCHAR,
95
+ experimentId VARCHAR,
96
+
97
+ -- Entity hierarchy
98
+ entityType VARCHAR,
99
+ entityId VARCHAR,
100
+ entityName VARCHAR,
101
+ entityVersionId VARCHAR,
102
+ parentEntityVersionId VARCHAR,
103
+ parentEntityType VARCHAR,
104
+ parentEntityId VARCHAR,
105
+ parentEntityName VARCHAR,
106
+ rootEntityVersionId VARCHAR,
107
+ rootEntityType VARCHAR,
108
+ rootEntityId VARCHAR,
109
+ rootEntityName VARCHAR,
110
+
111
+ -- Context
112
+ userId VARCHAR,
113
+ organizationId VARCHAR,
114
+ resourceId VARCHAR,
115
+ runId VARCHAR,
116
+ sessionId VARCHAR,
117
+ threadId VARCHAR,
118
+ requestId VARCHAR,
119
+ environment VARCHAR,
120
+ executionSource VARCHAR,
121
+ serviceName VARCHAR,
122
+
123
+ -- Metric-specific scalars
124
+ name VARCHAR NOT NULL,
125
+ value DOUBLE NOT NULL,
126
+ provider VARCHAR,
127
+ model VARCHAR,
128
+ estimatedCost DOUBLE,
129
+ costUnit VARCHAR,
130
+
131
+ -- JSON fields
132
+ tags JSON,
133
+ labels JSON,
134
+ costMetadata JSON,
135
+ metadata JSON,
136
+ scope JSON
137
+ )`;
138
+ /** DDL for the log_events append-only table. */
139
+ const LOG_EVENTS_DDL = `
140
+ CREATE TABLE IF NOT EXISTS log_events (
141
+ -- Event metadata
142
+ timestamp TIMESTAMP NOT NULL,
143
+ cursorId BIGINT,
144
+
145
+ -- IDs
146
+ logId VARCHAR NOT NULL PRIMARY KEY,
147
+ traceId VARCHAR,
148
+ spanId VARCHAR,
149
+ experimentId VARCHAR,
150
+
151
+ -- Entity hierarchy
152
+ entityType VARCHAR,
153
+ entityId VARCHAR,
154
+ entityName VARCHAR,
155
+ entityVersionId VARCHAR,
156
+ parentEntityVersionId VARCHAR,
157
+ parentEntityType VARCHAR,
158
+ parentEntityId VARCHAR,
159
+ parentEntityName VARCHAR,
160
+ rootEntityVersionId VARCHAR,
161
+ rootEntityType VARCHAR,
162
+ rootEntityId VARCHAR,
163
+ rootEntityName VARCHAR,
164
+
165
+ -- Context
166
+ userId VARCHAR,
167
+ organizationId VARCHAR,
168
+ resourceId VARCHAR,
169
+ runId VARCHAR,
170
+ sessionId VARCHAR,
171
+ threadId VARCHAR,
172
+ requestId VARCHAR,
173
+ environment VARCHAR,
174
+ executionSource VARCHAR,
175
+ serviceName VARCHAR,
176
+
177
+ -- Log-specific scalars
178
+ level VARCHAR NOT NULL,
179
+ message VARCHAR NOT NULL,
180
+
181
+ -- JSON fields
182
+ data JSON,
183
+ tags JSON,
184
+ metadata JSON,
185
+ scope JSON
186
+ )`;
187
+ /** DDL for the score_events append-only table. */
188
+ const SCORE_EVENTS_DDL = `
189
+ CREATE TABLE IF NOT EXISTS score_events (
190
+ -- Event metadata
191
+ timestamp TIMESTAMP NOT NULL,
192
+ cursorId BIGINT,
193
+
194
+ -- IDs
195
+ scoreId VARCHAR NOT NULL PRIMARY KEY,
196
+ traceId VARCHAR,
197
+ spanId VARCHAR,
198
+ experimentId VARCHAR,
199
+ scoreTraceId VARCHAR,
200
+
201
+ -- Entity hierarchy
202
+ entityType VARCHAR,
203
+ entityId VARCHAR,
204
+ entityName VARCHAR,
205
+ entityVersionId VARCHAR,
206
+ parentEntityVersionId VARCHAR,
207
+ parentEntityType VARCHAR,
208
+ parentEntityId VARCHAR,
209
+ parentEntityName VARCHAR,
210
+ rootEntityVersionId VARCHAR,
211
+ rootEntityType VARCHAR,
212
+ rootEntityId VARCHAR,
213
+ rootEntityName VARCHAR,
214
+
215
+ -- Context
216
+ userId VARCHAR,
217
+ organizationId VARCHAR,
218
+ resourceId VARCHAR,
219
+ runId VARCHAR,
220
+ sessionId VARCHAR,
221
+ threadId VARCHAR,
222
+ requestId VARCHAR,
223
+ environment VARCHAR,
224
+ executionSource VARCHAR,
225
+ serviceName VARCHAR,
226
+
227
+ -- Score-specific scalars
228
+ scorerId VARCHAR NOT NULL,
229
+ scorerVersion VARCHAR,
230
+ source VARCHAR,
231
+ scoreSource VARCHAR,
232
+ score DOUBLE NOT NULL,
233
+ reason VARCHAR,
234
+
235
+ -- JSON fields
236
+ tags JSON,
237
+ metadata JSON,
238
+ scope JSON
239
+ )`;
240
+ /** DDL for the feedback_events append-only table. */
241
+ const FEEDBACK_EVENTS_DDL = `
242
+ CREATE TABLE IF NOT EXISTS feedback_events (
243
+ -- Event metadata
244
+ timestamp TIMESTAMP NOT NULL,
245
+ cursorId BIGINT,
246
+
247
+ -- IDs
248
+ feedbackId VARCHAR NOT NULL PRIMARY KEY,
249
+ traceId VARCHAR,
250
+ spanId VARCHAR,
251
+ experimentId VARCHAR,
252
+ -- Entity hierarchy
253
+ entityType VARCHAR,
254
+ entityId VARCHAR,
255
+ entityName VARCHAR,
256
+ entityVersionId VARCHAR,
257
+ parentEntityVersionId VARCHAR,
258
+ parentEntityType VARCHAR,
259
+ parentEntityId VARCHAR,
260
+ parentEntityName VARCHAR,
261
+ rootEntityVersionId VARCHAR,
262
+ rootEntityType VARCHAR,
263
+ rootEntityId VARCHAR,
264
+ rootEntityName VARCHAR,
265
+
266
+ -- Context
267
+ userId VARCHAR,
268
+ organizationId VARCHAR,
269
+ resourceId VARCHAR,
270
+ runId VARCHAR,
271
+ sessionId VARCHAR,
272
+ threadId VARCHAR,
273
+ requestId VARCHAR,
274
+ environment VARCHAR,
275
+ executionSource VARCHAR,
276
+ serviceName VARCHAR,
277
+
278
+ -- Feedback actor / linkage
279
+ feedbackUserId VARCHAR,
280
+ sourceId VARCHAR,
281
+
282
+ -- Feedback-specific scalars
283
+ source VARCHAR,
284
+ feedbackSource VARCHAR NOT NULL,
285
+ feedbackType VARCHAR NOT NULL,
286
+ value VARCHAR NOT NULL,
287
+ comment VARCHAR,
288
+
289
+ -- JSON fields
290
+ tags JSON,
291
+ metadata JSON,
292
+ scope JSON
293
+ )`;
294
+ /** All observability DDL statements, in creation order. */
295
+ const ALL_DDL = [
296
+ SPAN_EVENTS_CURSOR_SEQUENCE_DDL,
297
+ METRIC_EVENTS_CURSOR_SEQUENCE_DDL,
298
+ LOG_EVENTS_CURSOR_SEQUENCE_DDL,
299
+ SCORE_EVENTS_CURSOR_SEQUENCE_DDL,
300
+ FEEDBACK_EVENTS_CURSOR_SEQUENCE_DDL,
301
+ SPAN_EVENTS_DDL,
302
+ METRIC_EVENTS_DDL,
303
+ LOG_EVENTS_DDL,
304
+ SCORE_EVENTS_DDL,
305
+ FEEDBACK_EVENTS_DDL
306
+ ];
307
+ /** Additive migrations for observability tables created by older versions. */
308
+ const ALL_MIGRATIONS = [
309
+ `CREATE SEQUENCE IF NOT EXISTS span_events_cursor_id_seq START 1`,
310
+ `CREATE SEQUENCE IF NOT EXISTS metric_events_cursor_id_seq START 1`,
311
+ `CREATE SEQUENCE IF NOT EXISTS log_events_cursor_id_seq START 1`,
312
+ `CREATE SEQUENCE IF NOT EXISTS score_events_cursor_id_seq START 1`,
313
+ `CREATE SEQUENCE IF NOT EXISTS feedback_events_cursor_id_seq START 1`,
314
+ `ALTER TABLE span_events ADD COLUMN IF NOT EXISTS cursorId BIGINT`,
315
+ `ALTER TABLE span_events ADD COLUMN IF NOT EXISTS entityVersionId VARCHAR`,
316
+ `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS cursorId BIGINT`,
317
+ `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS entityVersionId VARCHAR`,
318
+ `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS parentEntityVersionId VARCHAR`,
319
+ `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS rootEntityVersionId VARCHAR`,
320
+ `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS experimentId VARCHAR`,
321
+ `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS parentEntityType VARCHAR`,
322
+ `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS parentEntityId VARCHAR`,
323
+ `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS parentEntityName VARCHAR`,
324
+ `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS rootEntityType VARCHAR`,
325
+ `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS rootEntityId VARCHAR`,
326
+ `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS rootEntityName VARCHAR`,
327
+ `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS userId VARCHAR`,
328
+ `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS organizationId VARCHAR`,
329
+ `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS resourceId VARCHAR`,
330
+ `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS runId VARCHAR`,
331
+ `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS sessionId VARCHAR`,
332
+ `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS threadId VARCHAR`,
333
+ `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS requestId VARCHAR`,
334
+ `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS environment VARCHAR`,
335
+ `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS executionSource VARCHAR`,
336
+ `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS serviceName VARCHAR`,
337
+ `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS costMetadata JSON`,
338
+ `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS metadata JSON`,
339
+ `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS scope JSON`,
340
+ `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS cursorId BIGINT`,
341
+ `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS entityVersionId VARCHAR`,
342
+ `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS parentEntityVersionId VARCHAR`,
343
+ `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS rootEntityVersionId VARCHAR`,
344
+ `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS experimentId VARCHAR`,
345
+ `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS parentEntityType VARCHAR`,
346
+ `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS parentEntityId VARCHAR`,
347
+ `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS parentEntityName VARCHAR`,
348
+ `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS rootEntityType VARCHAR`,
349
+ `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS rootEntityId VARCHAR`,
350
+ `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS rootEntityName VARCHAR`,
351
+ `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS userId VARCHAR`,
352
+ `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS organizationId VARCHAR`,
353
+ `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS resourceId VARCHAR`,
354
+ `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS runId VARCHAR`,
355
+ `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS sessionId VARCHAR`,
356
+ `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS threadId VARCHAR`,
357
+ `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS requestId VARCHAR`,
358
+ `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS environment VARCHAR`,
359
+ `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS executionSource VARCHAR`,
360
+ `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS serviceName VARCHAR`,
361
+ `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS tags JSON`,
362
+ `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS metadata JSON`,
363
+ `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS scope JSON`,
364
+ `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS cursorId BIGINT`,
365
+ `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS entityVersionId VARCHAR`,
366
+ `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS parentEntityVersionId VARCHAR`,
367
+ `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS rootEntityVersionId VARCHAR`,
368
+ `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS entityType VARCHAR`,
369
+ `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS entityId VARCHAR`,
370
+ `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS entityName VARCHAR`,
371
+ `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS parentEntityType VARCHAR`,
372
+ `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS parentEntityId VARCHAR`,
373
+ `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS parentEntityName VARCHAR`,
374
+ `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS rootEntityType VARCHAR`,
375
+ `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS rootEntityId VARCHAR`,
376
+ `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS rootEntityName VARCHAR`,
377
+ `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS userId VARCHAR`,
378
+ `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS organizationId VARCHAR`,
379
+ `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS resourceId VARCHAR`,
380
+ `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS runId VARCHAR`,
381
+ `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS sessionId VARCHAR`,
382
+ `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS threadId VARCHAR`,
383
+ `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS requestId VARCHAR`,
384
+ `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS environment VARCHAR`,
385
+ `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS executionSource VARCHAR`,
386
+ `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS serviceName VARCHAR`,
387
+ `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS tags JSON`,
388
+ `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS scope JSON`,
389
+ `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS source VARCHAR`,
390
+ `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS scoreSource VARCHAR`,
391
+ `ALTER TABLE score_events ALTER COLUMN traceId DROP NOT NULL`,
392
+ `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS cursorId BIGINT`,
393
+ `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS entityVersionId VARCHAR`,
394
+ `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS parentEntityVersionId VARCHAR`,
395
+ `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS rootEntityVersionId VARCHAR`,
396
+ `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS entityType VARCHAR`,
397
+ `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS entityId VARCHAR`,
398
+ `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS entityName VARCHAR`,
399
+ `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS parentEntityType VARCHAR`,
400
+ `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS parentEntityId VARCHAR`,
401
+ `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS parentEntityName VARCHAR`,
402
+ `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS rootEntityType VARCHAR`,
403
+ `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS rootEntityId VARCHAR`,
404
+ `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS rootEntityName VARCHAR`,
405
+ `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS organizationId VARCHAR`,
406
+ `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS resourceId VARCHAR`,
407
+ `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS runId VARCHAR`,
408
+ `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS sessionId VARCHAR`,
409
+ `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS threadId VARCHAR`,
410
+ `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS requestId VARCHAR`,
411
+ `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS environment VARCHAR`,
412
+ `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS executionSource VARCHAR`,
413
+ `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS serviceName VARCHAR`,
414
+ `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS feedbackUserId VARCHAR`,
415
+ `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS sourceId VARCHAR`,
416
+ `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS tags JSON`,
417
+ `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS scope JSON`,
418
+ `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS source VARCHAR`,
419
+ `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS feedbackSource VARCHAR`,
420
+ `ALTER TABLE feedback_events ALTER COLUMN traceId DROP NOT NULL`
421
+ ];
422
+ //#endregion
423
+ //#region src/storage/domains/observability/discovery.ts
424
+ function unionDistinctQueries(selects, orderBy) {
425
+ return `${selects.join("\nUNION\n")}\nORDER BY ${orderBy}`;
426
+ }
427
+ /** Return distinct entity types across observability signals that carry them. */
428
+ async function getEntityTypes(db, _args) {
429
+ const rows = await db.query(unionDistinctQueries([
430
+ `SELECT entityType FROM span_events WHERE entityType IS NOT NULL`,
431
+ `SELECT entityType FROM metric_events WHERE entityType IS NOT NULL`,
432
+ `SELECT entityType FROM log_events WHERE entityType IS NOT NULL`
433
+ ], "entityType"));
434
+ const validTypes = new Set(Object.values(_mastra_core_observability.EntityType));
435
+ const typeSet = /* @__PURE__ */ new Set();
436
+ for (const row of rows) if (row.entityType && validTypes.has(row.entityType)) typeSet.add(row.entityType);
437
+ return { entityTypes: Array.from(typeSet).sort() };
438
+ }
439
+ /** Return distinct entity names across observability signals, optionally filtered by entity type. */
440
+ async function getEntityNames(db, args) {
441
+ const buildSelect = (table) => {
442
+ const conditions = [`entityName IS NOT NULL`];
443
+ if (args.entityType) conditions.push(`entityType = ?`);
444
+ return `SELECT entityName FROM ${table} WHERE ${conditions.join(" AND ")}`;
445
+ };
446
+ const params = args.entityType ? [
447
+ args.entityType,
448
+ args.entityType,
449
+ args.entityType
450
+ ] : [];
451
+ return { names: (await db.query(unionDistinctQueries([
452
+ buildSelect("span_events"),
453
+ buildSelect("metric_events"),
454
+ buildSelect("log_events")
455
+ ], "entityName"), params)).map((r) => r.entityName) };
456
+ }
457
+ /** Return distinct service names across observability signals. */
458
+ async function getServiceNames(db, _args) {
459
+ return { serviceNames: (await db.query(unionDistinctQueries([
460
+ `SELECT serviceName FROM span_events WHERE serviceName IS NOT NULL`,
461
+ `SELECT serviceName FROM metric_events WHERE serviceName IS NOT NULL`,
462
+ `SELECT serviceName FROM log_events WHERE serviceName IS NOT NULL`
463
+ ], "serviceName"))).map((r) => r.serviceName) };
464
+ }
465
+ /** Return distinct environment values across observability signals. */
466
+ async function getEnvironments(db, _args) {
467
+ return { environments: (await db.query(unionDistinctQueries([
468
+ `SELECT environment FROM span_events WHERE environment IS NOT NULL`,
469
+ `SELECT environment FROM metric_events WHERE environment IS NOT NULL`,
470
+ `SELECT environment FROM log_events WHERE environment IS NOT NULL`
471
+ ], "environment"))).map((r) => r.environment) };
472
+ }
473
+ /** Return distinct tags across observability signals, optionally filtered by entity type. */
474
+ async function getTags(db, args) {
475
+ const buildSelect = (table) => {
476
+ const conditions = [`tags IS NOT NULL`];
477
+ if (args.entityType) conditions.push(`entityType = ?`);
478
+ return `SELECT unnest(CAST(tags AS VARCHAR[])) AS tag FROM ${table} WHERE ${conditions.join(" AND ")}`;
479
+ };
480
+ const params = args.entityType ? [
481
+ args.entityType,
482
+ args.entityType,
483
+ args.entityType
484
+ ] : [];
485
+ return { tags: (await db.query(unionDistinctQueries([
486
+ buildSelect("span_events"),
487
+ buildSelect("metric_events"),
488
+ buildSelect("log_events")
489
+ ], "tag"), params)).map((r) => r.tag) };
490
+ }
491
+ //#endregion
492
+ //#region src/storage/domains/observability/filters.ts
493
+ function buildJsonPath(key) {
494
+ try {
495
+ return `$.${(0, _mastra_core_utils.parseFieldKey)(key)}`;
496
+ } catch {
497
+ return `$."${key.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
498
+ }
499
+ }
500
+ function normalizeJsonFilterValue(value) {
501
+ if (value === void 0) return null;
502
+ if (typeof value === "string") return value;
503
+ return JSON.stringify(value) ?? null;
504
+ }
505
+ function sanitizeColumn(column) {
506
+ return (0, _mastra_core_utils.parseFieldKey)(column);
507
+ }
508
+ /**
509
+ * Build a WHERE clause from a filter object.
510
+ * Returns { clause, params } for parameterized queries.
511
+ */
512
+ function buildWhereClause(filters, fieldMappings) {
513
+ if (!filters) return {
514
+ clause: "",
515
+ params: []
516
+ };
517
+ const conditions = [];
518
+ const params = [];
519
+ for (const [key, value] of Object.entries(filters)) {
520
+ if (value === void 0 || value === null) continue;
521
+ const column = sanitizeColumn(fieldMappings?.[key] ?? key);
522
+ if (key === "timestamp" || key === "startedAt" || key === "endedAt") {
523
+ const dateRange = value;
524
+ if (dateRange.start) {
525
+ const op = dateRange.startExclusive ? ">" : ">=";
526
+ conditions.push(`${column} ${op} ?`);
527
+ params.push(dateRange.start);
528
+ }
529
+ if (dateRange.end) {
530
+ const op = dateRange.endExclusive ? "<" : "<=";
531
+ conditions.push(`${column} ${op} ?`);
532
+ params.push(dateRange.end);
533
+ }
534
+ continue;
535
+ }
536
+ if (key === "labels") {
537
+ const labelsObj = value;
538
+ for (const [labelKey, labelValue] of Object.entries(labelsObj)) {
539
+ conditions.push(`json_extract_string(${column}, ?) = ?`);
540
+ params.push(buildJsonPath(labelKey), labelValue);
541
+ }
542
+ continue;
543
+ }
544
+ if (key === "tags") {
545
+ const tags = value;
546
+ for (const tag of tags) {
547
+ conditions.push(`list_contains(CAST(${column} AS VARCHAR[]), ?)`);
548
+ params.push(tag);
549
+ }
550
+ continue;
551
+ }
552
+ if (key === "status") {
553
+ const status = value;
554
+ if (status === "error") conditions.push(`error IS NOT NULL`);
555
+ else if (status === "running") conditions.push(`endedAt IS NULL AND error IS NULL`);
556
+ else if (status === "success") conditions.push(`endedAt IS NOT NULL AND error IS NULL`);
557
+ continue;
558
+ }
559
+ if (key === "hasChildError") continue;
560
+ if (key === "metadata" || key === "scope") {
561
+ const jsonObj = value;
562
+ for (const [jsonKey, jsonValue] of Object.entries(jsonObj)) {
563
+ const normalized = normalizeJsonFilterValue(jsonValue);
564
+ if (normalized === null) continue;
565
+ conditions.push(`json_extract_string(${column}, ?) = ?`);
566
+ params.push(buildJsonPath(jsonKey), normalized);
567
+ }
568
+ continue;
569
+ }
570
+ if (Array.isArray(value)) {
571
+ if (value.length === 0) continue;
572
+ const placeholders = value.map(() => "?").join(", ");
573
+ conditions.push(`${column} IN (${placeholders})`);
574
+ params.push(...value);
575
+ continue;
576
+ }
577
+ conditions.push(`${column} = ?`);
578
+ params.push(value);
579
+ }
580
+ return {
581
+ clause: conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "",
582
+ params
583
+ };
584
+ }
585
+ /**
586
+ * Build an ORDER BY clause from orderBy config.
587
+ */
588
+ function buildOrderByClause(orderBy) {
589
+ if (!orderBy) return "";
590
+ const dir = orderBy.direction.toUpperCase();
591
+ if (dir !== "ASC" && dir !== "DESC") throw new Error(`Invalid sort direction: ${orderBy.direction}`);
592
+ return `ORDER BY ${(0, _mastra_core_utils.parseFieldKey)(orderBy.field)} ${dir}`;
593
+ }
594
+ /**
595
+ * Build a LIMIT/OFFSET clause from pagination config.
596
+ */
597
+ function buildPaginationClause(pagination) {
598
+ if (!pagination) return {
599
+ clause: "",
600
+ params: []
601
+ };
602
+ if (!Number.isInteger(pagination.page) || pagination.page < 0) throw new Error(`Invalid page: ${pagination.page}`);
603
+ if (!Number.isInteger(pagination.perPage) || pagination.perPage <= 0) throw new Error(`Invalid perPage: ${pagination.perPage}`);
604
+ const offset = pagination.page * pagination.perPage;
605
+ return {
606
+ clause: `LIMIT ? OFFSET ?`,
607
+ params: [pagination.perPage, offset]
608
+ };
609
+ }
610
+ //#endregion
611
+ //#region src/storage/domains/observability/helpers.ts
612
+ /** Shorthand for {@link DuckDBConnection.sqlValue}. */
613
+ const v = require_db.DuckDBConnection.sqlValue;
614
+ /** Serialize a value to JSON then SQL-escape it, or return 'NULL'. */
615
+ function jsonV(val) {
616
+ if (val === null || val === void 0) return "NULL";
617
+ return require_db.DuckDBConnection.sqlValue(JSON.stringify(val));
618
+ }
619
+ /** Coerce a value to a Date. Throws if value is nullish. */
620
+ function toDate(val) {
621
+ if (val === null || val === void 0) throw new Error("Expected date value but received null/undefined");
622
+ const date = val instanceof Date ? val : new Date(String(val));
623
+ if (Number.isNaN(date.getTime())) throw new Error("Expected valid date but received invalid date");
624
+ return date;
625
+ }
626
+ /** Coerce a value to a Date, returning null for nullish values. */
627
+ function toDateOrNull(val) {
628
+ if (val === null || val === void 0) return null;
629
+ return val instanceof Date ? val : new Date(String(val));
630
+ }
631
+ /** Parse a JSON string, returning the original value if parsing fails. */
632
+ function parseJson(value) {
633
+ if (value === null || value === void 0) return null;
634
+ if (typeof value === "string") try {
635
+ return JSON.parse(value);
636
+ } catch {
637
+ return value;
638
+ }
639
+ return value;
640
+ }
641
+ /** Parse a JSON string and return the result only if it is an array. */
642
+ function parseJsonArray(value) {
643
+ if (value === null || value === void 0) return null;
644
+ const parsed = parseJson(value);
645
+ return Array.isArray(parsed) ? parsed : null;
646
+ }
647
+ //#endregion
648
+ //#region src/storage/domains/observability/polling.ts
649
+ const OBSERVABILITY_DELTA_POLLING_FEATURE = "observability-delta-polling";
650
+ function deltaPollingFeatureEnabled() {
651
+ return _mastra_core_features.coreFeatures.has(OBSERVABILITY_DELTA_POLLING_FEATURE);
652
+ }
653
+ function assertDeltaPollingEnabled() {
654
+ if (deltaPollingFeatureEnabled()) return;
655
+ throw new _mastra_core_error.MastraError({
656
+ id: "OBSERVABILITY_DELTA_POLLING_NOT_SUPPORTED",
657
+ domain: _mastra_core_error.ErrorDomain.MASTRA_OBSERVABILITY,
658
+ category: _mastra_core_error.ErrorCategory.SYSTEM,
659
+ text: "This storage provider does not support observability delta polling"
660
+ });
661
+ }
662
+ function encodeDeltaCursor(value) {
663
+ return String(value ?? 0);
664
+ }
665
+ function validateCursorId(cursor) {
666
+ if (!/^\d+$/.test(cursor)) throw new _mastra_core_error.MastraError({
667
+ id: "OBSERVABILITY_INVALID_DELTA_CURSOR",
668
+ domain: _mastra_core_error.ErrorDomain.MASTRA_OBSERVABILITY,
669
+ category: _mastra_core_error.ErrorCategory.USER,
670
+ text: "Invalid observability delta cursor"
671
+ });
672
+ return cursor;
673
+ }
674
+ function extendWhereClause(baseClause, extraConditions) {
675
+ const conditions = extraConditions.filter(Boolean);
676
+ if (conditions.length === 0) return baseClause;
677
+ if (!baseClause) return `WHERE ${conditions.join(" AND ")}`;
678
+ return `${baseClause} AND ${conditions.join(" AND ")}`;
679
+ }
680
+ //#endregion
681
+ //#region src/storage/domains/observability/feedback.ts
682
+ const FEEDBACK_GROUP_BY_COLUMNS = /* @__PURE__ */ new Set([
683
+ "timestamp",
684
+ "traceId",
685
+ "spanId",
686
+ "experimentId",
687
+ "entityType",
688
+ "entityId",
689
+ "entityName",
690
+ "entityVersionId",
691
+ "parentEntityVersionId",
692
+ "parentEntityType",
693
+ "parentEntityId",
694
+ "parentEntityName",
695
+ "rootEntityVersionId",
696
+ "rootEntityType",
697
+ "rootEntityId",
698
+ "rootEntityName",
699
+ "userId",
700
+ "organizationId",
701
+ "resourceId",
702
+ "runId",
703
+ "sessionId",
704
+ "threadId",
705
+ "requestId",
706
+ "environment",
707
+ "executionSource",
708
+ "serviceName",
709
+ "feedbackUserId",
710
+ "sourceId",
711
+ "feedbackSource",
712
+ "feedbackType",
713
+ "value",
714
+ "comment"
715
+ ]);
716
+ function getAggregationSql$2(aggregation, measure = "TRY_CAST(value AS DOUBLE)") {
717
+ switch (aggregation) {
718
+ case "sum": return `SUM(${measure})`;
719
+ case "avg": return `AVG(${measure})`;
720
+ case "min": return `MIN(${measure})`;
721
+ case "max": return `MAX(${measure})`;
722
+ case "count": return `CAST(COUNT(${measure}) AS DOUBLE)`;
723
+ case "last": return `arg_max(${measure}, timestamp)`;
724
+ default: return `SUM(${measure})`;
725
+ }
726
+ }
727
+ function getIntervalSql$2(interval) {
728
+ switch (interval) {
729
+ case "1m": return "1 minute";
730
+ case "5m": return "5 minutes";
731
+ case "15m": return "15 minutes";
732
+ case "1h": return "1 hour";
733
+ case "1d": return "1 day";
734
+ default: return "1 hour";
735
+ }
736
+ }
737
+ function getValidatedPercentiles$1(percentiles) {
738
+ if (!Array.isArray(percentiles) || percentiles.length === 0) throw new Error("Percentiles must include at least one value between 0 and 1.");
739
+ return percentiles.map((percentile) => {
740
+ if (!Number.isFinite(percentile) || percentile < 0 || percentile > 1) throw new Error("Percentiles must be finite numbers between 0 and 1.");
741
+ return percentile;
742
+ });
743
+ }
744
+ function buildFeedbackWhereClause(args, includeNumericGuard = false) {
745
+ const conditions = ["feedbackType = ?"];
746
+ const params = [args.feedbackType];
747
+ if (args.feedbackSource !== void 0) {
748
+ conditions.push("feedbackSource = ?");
749
+ params.push(args.feedbackSource);
750
+ }
751
+ const { clause: filterClause, params: filterParams } = buildWhereClause(args.filters, { source: "feedbackSource" });
752
+ if (filterClause) {
753
+ conditions.push(filterClause.replace("WHERE ", ""));
754
+ params.push(...filterParams);
755
+ }
756
+ if (includeNumericGuard) conditions.push("TRY_CAST(value AS DOUBLE) IS NOT NULL");
757
+ return {
758
+ clause: `WHERE ${conditions.join(" AND ")}`,
759
+ params
760
+ };
761
+ }
762
+ function resolveFeedbackGroupBy(groupBy) {
763
+ return groupBy.map((key, index) => {
764
+ const column = (0, _mastra_core_utils.parseFieldKey)(key);
765
+ if (!FEEDBACK_GROUP_BY_COLUMNS.has(column)) throw new Error(`Invalid groupBy column(s): ${key}`);
766
+ const alias = `group_by_${index}`;
767
+ return {
768
+ key,
769
+ selectSql: `${column} AS ${alias}`,
770
+ groupSql: alias
771
+ };
772
+ });
773
+ }
774
+ function toSeriesName$1(values) {
775
+ return values.map((value) => value === null || value === void 0 ? "" : String(value)).join("|");
776
+ }
777
+ function rowToFeedbackRecord(row) {
778
+ const rawValue = row.value;
779
+ let value = rawValue;
780
+ const numValue = Number(rawValue);
781
+ if (!isNaN(numValue)) value = numValue;
782
+ return {
783
+ feedbackId: row.feedbackId,
784
+ timestamp: toDate(row.timestamp),
785
+ traceId: row.traceId ?? null,
786
+ spanId: row.spanId ?? null,
787
+ experimentId: row.experimentId ?? null,
788
+ entityType: row.entityType ?? null,
789
+ entityId: row.entityId ?? null,
790
+ entityName: row.entityName ?? null,
791
+ entityVersionId: row.entityVersionId ?? null,
792
+ parentEntityVersionId: row.parentEntityVersionId ?? null,
793
+ parentEntityType: row.parentEntityType ?? null,
794
+ parentEntityId: row.parentEntityId ?? null,
795
+ parentEntityName: row.parentEntityName ?? null,
796
+ rootEntityVersionId: row.rootEntityVersionId ?? null,
797
+ rootEntityType: row.rootEntityType ?? null,
798
+ rootEntityId: row.rootEntityId ?? null,
799
+ rootEntityName: row.rootEntityName ?? null,
800
+ userId: row.userId ?? null,
801
+ organizationId: row.organizationId ?? null,
802
+ resourceId: row.resourceId ?? null,
803
+ runId: row.runId ?? null,
804
+ sessionId: row.sessionId ?? null,
805
+ threadId: row.threadId ?? null,
806
+ requestId: row.requestId ?? null,
807
+ environment: row.environment ?? null,
808
+ executionSource: row.executionSource ?? null,
809
+ serviceName: row.serviceName ?? null,
810
+ feedbackUserId: row.feedbackUserId ?? null,
811
+ sourceId: row.sourceId ?? null,
812
+ source: row.feedbackSource,
813
+ feedbackSource: row.feedbackSource,
814
+ feedbackType: row.feedbackType,
815
+ value,
816
+ comment: row.comment ?? null,
817
+ tags: parseJsonArray(row.tags),
818
+ metadata: parseJson(row.metadata),
819
+ scope: parseJson(row.scope)
820
+ };
821
+ }
822
+ function getComparisonDateRange$1(comparePeriod, timestamp) {
823
+ if (!timestamp.start || !timestamp.end) return null;
824
+ const duration = timestamp.end.getTime() - timestamp.start.getTime();
825
+ switch (comparePeriod) {
826
+ case "previous_period": return {
827
+ start: new Date(timestamp.start.getTime() - duration),
828
+ end: new Date(timestamp.end.getTime() - duration),
829
+ startExclusive: timestamp.startExclusive,
830
+ endExclusive: timestamp.endExclusive
831
+ };
832
+ case "previous_day": return {
833
+ start: /* @__PURE__ */ new Date(timestamp.start.getTime() - 864e5),
834
+ end: /* @__PURE__ */ new Date(timestamp.end.getTime() - 864e5),
835
+ startExclusive: timestamp.startExclusive,
836
+ endExclusive: timestamp.endExclusive
837
+ };
838
+ case "previous_week": return {
839
+ start: /* @__PURE__ */ new Date(timestamp.start.getTime() - 6048e5),
840
+ end: /* @__PURE__ */ new Date(timestamp.end.getTime() - 6048e5),
841
+ startExclusive: timestamp.startExclusive,
842
+ endExclusive: timestamp.endExclusive
843
+ };
844
+ }
845
+ }
846
+ /** Insert a single feedback event. */
847
+ async function createFeedback(db, args) {
848
+ const f = args.feedback;
849
+ const feedbackSource = f.feedbackSource ?? f.source ?? "";
850
+ const feedbackUserId = f.feedbackUserId ?? f.userId ?? null;
851
+ await db.execute(`INSERT INTO feedback_events (
852
+ feedbackId, timestamp, cursorId, traceId, spanId, experimentId,
853
+ entityType, entityId, entityName, entityVersionId, parentEntityVersionId, parentEntityType, parentEntityId, parentEntityName, rootEntityVersionId, rootEntityType, rootEntityId, rootEntityName,
854
+ userId, organizationId, resourceId, runId, sessionId, threadId, requestId, environment, executionSource, serviceName,
855
+ feedbackUserId, sourceId, feedbackSource, feedbackType, value, comment, tags, metadata, scope
856
+ )
857
+ VALUES (${[
858
+ v(f.feedbackId),
859
+ v(f.timestamp),
860
+ "nextval('feedback_events_cursor_id_seq')",
861
+ v(f.traceId),
862
+ v(f.spanId ?? null),
863
+ v(f.experimentId ?? null),
864
+ v(f.entityType ?? null),
865
+ v(f.entityId ?? null),
866
+ v(f.entityName ?? null),
867
+ v(f.entityVersionId ?? null),
868
+ v(f.parentEntityVersionId ?? null),
869
+ v(f.parentEntityType ?? null),
870
+ v(f.parentEntityId ?? null),
871
+ v(f.parentEntityName ?? null),
872
+ v(f.rootEntityVersionId ?? null),
873
+ v(f.rootEntityType ?? null),
874
+ v(f.rootEntityId ?? null),
875
+ v(f.rootEntityName ?? null),
876
+ v(f.userId ?? null),
877
+ v(f.organizationId ?? null),
878
+ v(f.resourceId ?? null),
879
+ v(f.runId ?? null),
880
+ v(f.sessionId ?? null),
881
+ v(f.threadId ?? null),
882
+ v(f.requestId ?? null),
883
+ v(f.environment ?? null),
884
+ v(f.executionSource ?? null),
885
+ v(f.serviceName ?? null),
886
+ v(feedbackUserId),
887
+ v(f.sourceId ?? null),
888
+ v(feedbackSource),
889
+ v(f.feedbackType),
890
+ v(String(f.value)),
891
+ v(f.comment ?? null),
892
+ jsonV(f.tags ?? null),
893
+ jsonV(f.metadata),
894
+ jsonV(f.scope ?? null)
895
+ ].join(", ")})
896
+ ON CONFLICT DO NOTHING`);
897
+ }
898
+ /** Insert multiple feedback events in a single statement. */
899
+ async function batchCreateFeedback(db, args) {
900
+ if (args.feedbacks.length === 0) return;
901
+ const tuples = args.feedbacks.map((f) => {
902
+ const legacyFeedback = f;
903
+ const feedbackSource = legacyFeedback.feedbackSource ?? legacyFeedback.source ?? "";
904
+ const feedbackUserId = legacyFeedback.feedbackUserId ?? legacyFeedback.userId ?? null;
905
+ return `(${[
906
+ v(legacyFeedback.feedbackId),
907
+ v(legacyFeedback.timestamp),
908
+ "nextval('feedback_events_cursor_id_seq')",
909
+ v(legacyFeedback.traceId),
910
+ v(legacyFeedback.spanId ?? null),
911
+ v(legacyFeedback.experimentId ?? null),
912
+ v(legacyFeedback.entityType ?? null),
913
+ v(legacyFeedback.entityId ?? null),
914
+ v(legacyFeedback.entityName ?? null),
915
+ v(legacyFeedback.entityVersionId ?? null),
916
+ v(legacyFeedback.parentEntityVersionId ?? null),
917
+ v(legacyFeedback.parentEntityType ?? null),
918
+ v(legacyFeedback.parentEntityId ?? null),
919
+ v(legacyFeedback.parentEntityName ?? null),
920
+ v(legacyFeedback.rootEntityVersionId ?? null),
921
+ v(legacyFeedback.rootEntityType ?? null),
922
+ v(legacyFeedback.rootEntityId ?? null),
923
+ v(legacyFeedback.rootEntityName ?? null),
924
+ v(legacyFeedback.userId ?? null),
925
+ v(legacyFeedback.organizationId ?? null),
926
+ v(legacyFeedback.resourceId ?? null),
927
+ v(legacyFeedback.runId ?? null),
928
+ v(legacyFeedback.sessionId ?? null),
929
+ v(legacyFeedback.threadId ?? null),
930
+ v(legacyFeedback.requestId ?? null),
931
+ v(legacyFeedback.environment ?? null),
932
+ v(legacyFeedback.executionSource ?? null),
933
+ v(legacyFeedback.serviceName ?? null),
934
+ v(feedbackUserId),
935
+ v(legacyFeedback.sourceId ?? null),
936
+ v(feedbackSource),
937
+ v(legacyFeedback.feedbackType),
938
+ v(String(legacyFeedback.value)),
939
+ v(legacyFeedback.comment ?? null),
940
+ jsonV(legacyFeedback.tags ?? null),
941
+ jsonV(legacyFeedback.metadata),
942
+ jsonV(legacyFeedback.scope ?? null)
943
+ ].join(", ")})`;
944
+ });
945
+ await db.execute(`INSERT INTO feedback_events (
946
+ feedbackId, timestamp, cursorId, traceId, spanId, experimentId,
947
+ entityType, entityId, entityName, entityVersionId, parentEntityVersionId, parentEntityType, parentEntityId, parentEntityName, rootEntityVersionId, rootEntityType, rootEntityId, rootEntityName,
948
+ userId, organizationId, resourceId, runId, sessionId, threadId, requestId, environment, executionSource, serviceName,
949
+ feedbackUserId, sourceId, feedbackSource, feedbackType, value, comment, tags, metadata, scope
950
+ )
951
+ VALUES ${tuples.join(",\n ")}
952
+ ON CONFLICT DO NOTHING`);
953
+ }
954
+ /** Query feedback events with filtering, ordering, and pagination. */
955
+ async function listFeedback(db, args) {
956
+ const { mode, filters, pagination, orderBy, after, limit } = _mastra_core_storage.listFeedbackArgsSchema.parse(args);
957
+ const page = Number(pagination.page);
958
+ const perPage = Number(pagination.perPage);
959
+ const { clause: filterClause, params: filterParams } = buildWhereClause(filters, { source: "feedbackSource" });
960
+ if (mode === "delta") {
961
+ assertDeltaPollingEnabled();
962
+ const streamHeadCursor = await getStreamHeadCursor$3(db);
963
+ if (after === void 0) return {
964
+ feedback: [],
965
+ delta: {
966
+ limit,
967
+ hasMore: false
968
+ },
969
+ deltaCursor: streamHeadCursor
970
+ };
971
+ const afterCursorId = validateCursorId(after);
972
+ const deltaWhereClause = extendWhereClause(filterClause, ["cursorId IS NOT NULL", `cursorId > CAST(? AS BIGINT)`]);
973
+ const rows = await db.query(`SELECT * FROM feedback_events ${deltaWhereClause} ORDER BY cursorId ASC LIMIT ?`, [
974
+ ...filterParams,
975
+ afterCursorId,
976
+ limit + 1
977
+ ]);
978
+ const visibleRows = rows.slice(0, limit).map((row) => ({
979
+ cursorId: row.cursorId,
980
+ feedback: rowToFeedbackRecord(row)
981
+ }));
982
+ return {
983
+ feedback: visibleRows.map((row) => row.feedback),
984
+ delta: {
985
+ limit,
986
+ hasMore: rows.length > limit
987
+ },
988
+ deltaCursor: visibleRows.length > 0 ? encodeDeltaCursor(visibleRows[visibleRows.length - 1]?.cursorId) : streamHeadCursor
989
+ };
990
+ }
991
+ const orderByClause = buildOrderByClause(orderBy);
992
+ const { clause: paginationClause, params: paginationParams } = buildPaginationClause({
993
+ page,
994
+ perPage
995
+ });
996
+ const currentDeltaCursor = deltaPollingFeatureEnabled() ? await getDeltaCursor$3(db, filterClause, filterParams) : void 0;
997
+ const countResult = await db.query(`SELECT COUNT(*) as total FROM feedback_events ${filterClause}`, filterParams);
998
+ const total = Number(countResult[0]?.total ?? 0);
999
+ const rows = await db.query(`SELECT * FROM feedback_events ${filterClause} ${orderByClause} ${paginationClause}`, [...filterParams, ...paginationParams]);
1000
+ return {
1001
+ pagination: {
1002
+ total,
1003
+ page,
1004
+ perPage,
1005
+ hasMore: (page + 1) * perPage < total
1006
+ },
1007
+ feedback: rows.map((row) => rowToFeedbackRecord(row)),
1008
+ ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {}
1009
+ };
1010
+ }
1011
+ async function getDeltaCursor$3(db, filterClause, filterParams) {
1012
+ const cursorId = (await db.query(`SELECT max(cursorId) AS cursorId FROM feedback_events ${filterClause}`, filterParams))[0]?.cursorId;
1013
+ if (cursorId !== null && cursorId !== void 0) return encodeDeltaCursor(cursorId);
1014
+ return encodeDeltaCursor((await db.query(`SELECT max(cursorId) AS cursorId FROM feedback_events`))[0]?.cursorId);
1015
+ }
1016
+ async function getStreamHeadCursor$3(db) {
1017
+ return encodeDeltaCursor((await db.query(`SELECT max(cursorId) AS cursorId FROM feedback_events`))[0]?.cursorId);
1018
+ }
1019
+ async function getFeedbackAggregate(db, args) {
1020
+ const aggSql = getAggregationSql$2(args.aggregation);
1021
+ const { clause, params } = buildFeedbackWhereClause(args, true);
1022
+ const rows = await db.query(`SELECT ${aggSql} AS value FROM feedback_events ${clause}`, params);
1023
+ const value = rows[0]?.value === null || rows[0]?.value === void 0 ? null : Number(rows[0]?.value);
1024
+ if (args.comparePeriod && args.filters?.timestamp) {
1025
+ const previousTimestamp = getComparisonDateRange$1(args.comparePeriod, args.filters.timestamp);
1026
+ if (previousTimestamp) {
1027
+ const previousWhere = buildFeedbackWhereClause({
1028
+ ...args,
1029
+ filters: {
1030
+ ...args.filters ?? {},
1031
+ timestamp: previousTimestamp
1032
+ }
1033
+ }, true);
1034
+ const prevRows = await db.query(`SELECT ${aggSql} AS value FROM feedback_events ${previousWhere.clause}`, previousWhere.params);
1035
+ const previousValue = prevRows[0]?.value === null || prevRows[0]?.value === void 0 ? null : Number(prevRows[0]?.value);
1036
+ let changePercent = null;
1037
+ if (previousValue !== null && previousValue !== 0 && value !== null) changePercent = (value - previousValue) / Math.abs(previousValue) * 100;
1038
+ return {
1039
+ value,
1040
+ previousValue,
1041
+ changePercent
1042
+ };
1043
+ }
1044
+ }
1045
+ return { value };
1046
+ }
1047
+ async function getFeedbackBreakdown(db, args) {
1048
+ const aggSql = getAggregationSql$2(args.aggregation);
1049
+ const { clause, params } = buildFeedbackWhereClause(args, true);
1050
+ const resolvedGroupBy = resolveFeedbackGroupBy(args.groupBy);
1051
+ const sql = `SELECT ${resolvedGroupBy.map((entry) => entry.selectSql).join(", ")}, ${aggSql} AS value FROM feedback_events ${clause} GROUP BY ${resolvedGroupBy.map((entry) => entry.groupSql).join(", ")} ORDER BY value DESC`;
1052
+ return { groups: (await db.query(sql, params)).map((row) => ({
1053
+ dimensions: Object.fromEntries(resolvedGroupBy.map((entry, index) => {
1054
+ const value = row[`group_by_${index}`];
1055
+ return [entry.key, value === null || value === void 0 ? null : String(value)];
1056
+ })),
1057
+ value: Number(row.value ?? 0)
1058
+ })) };
1059
+ }
1060
+ async function getFeedbackTimeSeries(db, args) {
1061
+ const aggSql = getAggregationSql$2(args.aggregation);
1062
+ const intervalSql = getIntervalSql$2(args.interval);
1063
+ const { clause, params } = buildFeedbackWhereClause(args, true);
1064
+ if (args.groupBy && args.groupBy.length > 0) {
1065
+ const resolvedGroupBy = resolveFeedbackGroupBy(args.groupBy);
1066
+ const sql = `
1067
+ SELECT time_bucket(INTERVAL '${intervalSql}', timestamp) AS bucket,
1068
+ ${resolvedGroupBy.map((entry) => entry.selectSql).join(", ")},
1069
+ ${aggSql} AS value
1070
+ FROM feedback_events ${clause}
1071
+ GROUP BY bucket, ${resolvedGroupBy.map((entry) => entry.groupSql).join(", ")}
1072
+ ORDER BY bucket
1073
+ `;
1074
+ const rows = await db.query(sql, params);
1075
+ const seriesMap = /* @__PURE__ */ new Map();
1076
+ for (const row of rows) {
1077
+ const groupValues = resolvedGroupBy.map((_, index) => row[`group_by_${index}`]);
1078
+ const key = JSON.stringify(groupValues);
1079
+ if (!seriesMap.has(key)) seriesMap.set(key, {
1080
+ name: toSeriesName$1(groupValues),
1081
+ points: []
1082
+ });
1083
+ seriesMap.get(key).points.push({
1084
+ timestamp: row.bucket instanceof Date ? row.bucket : new Date(String(row.bucket)),
1085
+ value: Number(row.value ?? 0)
1086
+ });
1087
+ }
1088
+ return { series: Array.from(seriesMap.values()) };
1089
+ }
1090
+ const rows = await db.query(`
1091
+ SELECT time_bucket(INTERVAL '${intervalSql}', timestamp) AS bucket,
1092
+ ${aggSql} AS value
1093
+ FROM feedback_events ${clause}
1094
+ GROUP BY bucket
1095
+ ORDER BY bucket
1096
+ `, params);
1097
+ return { series: [{
1098
+ name: args.feedbackSource ? `${args.feedbackType}|${args.feedbackSource}` : args.feedbackType,
1099
+ points: rows.map((row) => ({
1100
+ timestamp: row.bucket instanceof Date ? row.bucket : new Date(String(row.bucket)),
1101
+ value: Number(row.value ?? 0)
1102
+ }))
1103
+ }] };
1104
+ }
1105
+ async function getFeedbackPercentiles(db, args) {
1106
+ const intervalSql = getIntervalSql$2(args.interval);
1107
+ const { clause, params } = buildFeedbackWhereClause(args, true);
1108
+ const percentiles = getValidatedPercentiles$1(args.percentiles);
1109
+ const series = [];
1110
+ for (const percentile of percentiles) {
1111
+ const rows = await db.query(`
1112
+ SELECT time_bucket(INTERVAL '${intervalSql}', timestamp) AS bucket,
1113
+ percentile_cont(${percentile}) WITHIN GROUP (ORDER BY TRY_CAST(value AS DOUBLE)) AS pvalue
1114
+ FROM feedback_events ${clause}
1115
+ GROUP BY bucket
1116
+ ORDER BY bucket
1117
+ `, params);
1118
+ series.push({
1119
+ percentile,
1120
+ points: rows.map((row) => ({
1121
+ timestamp: row.bucket instanceof Date ? row.bucket : new Date(String(row.bucket)),
1122
+ value: Number(row.pvalue ?? 0)
1123
+ }))
1124
+ });
1125
+ }
1126
+ return { series };
1127
+ }
1128
+ //#endregion
1129
+ //#region src/storage/domains/observability/logs.ts
1130
+ const COLUMNS_SQL$1 = [
1131
+ "logId",
1132
+ "timestamp",
1133
+ "cursorId",
1134
+ "level",
1135
+ "message",
1136
+ "data",
1137
+ "traceId",
1138
+ "spanId",
1139
+ "entityType",
1140
+ "entityId",
1141
+ "entityName",
1142
+ "entityVersionId",
1143
+ "parentEntityVersionId",
1144
+ "parentEntityType",
1145
+ "parentEntityId",
1146
+ "parentEntityName",
1147
+ "rootEntityVersionId",
1148
+ "rootEntityType",
1149
+ "rootEntityId",
1150
+ "rootEntityName",
1151
+ "userId",
1152
+ "organizationId",
1153
+ "resourceId",
1154
+ "runId",
1155
+ "sessionId",
1156
+ "threadId",
1157
+ "requestId",
1158
+ "environment",
1159
+ "executionSource",
1160
+ "serviceName",
1161
+ "experimentId",
1162
+ "tags",
1163
+ "metadata",
1164
+ "scope"
1165
+ ].join(", ");
1166
+ function rowToLogRecord(row) {
1167
+ return {
1168
+ logId: row.logId,
1169
+ timestamp: toDate(row.timestamp),
1170
+ level: row.level,
1171
+ message: row.message,
1172
+ data: parseJson(row.data),
1173
+ traceId: row.traceId ?? null,
1174
+ spanId: row.spanId ?? null,
1175
+ entityType: row.entityType ?? null,
1176
+ entityId: row.entityId ?? null,
1177
+ entityName: row.entityName ?? null,
1178
+ entityVersionId: row.entityVersionId ?? null,
1179
+ parentEntityVersionId: row.parentEntityVersionId ?? null,
1180
+ parentEntityType: row.parentEntityType ?? null,
1181
+ parentEntityId: row.parentEntityId ?? null,
1182
+ parentEntityName: row.parentEntityName ?? null,
1183
+ rootEntityVersionId: row.rootEntityVersionId ?? null,
1184
+ rootEntityType: row.rootEntityType ?? null,
1185
+ rootEntityId: row.rootEntityId ?? null,
1186
+ rootEntityName: row.rootEntityName ?? null,
1187
+ userId: row.userId ?? null,
1188
+ organizationId: row.organizationId ?? null,
1189
+ resourceId: row.resourceId ?? null,
1190
+ runId: row.runId ?? null,
1191
+ sessionId: row.sessionId ?? null,
1192
+ threadId: row.threadId ?? null,
1193
+ requestId: row.requestId ?? null,
1194
+ environment: row.environment ?? null,
1195
+ executionSource: row.executionSource ?? null,
1196
+ serviceName: row.serviceName ?? null,
1197
+ experimentId: row.experimentId ?? null,
1198
+ tags: parseJsonArray(row.tags),
1199
+ metadata: parseJson(row.metadata),
1200
+ scope: parseJson(row.scope)
1201
+ };
1202
+ }
1203
+ /** Insert multiple log events in a single statement. */
1204
+ async function batchCreateLogs(db, args) {
1205
+ if (args.logs.length === 0) return;
1206
+ const tuples = args.logs.map((log) => {
1207
+ return `(${[
1208
+ v(log.logId),
1209
+ v(log.timestamp),
1210
+ "nextval('log_events_cursor_id_seq')",
1211
+ v(log.level),
1212
+ v(log.message),
1213
+ jsonV(log.data),
1214
+ v(log.traceId ?? null),
1215
+ v(log.spanId ?? null),
1216
+ v(log.entityType ?? null),
1217
+ v(log.entityId ?? null),
1218
+ v(log.entityName ?? null),
1219
+ v(log.entityVersionId ?? null),
1220
+ v(log.parentEntityVersionId ?? null),
1221
+ v(log.parentEntityType ?? null),
1222
+ v(log.parentEntityId ?? null),
1223
+ v(log.parentEntityName ?? null),
1224
+ v(log.rootEntityVersionId ?? null),
1225
+ v(log.rootEntityType ?? null),
1226
+ v(log.rootEntityId ?? null),
1227
+ v(log.rootEntityName ?? null),
1228
+ v(log.userId ?? null),
1229
+ v(log.organizationId ?? null),
1230
+ v(log.resourceId ?? null),
1231
+ v(log.runId ?? null),
1232
+ v(log.sessionId ?? null),
1233
+ v(log.threadId ?? null),
1234
+ v(log.requestId ?? null),
1235
+ v(log.environment ?? null),
1236
+ v(log.executionSource ?? null),
1237
+ v(log.serviceName ?? null),
1238
+ v(log.experimentId ?? null),
1239
+ jsonV(log.tags),
1240
+ jsonV(log.metadata),
1241
+ jsonV(log.scope)
1242
+ ].join(", ")})`;
1243
+ });
1244
+ await db.execute(`INSERT INTO log_events (${COLUMNS_SQL$1}) VALUES ${tuples.join(",\n")} ON CONFLICT DO NOTHING`);
1245
+ }
1246
+ /** Query log events with filtering, ordering, and pagination. */
1247
+ async function listLogs(db, args) {
1248
+ const { mode, filters, pagination, orderBy, after, limit } = _mastra_core_storage.listLogsArgsSchema.parse(args);
1249
+ const filterRecord = filters;
1250
+ const page = Number(pagination.page);
1251
+ const perPage = Number(pagination.perPage);
1252
+ const { clause: filterClause, params: filterParams } = buildWhereClause(filterRecord);
1253
+ if (mode === "delta") {
1254
+ assertDeltaPollingEnabled();
1255
+ const streamHeadCursor = await getStreamHeadCursor$2(db);
1256
+ if (after === void 0) return {
1257
+ logs: [],
1258
+ delta: {
1259
+ limit,
1260
+ hasMore: false
1261
+ },
1262
+ deltaCursor: streamHeadCursor
1263
+ };
1264
+ const afterCursorId = validateCursorId(after);
1265
+ const deltaWhereClause = extendWhereClause(filterClause, ["cursorId IS NOT NULL", `cursorId > CAST(? AS BIGINT)`]);
1266
+ const rows = await db.query(`SELECT * FROM log_events ${deltaWhereClause} ORDER BY cursorId ASC LIMIT ?`, [
1267
+ ...filterParams,
1268
+ afterCursorId,
1269
+ limit + 1
1270
+ ]);
1271
+ const visibleRows = rows.slice(0, limit).map((row) => ({
1272
+ cursorId: row.cursorId,
1273
+ log: rowToLogRecord(row)
1274
+ }));
1275
+ return {
1276
+ logs: visibleRows.map((row) => row.log),
1277
+ delta: {
1278
+ limit,
1279
+ hasMore: rows.length > limit
1280
+ },
1281
+ deltaCursor: visibleRows.length > 0 ? encodeDeltaCursor(visibleRows[visibleRows.length - 1]?.cursorId) : streamHeadCursor
1282
+ };
1283
+ }
1284
+ const orderByClause = buildOrderByClause(orderBy);
1285
+ const { clause: paginationClause, params: paginationParams } = buildPaginationClause({
1286
+ page,
1287
+ perPage
1288
+ });
1289
+ const currentDeltaCursor = deltaPollingFeatureEnabled() ? await getDeltaCursor$2(db, filterClause, filterParams) : void 0;
1290
+ const countResult = await db.query(`SELECT COUNT(*) as total FROM log_events ${filterClause}`, filterParams);
1291
+ const total = Number(countResult[0]?.total ?? 0);
1292
+ const logs = (await db.query(`SELECT * FROM log_events ${filterClause} ${orderByClause} ${paginationClause}`, [...filterParams, ...paginationParams])).map((row) => rowToLogRecord(row));
1293
+ return {
1294
+ pagination: {
1295
+ total,
1296
+ page,
1297
+ perPage,
1298
+ hasMore: (page + 1) * perPage < total
1299
+ },
1300
+ logs,
1301
+ ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {}
1302
+ };
1303
+ }
1304
+ async function getDeltaCursor$2(db, filterClause, filterParams) {
1305
+ const cursorId = (await db.query(`SELECT max(cursorId) AS cursorId FROM log_events ${filterClause}`, filterParams))[0]?.cursorId;
1306
+ if (cursorId !== null && cursorId !== void 0) return encodeDeltaCursor(cursorId);
1307
+ return encodeDeltaCursor((await db.query(`SELECT max(cursorId) AS cursorId FROM log_events`))[0]?.cursorId);
1308
+ }
1309
+ async function getStreamHeadCursor$2(db) {
1310
+ return encodeDeltaCursor((await db.query(`SELECT max(cursorId) AS cursorId FROM log_events`))[0]?.cursorId);
1311
+ }
1312
+ //#endregion
1313
+ //#region src/storage/domains/observability/metrics.ts
1314
+ function resolveDistinctColumnSql(distinctColumn) {
1315
+ if (!distinctColumn) throw new Error(`count_distinct aggregation requires a 'distinctColumn' argument`);
1316
+ if (!_mastra_core_storage.METRIC_DISTINCT_COLUMNS.includes(distinctColumn)) throw new Error(`Invalid distinctColumn: ${distinctColumn}`);
1317
+ return (0, _mastra_core_utils.parseFieldKey)(distinctColumn);
1318
+ }
1319
+ function getAggregationSql$1(aggregation, measure = "value", distinctColumn) {
1320
+ switch (aggregation) {
1321
+ case "sum": return `SUM(${measure})`;
1322
+ case "avg": return `AVG(${measure})`;
1323
+ case "min": return `MIN(${measure})`;
1324
+ case "max": return `MAX(${measure})`;
1325
+ case "count": return `CAST(COUNT(${measure}) AS DOUBLE)`;
1326
+ case "count_distinct": return `CAST(approx_count_distinct(${resolveDistinctColumnSql(distinctColumn)}) AS DOUBLE)`;
1327
+ case "last": return `arg_max(${measure}, timestamp)`;
1328
+ default: return `SUM(${measure})`;
1329
+ }
1330
+ }
1331
+ function getIntervalSql$1(interval) {
1332
+ switch (interval) {
1333
+ case "1m": return "1 minute";
1334
+ case "5m": return "5 minutes";
1335
+ case "15m": return "15 minutes";
1336
+ case "1h": return "1 hour";
1337
+ case "1d": return "1 day";
1338
+ default: return "1 hour";
1339
+ }
1340
+ }
1341
+ function buildMetricNameFilter(name) {
1342
+ if (Array.isArray(name)) return {
1343
+ clause: `name IN (${name.map(() => "?").join(", ")})`,
1344
+ params: name
1345
+ };
1346
+ return {
1347
+ clause: `name = ?`,
1348
+ params: [name]
1349
+ };
1350
+ }
1351
+ const METRIC_COLUMNS = [
1352
+ "metricId",
1353
+ "timestamp",
1354
+ "cursorId",
1355
+ "name",
1356
+ "value",
1357
+ "traceId",
1358
+ "spanId",
1359
+ "entityType",
1360
+ "entityId",
1361
+ "entityName",
1362
+ "entityVersionId",
1363
+ "parentEntityVersionId",
1364
+ "parentEntityType",
1365
+ "parentEntityId",
1366
+ "parentEntityName",
1367
+ "rootEntityVersionId",
1368
+ "rootEntityType",
1369
+ "rootEntityId",
1370
+ "rootEntityName",
1371
+ "userId",
1372
+ "organizationId",
1373
+ "resourceId",
1374
+ "runId",
1375
+ "sessionId",
1376
+ "threadId",
1377
+ "requestId",
1378
+ "environment",
1379
+ "executionSource",
1380
+ "serviceName",
1381
+ "experimentId",
1382
+ "provider",
1383
+ "model",
1384
+ "estimatedCost",
1385
+ "costUnit",
1386
+ "tags",
1387
+ "labels",
1388
+ "costMetadata",
1389
+ "metadata",
1390
+ "scope"
1391
+ ];
1392
+ const METRIC_COLUMNS_SQL = METRIC_COLUMNS.join(", ");
1393
+ const METRIC_COLUMN_SET = new Set(METRIC_COLUMNS);
1394
+ const METRIC_LABEL_ONLY_GROUP_BY_EXCLUDED = /* @__PURE__ */ new Set([
1395
+ "metadata",
1396
+ "scope",
1397
+ "costMetadata",
1398
+ "tags"
1399
+ ]);
1400
+ function buildGroupByAlias(index) {
1401
+ return `group_by_${index}`;
1402
+ }
1403
+ function toSeriesDisplayValue(value) {
1404
+ return value === null || value === void 0 ? "" : String(value);
1405
+ }
1406
+ function getCostSummarySelect(prefix = "") {
1407
+ const ref = (column) => `${prefix}${column}`;
1408
+ return [
1409
+ `SUM(${ref("estimatedCost")}) FILTER (WHERE ${ref("estimatedCost")} IS NOT NULL) AS estimatedCost`,
1410
+ `,`,
1411
+ `CASE`,
1412
+ ` WHEN COUNT(DISTINCT ${ref("costUnit")}) FILTER (WHERE ${ref("costUnit")} IS NOT NULL) = 1`,
1413
+ ` THEN MIN(${ref("costUnit")}) FILTER (WHERE ${ref("costUnit")} IS NOT NULL)`,
1414
+ ` ELSE NULL`,
1415
+ `END AS costUnit`
1416
+ ].join(" ");
1417
+ }
1418
+ function normalizeCostSummaryRow(row) {
1419
+ return {
1420
+ estimatedCost: row.estimatedCost === null || row.estimatedCost === void 0 ? null : Number(row.estimatedCost),
1421
+ costUnit: row.costUnit === null || row.costUnit === void 0 ? null : String(row.costUnit)
1422
+ };
1423
+ }
1424
+ function buildCombinedWhereClause(nameClause, nameParams, filterClause, filterParams) {
1425
+ const conditions = [nameClause];
1426
+ const params = [...nameParams];
1427
+ if (filterClause) {
1428
+ conditions.push(filterClause.replace("WHERE ", ""));
1429
+ params.push(...filterParams);
1430
+ }
1431
+ return {
1432
+ clause: `WHERE ${conditions.join(" AND ")}`,
1433
+ params
1434
+ };
1435
+ }
1436
+ function resolveGroupBy(groupBy) {
1437
+ return groupBy.map((key, index) => {
1438
+ if (METRIC_COLUMN_SET.has(key)) {
1439
+ const parsed = (0, _mastra_core_utils.parseFieldKey)(key);
1440
+ if (METRIC_LABEL_ONLY_GROUP_BY_EXCLUDED.has(parsed)) throw new Error(`Invalid groupBy column(s): ${key}`);
1441
+ return {
1442
+ kind: "column",
1443
+ key,
1444
+ selectSql: `${parsed} AS "${key}"`,
1445
+ groupSql: parsed,
1446
+ resultKey: key
1447
+ };
1448
+ }
1449
+ const labelExpr = `json_extract_string(labels, '${buildJsonPath(key).replace(/'/g, "''")}')`;
1450
+ const alias = buildGroupByAlias(index);
1451
+ return {
1452
+ kind: "label",
1453
+ key,
1454
+ selectSql: `${labelExpr} AS ${alias}`,
1455
+ groupSql: alias,
1456
+ resultKey: alias
1457
+ };
1458
+ });
1459
+ }
1460
+ function rowToMetricRecord(row) {
1461
+ return {
1462
+ metricId: row.metricId,
1463
+ timestamp: toDate(row.timestamp),
1464
+ name: row.name,
1465
+ value: Number(row.value),
1466
+ traceId: row.traceId ?? null,
1467
+ spanId: row.spanId ?? null,
1468
+ entityType: row.entityType ?? null,
1469
+ entityId: row.entityId ?? null,
1470
+ entityName: row.entityName ?? null,
1471
+ entityVersionId: row.entityVersionId ?? null,
1472
+ parentEntityVersionId: row.parentEntityVersionId ?? null,
1473
+ parentEntityType: row.parentEntityType ?? null,
1474
+ parentEntityId: row.parentEntityId ?? null,
1475
+ parentEntityName: row.parentEntityName ?? null,
1476
+ rootEntityVersionId: row.rootEntityVersionId ?? null,
1477
+ rootEntityType: row.rootEntityType ?? null,
1478
+ rootEntityId: row.rootEntityId ?? null,
1479
+ rootEntityName: row.rootEntityName ?? null,
1480
+ userId: row.userId ?? null,
1481
+ organizationId: row.organizationId ?? null,
1482
+ resourceId: row.resourceId ?? null,
1483
+ runId: row.runId ?? null,
1484
+ sessionId: row.sessionId ?? null,
1485
+ threadId: row.threadId ?? null,
1486
+ requestId: row.requestId ?? null,
1487
+ environment: row.environment ?? null,
1488
+ executionSource: row.executionSource ?? null,
1489
+ serviceName: row.serviceName ?? null,
1490
+ experimentId: row.experimentId ?? null,
1491
+ provider: row.provider ?? null,
1492
+ model: row.model ?? null,
1493
+ estimatedCost: row.estimatedCost === null || row.estimatedCost === void 0 ? null : Number(row.estimatedCost),
1494
+ costUnit: row.costUnit ?? null,
1495
+ costMetadata: parseJson(row.costMetadata),
1496
+ tags: parseJsonArray(row.tags),
1497
+ labels: parseJson(row.labels) ?? {},
1498
+ metadata: parseJson(row.metadata),
1499
+ scope: parseJson(row.scope)
1500
+ };
1501
+ }
1502
+ /** Insert multiple metric events in a single statement. */
1503
+ async function batchCreateMetrics(db, args) {
1504
+ if (args.metrics.length === 0) return;
1505
+ const tuples = args.metrics.map((m) => {
1506
+ return `(${[
1507
+ v(m.metricId),
1508
+ v(m.timestamp),
1509
+ "nextval('metric_events_cursor_id_seq')",
1510
+ v(m.name),
1511
+ v(m.value),
1512
+ v(m.traceId ?? null),
1513
+ v(m.spanId ?? null),
1514
+ v(m.entityType ?? null),
1515
+ v(m.entityId ?? null),
1516
+ v(m.entityName ?? null),
1517
+ v(m.entityVersionId ?? null),
1518
+ v(m.parentEntityVersionId ?? null),
1519
+ v(m.parentEntityType ?? null),
1520
+ v(m.parentEntityId ?? null),
1521
+ v(m.parentEntityName ?? null),
1522
+ v(m.rootEntityVersionId ?? null),
1523
+ v(m.rootEntityType ?? null),
1524
+ v(m.rootEntityId ?? null),
1525
+ v(m.rootEntityName ?? null),
1526
+ v(m.userId ?? null),
1527
+ v(m.organizationId ?? null),
1528
+ v(m.resourceId ?? null),
1529
+ v(m.runId ?? null),
1530
+ v(m.sessionId ?? null),
1531
+ v(m.threadId ?? null),
1532
+ v(m.requestId ?? null),
1533
+ v(m.environment ?? null),
1534
+ v(m.executionSource ?? null),
1535
+ v(m.serviceName ?? null),
1536
+ v(m.experimentId ?? null),
1537
+ v(m.provider ?? null),
1538
+ v(m.model ?? null),
1539
+ v(m.estimatedCost ?? null),
1540
+ v(m.costUnit ?? null),
1541
+ jsonV(m.tags ?? null),
1542
+ v(JSON.stringify(m.labels ?? {})),
1543
+ jsonV(m.costMetadata ?? null),
1544
+ jsonV(m.metadata ?? null),
1545
+ jsonV(m.scope ?? null)
1546
+ ].join(", ")})`;
1547
+ });
1548
+ await db.execute(`INSERT INTO metric_events (${METRIC_COLUMNS_SQL}) VALUES ${tuples.join(",\n")} ON CONFLICT DO NOTHING`);
1549
+ }
1550
+ /** Query metric events with filtering, ordering, and pagination. */
1551
+ async function listMetrics(db, args) {
1552
+ const { mode, filters, pagination, orderBy, after, limit } = _mastra_core_storage.listMetricsArgsSchema.parse(args);
1553
+ const filterRecord = filters;
1554
+ const page = Number(pagination.page);
1555
+ const perPage = Number(pagination.perPage);
1556
+ const { clause: filterClause, params: filterParams } = buildWhereClause(filterRecord);
1557
+ if (mode === "delta") {
1558
+ assertDeltaPollingEnabled();
1559
+ const streamHeadCursor = await getStreamHeadCursor$1(db);
1560
+ if (after === void 0) return {
1561
+ metrics: [],
1562
+ delta: {
1563
+ limit,
1564
+ hasMore: false
1565
+ },
1566
+ deltaCursor: streamHeadCursor
1567
+ };
1568
+ const afterCursorId = validateCursorId(after);
1569
+ const deltaWhereClause = extendWhereClause(filterClause, ["cursorId IS NOT NULL", `cursorId > CAST(? AS BIGINT)`]);
1570
+ const rows = await db.query(`SELECT * FROM metric_events ${deltaWhereClause} ORDER BY cursorId ASC LIMIT ?`, [
1571
+ ...filterParams,
1572
+ afterCursorId,
1573
+ limit + 1
1574
+ ]);
1575
+ const visibleRows = rows.slice(0, limit).map((row) => ({
1576
+ cursorId: row.cursorId,
1577
+ metric: rowToMetricRecord(row)
1578
+ }));
1579
+ return {
1580
+ metrics: visibleRows.map((row) => row.metric),
1581
+ delta: {
1582
+ limit,
1583
+ hasMore: rows.length > limit
1584
+ },
1585
+ deltaCursor: visibleRows.length > 0 ? encodeDeltaCursor(visibleRows[visibleRows.length - 1]?.cursorId) : streamHeadCursor
1586
+ };
1587
+ }
1588
+ const orderByClause = buildOrderByClause(orderBy);
1589
+ const { clause: paginationClause, params: paginationParams } = buildPaginationClause({
1590
+ page,
1591
+ perPage
1592
+ });
1593
+ const currentDeltaCursor = deltaPollingFeatureEnabled() ? await getDeltaCursor$1(db, filterClause, filterParams) : void 0;
1594
+ const countResult = await db.query(`SELECT COUNT(*) AS total FROM metric_events ${filterClause}`, filterParams);
1595
+ const total = Number(countResult[0]?.total ?? 0);
1596
+ const rows = await db.query(`SELECT * FROM metric_events ${filterClause} ${orderByClause} ${paginationClause}`, [...filterParams, ...paginationParams]);
1597
+ return {
1598
+ pagination: {
1599
+ total,
1600
+ page,
1601
+ perPage,
1602
+ hasMore: (page + 1) * perPage < total
1603
+ },
1604
+ metrics: rows.map((row) => rowToMetricRecord(row)),
1605
+ ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {}
1606
+ };
1607
+ }
1608
+ async function getDeltaCursor$1(db, filterClause, filterParams) {
1609
+ const cursorId = (await db.query(`SELECT max(cursorId) AS cursorId FROM metric_events ${filterClause}`, filterParams))[0]?.cursorId;
1610
+ if (cursorId !== null && cursorId !== void 0) return encodeDeltaCursor(cursorId);
1611
+ return encodeDeltaCursor((await db.query(`SELECT max(cursorId) AS cursorId FROM metric_events`))[0]?.cursorId);
1612
+ }
1613
+ async function getStreamHeadCursor$1(db) {
1614
+ return encodeDeltaCursor((await db.query(`SELECT max(cursorId) AS cursorId FROM metric_events`))[0]?.cursorId);
1615
+ }
1616
+ /** Compute an aggregate value (sum, avg, min, max, etc.) for a metric, with optional period comparison. */
1617
+ async function getMetricAggregate(db, args) {
1618
+ const aggSql = getAggregationSql$1(args.aggregation, "value", args.distinctColumn);
1619
+ const { clause: nameClause, params: nameParams } = buildMetricNameFilter(args.name);
1620
+ const { clause: filterClause, params: filterParams } = buildWhereClause(args.filters);
1621
+ const { clause: whereClause, params: allParams } = buildCombinedWhereClause(nameClause, nameParams, filterClause, filterParams);
1622
+ const sql = `SELECT ${aggSql} AS value, ${getCostSummarySelect()} FROM metric_events ${whereClause}`;
1623
+ const row = (await db.query(sql, allParams))[0] ?? {};
1624
+ const value = row.value === null || row.value === void 0 ? null : Number(row.value);
1625
+ const costSummary = normalizeCostSummaryRow(row);
1626
+ if (args.comparePeriod && args.filters?.timestamp) {
1627
+ const ts = args.filters.timestamp;
1628
+ if (ts.start && ts.end) {
1629
+ const duration = ts.end.getTime() - ts.start.getTime();
1630
+ let prevStart;
1631
+ let prevEnd;
1632
+ switch (args.comparePeriod) {
1633
+ case "previous_period":
1634
+ prevStart = new Date(ts.start.getTime() - duration);
1635
+ prevEnd = new Date(ts.end.getTime() - duration);
1636
+ break;
1637
+ case "previous_day":
1638
+ prevStart = /* @__PURE__ */ new Date(ts.start.getTime() - 864e5);
1639
+ prevEnd = /* @__PURE__ */ new Date(ts.end.getTime() - 864e5);
1640
+ break;
1641
+ case "previous_week":
1642
+ prevStart = /* @__PURE__ */ new Date(ts.start.getTime() - 6048e5);
1643
+ prevEnd = /* @__PURE__ */ new Date(ts.end.getTime() - 6048e5);
1644
+ break;
1645
+ default:
1646
+ prevStart = new Date(ts.start.getTime() - duration);
1647
+ prevEnd = new Date(ts.end.getTime() - duration);
1648
+ }
1649
+ const { clause: prevFilterClause, params: prevFilterParams } = buildWhereClause({
1650
+ ...args.filters ?? {},
1651
+ timestamp: {
1652
+ start: prevStart,
1653
+ end: prevEnd,
1654
+ startExclusive: ts.startExclusive,
1655
+ endExclusive: ts.endExclusive
1656
+ }
1657
+ });
1658
+ const { clause: prevWhereClause, params: prevParams } = buildCombinedWhereClause(nameClause, nameParams, prevFilterClause, prevFilterParams);
1659
+ const prevSql = `SELECT ${aggSql} AS value, ${getCostSummarySelect()} FROM metric_events ${prevWhereClause}`;
1660
+ const prevRow = (await db.query(prevSql, prevParams))[0] ?? {};
1661
+ const previousValue = prevRow.value === null || prevRow.value === void 0 ? null : Number(prevRow.value);
1662
+ const previousCostSummary = normalizeCostSummaryRow(prevRow);
1663
+ let changePercent = null;
1664
+ if (previousValue !== null && previousValue !== 0 && value !== null) changePercent = (value - previousValue) / Math.abs(previousValue) * 100;
1665
+ let costChangePercent = null;
1666
+ if (previousCostSummary.estimatedCost !== null && previousCostSummary.estimatedCost !== 0 && costSummary.estimatedCost !== null) costChangePercent = (costSummary.estimatedCost - previousCostSummary.estimatedCost) / Math.abs(previousCostSummary.estimatedCost) * 100;
1667
+ return {
1668
+ value,
1669
+ estimatedCost: costSummary.estimatedCost,
1670
+ costUnit: costSummary.costUnit,
1671
+ previousValue,
1672
+ previousEstimatedCost: previousCostSummary.estimatedCost,
1673
+ changePercent,
1674
+ costChangePercent
1675
+ };
1676
+ }
1677
+ }
1678
+ return {
1679
+ value,
1680
+ estimatedCost: costSummary.estimatedCost,
1681
+ costUnit: costSummary.costUnit
1682
+ };
1683
+ }
1684
+ /** Aggregate a metric grouped by one or more dimensions. */
1685
+ async function getMetricBreakdown(db, args) {
1686
+ const aggSql = getAggregationSql$1(args.aggregation, "value", args.distinctColumn);
1687
+ const { clause: nameClause, params: nameParams } = buildMetricNameFilter(args.name);
1688
+ const { clause: filterClause, params: filterParams } = buildWhereClause(args.filters);
1689
+ const { clause: whereClause, params: allParams } = buildCombinedWhereClause(nameClause, nameParams, filterClause, filterParams);
1690
+ const resolvedGroupBy = resolveGroupBy(args.groupBy);
1691
+ const selectGroupBy = resolvedGroupBy.map((entry) => entry.selectSql).join(", ");
1692
+ const groupByCols = resolvedGroupBy.map((entry) => entry.groupSql).join(", ");
1693
+ const orderDirection = args.orderDirection === "ASC" ? "ASC" : "DESC";
1694
+ const limitClause = typeof args.limit === "number" ? `LIMIT ?` : "";
1695
+ const limitParams = typeof args.limit === "number" ? [args.limit] : [];
1696
+ const sql = `SELECT ${selectGroupBy}, ${aggSql} AS value, ${getCostSummarySelect()} FROM metric_events ${whereClause} GROUP BY ${groupByCols} ORDER BY value ${orderDirection} ${limitClause}`;
1697
+ return { groups: (await db.query(sql, [...allParams, ...limitParams])).map((row) => {
1698
+ const dimensions = {};
1699
+ for (const entry of resolvedGroupBy) {
1700
+ const value = row[entry.resultKey];
1701
+ dimensions[entry.key] = value === null || value === void 0 ? null : String(value);
1702
+ }
1703
+ const costSummary = normalizeCostSummaryRow(row);
1704
+ return {
1705
+ dimensions,
1706
+ value: Number(row.value ?? 0),
1707
+ estimatedCost: costSummary.estimatedCost,
1708
+ costUnit: costSummary.costUnit
1709
+ };
1710
+ }) };
1711
+ }
1712
+ /** Aggregate a metric into time-bucketed series, with optional group-by dimensions. */
1713
+ async function getMetricTimeSeries(db, args) {
1714
+ const aggSql = getAggregationSql$1(args.aggregation, "value", args.distinctColumn);
1715
+ const intervalSql = getIntervalSql$1(args.interval);
1716
+ const { clause: nameClause, params: nameParams } = buildMetricNameFilter(args.name);
1717
+ const { clause: filterClause, params: filterParams } = buildWhereClause(args.filters);
1718
+ const { clause: whereClause, params: allParams } = buildCombinedWhereClause(nameClause, nameParams, filterClause, filterParams);
1719
+ if (args.groupBy && args.groupBy.length > 0) {
1720
+ const resolvedGroupBy = resolveGroupBy(args.groupBy);
1721
+ const selectGroupBy = resolvedGroupBy.map((entry) => entry.selectSql).join(", ");
1722
+ const groupByCols = resolvedGroupBy.map((entry) => entry.groupSql).join(", ");
1723
+ const sql = `
1724
+ SELECT time_bucket(INTERVAL '${intervalSql}', timestamp) AS bucket,
1725
+ ${selectGroupBy},
1726
+ ${aggSql} AS value,
1727
+ ${getCostSummarySelect()}
1728
+ FROM metric_events ${whereClause}
1729
+ GROUP BY bucket, ${groupByCols}
1730
+ ORDER BY bucket
1731
+ `;
1732
+ const rows = await db.query(sql, allParams);
1733
+ const seriesMap = /* @__PURE__ */ new Map();
1734
+ for (const row of rows) {
1735
+ const dimensionValues = resolvedGroupBy.map((entry) => row[entry.resultKey]);
1736
+ const seriesKey = JSON.stringify(dimensionValues);
1737
+ const name = dimensionValues.map(toSeriesDisplayValue).join("|");
1738
+ const costSummary = normalizeCostSummaryRow(row);
1739
+ if (!seriesMap.has(seriesKey)) seriesMap.set(seriesKey, {
1740
+ name,
1741
+ costUnits: /* @__PURE__ */ new Set(),
1742
+ points: []
1743
+ });
1744
+ if (costSummary.costUnit) seriesMap.get(seriesKey).costUnits.add(costSummary.costUnit);
1745
+ seriesMap.get(seriesKey).points.push({
1746
+ timestamp: row.bucket instanceof Date ? row.bucket : new Date(String(row.bucket)),
1747
+ value: Number(row.value ?? 0),
1748
+ estimatedCost: costSummary.estimatedCost
1749
+ });
1750
+ }
1751
+ return { series: Array.from(seriesMap.values()).map((series) => ({
1752
+ name: series.name,
1753
+ costUnit: series.costUnits.size === 1 ? Array.from(series.costUnits)[0] : null,
1754
+ points: series.points
1755
+ })) };
1756
+ }
1757
+ const sql = `
1758
+ SELECT time_bucket(INTERVAL '${intervalSql}', timestamp) AS bucket,
1759
+ ${aggSql} AS value,
1760
+ ${getCostSummarySelect()}
1761
+ FROM metric_events ${whereClause}
1762
+ GROUP BY bucket
1763
+ ORDER BY bucket
1764
+ `;
1765
+ const rows = await db.query(sql, allParams);
1766
+ const metricName = Array.isArray(args.name) ? args.name.join(",") : args.name;
1767
+ const overallCostUnits = new Set(rows.map((row) => row.costUnit).filter((value) => typeof value === "string"));
1768
+ return { series: [{
1769
+ name: metricName,
1770
+ costUnit: overallCostUnits.size === 1 ? Array.from(overallCostUnits)[0] : null,
1771
+ points: rows.map((row) => {
1772
+ const costSummary = normalizeCostSummaryRow(row);
1773
+ return {
1774
+ timestamp: row.bucket instanceof Date ? row.bucket : new Date(String(row.bucket)),
1775
+ value: Number(row.value ?? 0),
1776
+ estimatedCost: costSummary.estimatedCost
1777
+ };
1778
+ })
1779
+ }] };
1780
+ }
1781
+ /** Compute percentile time series for a metric using `percentile_cont`. */
1782
+ async function getMetricPercentiles(db, args) {
1783
+ const intervalSql = getIntervalSql$1(args.interval);
1784
+ const { clause: filterClause, params: filterParams } = buildWhereClause(args.filters);
1785
+ const allConditions = [`name = ?`];
1786
+ const allParams = [args.name];
1787
+ if (filterClause) {
1788
+ allConditions.push(filterClause.replace("WHERE ", ""));
1789
+ allParams.push(...filterParams);
1790
+ }
1791
+ const whereClause = `WHERE ${allConditions.join(" AND ")}`;
1792
+ const series = [];
1793
+ for (const p of args.percentiles) {
1794
+ const sql = `
1795
+ SELECT time_bucket(INTERVAL '${intervalSql}', timestamp) AS bucket,
1796
+ percentile_cont(${p}) WITHIN GROUP (ORDER BY value) AS pvalue
1797
+ FROM metric_events ${whereClause}
1798
+ GROUP BY bucket
1799
+ ORDER BY bucket
1800
+ `;
1801
+ const rows = await db.query(sql, allParams);
1802
+ series.push({
1803
+ percentile: p,
1804
+ points: rows.map((row) => ({
1805
+ timestamp: row.bucket instanceof Date ? row.bucket : new Date(String(row.bucket)),
1806
+ value: Number(row.pvalue ?? 0)
1807
+ }))
1808
+ });
1809
+ }
1810
+ return { series };
1811
+ }
1812
+ /** Return distinct metric names, optionally filtered by prefix. */
1813
+ async function getMetricNames(db, args) {
1814
+ const conditions = [];
1815
+ const params = [];
1816
+ if (args.prefix) {
1817
+ conditions.push(`name LIKE ?`);
1818
+ params.push(`${args.prefix}%`);
1819
+ }
1820
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1821
+ const limitClause = args.limit ? `LIMIT ?` : "";
1822
+ if (args.limit) params.push(args.limit);
1823
+ return { names: (await db.query(`SELECT DISTINCT name FROM metric_events ${whereClause} ORDER BY name ${limitClause}`, params)).map((r) => r.name) };
1824
+ }
1825
+ /** Return distinct label keys for a given metric name. */
1826
+ async function getMetricLabelKeys(db, args) {
1827
+ return { keys: (await db.query(`SELECT DISTINCT unnest(json_keys(labels)) AS key FROM metric_events WHERE name = ? AND labels IS NOT NULL`, [args.metricName])).map((r) => r.key) };
1828
+ }
1829
+ /** Return distinct values for a specific label key on a metric. */
1830
+ async function getMetricLabelValues(db, args) {
1831
+ const labelPath = buildJsonPath(args.labelKey);
1832
+ const conditions = [`name = ?`, `json_extract_string(labels, ?) IS NOT NULL`];
1833
+ const params = [args.metricName, labelPath];
1834
+ if (args.prefix) {
1835
+ conditions.push(`json_extract_string(labels, ?) LIKE ?`);
1836
+ params.push(labelPath, `${args.prefix}%`);
1837
+ }
1838
+ const limitClause = args.limit ? `LIMIT ?` : "";
1839
+ if (args.limit) params.push(args.limit);
1840
+ return { values: (await db.query(`SELECT DISTINCT json_extract_string(labels, ?) AS val FROM metric_events WHERE ${conditions.join(" AND ")} ORDER BY val ${limitClause}`, [labelPath, ...params])).map((r) => r.val) };
1841
+ }
1842
+ //#endregion
1843
+ //#region src/storage/domains/observability/migration.ts
1844
+ const CURSOR_ID_TABLES = [
1845
+ "span_events",
1846
+ "metric_events",
1847
+ "log_events",
1848
+ "score_events",
1849
+ "feedback_events"
1850
+ ];
1851
+ /**
1852
+ * Drop any leftover `DEFAULT nextval(...)` on observability `cursorId` columns.
1853
+ *
1854
+ * A previous version of the migration set this default via
1855
+ * `ALTER COLUMN cursorId SET DEFAULT nextval(...)`. DuckDB WAL replay cannot
1856
+ * bind that function expression before the default database is attached, so
1857
+ * affected databases fail to reopen. Insert paths now write cursor IDs
1858
+ * explicitly, so the catalog default is unnecessary and should be removed.
1859
+ *
1860
+ * We query `information_schema` first and only emit the `ALTER` for tables that
1861
+ * actually carry the bad default; this avoids writing redundant SetDefault
1862
+ * entries to the WAL on every startup for healthy databases.
1863
+ */
1864
+ async function dropLegacyCursorIdDefaults(db) {
1865
+ const rows = await db.query(`SELECT table_name FROM information_schema.columns
1866
+ WHERE column_name = 'cursorId'
1867
+ AND column_default IS NOT NULL
1868
+ AND table_name IN (${CURSOR_ID_TABLES.map((t) => `'${t}'`).join(", ")})`);
1869
+ if (rows.length === 0) return;
1870
+ await db.executeBatch(rows.map((row) => `ALTER TABLE ${row.table_name} ALTER COLUMN cursorId DROP DEFAULT`));
1871
+ }
1872
+ const SIGNAL_MIGRATIONS = [
1873
+ {
1874
+ table: "metric_events",
1875
+ createDDL: METRIC_EVENTS_DDL,
1876
+ idColumn: "metricId",
1877
+ cursorSequenceDDL: METRIC_EVENTS_CURSOR_SEQUENCE_DDL
1878
+ },
1879
+ {
1880
+ table: "log_events",
1881
+ createDDL: LOG_EVENTS_DDL,
1882
+ idColumn: "logId",
1883
+ cursorSequenceDDL: LOG_EVENTS_CURSOR_SEQUENCE_DDL
1884
+ },
1885
+ {
1886
+ table: "score_events",
1887
+ createDDL: SCORE_EVENTS_DDL,
1888
+ idColumn: "scoreId",
1889
+ cursorSequenceDDL: SCORE_EVENTS_CURSOR_SEQUENCE_DDL
1890
+ },
1891
+ {
1892
+ table: "feedback_events",
1893
+ createDDL: FEEDBACK_EVENTS_DDL,
1894
+ idColumn: "feedbackId",
1895
+ cursorSequenceDDL: FEEDBACK_EVENTS_CURSOR_SEQUENCE_DDL
1896
+ }
1897
+ ];
1898
+ async function tableExists(db, table) {
1899
+ return (await db.query(`SELECT table_name FROM information_schema.tables WHERE table_name = ?`, [table])).length > 0;
1900
+ }
1901
+ async function hasPrimaryKey(db, table) {
1902
+ return (await db.query(`SELECT constraint_type FROM information_schema.table_constraints
1903
+ WHERE table_name = ? AND constraint_type = 'PRIMARY KEY'`, [table])).length > 0;
1904
+ }
1905
+ async function getColumns(db, table) {
1906
+ return (await db.query(`SELECT column_name FROM information_schema.columns WHERE table_name = ?`, [table])).map((r) => r.column_name);
1907
+ }
1908
+ function buildTemporaryTableDDL(createDDL, table, tempTable) {
1909
+ return createDDL.replace(`CREATE TABLE IF NOT EXISTS ${table}`, `CREATE TABLE ${tempTable}`);
1910
+ }
1911
+ async function dropTableIfExists(db, table) {
1912
+ if (await tableExists(db, table)) await db.execute(`DROP TABLE ${table}`);
1913
+ }
1914
+ function createMigrationError(args, error) {
1915
+ return new _mastra_core_error.MastraError({
1916
+ id: (0, _mastra_core_storage.createStorageErrorId)("DUCKDB", "MIGRATE_SIGNAL_TABLES", "FAILED"),
1917
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
1918
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
1919
+ details: args
1920
+ }, error);
1921
+ }
1922
+ async function checkSignalTablesMigrationStatus(db) {
1923
+ const tables = [];
1924
+ for (const { table, idColumn } of SIGNAL_MIGRATIONS) {
1925
+ if (!await tableExists(db, table)) continue;
1926
+ if (await hasPrimaryKey(db, table)) continue;
1927
+ tables.push({
1928
+ table,
1929
+ idColumn
1930
+ });
1931
+ }
1932
+ return {
1933
+ needsMigration: tables.length > 0,
1934
+ tables
1935
+ };
1936
+ }
1937
+ /**
1938
+ * Migrate signal tables to a schema with PRIMARY KEY + NOT NULL on the signal ID
1939
+ * without dropping data. Copy-and-swap: create temp → INSERT…SELECT
1940
+ * (generating IDs) → rename old to backup → rename temp to live → drop backup.
1941
+ * The live table is only touched during the final swap step.
1942
+ */
1943
+ async function migrateSignalTables(db, logger) {
1944
+ for (const { table, createDDL, idColumn, cursorSequenceDDL } of SIGNAL_MIGRATIONS) {
1945
+ if (!await tableExists(db, table)) continue;
1946
+ if (await hasPrimaryKey(db, table)) continue;
1947
+ logger?.info?.(`Migrating ${table} to schema with ${idColumn} PRIMARY KEY`);
1948
+ const temp = `${table}_migrating_${Date.now()}`;
1949
+ const backup = `${table}_backup_${Date.now()}`;
1950
+ let originalRenamed = false;
1951
+ let swapCompleted = false;
1952
+ try {
1953
+ await db.execute(cursorSequenceDDL);
1954
+ await db.execute(buildTemporaryTableDDL(createDDL, table, temp));
1955
+ const newColumns = await getColumns(db, temp);
1956
+ const currentColumns = new Set(await getColumns(db, table));
1957
+ const columnList = newColumns.map((c) => `"${c}"`).join(", ");
1958
+ const selectExprs = newColumns.map((c) => {
1959
+ if (c === idColumn) return currentColumns.has(c) ? `COALESCE(NULLIF("${c}", ''), CAST(uuid() AS VARCHAR)) AS "${c}"` : `CAST(uuid() AS VARCHAR) AS "${c}"`;
1960
+ return currentColumns.has(c) ? `"${c}"` : `NULL AS "${c}"`;
1961
+ }).join(", ");
1962
+ await db.execute(`INSERT INTO ${temp} (${columnList}) SELECT ${selectExprs} FROM ${table}`);
1963
+ await db.execute(`ALTER TABLE ${table} RENAME TO ${backup}`);
1964
+ originalRenamed = true;
1965
+ await db.execute(`ALTER TABLE ${temp} RENAME TO ${table}`);
1966
+ swapCompleted = true;
1967
+ try {
1968
+ await db.execute(`DROP TABLE ${backup}`);
1969
+ } catch (cleanupError) {
1970
+ logger?.warn?.(`Migration of ${table} completed, but failed to drop backup ${backup}: ${cleanupError.message}`);
1971
+ }
1972
+ logger?.info?.(`Successfully migrated ${table}`);
1973
+ } catch (error) {
1974
+ logger?.error?.(`Migration of ${table} failed: ${error.message}`);
1975
+ try {
1976
+ await dropTableIfExists(db, temp);
1977
+ } catch (restoreError) {
1978
+ logger?.error?.(`Failed to clean up temporary table ${temp}: ${restoreError.message}`);
1979
+ }
1980
+ if (originalRenamed && !swapCompleted) try {
1981
+ await db.execute(`ALTER TABLE ${backup} RENAME TO ${table}`);
1982
+ } catch (restoreError) {
1983
+ logger?.error?.(`Failed to restore original table ${table} from backup ${backup}: ${restoreError.message}`);
1984
+ }
1985
+ throw createMigrationError({
1986
+ table,
1987
+ idColumn
1988
+ }, error);
1989
+ }
1990
+ }
1991
+ }
1992
+ //#endregion
1993
+ //#region src/storage/domains/observability/scores.ts
1994
+ const SCORE_GROUP_BY_COLUMNS = /* @__PURE__ */ new Set([
1995
+ "timestamp",
1996
+ "traceId",
1997
+ "spanId",
1998
+ "experimentId",
1999
+ "scoreTraceId",
2000
+ "entityType",
2001
+ "entityId",
2002
+ "entityName",
2003
+ "entityVersionId",
2004
+ "parentEntityVersionId",
2005
+ "parentEntityType",
2006
+ "parentEntityId",
2007
+ "parentEntityName",
2008
+ "rootEntityVersionId",
2009
+ "rootEntityType",
2010
+ "rootEntityId",
2011
+ "rootEntityName",
2012
+ "userId",
2013
+ "organizationId",
2014
+ "resourceId",
2015
+ "runId",
2016
+ "sessionId",
2017
+ "threadId",
2018
+ "requestId",
2019
+ "environment",
2020
+ "executionSource",
2021
+ "serviceName",
2022
+ "scorerId",
2023
+ "scorerVersion",
2024
+ "scoreSource",
2025
+ "score",
2026
+ "reason"
2027
+ ]);
2028
+ function getAggregationSql(aggregation, measure = "score") {
2029
+ switch (aggregation) {
2030
+ case "sum": return `SUM(${measure})`;
2031
+ case "avg": return `AVG(${measure})`;
2032
+ case "min": return `MIN(${measure})`;
2033
+ case "max": return `MAX(${measure})`;
2034
+ case "count": return `CAST(COUNT(${measure}) AS DOUBLE)`;
2035
+ case "last": return `arg_max(${measure}, timestamp)`;
2036
+ default: return `SUM(${measure})`;
2037
+ }
2038
+ }
2039
+ function getIntervalSql(interval) {
2040
+ switch (interval) {
2041
+ case "1m": return "1 minute";
2042
+ case "5m": return "5 minutes";
2043
+ case "15m": return "15 minutes";
2044
+ case "1h": return "1 hour";
2045
+ case "1d": return "1 day";
2046
+ default: return "1 hour";
2047
+ }
2048
+ }
2049
+ function getValidatedPercentiles(percentiles) {
2050
+ if (!Array.isArray(percentiles) || percentiles.length === 0) throw new Error("Percentiles must include at least one value between 0 and 1.");
2051
+ return percentiles.map((percentile) => {
2052
+ if (!Number.isFinite(percentile) || percentile < 0 || percentile > 1) throw new Error("Percentiles must be finite numbers between 0 and 1.");
2053
+ return percentile;
2054
+ });
2055
+ }
2056
+ function buildScoreWhereClause(args) {
2057
+ const conditions = ["scorerId = ?"];
2058
+ const params = [args.scorerId];
2059
+ if (args.scoreSource !== void 0) {
2060
+ conditions.push("scoreSource = ?");
2061
+ params.push(args.scoreSource);
2062
+ }
2063
+ const { clause: filterClause, params: filterParams } = buildWhereClause(args.filters, { source: "scoreSource" });
2064
+ if (filterClause) {
2065
+ conditions.push(filterClause.replace("WHERE ", ""));
2066
+ params.push(...filterParams);
2067
+ }
2068
+ return {
2069
+ clause: `WHERE ${conditions.join(" AND ")}`,
2070
+ params
2071
+ };
2072
+ }
2073
+ function resolveScoreGroupBy(groupBy) {
2074
+ return groupBy.map((key, index) => {
2075
+ const column = (0, _mastra_core_utils.parseFieldKey)(key);
2076
+ if (!SCORE_GROUP_BY_COLUMNS.has(column)) throw new Error(`Invalid groupBy column(s): ${key}`);
2077
+ const alias = `group_by_${index}`;
2078
+ return {
2079
+ key,
2080
+ selectSql: `${column} AS ${alias}`,
2081
+ groupSql: alias
2082
+ };
2083
+ });
2084
+ }
2085
+ function toSeriesName(values) {
2086
+ return values.map((value) => value === null || value === void 0 ? "" : String(value)).join("|");
2087
+ }
2088
+ function rowToScoreRecord(row) {
2089
+ return {
2090
+ scoreId: row.scoreId,
2091
+ timestamp: toDate(row.timestamp),
2092
+ traceId: row.traceId ?? null,
2093
+ spanId: row.spanId ?? null,
2094
+ experimentId: row.experimentId ?? null,
2095
+ scoreTraceId: row.scoreTraceId ?? null,
2096
+ entityType: row.entityType ?? null,
2097
+ entityId: row.entityId ?? null,
2098
+ entityName: row.entityName ?? null,
2099
+ entityVersionId: row.entityVersionId ?? null,
2100
+ parentEntityVersionId: row.parentEntityVersionId ?? null,
2101
+ parentEntityType: row.parentEntityType ?? null,
2102
+ parentEntityId: row.parentEntityId ?? null,
2103
+ parentEntityName: row.parentEntityName ?? null,
2104
+ rootEntityVersionId: row.rootEntityVersionId ?? null,
2105
+ rootEntityType: row.rootEntityType ?? null,
2106
+ rootEntityId: row.rootEntityId ?? null,
2107
+ rootEntityName: row.rootEntityName ?? null,
2108
+ userId: row.userId ?? null,
2109
+ organizationId: row.organizationId ?? null,
2110
+ resourceId: row.resourceId ?? null,
2111
+ runId: row.runId ?? null,
2112
+ sessionId: row.sessionId ?? null,
2113
+ threadId: row.threadId ?? null,
2114
+ requestId: row.requestId ?? null,
2115
+ environment: row.environment ?? null,
2116
+ executionSource: row.executionSource ?? null,
2117
+ serviceName: row.serviceName ?? null,
2118
+ scorerId: row.scorerId,
2119
+ scorerVersion: row.scorerVersion ?? null,
2120
+ source: row.scoreSource ?? null,
2121
+ scoreSource: row.scoreSource ?? null,
2122
+ score: Number(row.score),
2123
+ reason: row.reason ?? null,
2124
+ tags: parseJsonArray(row.tags),
2125
+ metadata: parseJson(row.metadata),
2126
+ scope: parseJson(row.scope)
2127
+ };
2128
+ }
2129
+ function getComparisonDateRange(comparePeriod, timestamp) {
2130
+ if (!timestamp.start || !timestamp.end) return null;
2131
+ const duration = timestamp.end.getTime() - timestamp.start.getTime();
2132
+ switch (comparePeriod) {
2133
+ case "previous_period": return {
2134
+ start: new Date(timestamp.start.getTime() - duration),
2135
+ end: new Date(timestamp.end.getTime() - duration),
2136
+ startExclusive: timestamp.startExclusive,
2137
+ endExclusive: timestamp.endExclusive
2138
+ };
2139
+ case "previous_day": return {
2140
+ start: /* @__PURE__ */ new Date(timestamp.start.getTime() - 864e5),
2141
+ end: /* @__PURE__ */ new Date(timestamp.end.getTime() - 864e5),
2142
+ startExclusive: timestamp.startExclusive,
2143
+ endExclusive: timestamp.endExclusive
2144
+ };
2145
+ case "previous_week": return {
2146
+ start: /* @__PURE__ */ new Date(timestamp.start.getTime() - 6048e5),
2147
+ end: /* @__PURE__ */ new Date(timestamp.end.getTime() - 6048e5),
2148
+ startExclusive: timestamp.startExclusive,
2149
+ endExclusive: timestamp.endExclusive
2150
+ };
2151
+ }
2152
+ }
2153
+ /** Insert a single score event. */
2154
+ async function createScore(db, args) {
2155
+ const s = args.score;
2156
+ const scoreSource = s.scoreSource ?? s.source ?? null;
2157
+ await db.execute(`INSERT INTO score_events (
2158
+ scoreId, timestamp, cursorId, traceId, spanId, experimentId, scoreTraceId,
2159
+ entityType, entityId, entityName, entityVersionId, parentEntityVersionId, parentEntityType, parentEntityId, parentEntityName, rootEntityVersionId, rootEntityType, rootEntityId, rootEntityName,
2160
+ userId, organizationId, resourceId, runId, sessionId, threadId, requestId, environment, executionSource, serviceName,
2161
+ scorerId, scorerVersion, scoreSource, score, reason, tags, metadata, scope
2162
+ )
2163
+ VALUES (${[
2164
+ v(s.scoreId),
2165
+ v(s.timestamp),
2166
+ "nextval('score_events_cursor_id_seq')",
2167
+ v(s.traceId),
2168
+ v(s.spanId ?? null),
2169
+ v(s.experimentId ?? null),
2170
+ v(s.scoreTraceId ?? null),
2171
+ v(s.entityType ?? null),
2172
+ v(s.entityId ?? null),
2173
+ v(s.entityName ?? null),
2174
+ v(s.entityVersionId ?? null),
2175
+ v(s.parentEntityVersionId ?? null),
2176
+ v(s.parentEntityType ?? null),
2177
+ v(s.parentEntityId ?? null),
2178
+ v(s.parentEntityName ?? null),
2179
+ v(s.rootEntityVersionId ?? null),
2180
+ v(s.rootEntityType ?? null),
2181
+ v(s.rootEntityId ?? null),
2182
+ v(s.rootEntityName ?? null),
2183
+ v(s.userId ?? null),
2184
+ v(s.organizationId ?? null),
2185
+ v(s.resourceId ?? null),
2186
+ v(s.runId ?? null),
2187
+ v(s.sessionId ?? null),
2188
+ v(s.threadId ?? null),
2189
+ v(s.requestId ?? null),
2190
+ v(s.environment ?? null),
2191
+ v(s.executionSource ?? null),
2192
+ v(s.serviceName ?? null),
2193
+ v(s.scorerId),
2194
+ v(s.scorerVersion ?? null),
2195
+ v(scoreSource),
2196
+ v(s.score),
2197
+ v(s.reason ?? null),
2198
+ jsonV(s.tags ?? null),
2199
+ jsonV(s.metadata),
2200
+ jsonV(s.scope ?? null)
2201
+ ].join(", ")})
2202
+ ON CONFLICT DO NOTHING`);
2203
+ }
2204
+ /** Insert multiple score events in a single statement. */
2205
+ async function batchCreateScores(db, args) {
2206
+ if (args.scores.length === 0) return;
2207
+ const tuples = args.scores.map((s) => {
2208
+ const legacyScore = s;
2209
+ const scoreSource = legacyScore.scoreSource ?? legacyScore.source ?? null;
2210
+ return `(${[
2211
+ v(legacyScore.scoreId),
2212
+ v(legacyScore.timestamp),
2213
+ "nextval('score_events_cursor_id_seq')",
2214
+ v(legacyScore.traceId),
2215
+ v(legacyScore.spanId ?? null),
2216
+ v(legacyScore.experimentId ?? null),
2217
+ v(legacyScore.scoreTraceId ?? null),
2218
+ v(legacyScore.entityType ?? null),
2219
+ v(legacyScore.entityId ?? null),
2220
+ v(legacyScore.entityName ?? null),
2221
+ v(legacyScore.entityVersionId ?? null),
2222
+ v(legacyScore.parentEntityVersionId ?? null),
2223
+ v(legacyScore.parentEntityType ?? null),
2224
+ v(legacyScore.parentEntityId ?? null),
2225
+ v(legacyScore.parentEntityName ?? null),
2226
+ v(legacyScore.rootEntityVersionId ?? null),
2227
+ v(legacyScore.rootEntityType ?? null),
2228
+ v(legacyScore.rootEntityId ?? null),
2229
+ v(legacyScore.rootEntityName ?? null),
2230
+ v(legacyScore.userId ?? null),
2231
+ v(legacyScore.organizationId ?? null),
2232
+ v(legacyScore.resourceId ?? null),
2233
+ v(legacyScore.runId ?? null),
2234
+ v(legacyScore.sessionId ?? null),
2235
+ v(legacyScore.threadId ?? null),
2236
+ v(legacyScore.requestId ?? null),
2237
+ v(legacyScore.environment ?? null),
2238
+ v(legacyScore.executionSource ?? null),
2239
+ v(legacyScore.serviceName ?? null),
2240
+ v(legacyScore.scorerId),
2241
+ v(legacyScore.scorerVersion ?? null),
2242
+ v(scoreSource),
2243
+ v(legacyScore.score),
2244
+ v(legacyScore.reason ?? null),
2245
+ jsonV(legacyScore.tags ?? null),
2246
+ jsonV(legacyScore.metadata),
2247
+ jsonV(legacyScore.scope ?? null)
2248
+ ].join(", ")})`;
2249
+ });
2250
+ await db.execute(`INSERT INTO score_events (
2251
+ scoreId, timestamp, cursorId, traceId, spanId, experimentId, scoreTraceId,
2252
+ entityType, entityId, entityName, entityVersionId, parentEntityVersionId, parentEntityType, parentEntityId, parentEntityName, rootEntityVersionId, rootEntityType, rootEntityId, rootEntityName,
2253
+ userId, organizationId, resourceId, runId, sessionId, threadId, requestId, environment, executionSource, serviceName,
2254
+ scorerId, scorerVersion, scoreSource, score, reason, tags, metadata, scope
2255
+ )
2256
+ VALUES ${tuples.join(",\n ")}
2257
+ ON CONFLICT DO NOTHING`);
2258
+ }
2259
+ /** Query score events with filtering, ordering, and pagination. */
2260
+ async function listScores(db, args) {
2261
+ const { mode, filters, pagination, orderBy, after, limit } = _mastra_core_storage.listScoresArgsSchema.parse(args);
2262
+ const page = Number(pagination.page);
2263
+ const perPage = Number(pagination.perPage);
2264
+ const { clause: filterClause, params: filterParams } = buildWhereClause(filters, { source: "scoreSource" });
2265
+ if (mode === "delta") {
2266
+ assertDeltaPollingEnabled();
2267
+ const streamHeadCursor = await getStreamHeadCursor(db);
2268
+ if (after === void 0) return {
2269
+ scores: [],
2270
+ delta: {
2271
+ limit,
2272
+ hasMore: false
2273
+ },
2274
+ deltaCursor: streamHeadCursor
2275
+ };
2276
+ const afterCursorId = validateCursorId(after);
2277
+ const deltaWhereClause = extendWhereClause(filterClause, ["cursorId IS NOT NULL", `cursorId > CAST(? AS BIGINT)`]);
2278
+ const rows = await db.query(`SELECT * FROM score_events ${deltaWhereClause} ORDER BY cursorId ASC LIMIT ?`, [
2279
+ ...filterParams,
2280
+ afterCursorId,
2281
+ limit + 1
2282
+ ]);
2283
+ const visibleRows = rows.slice(0, limit).map((row) => ({
2284
+ cursorId: row.cursorId,
2285
+ score: rowToScoreRecord(row)
2286
+ }));
2287
+ return {
2288
+ scores: visibleRows.map((row) => row.score),
2289
+ delta: {
2290
+ limit,
2291
+ hasMore: rows.length > limit
2292
+ },
2293
+ deltaCursor: visibleRows.length > 0 ? encodeDeltaCursor(visibleRows[visibleRows.length - 1]?.cursorId) : streamHeadCursor
2294
+ };
2295
+ }
2296
+ const orderByClause = buildOrderByClause(orderBy);
2297
+ const { clause: paginationClause, params: paginationParams } = buildPaginationClause({
2298
+ page,
2299
+ perPage
2300
+ });
2301
+ const currentDeltaCursor = deltaPollingFeatureEnabled() ? await getDeltaCursor(db, filterClause, filterParams) : void 0;
2302
+ const countResult = await db.query(`SELECT COUNT(*) as total FROM score_events ${filterClause}`, filterParams);
2303
+ const total = Number(countResult[0]?.total ?? 0);
2304
+ const rows = await db.query(`SELECT * FROM score_events ${filterClause} ${orderByClause} ${paginationClause}`, [...filterParams, ...paginationParams]);
2305
+ return {
2306
+ pagination: {
2307
+ total,
2308
+ page,
2309
+ perPage,
2310
+ hasMore: (page + 1) * perPage < total
2311
+ },
2312
+ scores: rows.map((row) => rowToScoreRecord(row)),
2313
+ ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {}
2314
+ };
2315
+ }
2316
+ async function getDeltaCursor(db, filterClause, filterParams) {
2317
+ const cursorId = (await db.query(`SELECT max(cursorId) AS cursorId FROM score_events ${filterClause}`, filterParams))[0]?.cursorId;
2318
+ if (cursorId !== null && cursorId !== void 0) return encodeDeltaCursor(cursorId);
2319
+ return encodeDeltaCursor((await db.query(`SELECT max(cursorId) AS cursorId FROM score_events`))[0]?.cursorId);
2320
+ }
2321
+ async function getStreamHeadCursor(db) {
2322
+ return encodeDeltaCursor((await db.query(`SELECT max(cursorId) AS cursorId FROM score_events`))[0]?.cursorId);
2323
+ }
2324
+ async function getScoreById(db, scoreId) {
2325
+ const rows = await db.query(`SELECT * FROM score_events WHERE scoreId = ? LIMIT 1`, [scoreId]);
2326
+ return rows[0] ? rowToScoreRecord(rows[0]) : null;
2327
+ }
2328
+ async function getScoreAggregate(db, args) {
2329
+ const aggSql = getAggregationSql(args.aggregation);
2330
+ const { clause, params } = buildScoreWhereClause(args);
2331
+ const rows = await db.query(`SELECT ${aggSql} AS value FROM score_events ${clause}`, params);
2332
+ const value = rows[0]?.value === null || rows[0]?.value === void 0 ? null : Number(rows[0]?.value);
2333
+ if (args.comparePeriod && args.filters?.timestamp) {
2334
+ const previousTimestamp = getComparisonDateRange(args.comparePeriod, args.filters.timestamp);
2335
+ if (previousTimestamp) {
2336
+ const prevRows = await db.query(`SELECT ${aggSql} AS value FROM score_events ${buildScoreWhereClause({
2337
+ ...args,
2338
+ filters: {
2339
+ ...args.filters ?? {},
2340
+ timestamp: previousTimestamp
2341
+ }
2342
+ }).clause}`, buildScoreWhereClause({
2343
+ ...args,
2344
+ filters: {
2345
+ ...args.filters ?? {},
2346
+ timestamp: previousTimestamp
2347
+ }
2348
+ }).params);
2349
+ const previousValue = prevRows[0]?.value === null || prevRows[0]?.value === void 0 ? null : Number(prevRows[0]?.value);
2350
+ let changePercent = null;
2351
+ if (previousValue !== null && previousValue !== 0 && value !== null) changePercent = (value - previousValue) / Math.abs(previousValue) * 100;
2352
+ return {
2353
+ value,
2354
+ previousValue,
2355
+ changePercent
2356
+ };
2357
+ }
2358
+ }
2359
+ return { value };
2360
+ }
2361
+ async function getScoreBreakdown(db, args) {
2362
+ const aggSql = getAggregationSql(args.aggregation);
2363
+ const { clause, params } = buildScoreWhereClause(args);
2364
+ const resolvedGroupBy = resolveScoreGroupBy(args.groupBy);
2365
+ const sql = `SELECT ${resolvedGroupBy.map((entry) => entry.selectSql).join(", ")}, ${aggSql} AS value FROM score_events ${clause} GROUP BY ${resolvedGroupBy.map((entry) => entry.groupSql).join(", ")} ORDER BY value DESC`;
2366
+ return { groups: (await db.query(sql, params)).map((row) => ({
2367
+ dimensions: Object.fromEntries(resolvedGroupBy.map((entry, index) => {
2368
+ const value = row[`group_by_${index}`];
2369
+ return [entry.key, value === null || value === void 0 ? null : String(value)];
2370
+ })),
2371
+ value: Number(row.value ?? 0)
2372
+ })) };
2373
+ }
2374
+ async function getScoreTimeSeries(db, args) {
2375
+ const aggSql = getAggregationSql(args.aggregation);
2376
+ const intervalSql = getIntervalSql(args.interval);
2377
+ const { clause, params } = buildScoreWhereClause(args);
2378
+ if (args.groupBy && args.groupBy.length > 0) {
2379
+ const resolvedGroupBy = resolveScoreGroupBy(args.groupBy);
2380
+ const sql = `
2381
+ SELECT time_bucket(INTERVAL '${intervalSql}', timestamp) AS bucket,
2382
+ ${resolvedGroupBy.map((entry) => entry.selectSql).join(", ")},
2383
+ ${aggSql} AS value
2384
+ FROM score_events ${clause}
2385
+ GROUP BY bucket, ${resolvedGroupBy.map((entry) => entry.groupSql).join(", ")}
2386
+ ORDER BY bucket
2387
+ `;
2388
+ const rows = await db.query(sql, params);
2389
+ const seriesMap = /* @__PURE__ */ new Map();
2390
+ for (const row of rows) {
2391
+ const groupValues = resolvedGroupBy.map((_, index) => row[`group_by_${index}`]);
2392
+ const key = JSON.stringify(groupValues);
2393
+ if (!seriesMap.has(key)) seriesMap.set(key, {
2394
+ name: toSeriesName(groupValues),
2395
+ points: []
2396
+ });
2397
+ seriesMap.get(key).points.push({
2398
+ timestamp: row.bucket instanceof Date ? row.bucket : new Date(String(row.bucket)),
2399
+ value: Number(row.value ?? 0)
2400
+ });
2401
+ }
2402
+ return { series: Array.from(seriesMap.values()) };
2403
+ }
2404
+ const rows = await db.query(`
2405
+ SELECT time_bucket(INTERVAL '${intervalSql}', timestamp) AS bucket,
2406
+ ${aggSql} AS value
2407
+ FROM score_events ${clause}
2408
+ GROUP BY bucket
2409
+ ORDER BY bucket
2410
+ `, params);
2411
+ return { series: [{
2412
+ name: args.scoreSource ? `${args.scorerId}|${args.scoreSource}` : args.scorerId,
2413
+ points: rows.map((row) => ({
2414
+ timestamp: row.bucket instanceof Date ? row.bucket : new Date(String(row.bucket)),
2415
+ value: Number(row.value ?? 0)
2416
+ }))
2417
+ }] };
2418
+ }
2419
+ async function getScorePercentiles(db, args) {
2420
+ const intervalSql = getIntervalSql(args.interval);
2421
+ const { clause, params } = buildScoreWhereClause(args);
2422
+ const percentiles = getValidatedPercentiles(args.percentiles);
2423
+ const series = [];
2424
+ for (const percentile of percentiles) {
2425
+ const rows = await db.query(`
2426
+ SELECT time_bucket(INTERVAL '${intervalSql}', timestamp) AS bucket,
2427
+ percentile_cont(${percentile}) WITHIN GROUP (ORDER BY score) AS pvalue
2428
+ FROM score_events ${clause}
2429
+ GROUP BY bucket
2430
+ ORDER BY bucket
2431
+ `, params);
2432
+ series.push({
2433
+ percentile,
2434
+ points: rows.map((row) => ({
2435
+ timestamp: row.bucket instanceof Date ? row.bucket : new Date(String(row.bucket)),
2436
+ value: Number(row.pvalue ?? 0)
2437
+ }))
2438
+ });
2439
+ }
2440
+ return { series };
2441
+ }
2442
+ //#endregion
2443
+ //#region src/storage/domains/observability/tracing.ts
2444
+ const COLUMNS_SQL = [
2445
+ "eventType",
2446
+ "timestamp",
2447
+ "cursorId",
2448
+ "traceId",
2449
+ "spanId",
2450
+ "parentSpanId",
2451
+ "name",
2452
+ "spanType",
2453
+ "isEvent",
2454
+ "endedAt",
2455
+ "experimentId",
2456
+ "entityType",
2457
+ "entityId",
2458
+ "entityName",
2459
+ "entityVersionId",
2460
+ "userId",
2461
+ "organizationId",
2462
+ "resourceId",
2463
+ "runId",
2464
+ "sessionId",
2465
+ "threadId",
2466
+ "requestId",
2467
+ "environment",
2468
+ "source",
2469
+ "serviceName",
2470
+ "attributes",
2471
+ "metadata",
2472
+ "tags",
2473
+ "scope",
2474
+ "links",
2475
+ "input",
2476
+ "output",
2477
+ "error",
2478
+ "requestContext"
2479
+ ].join(", ");
2480
+ /**
2481
+ * Reconstruction query uses `arg_max(field, timestamp) FILTER (WHERE field IS NOT NULL)`
2482
+ * so that the final end event supplies the terminal span fields without wiping
2483
+ * stable values emitted on the start event.
2484
+ */
2485
+ function argMaxNonNull(col) {
2486
+ return `arg_max(${col}, timestamp) FILTER (WHERE ${col} IS NOT NULL) as ${col}`;
2487
+ }
2488
+ const SPAN_RECONSTRUCT_SELECT = `
2489
+ SELECT
2490
+ traceId, spanId,
2491
+ ${argMaxNonNull("name")},
2492
+ ${argMaxNonNull("spanType")},
2493
+ ${argMaxNonNull("parentSpanId")},
2494
+ ${argMaxNonNull("isEvent")},
2495
+ coalesce(min(timestamp) FILTER (WHERE eventType = 'start'), min(timestamp)) as startedAt,
2496
+ ${argMaxNonNull("endedAt")},
2497
+ ${argMaxNonNull("experimentId")},
2498
+ ${argMaxNonNull("entityType")},
2499
+ ${argMaxNonNull("entityId")},
2500
+ ${argMaxNonNull("entityName")},
2501
+ ${argMaxNonNull("entityVersionId")},
2502
+ ${argMaxNonNull("userId")},
2503
+ ${argMaxNonNull("organizationId")},
2504
+ ${argMaxNonNull("resourceId")},
2505
+ ${argMaxNonNull("runId")},
2506
+ ${argMaxNonNull("sessionId")},
2507
+ ${argMaxNonNull("threadId")},
2508
+ ${argMaxNonNull("requestId")},
2509
+ ${argMaxNonNull("environment")},
2510
+ ${argMaxNonNull("source")},
2511
+ ${argMaxNonNull("serviceName")},
2512
+ ${argMaxNonNull("attributes")},
2513
+ ${argMaxNonNull("metadata")},
2514
+ ${argMaxNonNull("tags")},
2515
+ ${argMaxNonNull("scope")},
2516
+ ${argMaxNonNull("links")},
2517
+ ${argMaxNonNull("input")},
2518
+ ${argMaxNonNull("output")},
2519
+ ${argMaxNonNull("error")},
2520
+ ${argMaxNonNull("requestContext")}
2521
+ FROM span_events
2522
+ `;
2523
+ /** Lightweight variant — only timeline-relevant columns. */
2524
+ const SPAN_RECONSTRUCT_SELECT_LIGHT = `
2525
+ SELECT
2526
+ traceId, spanId,
2527
+ ${argMaxNonNull("name")},
2528
+ ${argMaxNonNull("spanType")},
2529
+ ${argMaxNonNull("parentSpanId")},
2530
+ ${argMaxNonNull("isEvent")},
2531
+ coalesce(min(timestamp) FILTER (WHERE eventType = 'start'), min(timestamp)) as startedAt,
2532
+ ${argMaxNonNull("endedAt")},
2533
+ ${argMaxNonNull("entityType")},
2534
+ ${argMaxNonNull("entityId")},
2535
+ ${argMaxNonNull("entityName")},
2536
+ ${argMaxNonNull("error")}
2537
+ FROM span_events
2538
+ `;
2539
+ /**
2540
+ * Which reconstructed columns each post-aggregation filter key can reference.
2541
+ * `status` is derived from endedAt + error (see buildWhereClause).
2542
+ */
2543
+ const POSTAGG_FILTER_COLUMNS = {
2544
+ status: ["endedAt", "error"],
2545
+ endedAt: ["endedAt"],
2546
+ tags: ["tags"],
2547
+ metadata: ["metadata"],
2548
+ scope: ["scope"]
2549
+ };
2550
+ /**
2551
+ * Narrow reconstruction used by the slow list paths to evaluate
2552
+ * post-aggregation filters and ordering before pagination. Includes only the
2553
+ * columns the active filters and order field actually reference, so the
2554
+ * full-set aggregate never decompresses the heavy JSON payload columns
2555
+ * (attributes, links, input, output, requestContext) and skips even the
2556
+ * cheaper JSON columns (tags/metadata/scope) unless a filter needs them.
2557
+ */
2558
+ function buildPostAggReconstructSelect(postAgg, orderByField) {
2559
+ const columns = /* @__PURE__ */ new Set();
2560
+ if (orderByField === "endedAt") columns.add("endedAt");
2561
+ for (const key of Object.keys(postAgg)) for (const column of POSTAGG_FILTER_COLUMNS[key] ?? []) columns.add(column);
2562
+ return `
2563
+ SELECT
2564
+ traceId, spanId,
2565
+ ${[...columns].map((col) => `${argMaxNonNull(col)},`).join("\n ")}
2566
+ coalesce(min(timestamp) FILTER (WHERE eventType = 'start'), min(timestamp)) as startedAt
2567
+ FROM span_events
2568
+ `;
2569
+ }
2570
+ /**
2571
+ * Reconstruct spans for the `(traceId, spanId)` pairs selected by `anchorCte`,
2572
+ * scanning only events at/after the CTE's earliest `anchorStartedAt`.
2573
+ *
2574
+ * The `(traceId, spanId) IN (subquery)` semi-join alone cannot be pushed into
2575
+ * the table scan, so without a bound DuckDB decompresses every column of the
2576
+ * entire table just to emit one page of spans. The time bound is a plain range
2577
+ * predicate that zone maps can prune on (insertion order tracks event time).
2578
+ *
2579
+ * Correctness: span events are only 'start' (timestamp = startedAt) and 'end'
2580
+ * (timestamp = endedAt >= startedAt), so every event of an anchored span has
2581
+ * timestamp >= its start-row timestamp >= min(anchorStartedAt). An empty
2582
+ * anchor set makes the bound NULL, which matches the empty IN-list result.
2583
+ */
2584
+ function reconstructForAnchors(reconstructSelect, anchorCte) {
2585
+ return `
2586
+ ${reconstructSelect}
2587
+ WHERE timestamp >= (SELECT min(anchorStartedAt) FROM ${anchorCte})
2588
+ AND (traceId, spanId) IN (SELECT traceId, spanId FROM ${anchorCte})
2589
+ GROUP BY traceId, spanId`;
2590
+ }
2591
+ /**
2592
+ * Same time-bound trick as {@link reconstructForAnchors}, but with the bound
2593
+ * precomputed in JS and passed as a `?` parameter. The delta-poll query shape
2594
+ * references its candidate CTE multiple times, which makes DuckDB materialize
2595
+ * it and lose the dynamic-filter pushdown a scalar subquery bound relies on —
2596
+ * a literal parameter always reaches the scan as a plain range filter.
2597
+ */
2598
+ function reconstructForAnchorsWithBoundParam(reconstructSelect, anchorCte) {
2599
+ return `
2600
+ ${reconstructSelect}
2601
+ WHERE timestamp >= ?
2602
+ AND (traceId, spanId) IN (SELECT traceId, spanId FROM ${anchorCte})
2603
+ GROUP BY traceId, spanId`;
2604
+ }
2605
+ function rowToLightSpanRecord(row) {
2606
+ return {
2607
+ traceId: row.traceId,
2608
+ spanId: row.spanId,
2609
+ name: row.name,
2610
+ spanType: row.spanType,
2611
+ parentSpanId: row.parentSpanId ?? null,
2612
+ isEvent: row.isEvent,
2613
+ startedAt: toDate(row.startedAt),
2614
+ endedAt: toDateOrNull(row.endedAt),
2615
+ entityType: row.entityType ?? null,
2616
+ entityId: row.entityId ?? null,
2617
+ entityName: row.entityName ?? null,
2618
+ error: parseJson(row.error),
2619
+ createdAt: toDate(row.startedAt),
2620
+ updatedAt: toDateOrNull(row.endedAt)
2621
+ };
2622
+ }
2623
+ function rowToSpanRecord(row) {
2624
+ return {
2625
+ traceId: row.traceId,
2626
+ spanId: row.spanId,
2627
+ name: row.name,
2628
+ spanType: row.spanType,
2629
+ parentSpanId: row.parentSpanId ?? null,
2630
+ isEvent: row.isEvent,
2631
+ startedAt: toDate(row.startedAt),
2632
+ endedAt: toDateOrNull(row.endedAt),
2633
+ experimentId: row.experimentId ?? null,
2634
+ entityType: row.entityType ?? null,
2635
+ entityId: row.entityId ?? null,
2636
+ entityName: row.entityName ?? null,
2637
+ entityVersionId: row.entityVersionId ?? null,
2638
+ userId: row.userId ?? null,
2639
+ organizationId: row.organizationId ?? null,
2640
+ resourceId: row.resourceId ?? null,
2641
+ runId: row.runId ?? null,
2642
+ sessionId: row.sessionId ?? null,
2643
+ threadId: row.threadId ?? null,
2644
+ requestId: row.requestId ?? null,
2645
+ environment: row.environment ?? null,
2646
+ source: row.source ?? null,
2647
+ serviceName: row.serviceName ?? null,
2648
+ attributes: parseJson(row.attributes),
2649
+ metadata: parseJson(row.metadata),
2650
+ tags: parseJsonArray(row.tags),
2651
+ scope: parseJson(row.scope),
2652
+ links: parseJsonArray(row.links),
2653
+ input: parseJson(row.input),
2654
+ output: parseJson(row.output),
2655
+ error: parseJson(row.error),
2656
+ requestContext: parseJson(row.requestContext),
2657
+ createdAt: toDate(row.startedAt),
2658
+ updatedAt: null
2659
+ };
2660
+ }
2661
+ function buildHasChildErrorClause(hasChildError, rootAlias) {
2662
+ if (hasChildError === void 0) return "";
2663
+ const base = `SELECT 1 FROM span_events c WHERE c.traceId = ${rootAlias}.traceId AND c.spanId != ${rootAlias}.spanId AND c.error IS NOT NULL`;
2664
+ return hasChildError ? `EXISTS (${base})` : `NOT EXISTS (${base})`;
2665
+ }
2666
+ /**
2667
+ * Filter keys that can be evaluated directly against raw `span_events` start
2668
+ * rows. These are stable scalar columns whose value on the start row matches
2669
+ * the reconstructed span value, so pushing them down before reconstruction is
2670
+ * observation-equivalent to reconstructing first and filtering after.
2671
+ */
2672
+ const PREFILTER_KEYS = /* @__PURE__ */ new Set([
2673
+ "traceId",
2674
+ "spanId",
2675
+ "parentSpanId",
2676
+ "name",
2677
+ "spanType",
2678
+ "source",
2679
+ "entityType",
2680
+ "entityId",
2681
+ "entityName",
2682
+ "entityVersionId",
2683
+ "experimentId",
2684
+ "userId",
2685
+ "organizationId",
2686
+ "resourceId",
2687
+ "runId",
2688
+ "sessionId",
2689
+ "threadId",
2690
+ "requestId",
2691
+ "environment",
2692
+ "serviceName"
2693
+ ]);
2694
+ /**
2695
+ * Order-by fields whose start-row value matches the reconstructed root-span
2696
+ * value, so ordering inside the prefilter (before GROUP BY) yields the same
2697
+ * sequence as ordering on reconstructed rows. Anything outside this set must
2698
+ * fall back to the slow path so pagination stays correct.
2699
+ *
2700
+ * `endedAt` is intentionally excluded — start rows always have NULL `endedAt`,
2701
+ * so ordering by it on raw rows would compare NULLs and produce wrong pages.
2702
+ */
2703
+ const SAFE_PREFILTER_ORDER_FIELDS = /* @__PURE__ */ new Set(["startedAt"]);
2704
+ /**
2705
+ * Intersect the existing prefilter timestamp range with an incoming bound.
2706
+ * Each bound is an exact constraint on the start-row `timestamp`, so the
2707
+ * intersection is the **tighter** of the two on each side: the later `start`
2708
+ * wins, the earlier `end` wins. When two bounds tie on a side, the result is
2709
+ * exclusive if either input was exclusive (the union of exclusivity).
2710
+ *
2711
+ * Required for cases like `{ startedAt: { end: B }, endedAt: { end: C } }`:
2712
+ * both bound the start-row timestamp from above and we want `min(B, C)`,
2713
+ * regardless of insertion order.
2714
+ */
2715
+ function intersectTimestampRange(existing, incoming) {
2716
+ if (!existing) return { ...incoming };
2717
+ const merged = { ...existing };
2718
+ if (incoming.start !== void 0) {
2719
+ if (merged.start === void 0 || incoming.start.getTime() > merged.start.getTime()) {
2720
+ merged.start = incoming.start;
2721
+ merged.startExclusive = incoming.startExclusive;
2722
+ } else if (incoming.start.getTime() === merged.start.getTime()) merged.startExclusive = (merged.startExclusive ?? false) || (incoming.startExclusive ?? false);
2723
+ }
2724
+ if (incoming.end !== void 0) {
2725
+ if (merged.end === void 0 || incoming.end.getTime() < merged.end.getTime()) {
2726
+ merged.end = incoming.end;
2727
+ merged.endExclusive = incoming.endExclusive;
2728
+ } else if (incoming.end.getTime() === merged.end.getTime()) merged.endExclusive = (merged.endExclusive ?? false) || (incoming.endExclusive ?? false);
2729
+ }
2730
+ return merged;
2731
+ }
2732
+ /**
2733
+ * Split a span-anchor filter set into a `prefilter` half (pushed to raw
2734
+ * `span_events` start rows) and a `postAgg` half (applied after the
2735
+ * reconstruction GROUP BY). Used by both `listTraces` and `listBranches`.
2736
+ *
2737
+ * `hasChildError` is split out separately since it doesn't run via
2738
+ * `buildWhereClause` — `listTraces` wires it up via EXISTS, `listBranches`
2739
+ * never sees it (not in `branchesFilterSchema`).
2740
+ */
2741
+ function partitionAnchorFilters(filters) {
2742
+ const prefilter = {};
2743
+ const postAgg = {};
2744
+ let hasChildError;
2745
+ for (const [key, value] of Object.entries(filters)) {
2746
+ if (value === void 0 || value === null) continue;
2747
+ if (key === "hasChildError") {
2748
+ if (typeof value === "boolean") hasChildError = value;
2749
+ continue;
2750
+ }
2751
+ if (key === "startedAt") {
2752
+ prefilter.timestamp = intersectTimestampRange(prefilter.timestamp, value);
2753
+ continue;
2754
+ }
2755
+ if (key === "endedAt") {
2756
+ postAgg.endedAt = value;
2757
+ const dateRange = value;
2758
+ if (dateRange?.end) prefilter.timestamp = intersectTimestampRange(prefilter.timestamp, {
2759
+ end: dateRange.end,
2760
+ endExclusive: dateRange.endExclusive
2761
+ });
2762
+ continue;
2763
+ }
2764
+ if (PREFILTER_KEYS.has(key)) {
2765
+ prefilter[key] = value;
2766
+ continue;
2767
+ }
2768
+ postAgg[key] = value;
2769
+ }
2770
+ return {
2771
+ prefilter,
2772
+ postAgg,
2773
+ hasChildError
2774
+ };
2775
+ }
2776
+ function toValuesTuple(row) {
2777
+ return [
2778
+ v(row.eventType),
2779
+ v(row.timestamp),
2780
+ "nextval('span_events_cursor_id_seq')",
2781
+ v(row.traceId),
2782
+ v(row.spanId),
2783
+ v(row.parentSpanId),
2784
+ v(row.name),
2785
+ v(row.spanType),
2786
+ v(row.isEvent),
2787
+ v(row.endedAt),
2788
+ v(row.experimentId),
2789
+ v(row.entityType),
2790
+ v(row.entityId),
2791
+ v(row.entityName),
2792
+ v(row.entityVersionId),
2793
+ v(row.userId),
2794
+ v(row.organizationId),
2795
+ v(row.resourceId),
2796
+ v(row.runId),
2797
+ v(row.sessionId),
2798
+ v(row.threadId),
2799
+ v(row.requestId),
2800
+ v(row.environment),
2801
+ v(row.source),
2802
+ v(row.serviceName),
2803
+ jsonV(row.attributes),
2804
+ jsonV(row.metadata),
2805
+ jsonV(row.tags),
2806
+ jsonV(row.scope),
2807
+ jsonV(row.links),
2808
+ jsonV(row.input),
2809
+ jsonV(row.output),
2810
+ jsonV(row.error),
2811
+ jsonV(row.requestContext)
2812
+ ].join(", ");
2813
+ }
2814
+ async function insertSpanEvents(db, rows) {
2815
+ if (rows.length === 0) return;
2816
+ const tuples = rows.map((row) => `(${toValuesTuple(row)})`).join(",\n");
2817
+ await db.execute(`INSERT INTO span_events (${COLUMNS_SQL}) VALUES ${tuples}`);
2818
+ }
2819
+ function createStartSpanRow(s) {
2820
+ return {
2821
+ eventType: "start",
2822
+ timestamp: s.startedAt,
2823
+ traceId: s.traceId,
2824
+ spanId: s.spanId,
2825
+ parentSpanId: s.parentSpanId ?? null,
2826
+ name: s.name,
2827
+ spanType: s.spanType,
2828
+ isEvent: s.isEvent,
2829
+ endedAt: null,
2830
+ experimentId: s.experimentId ?? null,
2831
+ entityType: s.entityType ?? null,
2832
+ entityId: s.entityId ?? null,
2833
+ entityName: s.entityName ?? null,
2834
+ entityVersionId: s.entityVersionId ?? null,
2835
+ userId: s.userId ?? null,
2836
+ organizationId: s.organizationId ?? null,
2837
+ resourceId: s.resourceId ?? null,
2838
+ runId: s.runId ?? null,
2839
+ sessionId: s.sessionId ?? null,
2840
+ threadId: s.threadId ?? null,
2841
+ requestId: s.requestId ?? null,
2842
+ environment: s.environment ?? null,
2843
+ source: s.source ?? null,
2844
+ serviceName: s.serviceName ?? null,
2845
+ attributes: s.attributes ?? null,
2846
+ metadata: s.metadata ?? null,
2847
+ tags: s.tags ?? null,
2848
+ scope: s.scope ?? null,
2849
+ links: null,
2850
+ input: s.input ?? null,
2851
+ output: null,
2852
+ error: null,
2853
+ requestContext: s.requestContext ?? null
2854
+ };
2855
+ }
2856
+ function createEndSpanRow(s) {
2857
+ return {
2858
+ eventType: "end",
2859
+ timestamp: s.endedAt,
2860
+ traceId: s.traceId,
2861
+ spanId: s.spanId,
2862
+ parentSpanId: s.parentSpanId ?? null,
2863
+ name: s.name,
2864
+ spanType: s.spanType,
2865
+ isEvent: s.isEvent,
2866
+ endedAt: s.endedAt ?? null,
2867
+ experimentId: s.experimentId ?? null,
2868
+ entityType: s.entityType ?? null,
2869
+ entityId: s.entityId ?? null,
2870
+ entityName: s.entityName ?? null,
2871
+ entityVersionId: s.entityVersionId ?? null,
2872
+ userId: s.userId ?? null,
2873
+ organizationId: s.organizationId ?? null,
2874
+ resourceId: s.resourceId ?? null,
2875
+ runId: s.runId ?? null,
2876
+ sessionId: s.sessionId ?? null,
2877
+ threadId: s.threadId ?? null,
2878
+ requestId: s.requestId ?? null,
2879
+ environment: s.environment ?? null,
2880
+ source: s.source ?? null,
2881
+ serviceName: s.serviceName ?? null,
2882
+ attributes: s.attributes ?? null,
2883
+ metadata: s.metadata ?? null,
2884
+ tags: s.tags ?? null,
2885
+ scope: s.scope ?? null,
2886
+ links: s.links ?? null,
2887
+ input: s.input ?? null,
2888
+ output: s.output ?? null,
2889
+ error: s.error ?? null,
2890
+ requestContext: s.requestContext ?? null
2891
+ };
2892
+ }
2893
+ /** Insert a 'start' event for a new span. */
2894
+ async function createSpan(db, args) {
2895
+ const rows = [createStartSpanRow(args.span)];
2896
+ if (args.span.endedAt) rows.push(createEndSpanRow(args.span));
2897
+ await insertSpanEvents(db, rows);
2898
+ }
2899
+ /** Insert 'start' events for multiple spans in a single statement. */
2900
+ async function batchCreateSpans(db, args) {
2901
+ if (args.records.length === 0) return;
2902
+ await insertSpanEvents(db, args.records.flatMap((record) => {
2903
+ const events = [createStartSpanRow(record)];
2904
+ if (record.endedAt) events.push(createEndSpanRow(record));
2905
+ return events;
2906
+ }));
2907
+ }
2908
+ /** Delete all span events for the given trace IDs. */
2909
+ async function batchDeleteTraces(db, args) {
2910
+ if (args.traceIds.length === 0) return;
2911
+ const placeholders = args.traceIds.map(() => "?").join(", ");
2912
+ await db.execute(`DELETE FROM span_events WHERE traceId IN (${placeholders})`, args.traceIds);
2913
+ }
2914
+ /** Reconstruct a single span from its event history. */
2915
+ async function getSpan(db, args) {
2916
+ const rows = await db.query(`${SPAN_RECONSTRUCT_SELECT} WHERE traceId = ? AND spanId = ? GROUP BY traceId, spanId`, [args.traceId, args.spanId]);
2917
+ if (rows.length === 0) return null;
2918
+ return { span: rowToSpanRecord(rows[0]) };
2919
+ }
2920
+ /** Reconstruct the root span (no parent) for a trace. */
2921
+ async function getRootSpan(db, args) {
2922
+ const rows = await db.query(`${SPAN_RECONSTRUCT_SELECT} WHERE traceId = ? GROUP BY traceId, spanId HAVING arg_max(parentSpanId, timestamp) IS NULL LIMIT 1`, [args.traceId]);
2923
+ if (rows.length === 0) return null;
2924
+ return { span: rowToSpanRecord(rows[0]) };
2925
+ }
2926
+ /** Reconstruct all spans belonging to a trace. */
2927
+ async function getTrace(db, args) {
2928
+ const rows = await db.query(`${SPAN_RECONSTRUCT_SELECT} WHERE traceId = ? GROUP BY traceId, spanId`, [args.traceId]);
2929
+ if (rows.length === 0) return null;
2930
+ return {
2931
+ traceId: args.traceId,
2932
+ spans: rows.map((row) => rowToSpanRecord(row))
2933
+ };
2934
+ }
2935
+ /** Reconstruct lightweight spans belonging to a trace (timeline fields only). */
2936
+ async function getTraceLight(db, args) {
2937
+ const rows = await db.query(`${SPAN_RECONSTRUCT_SELECT_LIGHT} WHERE traceId = ? GROUP BY traceId, spanId`, [args.traceId]);
2938
+ if (rows.length === 0) return null;
2939
+ return {
2940
+ traceId: args.traceId,
2941
+ spans: rows.map((row) => rowToLightSpanRecord(row))
2942
+ };
2943
+ }
2944
+ /**
2945
+ * List root spans (traces) with filtering, ordering, and pagination.
2946
+ *
2947
+ * Instead of reconstructing every span in the table and then filtering, we:
2948
+ * 1. Pick candidate root `(traceId, spanId)` tuples from raw `span_events`
2949
+ * by looking only at rows where `eventType = 'start'` and
2950
+ * `parentSpanId IS NULL`. All scalar filters (entity*, *Id, service,
2951
+ * environment, startedAt range, ...) run here, against raw columns.
2952
+ * 2. Fully reconstruct spans only for that narrowed set, then apply
2953
+ * post-aggregation filters (status/tags/metadata/scope/endedAt/
2954
+ * hasChildError).
2955
+ *
2956
+ * When there are no post-aggregation filters, ordering + pagination happen
2957
+ * inside the prefilter CTE so reconstruction runs on at most `perPage` rows.
2958
+ */
2959
+ async function listTraceRows(db, args, reconstructSelect, mapRow, toSpans) {
2960
+ const filters = args.filters ?? {};
2961
+ const page = Number(args.pagination?.page ?? 0);
2962
+ const perPage = Number(args.pagination?.perPage ?? 10);
2963
+ const orderBy = {
2964
+ field: args.orderBy?.field ?? "startedAt",
2965
+ direction: args.orderBy?.direction ?? "DESC"
2966
+ };
2967
+ const { prefilter, postAgg, hasChildError } = partitionAnchorFilters(filters);
2968
+ const { clause: prefilterClause, params: prefilterParams } = buildWhereClause(prefilter);
2969
+ const prefilterParts = [`eventType = 'start'`, `parentSpanId IS NULL`];
2970
+ if (prefilterClause) prefilterParts.push(prefilterClause.replace(/^WHERE\s+/i, ""));
2971
+ const prefilterWhere = `WHERE ${prefilterParts.join(" AND ")}`;
2972
+ const outerAlias = "outer_root";
2973
+ const orderDir = orderBy.direction.toUpperCase();
2974
+ if (orderDir !== "ASC" && orderDir !== "DESC") throw new Error(`Invalid sort direction: ${orderBy.direction}`);
2975
+ const canOrderInPrefilter = SAFE_PREFILTER_ORDER_FIELDS.has(orderBy.field);
2976
+ if (!(Object.keys(postAgg).length > 0 || hasChildError !== void 0 || !canOrderInPrefilter)) {
2977
+ const prefilterOrderBy = `ORDER BY timestamp ${orderDir}`;
2978
+ const offset = page * perPage;
2979
+ const countSql = `
2980
+ SELECT COUNT(*) as total
2981
+ FROM span_events AS ${outerAlias}
2982
+ ${prefilterWhere}
2983
+ `;
2984
+ const countResult = await db.query(countSql, prefilterParams);
2985
+ const total = Number(countResult[0]?.total ?? 0);
2986
+ const pageSql = `
2987
+ WITH page_roots AS (
2988
+ SELECT traceId, spanId, timestamp AS anchorStartedAt
2989
+ FROM span_events AS ${outerAlias}
2990
+ ${prefilterWhere}
2991
+ ${prefilterOrderBy}
2992
+ LIMIT ? OFFSET ?
2993
+ )
2994
+ ${reconstructForAnchors(reconstructSelect, "page_roots")}
2995
+ ${buildOrderByClause(orderBy)}
2996
+ `;
2997
+ const spans = (await db.query(pageSql, [
2998
+ ...prefilterParams,
2999
+ perPage,
3000
+ offset
3001
+ ])).map((row) => mapRow(row));
3002
+ return {
3003
+ pagination: {
3004
+ total,
3005
+ page,
3006
+ perPage,
3007
+ hasMore: (page + 1) * perPage < total
3008
+ },
3009
+ spans: toSpans(spans)
3010
+ };
3011
+ }
3012
+ const { clause: postAggClause, params: postAggParams } = buildWhereClause(postAgg);
3013
+ const postAggParts = [];
3014
+ if (postAggClause) postAggParts.push(postAggClause.replace(/^WHERE\s+/i, ""));
3015
+ const childErrorClause = buildHasChildErrorClause(hasChildError, "root_spans");
3016
+ if (childErrorClause) postAggParts.push(childErrorClause);
3017
+ const postAggWhere = postAggParts.length > 0 ? `WHERE ${postAggParts.join(" AND ")}` : "";
3018
+ const cteSql = `
3019
+ WITH candidate_roots AS (
3020
+ SELECT traceId, spanId
3021
+ FROM span_events AS ${outerAlias}
3022
+ ${prefilterWhere}
3023
+ ),
3024
+ root_spans AS (
3025
+ ${buildPostAggReconstructSelect(postAgg, orderBy.field)}
3026
+ WHERE (traceId, spanId) IN (SELECT traceId, spanId FROM candidate_roots)
3027
+ GROUP BY traceId, spanId
3028
+ )
3029
+ `;
3030
+ const orderByClause = buildOrderByClause(orderBy);
3031
+ const { clause: paginationClause, params: paginationParams } = buildPaginationClause({
3032
+ page,
3033
+ perPage
3034
+ });
3035
+ const countSql = `
3036
+ ${cteSql}
3037
+ SELECT COUNT(*) as total FROM root_spans ${postAggWhere}
3038
+ `;
3039
+ const countResult = await db.query(countSql, [...prefilterParams, ...postAggParams]);
3040
+ const total = Number(countResult[0]?.total ?? 0);
3041
+ const dataSql = `
3042
+ ${cteSql},
3043
+ page_roots AS (
3044
+ SELECT traceId, spanId, startedAt AS anchorStartedAt
3045
+ FROM root_spans ${postAggWhere} ${orderByClause} ${paginationClause}
3046
+ )
3047
+ ${reconstructForAnchors(reconstructSelect, "page_roots")}
3048
+ ${orderByClause}
3049
+ `;
3050
+ const spans = (await db.query(dataSql, [
3051
+ ...prefilterParams,
3052
+ ...postAggParams,
3053
+ ...paginationParams
3054
+ ])).map((row) => mapRow(row));
3055
+ return {
3056
+ pagination: {
3057
+ total,
3058
+ page,
3059
+ perPage,
3060
+ hasMore: (page + 1) * perPage < total
3061
+ },
3062
+ spans: toSpans(spans)
3063
+ };
3064
+ }
3065
+ async function listTraces(db, args) {
3066
+ const { mode, filters, pagination, orderBy, after, limit } = _mastra_core_storage.listTracesArgsSchema.parse(args);
3067
+ const filterRecord = filters ?? {};
3068
+ if (mode === "delta") {
3069
+ assertDeltaPollingEnabled();
3070
+ const streamHeadCursor = await getTraceStreamHeadCursor(db);
3071
+ if (after === void 0) return {
3072
+ spans: [],
3073
+ delta: {
3074
+ limit,
3075
+ hasMore: false
3076
+ },
3077
+ deltaCursor: streamHeadCursor
3078
+ };
3079
+ const afterCursorId = validateCursorId(after);
3080
+ const { prefilter, postAgg, hasChildError } = partitionAnchorFilters(filterRecord);
3081
+ const { clause: prefilterClause, params: prefilterParams } = buildWhereClause(prefilter);
3082
+ const prefilterParts = [
3083
+ `eventType = 'start'`,
3084
+ `parentSpanId IS NULL`,
3085
+ `cursorId IS NOT NULL`,
3086
+ `cursorId > CAST(? AS BIGINT)`
3087
+ ];
3088
+ if (prefilterClause) prefilterParts.push(prefilterClause.replace(/^WHERE\s+/i, ""));
3089
+ const prefilterWhere = `WHERE ${prefilterParts.join(" AND ")}`;
3090
+ const { clause: postAggClause, params: postAggParams } = buildWhereClause(postAgg);
3091
+ const postAggParts = [];
3092
+ if (postAggClause) postAggParts.push(postAggClause.replace(/^WHERE\s+/i, ""));
3093
+ const childErrorClause = buildHasChildErrorClause(hasChildError, "root_spans");
3094
+ if (childErrorClause) postAggParts.push(childErrorClause);
3095
+ const postAggWhere = postAggParts.length > 0 ? `WHERE ${postAggParts.join(" AND ")}` : "";
3096
+ const outerAlias = "outer_root";
3097
+ const minTs = (await db.query(`SELECT min(timestamp) as minTs FROM span_events AS ${outerAlias} ${prefilterWhere}`, [afterCursorId, ...prefilterParams]))[0]?.minTs ?? null;
3098
+ if (minTs === null) return {
3099
+ spans: [],
3100
+ delta: {
3101
+ limit,
3102
+ hasMore: false
3103
+ },
3104
+ deltaCursor: streamHeadCursor
3105
+ };
3106
+ const dataSql = `
3107
+ WITH candidate_roots AS (
3108
+ SELECT traceId, spanId, cursorId
3109
+ FROM span_events AS ${outerAlias}
3110
+ ${prefilterWhere}
3111
+ ),
3112
+ root_spans AS (
3113
+ SELECT reconstructed.*, candidate_roots.cursorId AS anchorCursorId
3114
+ FROM (
3115
+ ${reconstructForAnchorsWithBoundParam(SPAN_RECONSTRUCT_SELECT, "candidate_roots")}
3116
+ ) AS reconstructed
3117
+ INNER JOIN candidate_roots USING (traceId, spanId)
3118
+ )
3119
+ SELECT * FROM root_spans ${postAggWhere} ORDER BY anchorCursorId ASC LIMIT ?
3120
+ `;
3121
+ const rows = await db.query(dataSql, [
3122
+ afterCursorId,
3123
+ ...prefilterParams,
3124
+ minTs,
3125
+ ...postAggParams,
3126
+ limit + 1
3127
+ ]);
3128
+ const visibleRows = rows.slice(0, limit).map((row) => ({
3129
+ cursorId: row.anchorCursorId,
3130
+ span: rowToSpanRecord(row)
3131
+ }));
3132
+ return {
3133
+ spans: (0, _mastra_core_storage.toTraceSpans)(visibleRows.map((row) => row.span)),
3134
+ delta: {
3135
+ limit,
3136
+ hasMore: rows.length > limit
3137
+ },
3138
+ deltaCursor: visibleRows.length > 0 ? encodeDeltaCursor(visibleRows[visibleRows.length - 1]?.cursorId) : streamHeadCursor
3139
+ };
3140
+ }
3141
+ const currentDeltaCursor = deltaPollingFeatureEnabled() ? await getTraceDeltaCursor(db, filters) : void 0;
3142
+ const { pagination: resultPagination, spans } = await listTraceRows(db, {
3143
+ filters,
3144
+ pagination,
3145
+ orderBy
3146
+ }, SPAN_RECONSTRUCT_SELECT, rowToSpanRecord, _mastra_core_storage.toTraceSpans);
3147
+ return {
3148
+ pagination: resultPagination,
3149
+ spans,
3150
+ ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {}
3151
+ };
3152
+ }
3153
+ async function listTracesLight(db, args) {
3154
+ return listTraceRows(db, args, SPAN_RECONSTRUCT_SELECT_LIGHT, rowToLightSpanRecord, (spans) => spans);
3155
+ }
3156
+ const BRANCH_SPAN_TYPE_PLACEHOLDERS = _mastra_core_storage.BRANCH_SPAN_TYPES.map(() => "?").join(", ");
3157
+ /**
3158
+ * Reconstruct multiple spans by spanId within a single trace. Single round-trip
3159
+ * fetch used by the optimized {@link import('@mastra/core/storage').getBranch}
3160
+ * path: getStructure walks the skeleton to identify branch spanIds, then this
3161
+ * pulls full data for only those spans instead of the whole trace.
3162
+ */
3163
+ async function getSpans(db, args) {
3164
+ if (args.spanIds.length === 0) return {
3165
+ traceId: args.traceId,
3166
+ spans: []
3167
+ };
3168
+ const placeholders = args.spanIds.map(() => "?").join(", ");
3169
+ const rows = await db.query(`${SPAN_RECONSTRUCT_SELECT}
3170
+ WHERE traceId = ? AND spanId IN (${placeholders})
3171
+ GROUP BY traceId, spanId`, [args.traceId, ...args.spanIds]);
3172
+ return {
3173
+ traceId: args.traceId,
3174
+ spans: rows.map((row) => rowToSpanRecord(row))
3175
+ };
3176
+ }
3177
+ /**
3178
+ * List branch anchor spans (named-entity invocations) across all traces with
3179
+ * filtering, ordering, and pagination.
3180
+ *
3181
+ * Same two-stage strategy as `listTraces`:
3182
+ * 1. Pick candidate anchor `(traceId, spanId)` tuples from raw `span_events`
3183
+ * by looking only at `eventType = 'start'` rows whose `spanType` is in
3184
+ * {@link BRANCH_SPAN_TYPES}. Scalar filters (entity*, *Id, environment,
3185
+ * serviceName, startedAt range, ...) run here, against raw columns. This
3186
+ * avoids paying reconstruction cost for the high-volume sub-operation
3187
+ * events (MODEL_STEP, MODEL_CHUNK, ...) that are never anchors.
3188
+ * 2. Reconstruct full span data only for that narrowed set, then apply
3189
+ * post-aggregation filters (status / metadata / tags / endedAt range).
3190
+ *
3191
+ * When there are no post-aggregation filters, ordering + pagination happen
3192
+ * inside the prefilter so reconstruction runs on at most `perPage` rows.
3193
+ */
3194
+ async function listBranches(db, args) {
3195
+ const { mode, filters, pagination, orderBy, after, limit } = _mastra_core_storage.listBranchesArgsSchema.parse(args);
3196
+ const filterRecord = filters ?? {};
3197
+ const page = Number(pagination.page);
3198
+ const perPage = Number(pagination.perPage);
3199
+ const userSpanType = filterRecord.spanType;
3200
+ if (typeof userSpanType === "string" && !_mastra_core_storage.BRANCH_SPAN_TYPES.includes(userSpanType)) {
3201
+ const currentDeltaCursor = deltaPollingFeatureEnabled() ? await getBranchDeltaCursor(db, filters) : void 0;
3202
+ if (mode === "delta") {
3203
+ assertDeltaPollingEnabled();
3204
+ return {
3205
+ branches: [],
3206
+ delta: {
3207
+ limit,
3208
+ hasMore: false
3209
+ },
3210
+ deltaCursor: currentDeltaCursor
3211
+ };
3212
+ }
3213
+ return {
3214
+ pagination: {
3215
+ total: 0,
3216
+ page,
3217
+ perPage,
3218
+ hasMore: false
3219
+ },
3220
+ branches: [],
3221
+ ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {}
3222
+ };
3223
+ }
3224
+ if (mode === "delta") {
3225
+ assertDeltaPollingEnabled();
3226
+ const streamHeadCursor = await getBranchStreamHeadCursor(db, typeof userSpanType === "string" ? userSpanType : null);
3227
+ if (after === void 0) return {
3228
+ branches: [],
3229
+ delta: {
3230
+ limit,
3231
+ hasMore: false
3232
+ },
3233
+ deltaCursor: streamHeadCursor
3234
+ };
3235
+ const afterCursorId = validateCursorId(after);
3236
+ const { spanType: _spanType, ...rest } = filterRecord;
3237
+ const { prefilter, postAgg, hasChildError: _hasChildError } = partitionAnchorFilters(rest);
3238
+ const { clause: prefilterClause, params: prefilterFilterParams } = buildWhereClause(prefilter);
3239
+ const prefilterParts = [
3240
+ `eventType = 'start'`,
3241
+ `cursorId IS NOT NULL`,
3242
+ `cursorId > CAST(? AS BIGINT)`
3243
+ ];
3244
+ let spanTypeParams;
3245
+ if (typeof userSpanType === "string") {
3246
+ prefilterParts.push(`spanType = ?`);
3247
+ spanTypeParams = [userSpanType];
3248
+ } else {
3249
+ prefilterParts.push(`spanType IN (${BRANCH_SPAN_TYPE_PLACEHOLDERS})`);
3250
+ spanTypeParams = [..._mastra_core_storage.BRANCH_SPAN_TYPES];
3251
+ }
3252
+ if (prefilterClause) prefilterParts.push(prefilterClause.replace(/^WHERE\s+/i, ""));
3253
+ const prefilterWhere = `WHERE ${prefilterParts.join(" AND ")}`;
3254
+ const prefilterParams = [
3255
+ afterCursorId,
3256
+ ...spanTypeParams,
3257
+ ...prefilterFilterParams
3258
+ ];
3259
+ const { clause: postAggClause, params: postAggParams } = buildWhereClause(postAgg);
3260
+ const postAggWhere = postAggClause ? postAggClause : "";
3261
+ const outerAlias = "outer_anchor";
3262
+ const minTs = (await db.query(`SELECT min(timestamp) as minTs FROM span_events AS ${outerAlias} ${prefilterWhere}`, prefilterParams))[0]?.minTs ?? null;
3263
+ if (minTs === null) return {
3264
+ branches: [],
3265
+ delta: {
3266
+ limit,
3267
+ hasMore: false
3268
+ },
3269
+ deltaCursor: streamHeadCursor
3270
+ };
3271
+ const dataSql = `
3272
+ WITH candidate_anchors AS (
3273
+ SELECT traceId, spanId, cursorId
3274
+ FROM span_events AS ${outerAlias}
3275
+ ${prefilterWhere}
3276
+ ),
3277
+ branch_anchors AS (
3278
+ SELECT reconstructed.*, candidate_anchors.cursorId AS anchorCursorId
3279
+ FROM (
3280
+ ${reconstructForAnchorsWithBoundParam(SPAN_RECONSTRUCT_SELECT, "candidate_anchors")}
3281
+ ) AS reconstructed
3282
+ INNER JOIN candidate_anchors USING (traceId, spanId)
3283
+ )
3284
+ SELECT * FROM branch_anchors ${postAggWhere} ORDER BY anchorCursorId ASC LIMIT ?
3285
+ `;
3286
+ const rows = await db.query(dataSql, [
3287
+ ...prefilterParams,
3288
+ minTs,
3289
+ ...postAggParams,
3290
+ limit + 1
3291
+ ]);
3292
+ const visibleRows = rows.slice(0, limit).map((row) => ({
3293
+ cursorId: row.anchorCursorId,
3294
+ branch: rowToSpanRecord(row)
3295
+ }));
3296
+ return {
3297
+ branches: (0, _mastra_core_storage.toTraceSpans)(visibleRows.map((row) => row.branch)),
3298
+ delta: {
3299
+ limit,
3300
+ hasMore: rows.length > limit
3301
+ },
3302
+ deltaCursor: visibleRows.length > 0 ? encodeDeltaCursor(visibleRows[visibleRows.length - 1]?.cursorId) : streamHeadCursor
3303
+ };
3304
+ }
3305
+ const { spanType: _spanType, ...rest } = filterRecord;
3306
+ const { prefilter, postAgg, hasChildError: _hasChildError } = partitionAnchorFilters(rest);
3307
+ const { clause: prefilterClause, params: prefilterFilterParams } = buildWhereClause(prefilter);
3308
+ const prefilterParts = [`eventType = 'start'`];
3309
+ let spanTypeParams;
3310
+ if (typeof userSpanType === "string") {
3311
+ prefilterParts.push(`spanType = ?`);
3312
+ spanTypeParams = [userSpanType];
3313
+ } else {
3314
+ prefilterParts.push(`spanType IN (${BRANCH_SPAN_TYPE_PLACEHOLDERS})`);
3315
+ spanTypeParams = [..._mastra_core_storage.BRANCH_SPAN_TYPES];
3316
+ }
3317
+ if (prefilterClause) prefilterParts.push(prefilterClause.replace(/^WHERE\s+/i, ""));
3318
+ const prefilterWhere = `WHERE ${prefilterParts.join(" AND ")}`;
3319
+ const prefilterParams = [...spanTypeParams, ...prefilterFilterParams];
3320
+ const outerAlias = "outer_anchor";
3321
+ const orderDir = orderBy.direction.toUpperCase();
3322
+ if (orderDir !== "ASC" && orderDir !== "DESC") throw new Error(`Invalid sort direction: ${orderBy.direction}`);
3323
+ const currentDeltaCursor = deltaPollingFeatureEnabled() ? await getBranchDeltaCursor(db, filters) : void 0;
3324
+ const canOrderInPrefilter = SAFE_PREFILTER_ORDER_FIELDS.has(orderBy.field);
3325
+ if (!(Object.keys(postAgg).length > 0 || !canOrderInPrefilter)) {
3326
+ const prefilterOrderBy = `ORDER BY timestamp ${orderDir}`;
3327
+ const offset = page * perPage;
3328
+ const countSql = `
3329
+ SELECT COUNT(*) as total
3330
+ FROM span_events AS ${outerAlias}
3331
+ ${prefilterWhere}
3332
+ `;
3333
+ const countResult = await db.query(countSql, prefilterParams);
3334
+ const total = Number(countResult[0]?.total ?? 0);
3335
+ if (total === 0) return {
3336
+ pagination: {
3337
+ total: 0,
3338
+ page,
3339
+ perPage,
3340
+ hasMore: false
3341
+ },
3342
+ branches: [],
3343
+ ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {}
3344
+ };
3345
+ const pageSql = `
3346
+ WITH page_anchors AS (
3347
+ SELECT traceId, spanId, timestamp AS anchorStartedAt
3348
+ FROM span_events AS ${outerAlias}
3349
+ ${prefilterWhere}
3350
+ ${prefilterOrderBy}
3351
+ LIMIT ? OFFSET ?
3352
+ )
3353
+ ${reconstructForAnchors(SPAN_RECONSTRUCT_SELECT, "page_anchors")}
3354
+ ${buildOrderByClause(orderBy)}
3355
+ `;
3356
+ const spans = (await db.query(pageSql, [
3357
+ ...prefilterParams,
3358
+ perPage,
3359
+ offset
3360
+ ])).map((row) => rowToSpanRecord(row));
3361
+ return {
3362
+ pagination: {
3363
+ total,
3364
+ page,
3365
+ perPage,
3366
+ hasMore: (page + 1) * perPage < total
3367
+ },
3368
+ branches: (0, _mastra_core_storage.toTraceSpans)(spans),
3369
+ ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {}
3370
+ };
3371
+ }
3372
+ const { clause: postAggClause, params: postAggParams } = buildWhereClause(postAgg);
3373
+ const postAggWhere = postAggClause ? postAggClause : "";
3374
+ const orderByClause = buildOrderByClause(orderBy);
3375
+ const { clause: paginationClause, params: paginationParams } = buildPaginationClause({
3376
+ page,
3377
+ perPage
3378
+ });
3379
+ const cteSql = `
3380
+ WITH candidate_anchors AS (
3381
+ SELECT traceId, spanId
3382
+ FROM span_events AS ${outerAlias}
3383
+ ${prefilterWhere}
3384
+ ),
3385
+ branch_anchors AS (
3386
+ ${buildPostAggReconstructSelect(postAgg, orderBy.field)}
3387
+ WHERE (traceId, spanId) IN (SELECT traceId, spanId FROM candidate_anchors)
3388
+ GROUP BY traceId, spanId
3389
+ )
3390
+ `;
3391
+ const countSql = `
3392
+ ${cteSql}
3393
+ SELECT COUNT(*) as total FROM branch_anchors ${postAggWhere}
3394
+ `;
3395
+ const countResult = await db.query(countSql, [...prefilterParams, ...postAggParams]);
3396
+ const total = Number(countResult[0]?.total ?? 0);
3397
+ if (total === 0) return {
3398
+ pagination: {
3399
+ total: 0,
3400
+ page,
3401
+ perPage,
3402
+ hasMore: false
3403
+ },
3404
+ branches: [],
3405
+ ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {}
3406
+ };
3407
+ const dataSql = `
3408
+ ${cteSql},
3409
+ page_anchors AS (
3410
+ SELECT traceId, spanId, startedAt AS anchorStartedAt
3411
+ FROM branch_anchors ${postAggWhere} ${orderByClause} ${paginationClause}
3412
+ )
3413
+ ${reconstructForAnchors(SPAN_RECONSTRUCT_SELECT, "page_anchors")}
3414
+ ${orderByClause}
3415
+ `;
3416
+ const spans = (await db.query(dataSql, [
3417
+ ...prefilterParams,
3418
+ ...postAggParams,
3419
+ ...paginationParams
3420
+ ])).map((row) => rowToSpanRecord(row));
3421
+ return {
3422
+ pagination: {
3423
+ total,
3424
+ page,
3425
+ perPage,
3426
+ hasMore: (page + 1) * perPage < total
3427
+ },
3428
+ branches: (0, _mastra_core_storage.toTraceSpans)(spans),
3429
+ ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {}
3430
+ };
3431
+ }
3432
+ async function getTraceDeltaCursor(db, filters) {
3433
+ const { prefilter, postAgg, hasChildError } = partitionAnchorFilters(filters ?? {});
3434
+ const { clause: prefilterClause, params: prefilterParams } = buildWhereClause(prefilter);
3435
+ const prefilterParts = [
3436
+ `eventType = 'start'`,
3437
+ `parentSpanId IS NULL`,
3438
+ `cursorId IS NOT NULL`
3439
+ ];
3440
+ if (prefilterClause) prefilterParts.push(prefilterClause.replace(/^WHERE\s+/i, ""));
3441
+ const prefilterWhere = `WHERE ${prefilterParts.join(" AND ")}`;
3442
+ const outerAlias = "outer_root";
3443
+ const { clause: postAggClause, params: postAggParams } = buildWhereClause(postAgg);
3444
+ const postAggParts = [];
3445
+ if (postAggClause) postAggParts.push(postAggClause.replace(/^WHERE\s+/i, ""));
3446
+ const childErrorClause = buildHasChildErrorClause(hasChildError, "root_spans");
3447
+ if (childErrorClause) postAggParts.push(childErrorClause);
3448
+ const postAggWhere = postAggParts.length > 0 ? `WHERE ${postAggParts.join(" AND ")}` : "";
3449
+ if (postAggWhere === "") {
3450
+ const cursorId = (await db.query(`SELECT max(cursorId) AS cursorId FROM span_events AS ${outerAlias} ${prefilterWhere}`, prefilterParams))[0]?.cursorId;
3451
+ if (cursorId !== null && cursorId !== void 0) return encodeDeltaCursor(cursorId);
3452
+ return encodeDeltaCursor((await db.query(`SELECT max(cursorId) AS cursorId FROM span_events WHERE eventType = 'start' AND parentSpanId IS NULL AND cursorId IS NOT NULL`))[0]?.cursorId);
3453
+ }
3454
+ const cteSql = `
3455
+ WITH candidate_roots AS (
3456
+ SELECT traceId, spanId, cursorId
3457
+ FROM span_events AS ${outerAlias}
3458
+ ${prefilterWhere}
3459
+ ),
3460
+ root_spans AS (
3461
+ SELECT reconstructed.*, candidate_roots.cursorId AS anchorCursorId
3462
+ FROM (
3463
+ ${SPAN_RECONSTRUCT_SELECT}
3464
+ WHERE (traceId, spanId) IN (SELECT traceId, spanId FROM candidate_roots)
3465
+ GROUP BY traceId, spanId
3466
+ ) AS reconstructed
3467
+ INNER JOIN candidate_roots USING (traceId, spanId)
3468
+ )
3469
+ `;
3470
+ const cursorId = (await db.query(`${cteSql} SELECT max(anchorCursorId) AS cursorId FROM root_spans ${postAggWhere}`, [...prefilterParams, ...postAggParams]))[0]?.cursorId;
3471
+ if (cursorId !== null && cursorId !== void 0) return encodeDeltaCursor(cursorId);
3472
+ return encodeDeltaCursor((await db.query(`SELECT max(cursorId) AS cursorId FROM span_events WHERE eventType = 'start' AND parentSpanId IS NULL AND cursorId IS NOT NULL`))[0]?.cursorId);
3473
+ }
3474
+ async function getTraceStreamHeadCursor(db) {
3475
+ return encodeDeltaCursor((await db.query(`SELECT max(cursorId) AS cursorId FROM span_events WHERE eventType = 'start' AND parentSpanId IS NULL AND cursorId IS NOT NULL`))[0]?.cursorId);
3476
+ }
3477
+ async function getBranchDeltaCursor(db, filters) {
3478
+ const filterRecord = filters ?? {};
3479
+ const userSpanType = filterRecord.spanType;
3480
+ const { spanType: _spanType, ...rest } = filterRecord;
3481
+ const { prefilter, postAgg } = partitionAnchorFilters(rest);
3482
+ const { clause: prefilterClause, params: prefilterFilterParams } = buildWhereClause(prefilter);
3483
+ const prefilterParts = [`eventType = 'start'`, `cursorId IS NOT NULL`];
3484
+ let spanTypeParams;
3485
+ if (typeof userSpanType === "string") {
3486
+ prefilterParts.push(`spanType = ?`);
3487
+ spanTypeParams = [userSpanType];
3488
+ } else {
3489
+ prefilterParts.push(`spanType IN (${BRANCH_SPAN_TYPE_PLACEHOLDERS})`);
3490
+ spanTypeParams = [..._mastra_core_storage.BRANCH_SPAN_TYPES];
3491
+ }
3492
+ if (prefilterClause) prefilterParts.push(prefilterClause.replace(/^WHERE\s+/i, ""));
3493
+ const prefilterWhere = `WHERE ${prefilterParts.join(" AND ")}`;
3494
+ const prefilterParams = [...spanTypeParams, ...prefilterFilterParams];
3495
+ const outerAlias = "outer_anchor";
3496
+ const { clause: postAggClause, params: postAggParams } = buildWhereClause(postAgg);
3497
+ if (!postAggClause) {
3498
+ const cursorId = (await db.query(`SELECT max(cursorId) AS cursorId FROM span_events AS ${outerAlias} ${prefilterWhere}`, prefilterParams))[0]?.cursorId;
3499
+ if (cursorId !== null && cursorId !== void 0) return encodeDeltaCursor(cursorId);
3500
+ return encodeDeltaCursor((await db.query(`SELECT max(cursorId) AS cursorId FROM span_events WHERE eventType = 'start' AND spanType IN (${BRANCH_SPAN_TYPE_PLACEHOLDERS}) AND cursorId IS NOT NULL`, [..._mastra_core_storage.BRANCH_SPAN_TYPES]))[0]?.cursorId);
3501
+ }
3502
+ const cteSql = `
3503
+ WITH candidate_anchors AS (
3504
+ SELECT traceId, spanId, cursorId
3505
+ FROM span_events AS ${outerAlias}
3506
+ ${prefilterWhere}
3507
+ ),
3508
+ branch_anchors AS (
3509
+ SELECT reconstructed.*, candidate_anchors.cursorId AS anchorCursorId
3510
+ FROM (
3511
+ ${SPAN_RECONSTRUCT_SELECT}
3512
+ WHERE (traceId, spanId) IN (SELECT traceId, spanId FROM candidate_anchors)
3513
+ GROUP BY traceId, spanId
3514
+ ) AS reconstructed
3515
+ INNER JOIN candidate_anchors USING (traceId, spanId)
3516
+ )
3517
+ `;
3518
+ const cursorId = (await db.query(`${cteSql} SELECT max(anchorCursorId) AS cursorId FROM branch_anchors ${postAggClause}`, [...prefilterParams, ...postAggParams]))[0]?.cursorId;
3519
+ if (cursorId !== null && cursorId !== void 0) return encodeDeltaCursor(cursorId);
3520
+ return encodeDeltaCursor((await db.query(`SELECT max(cursorId) AS cursorId FROM span_events WHERE eventType = 'start' AND spanType IN (${BRANCH_SPAN_TYPE_PLACEHOLDERS}) AND cursorId IS NOT NULL`, [..._mastra_core_storage.BRANCH_SPAN_TYPES]))[0]?.cursorId);
3521
+ }
3522
+ async function getBranchStreamHeadCursor(db, userSpanType) {
3523
+ if (userSpanType) return encodeDeltaCursor((await db.query(`SELECT max(cursorId) AS cursorId FROM span_events WHERE eventType = 'start' AND spanType = ? AND cursorId IS NOT NULL`, [userSpanType]))[0]?.cursorId);
3524
+ return encodeDeltaCursor((await db.query(`SELECT max(cursorId) AS cursorId FROM span_events WHERE eventType = 'start' AND spanType IN (${BRANCH_SPAN_TYPE_PLACEHOLDERS}) AND cursorId IS NOT NULL`, [..._mastra_core_storage.BRANCH_SPAN_TYPES]))[0]?.cursorId);
3525
+ }
3526
+ //#endregion
3527
+ //#region src/storage/domains/observability/index.ts
3528
+ function buildSignalMigrationRequiredMessage(args) {
3529
+ return `
3530
+ ===========================================================================
3531
+ MIGRATION REQUIRED: DuckDB observability signal tables need signal IDs
3532
+ ===========================================================================
3533
+
3534
+ The following signal tables still use the legacy schema and must be migrated
3535
+ before observability storage can initialize:
3536
+
3537
+ ${args.tables.map((table) => ` - ${table.table}`).join("\n")}\n\nTo fix this, run the manual migration command:\n\n npx mastra migrate\n\nThis command will:\n 1. Create replacement signal tables with signal-ID primary keys\n 2. Backfill missing signal IDs for legacy rows\n 3. Swap the migrated tables into place\n\nWARNING: This migration recreates the signal tables and may take significant\ntime for large databases. Please ensure you have a backup before proceeding.\n===========================================================================\n`;
3538
+ }
3539
+ /**
3540
+ * DuckDB-backed observability storage for traces, metrics, logs, scores, and feedback.
3541
+ * Uses an append-only event-sourced model with SQL-based reconstruction for spans.
3542
+ */
3543
+ var ObservabilityStorageDuckDB = class extends _mastra_core_storage.ObservabilityStorage {
3544
+ db;
3545
+ constructor(config) {
3546
+ super();
3547
+ this.db = config.db;
3548
+ }
3549
+ /** Create all observability tables if they don't exist. */
3550
+ async init() {
3551
+ const migrationStatus = await checkSignalTablesMigrationStatus(this.db);
3552
+ if (migrationStatus.needsMigration) throw new _mastra_core_error.MastraError({
3553
+ id: (0, _mastra_core_storage.createStorageErrorId)("DUCKDB", "MIGRATION_REQUIRED", "SIGNAL_TABLES"),
3554
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
3555
+ category: _mastra_core_error.ErrorCategory.USER,
3556
+ text: buildSignalMigrationRequiredMessage({ tables: migrationStatus.tables.map(({ table }) => ({ table })) })
3557
+ });
3558
+ await this.db.executeBatch([...ALL_DDL, ...ALL_MIGRATIONS]);
3559
+ await dropLegacyCursorIdDefaults(this.db);
3560
+ }
3561
+ /**
3562
+ * Manually migrate legacy signal tables to the signal-ID primary-key schema.
3563
+ * The public method name is historical; the CLI still calls `migrateSpans()`
3564
+ * for observability migrations even though this now also migrates signal tables.
3565
+ */
3566
+ async migrateSpans() {
3567
+ const migrationStatus = await checkSignalTablesMigrationStatus(this.db);
3568
+ if (!migrationStatus.needsMigration) return {
3569
+ success: true,
3570
+ alreadyMigrated: true,
3571
+ duplicatesRemoved: 0,
3572
+ message: "Migration already complete. Signal tables already use signal-ID primary keys."
3573
+ };
3574
+ await migrateSignalTables(this.db, this.logger);
3575
+ return {
3576
+ success: true,
3577
+ alreadyMigrated: false,
3578
+ duplicatesRemoved: 0,
3579
+ message: `Migration complete. Migrated signal tables: ${migrationStatus.tables.map((t) => t.table).join(", ")}.`
3580
+ };
3581
+ }
3582
+ /** Delete all rows from every observability table. Use with caution. */
3583
+ async dangerouslyClearAll() {
3584
+ for (const table of [
3585
+ "span_events",
3586
+ "metric_events",
3587
+ "log_events",
3588
+ "score_events",
3589
+ "feedback_events"
3590
+ ]) await this.db.execute(`TRUNCATE TABLE ${table}`);
3591
+ }
3592
+ get observabilityStrategy() {
3593
+ return {
3594
+ preferred: "event-sourced",
3595
+ supported: ["event-sourced"]
3596
+ };
3597
+ }
3598
+ getFeatures() {
3599
+ if (!deltaPollingFeatureEnabled()) return;
3600
+ return ["delta-polling"];
3601
+ }
3602
+ async createSpan(args) {
3603
+ return createSpan(this.db, args);
3604
+ }
3605
+ async batchCreateSpans(args) {
3606
+ return batchCreateSpans(this.db, args);
3607
+ }
3608
+ async batchDeleteTraces(args) {
3609
+ return batchDeleteTraces(this.db, args);
3610
+ }
3611
+ async getSpan(args) {
3612
+ return getSpan(this.db, args);
3613
+ }
3614
+ async getSpans(args) {
3615
+ return getSpans(this.db, args);
3616
+ }
3617
+ async getRootSpan(args) {
3618
+ return getRootSpan(this.db, args);
3619
+ }
3620
+ async getTrace(args) {
3621
+ return getTrace(this.db, args);
3622
+ }
3623
+ async getTraceLight(args) {
3624
+ return getTraceLight(this.db, args);
3625
+ }
3626
+ async listTraces(args) {
3627
+ return listTraces(this.db, args);
3628
+ }
3629
+ async listTracesLight(args) {
3630
+ return listTracesLight(this.db, args);
3631
+ }
3632
+ async listBranches(args) {
3633
+ return listBranches(this.db, args);
3634
+ }
3635
+ async batchCreateLogs(args) {
3636
+ return batchCreateLogs(this.db, args);
3637
+ }
3638
+ async listLogs(args) {
3639
+ return listLogs(this.db, args);
3640
+ }
3641
+ async batchCreateMetrics(args) {
3642
+ return batchCreateMetrics(this.db, args);
3643
+ }
3644
+ async listMetrics(args) {
3645
+ return listMetrics(this.db, args);
3646
+ }
3647
+ async getMetricAggregate(args) {
3648
+ return getMetricAggregate(this.db, args);
3649
+ }
3650
+ async getMetricBreakdown(args) {
3651
+ return getMetricBreakdown(this.db, args);
3652
+ }
3653
+ async getMetricTimeSeries(args) {
3654
+ return getMetricTimeSeries(this.db, args);
3655
+ }
3656
+ async getMetricPercentiles(args) {
3657
+ return getMetricPercentiles(this.db, args);
3658
+ }
3659
+ async getMetricNames(args) {
3660
+ return getMetricNames(this.db, args);
3661
+ }
3662
+ async getMetricLabelKeys(args) {
3663
+ return getMetricLabelKeys(this.db, args);
3664
+ }
3665
+ async getMetricLabelValues(args) {
3666
+ return getMetricLabelValues(this.db, args);
3667
+ }
3668
+ async getEntityTypes(args) {
3669
+ return getEntityTypes(this.db, args);
3670
+ }
3671
+ async getEntityNames(args) {
3672
+ return getEntityNames(this.db, args);
3673
+ }
3674
+ async getServiceNames(args) {
3675
+ return getServiceNames(this.db, args);
3676
+ }
3677
+ async getEnvironments(args) {
3678
+ return getEnvironments(this.db, args);
3679
+ }
3680
+ async getTags(args) {
3681
+ return getTags(this.db, args);
3682
+ }
3683
+ async createScore(args) {
3684
+ return createScore(this.db, args);
3685
+ }
3686
+ async batchCreateScores(args) {
3687
+ return batchCreateScores(this.db, args);
3688
+ }
3689
+ async listScores(args) {
3690
+ return listScores(this.db, args);
3691
+ }
3692
+ async getScoreById(scoreId) {
3693
+ return getScoreById(this.db, scoreId);
3694
+ }
3695
+ async getScoreAggregate(args) {
3696
+ return getScoreAggregate(this.db, args);
3697
+ }
3698
+ async getScoreBreakdown(args) {
3699
+ return getScoreBreakdown(this.db, args);
3700
+ }
3701
+ async getScoreTimeSeries(args) {
3702
+ return getScoreTimeSeries(this.db, args);
3703
+ }
3704
+ async getScorePercentiles(args) {
3705
+ return getScorePercentiles(this.db, args);
3706
+ }
3707
+ async createFeedback(args) {
3708
+ return createFeedback(this.db, args);
3709
+ }
3710
+ async batchCreateFeedback(args) {
3711
+ return batchCreateFeedback(this.db, args);
3712
+ }
3713
+ async listFeedback(args) {
3714
+ return listFeedback(this.db, args);
3715
+ }
3716
+ async getFeedbackAggregate(args) {
3717
+ return getFeedbackAggregate(this.db, args);
3718
+ }
3719
+ async getFeedbackBreakdown(args) {
3720
+ return getFeedbackBreakdown(this.db, args);
3721
+ }
3722
+ async getFeedbackTimeSeries(args) {
3723
+ return getFeedbackTimeSeries(this.db, args);
3724
+ }
3725
+ async getFeedbackPercentiles(args) {
3726
+ return getFeedbackPercentiles(this.db, args);
3727
+ }
3728
+ };
3729
+ //#endregion
3730
+ exports.ObservabilityStorageDuckDB = ObservabilityStorageDuckDB;
3731
+
3732
+ //# sourceMappingURL=observability-Dc-V68UX.cjs.map