'use strict'; var chunkSMRZJTCI_cjs = require('./chunk-SMRZJTCI.cjs'); var error = require('@mastra/core/error'); var storage = require('@mastra/core/storage'); var observability = require('@mastra/core/observability'); var utils = require('@mastra/core/utils'); var features = require('@mastra/core/features'); // src/storage/domains/observability/ddl.ts var SPAN_EVENTS_CURSOR_SEQUENCE_DDL = ` CREATE SEQUENCE IF NOT EXISTS span_events_cursor_id_seq START 1 `; var METRIC_EVENTS_CURSOR_SEQUENCE_DDL = ` CREATE SEQUENCE IF NOT EXISTS metric_events_cursor_id_seq START 1 `; var LOG_EVENTS_CURSOR_SEQUENCE_DDL = ` CREATE SEQUENCE IF NOT EXISTS log_events_cursor_id_seq START 1 `; var SCORE_EVENTS_CURSOR_SEQUENCE_DDL = ` CREATE SEQUENCE IF NOT EXISTS score_events_cursor_id_seq START 1 `; var FEEDBACK_EVENTS_CURSOR_SEQUENCE_DDL = ` CREATE SEQUENCE IF NOT EXISTS feedback_events_cursor_id_seq START 1 `; var SPAN_EVENTS_DDL = ` CREATE TABLE IF NOT EXISTS span_events ( -- Event metadata eventType VARCHAR NOT NULL, timestamp TIMESTAMP NOT NULL, cursorId BIGINT, -- IDs traceId VARCHAR NOT NULL, spanId VARCHAR NOT NULL, parentSpanId VARCHAR, experimentId VARCHAR, -- Entity entityType VARCHAR, entityId VARCHAR, entityName VARCHAR, entityVersionId VARCHAR, -- Context userId VARCHAR, organizationId VARCHAR, resourceId VARCHAR, runId VARCHAR, sessionId VARCHAR, threadId VARCHAR, requestId VARCHAR, environment VARCHAR, source VARCHAR, serviceName VARCHAR, requestContext JSON, -- Span-specific scalars name VARCHAR, spanType VARCHAR, isEvent BOOLEAN, endedAt TIMESTAMP, -- JSON fields attributes JSON, metadata JSON, tags JSON, scope JSON, links JSON, input JSON, output JSON, error JSON )`; var METRIC_EVENTS_DDL = ` CREATE TABLE IF NOT EXISTS metric_events ( -- Event metadata timestamp TIMESTAMP NOT NULL, cursorId BIGINT, -- IDs metricId VARCHAR NOT NULL PRIMARY KEY, traceId VARCHAR, spanId VARCHAR, experimentId VARCHAR, -- Entity hierarchy entityType VARCHAR, entityId VARCHAR, entityName VARCHAR, entityVersionId VARCHAR, parentEntityVersionId VARCHAR, parentEntityType VARCHAR, parentEntityId VARCHAR, parentEntityName VARCHAR, rootEntityVersionId VARCHAR, rootEntityType VARCHAR, rootEntityId VARCHAR, rootEntityName VARCHAR, -- Context userId VARCHAR, organizationId VARCHAR, resourceId VARCHAR, runId VARCHAR, sessionId VARCHAR, threadId VARCHAR, requestId VARCHAR, environment VARCHAR, executionSource VARCHAR, serviceName VARCHAR, -- Metric-specific scalars name VARCHAR NOT NULL, value DOUBLE NOT NULL, provider VARCHAR, model VARCHAR, estimatedCost DOUBLE, costUnit VARCHAR, -- JSON fields tags JSON, labels JSON, costMetadata JSON, metadata JSON, scope JSON )`; var LOG_EVENTS_DDL = ` CREATE TABLE IF NOT EXISTS log_events ( -- Event metadata timestamp TIMESTAMP NOT NULL, cursorId BIGINT, -- IDs logId VARCHAR NOT NULL PRIMARY KEY, traceId VARCHAR, spanId VARCHAR, experimentId VARCHAR, -- Entity hierarchy entityType VARCHAR, entityId VARCHAR, entityName VARCHAR, entityVersionId VARCHAR, parentEntityVersionId VARCHAR, parentEntityType VARCHAR, parentEntityId VARCHAR, parentEntityName VARCHAR, rootEntityVersionId VARCHAR, rootEntityType VARCHAR, rootEntityId VARCHAR, rootEntityName VARCHAR, -- Context userId VARCHAR, organizationId VARCHAR, resourceId VARCHAR, runId VARCHAR, sessionId VARCHAR, threadId VARCHAR, requestId VARCHAR, environment VARCHAR, executionSource VARCHAR, serviceName VARCHAR, -- Log-specific scalars level VARCHAR NOT NULL, message VARCHAR NOT NULL, -- JSON fields data JSON, tags JSON, metadata JSON, scope JSON )`; var SCORE_EVENTS_DDL = ` CREATE TABLE IF NOT EXISTS score_events ( -- Event metadata timestamp TIMESTAMP NOT NULL, cursorId BIGINT, -- IDs scoreId VARCHAR NOT NULL PRIMARY KEY, traceId VARCHAR, spanId VARCHAR, experimentId VARCHAR, scoreTraceId VARCHAR, -- Entity hierarchy entityType VARCHAR, entityId VARCHAR, entityName VARCHAR, entityVersionId VARCHAR, parentEntityVersionId VARCHAR, parentEntityType VARCHAR, parentEntityId VARCHAR, parentEntityName VARCHAR, rootEntityVersionId VARCHAR, rootEntityType VARCHAR, rootEntityId VARCHAR, rootEntityName VARCHAR, -- Context userId VARCHAR, organizationId VARCHAR, resourceId VARCHAR, runId VARCHAR, sessionId VARCHAR, threadId VARCHAR, requestId VARCHAR, environment VARCHAR, executionSource VARCHAR, serviceName VARCHAR, -- Score-specific scalars scorerId VARCHAR NOT NULL, scorerVersion VARCHAR, source VARCHAR, scoreSource VARCHAR, score DOUBLE NOT NULL, reason VARCHAR, -- JSON fields tags JSON, metadata JSON, scope JSON )`; var FEEDBACK_EVENTS_DDL = ` CREATE TABLE IF NOT EXISTS feedback_events ( -- Event metadata timestamp TIMESTAMP NOT NULL, cursorId BIGINT, -- IDs feedbackId VARCHAR NOT NULL PRIMARY KEY, traceId VARCHAR, spanId VARCHAR, experimentId VARCHAR, -- Entity hierarchy entityType VARCHAR, entityId VARCHAR, entityName VARCHAR, entityVersionId VARCHAR, parentEntityVersionId VARCHAR, parentEntityType VARCHAR, parentEntityId VARCHAR, parentEntityName VARCHAR, rootEntityVersionId VARCHAR, rootEntityType VARCHAR, rootEntityId VARCHAR, rootEntityName VARCHAR, -- Context userId VARCHAR, organizationId VARCHAR, resourceId VARCHAR, runId VARCHAR, sessionId VARCHAR, threadId VARCHAR, requestId VARCHAR, environment VARCHAR, executionSource VARCHAR, serviceName VARCHAR, -- Feedback actor / linkage feedbackUserId VARCHAR, sourceId VARCHAR, -- Feedback-specific scalars source VARCHAR, feedbackSource VARCHAR NOT NULL, feedbackType VARCHAR NOT NULL, value VARCHAR NOT NULL, comment VARCHAR, -- JSON fields tags JSON, metadata JSON, scope JSON )`; var ALL_DDL = [ SPAN_EVENTS_CURSOR_SEQUENCE_DDL, METRIC_EVENTS_CURSOR_SEQUENCE_DDL, LOG_EVENTS_CURSOR_SEQUENCE_DDL, SCORE_EVENTS_CURSOR_SEQUENCE_DDL, FEEDBACK_EVENTS_CURSOR_SEQUENCE_DDL, SPAN_EVENTS_DDL, METRIC_EVENTS_DDL, LOG_EVENTS_DDL, SCORE_EVENTS_DDL, FEEDBACK_EVENTS_DDL ]; var ALL_MIGRATIONS = [ `CREATE SEQUENCE IF NOT EXISTS span_events_cursor_id_seq START 1`, `CREATE SEQUENCE IF NOT EXISTS metric_events_cursor_id_seq START 1`, `CREATE SEQUENCE IF NOT EXISTS log_events_cursor_id_seq START 1`, `CREATE SEQUENCE IF NOT EXISTS score_events_cursor_id_seq START 1`, `CREATE SEQUENCE IF NOT EXISTS feedback_events_cursor_id_seq START 1`, // Existing rows intentionally keep NULL cursorId values; delta polling only // applies to rows written by insert paths that explicitly call nextval(). // Databases upgraded from a prior version may still carry a // `DEFAULT nextval(...)` on cursorId, which breaks DuckDB WAL replay; that // remediation lives in `dropLegacyCursorIdDefaults` and only runs when the // bad default is detected in information_schema. `ALTER TABLE span_events ADD COLUMN IF NOT EXISTS cursorId BIGINT`, `ALTER TABLE span_events ADD COLUMN IF NOT EXISTS entityVersionId VARCHAR`, // Metrics. Legacy rows remain page-visible but are not part of delta polling. `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS cursorId BIGINT`, `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS entityVersionId VARCHAR`, `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS parentEntityVersionId VARCHAR`, `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS rootEntityVersionId VARCHAR`, `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS experimentId VARCHAR`, `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS parentEntityType VARCHAR`, `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS parentEntityId VARCHAR`, `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS parentEntityName VARCHAR`, `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS rootEntityType VARCHAR`, `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS rootEntityId VARCHAR`, `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS rootEntityName VARCHAR`, `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS userId VARCHAR`, `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS organizationId VARCHAR`, `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS resourceId VARCHAR`, `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS runId VARCHAR`, `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS sessionId VARCHAR`, `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS threadId VARCHAR`, `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS requestId VARCHAR`, `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS environment VARCHAR`, `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS executionSource VARCHAR`, `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS serviceName VARCHAR`, `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS costMetadata JSON`, `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS metadata JSON`, `ALTER TABLE metric_events ADD COLUMN IF NOT EXISTS scope JSON`, // Logs. Legacy rows remain page-visible but are not part of delta polling. `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS cursorId BIGINT`, `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS entityVersionId VARCHAR`, `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS parentEntityVersionId VARCHAR`, `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS rootEntityVersionId VARCHAR`, `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS experimentId VARCHAR`, `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS parentEntityType VARCHAR`, `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS parentEntityId VARCHAR`, `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS parentEntityName VARCHAR`, `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS rootEntityType VARCHAR`, `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS rootEntityId VARCHAR`, `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS rootEntityName VARCHAR`, `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS userId VARCHAR`, `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS organizationId VARCHAR`, `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS resourceId VARCHAR`, `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS runId VARCHAR`, `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS sessionId VARCHAR`, `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS threadId VARCHAR`, `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS requestId VARCHAR`, `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS environment VARCHAR`, `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS executionSource VARCHAR`, `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS serviceName VARCHAR`, `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS tags JSON`, `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS metadata JSON`, `ALTER TABLE log_events ADD COLUMN IF NOT EXISTS scope JSON`, // Scores. Legacy rows remain page-visible but are not part of delta polling. `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS cursorId BIGINT`, `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS entityVersionId VARCHAR`, `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS parentEntityVersionId VARCHAR`, `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS rootEntityVersionId VARCHAR`, `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS entityType VARCHAR`, `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS entityId VARCHAR`, `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS entityName VARCHAR`, `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS parentEntityType VARCHAR`, `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS parentEntityId VARCHAR`, `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS parentEntityName VARCHAR`, `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS rootEntityType VARCHAR`, `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS rootEntityId VARCHAR`, `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS rootEntityName VARCHAR`, `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS userId VARCHAR`, `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS organizationId VARCHAR`, `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS resourceId VARCHAR`, `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS runId VARCHAR`, `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS sessionId VARCHAR`, `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS threadId VARCHAR`, `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS requestId VARCHAR`, `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS environment VARCHAR`, `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS executionSource VARCHAR`, `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS serviceName VARCHAR`, `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS tags JSON`, `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS scope JSON`, `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS source VARCHAR`, `ALTER TABLE score_events ADD COLUMN IF NOT EXISTS scoreSource VARCHAR`, `ALTER TABLE score_events ALTER COLUMN traceId DROP NOT NULL`, // Feedback. Legacy rows remain page-visible but are not part of delta polling. `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS cursorId BIGINT`, `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS entityVersionId VARCHAR`, `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS parentEntityVersionId VARCHAR`, `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS rootEntityVersionId VARCHAR`, `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS entityType VARCHAR`, `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS entityId VARCHAR`, `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS entityName VARCHAR`, `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS parentEntityType VARCHAR`, `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS parentEntityId VARCHAR`, `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS parentEntityName VARCHAR`, `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS rootEntityType VARCHAR`, `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS rootEntityId VARCHAR`, `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS rootEntityName VARCHAR`, `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS organizationId VARCHAR`, `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS resourceId VARCHAR`, `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS runId VARCHAR`, `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS sessionId VARCHAR`, `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS threadId VARCHAR`, `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS requestId VARCHAR`, `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS environment VARCHAR`, `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS executionSource VARCHAR`, `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS serviceName VARCHAR`, `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS feedbackUserId VARCHAR`, `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS sourceId VARCHAR`, `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS tags JSON`, `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS scope JSON`, `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS source VARCHAR`, `ALTER TABLE feedback_events ADD COLUMN IF NOT EXISTS feedbackSource VARCHAR`, `ALTER TABLE feedback_events ALTER COLUMN traceId DROP NOT NULL` ]; function unionDistinctQueries(selects, orderBy) { return `${selects.join("\nUNION\n")} ORDER BY ${orderBy}`; } async function getEntityTypes(db, _args) { const rows = await db.query( unionDistinctQueries( [ `SELECT entityType FROM span_events WHERE entityType IS NOT NULL`, `SELECT entityType FROM metric_events WHERE entityType IS NOT NULL`, `SELECT entityType FROM log_events WHERE entityType IS NOT NULL` ], "entityType" ) ); const validTypes = new Set(Object.values(observability.EntityType)); const typeSet = /* @__PURE__ */ new Set(); for (const row of rows) { if (row.entityType && validTypes.has(row.entityType)) { typeSet.add(row.entityType); } } return { entityTypes: Array.from(typeSet).sort() }; } async function getEntityNames(db, args) { const buildSelect = (table) => { const conditions = [`entityName IS NOT NULL`]; if (args.entityType) { conditions.push(`entityType = ?`); } return `SELECT entityName FROM ${table} WHERE ${conditions.join(" AND ")}`; }; const params = args.entityType ? [args.entityType, args.entityType, args.entityType] : []; const rows = await db.query( unionDistinctQueries( [buildSelect("span_events"), buildSelect("metric_events"), buildSelect("log_events")], "entityName" ), params ); return { names: rows.map((r) => r.entityName) }; } async function getServiceNames(db, _args) { const rows = await db.query( unionDistinctQueries( [ `SELECT serviceName FROM span_events WHERE serviceName IS NOT NULL`, `SELECT serviceName FROM metric_events WHERE serviceName IS NOT NULL`, `SELECT serviceName FROM log_events WHERE serviceName IS NOT NULL` ], "serviceName" ) ); return { serviceNames: rows.map((r) => r.serviceName) }; } async function getEnvironments(db, _args) { const rows = await db.query( unionDistinctQueries( [ `SELECT environment FROM span_events WHERE environment IS NOT NULL`, `SELECT environment FROM metric_events WHERE environment IS NOT NULL`, `SELECT environment FROM log_events WHERE environment IS NOT NULL` ], "environment" ) ); return { environments: rows.map((r) => r.environment) }; } async function getTags(db, args) { const buildSelect = (table) => { const conditions = [`tags IS NOT NULL`]; if (args.entityType) { conditions.push(`entityType = ?`); } return `SELECT unnest(CAST(tags AS VARCHAR[])) AS tag FROM ${table} WHERE ${conditions.join(" AND ")}`; }; const params = args.entityType ? [args.entityType, args.entityType, args.entityType] : []; const rows = await db.query( unionDistinctQueries([buildSelect("span_events"), buildSelect("metric_events"), buildSelect("log_events")], "tag"), params ); return { tags: rows.map((r) => r.tag) }; } function buildJsonPath(key) { try { return `$.${utils.parseFieldKey(key)}`; } catch { const escaped = key.replace(/\\/g, "\\\\").replace(/"/g, '\\"'); return `$."${escaped}"`; } } function normalizeJsonFilterValue(value) { if (value === void 0) return null; if (typeof value === "string") return value; const json = JSON.stringify(value); return json ?? null; } function sanitizeColumn(column) { return utils.parseFieldKey(column); } function buildWhereClause(filters, fieldMappings) { if (!filters) return { clause: "", params: [] }; const conditions = []; const params = []; for (const [key, value] of Object.entries(filters)) { if (value === void 0 || value === null) continue; const column = sanitizeColumn(fieldMappings?.[key] ?? key); if (key === "timestamp" || key === "startedAt" || key === "endedAt") { const dateRange = value; if (dateRange.start) { const op = dateRange.startExclusive ? ">" : ">="; conditions.push(`${column} ${op} ?`); params.push(dateRange.start); } if (dateRange.end) { const op = dateRange.endExclusive ? "<" : "<="; conditions.push(`${column} ${op} ?`); params.push(dateRange.end); } continue; } if (key === "labels") { const labelsObj = value; for (const [labelKey, labelValue] of Object.entries(labelsObj)) { conditions.push(`json_extract_string(${column}, ?) = ?`); params.push(buildJsonPath(labelKey), labelValue); } continue; } if (key === "tags") { const tags = value; for (const tag of tags) { conditions.push(`list_contains(CAST(${column} AS VARCHAR[]), ?)`); params.push(tag); } continue; } if (key === "status") { const status = value; if (status === "error") { conditions.push(`error IS NOT NULL`); } else if (status === "running") { conditions.push(`endedAt IS NULL AND error IS NULL`); } else if (status === "success") { conditions.push(`endedAt IS NOT NULL AND error IS NULL`); } continue; } if (key === "hasChildError") { continue; } if (key === "metadata" || key === "scope") { const jsonObj = value; for (const [jsonKey, jsonValue] of Object.entries(jsonObj)) { const normalized = normalizeJsonFilterValue(jsonValue); if (normalized === null) continue; conditions.push(`json_extract_string(${column}, ?) = ?`); params.push(buildJsonPath(jsonKey), normalized); } continue; } if (Array.isArray(value)) { if (value.length === 0) continue; const placeholders = value.map(() => "?").join(", "); conditions.push(`${column} IN (${placeholders})`); params.push(...value); continue; } conditions.push(`${column} = ?`); params.push(value); } const clause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : ""; return { clause, params }; } function buildOrderByClause(orderBy) { if (!orderBy) return ""; const dir = orderBy.direction.toUpperCase(); if (dir !== "ASC" && dir !== "DESC") { throw new Error(`Invalid sort direction: ${orderBy.direction}`); } const field = utils.parseFieldKey(orderBy.field); return `ORDER BY ${field} ${dir}`; } function buildPaginationClause(pagination) { if (!pagination) return { clause: "", params: [] }; if (!Number.isInteger(pagination.page) || pagination.page < 0) { throw new Error(`Invalid page: ${pagination.page}`); } if (!Number.isInteger(pagination.perPage) || pagination.perPage <= 0) { throw new Error(`Invalid perPage: ${pagination.perPage}`); } const offset = pagination.page * pagination.perPage; return { clause: `LIMIT ? OFFSET ?`, params: [pagination.perPage, offset] }; } // src/storage/domains/observability/helpers.ts var v = chunkSMRZJTCI_cjs.DuckDBConnection.sqlValue; function jsonV(val) { if (val === null || val === void 0) return "NULL"; return chunkSMRZJTCI_cjs.DuckDBConnection.sqlValue(JSON.stringify(val)); } function toDate(val) { if (val === null || val === void 0) { throw new Error("Expected date value but received null/undefined"); } const date = val instanceof Date ? val : new Date(String(val)); if (Number.isNaN(date.getTime())) { throw new Error("Expected valid date but received invalid date"); } return date; } function toDateOrNull(val) { if (val === null || val === void 0) return null; return val instanceof Date ? val : new Date(String(val)); } function parseJson(value) { if (value === null || value === void 0) return null; if (typeof value === "string") { try { return JSON.parse(value); } catch { return value; } } return value; } function parseJsonArray(value) { if (value === null || value === void 0) return null; const parsed = parseJson(value); return Array.isArray(parsed) ? parsed : null; } var OBSERVABILITY_DELTA_POLLING_FEATURE = "observability-delta-polling"; function deltaPollingFeatureEnabled() { return features.coreFeatures.has(OBSERVABILITY_DELTA_POLLING_FEATURE); } function assertDeltaPollingEnabled() { if (deltaPollingFeatureEnabled()) { return; } throw new error.MastraError({ id: "OBSERVABILITY_DELTA_POLLING_NOT_SUPPORTED", domain: error.ErrorDomain.MASTRA_OBSERVABILITY, category: error.ErrorCategory.SYSTEM, text: "This storage provider does not support observability delta polling" }); } function encodeDeltaCursor(value) { return String(value ?? 0); } function validateCursorId(cursor) { if (!/^\d+$/.test(cursor)) { throw new error.MastraError({ id: "OBSERVABILITY_INVALID_DELTA_CURSOR", domain: error.ErrorDomain.MASTRA_OBSERVABILITY, category: error.ErrorCategory.USER, text: "Invalid observability delta cursor" }); } return cursor; } function extendWhereClause(baseClause, extraConditions) { const conditions = extraConditions.filter(Boolean); if (conditions.length === 0) { return baseClause; } if (!baseClause) { return `WHERE ${conditions.join(" AND ")}`; } return `${baseClause} AND ${conditions.join(" AND ")}`; } // src/storage/domains/observability/feedback.ts var FEEDBACK_GROUP_BY_COLUMNS = /* @__PURE__ */ new Set([ "timestamp", "traceId", "spanId", "experimentId", "entityType", "entityId", "entityName", "entityVersionId", "parentEntityVersionId", "parentEntityType", "parentEntityId", "parentEntityName", "rootEntityVersionId", "rootEntityType", "rootEntityId", "rootEntityName", "userId", "organizationId", "resourceId", "runId", "sessionId", "threadId", "requestId", "environment", "executionSource", "serviceName", "feedbackUserId", "sourceId", "feedbackSource", "feedbackType", "value", "comment" ]); function getAggregationSql(aggregation, measure = "TRY_CAST(value AS DOUBLE)") { switch (aggregation) { case "sum": return `SUM(${measure})`; case "avg": return `AVG(${measure})`; case "min": return `MIN(${measure})`; case "max": return `MAX(${measure})`; case "count": return `CAST(COUNT(${measure}) AS DOUBLE)`; case "last": return `arg_max(${measure}, timestamp)`; default: return `SUM(${measure})`; } } function getIntervalSql(interval) { switch (interval) { case "1m": return "1 minute"; case "5m": return "5 minutes"; case "15m": return "15 minutes"; case "1h": return "1 hour"; case "1d": return "1 day"; default: return "1 hour"; } } function getValidatedPercentiles(percentiles) { if (!Array.isArray(percentiles) || percentiles.length === 0) { throw new Error("Percentiles must include at least one value between 0 and 1."); } return percentiles.map((percentile) => { if (!Number.isFinite(percentile) || percentile < 0 || percentile > 1) { throw new Error("Percentiles must be finite numbers between 0 and 1."); } return percentile; }); } function buildFeedbackWhereClause(args, includeNumericGuard = false) { const conditions = ["feedbackType = ?"]; const params = [args.feedbackType]; if (args.feedbackSource !== void 0) { conditions.push("feedbackSource = ?"); params.push(args.feedbackSource); } const { clause: filterClause, params: filterParams } = buildWhereClause( args.filters, { source: "feedbackSource" } ); if (filterClause) { conditions.push(filterClause.replace("WHERE ", "")); params.push(...filterParams); } if (includeNumericGuard) { conditions.push("TRY_CAST(value AS DOUBLE) IS NOT NULL"); } return { clause: `WHERE ${conditions.join(" AND ")}`, params }; } function resolveFeedbackGroupBy(groupBy) { return groupBy.map((key, index) => { const column = utils.parseFieldKey(key); if (!FEEDBACK_GROUP_BY_COLUMNS.has(column)) { throw new Error(`Invalid groupBy column(s): ${key}`); } const alias = `group_by_${index}`; return { key, selectSql: `${column} AS ${alias}`, groupSql: alias }; }); } function toSeriesName(values) { return values.map((value) => value === null || value === void 0 ? "" : String(value)).join("|"); } function rowToFeedbackRecord(row) { const rawValue = row.value; let value = rawValue; const numValue = Number(rawValue); if (!isNaN(numValue)) value = numValue; return { feedbackId: row.feedbackId, timestamp: toDate(row.timestamp), traceId: row.traceId ?? null, spanId: row.spanId ?? null, experimentId: row.experimentId ?? null, entityType: row.entityType ?? null, entityId: row.entityId ?? null, entityName: row.entityName ?? null, entityVersionId: row.entityVersionId ?? null, parentEntityVersionId: row.parentEntityVersionId ?? null, parentEntityType: row.parentEntityType ?? null, parentEntityId: row.parentEntityId ?? null, parentEntityName: row.parentEntityName ?? null, rootEntityVersionId: row.rootEntityVersionId ?? null, rootEntityType: row.rootEntityType ?? null, rootEntityId: row.rootEntityId ?? null, rootEntityName: row.rootEntityName ?? null, userId: row.userId ?? null, organizationId: row.organizationId ?? null, resourceId: row.resourceId ?? null, runId: row.runId ?? null, sessionId: row.sessionId ?? null, threadId: row.threadId ?? null, requestId: row.requestId ?? null, environment: row.environment ?? null, executionSource: row.executionSource ?? null, serviceName: row.serviceName ?? null, feedbackUserId: row.feedbackUserId ?? null, sourceId: row.sourceId ?? null, source: row.feedbackSource, feedbackSource: row.feedbackSource, feedbackType: row.feedbackType, value, comment: row.comment ?? null, tags: parseJsonArray(row.tags), metadata: parseJson(row.metadata), scope: parseJson(row.scope) }; } function getComparisonDateRange(comparePeriod, timestamp) { if (!timestamp.start || !timestamp.end) return null; const duration = timestamp.end.getTime() - timestamp.start.getTime(); switch (comparePeriod) { case "previous_period": return { start: new Date(timestamp.start.getTime() - duration), end: new Date(timestamp.end.getTime() - duration), startExclusive: timestamp.startExclusive, endExclusive: timestamp.endExclusive }; case "previous_day": return { start: new Date(timestamp.start.getTime() - 864e5), end: new Date(timestamp.end.getTime() - 864e5), startExclusive: timestamp.startExclusive, endExclusive: timestamp.endExclusive }; case "previous_week": return { start: new Date(timestamp.start.getTime() - 6048e5), end: new Date(timestamp.end.getTime() - 6048e5), startExclusive: timestamp.startExclusive, endExclusive: timestamp.endExclusive }; } } async function createFeedback(db, args) { const f = args.feedback; const feedbackSource = f.feedbackSource ?? f.source ?? ""; const feedbackUserId = f.feedbackUserId ?? f.userId ?? null; await db.execute( `INSERT INTO feedback_events ( feedbackId, timestamp, cursorId, traceId, spanId, experimentId, entityType, entityId, entityName, entityVersionId, parentEntityVersionId, parentEntityType, parentEntityId, parentEntityName, rootEntityVersionId, rootEntityType, rootEntityId, rootEntityName, userId, organizationId, resourceId, runId, sessionId, threadId, requestId, environment, executionSource, serviceName, feedbackUserId, sourceId, feedbackSource, feedbackType, value, comment, tags, metadata, scope ) VALUES (${[ v(f.feedbackId), v(f.timestamp), "nextval('feedback_events_cursor_id_seq')", v(f.traceId), v(f.spanId ?? null), v(f.experimentId ?? null), v(f.entityType ?? null), v(f.entityId ?? null), v(f.entityName ?? null), v(f.entityVersionId ?? null), v(f.parentEntityVersionId ?? null), v(f.parentEntityType ?? null), v(f.parentEntityId ?? null), v(f.parentEntityName ?? null), v(f.rootEntityVersionId ?? null), v(f.rootEntityType ?? null), v(f.rootEntityId ?? null), v(f.rootEntityName ?? null), v(f.userId ?? null), v(f.organizationId ?? null), v(f.resourceId ?? null), v(f.runId ?? null), v(f.sessionId ?? null), v(f.threadId ?? null), v(f.requestId ?? null), v(f.environment ?? null), v(f.executionSource ?? null), v(f.serviceName ?? null), v(feedbackUserId), v(f.sourceId ?? null), v(feedbackSource), v(f.feedbackType), v(String(f.value)), v(f.comment ?? null), jsonV(f.tags ?? null), jsonV(f.metadata), jsonV(f.scope ?? null) ].join(", ")}) ON CONFLICT DO NOTHING` ); } async function batchCreateFeedback(db, args) { if (args.feedbacks.length === 0) return; const tuples = args.feedbacks.map((f) => { const legacyFeedback = f; const feedbackSource = legacyFeedback.feedbackSource ?? legacyFeedback.source ?? ""; const feedbackUserId = legacyFeedback.feedbackUserId ?? legacyFeedback.userId ?? null; return `(${[ v(legacyFeedback.feedbackId), v(legacyFeedback.timestamp), "nextval('feedback_events_cursor_id_seq')", v(legacyFeedback.traceId), v(legacyFeedback.spanId ?? null), v(legacyFeedback.experimentId ?? null), v(legacyFeedback.entityType ?? null), v(legacyFeedback.entityId ?? null), v(legacyFeedback.entityName ?? null), v(legacyFeedback.entityVersionId ?? null), v(legacyFeedback.parentEntityVersionId ?? null), v(legacyFeedback.parentEntityType ?? null), v(legacyFeedback.parentEntityId ?? null), v(legacyFeedback.parentEntityName ?? null), v(legacyFeedback.rootEntityVersionId ?? null), v(legacyFeedback.rootEntityType ?? null), v(legacyFeedback.rootEntityId ?? null), v(legacyFeedback.rootEntityName ?? null), v(legacyFeedback.userId ?? null), v(legacyFeedback.organizationId ?? null), v(legacyFeedback.resourceId ?? null), v(legacyFeedback.runId ?? null), v(legacyFeedback.sessionId ?? null), v(legacyFeedback.threadId ?? null), v(legacyFeedback.requestId ?? null), v(legacyFeedback.environment ?? null), v(legacyFeedback.executionSource ?? null), v(legacyFeedback.serviceName ?? null), v(feedbackUserId), v(legacyFeedback.sourceId ?? null), v(feedbackSource), v(legacyFeedback.feedbackType), v(String(legacyFeedback.value)), v(legacyFeedback.comment ?? null), jsonV(legacyFeedback.tags ?? null), jsonV(legacyFeedback.metadata), jsonV(legacyFeedback.scope ?? null) ].join(", ")})`; }); await db.execute( `INSERT INTO feedback_events ( feedbackId, timestamp, cursorId, traceId, spanId, experimentId, entityType, entityId, entityName, entityVersionId, parentEntityVersionId, parentEntityType, parentEntityId, parentEntityName, rootEntityVersionId, rootEntityType, rootEntityId, rootEntityName, userId, organizationId, resourceId, runId, sessionId, threadId, requestId, environment, executionSource, serviceName, feedbackUserId, sourceId, feedbackSource, feedbackType, value, comment, tags, metadata, scope ) VALUES ${tuples.join(",\n ")} ON CONFLICT DO NOTHING` ); } async function listFeedback(db, args) { const { mode, filters, pagination, orderBy, after, limit } = storage.listFeedbackArgsSchema.parse(args); const page = Number(pagination.page); const perPage = Number(pagination.perPage); const { clause: filterClause, params: filterParams } = buildWhereClause(filters, { source: "feedbackSource" }); if (mode === "delta") { assertDeltaPollingEnabled(); const streamHeadCursor = await getStreamHeadCursor(db); if (after === void 0) { return { feedback: [], delta: { limit, hasMore: false }, deltaCursor: streamHeadCursor }; } const afterCursorId = validateCursorId(after); const deltaWhereClause = extendWhereClause(filterClause, ["cursorId IS NOT NULL", `cursorId > CAST(? AS BIGINT)`]); const rows2 = await db.query( `SELECT * FROM feedback_events ${deltaWhereClause} ORDER BY cursorId ASC LIMIT ?`, [...filterParams, afterCursorId, limit + 1] ); const visibleRows = rows2.slice(0, limit).map((row) => ({ cursorId: row.cursorId, feedback: rowToFeedbackRecord(row) })); return { feedback: visibleRows.map((row) => row.feedback), delta: { limit, hasMore: rows2.length > limit }, deltaCursor: visibleRows.length > 0 ? encodeDeltaCursor(visibleRows[visibleRows.length - 1]?.cursorId) : streamHeadCursor }; } const orderByClause = buildOrderByClause(orderBy); const { clause: paginationClause, params: paginationParams } = buildPaginationClause({ page, perPage }); const currentDeltaCursor = deltaPollingFeatureEnabled() ? await getDeltaCursor(db, filterClause, filterParams) : void 0; const countResult = await db.query( `SELECT COUNT(*) as total FROM feedback_events ${filterClause}`, filterParams ); const total = Number(countResult[0]?.total ?? 0); const rows = await db.query( `SELECT * FROM feedback_events ${filterClause} ${orderByClause} ${paginationClause}`, [...filterParams, ...paginationParams] ); return { pagination: { total, page, perPage, hasMore: (page + 1) * perPage < total }, feedback: rows.map((row) => rowToFeedbackRecord(row)), ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {} }; } async function getDeltaCursor(db, filterClause, filterParams) { const rows = await db.query( `SELECT max(cursorId) AS cursorId FROM feedback_events ${filterClause}`, filterParams ); const cursorId = rows[0]?.cursorId; if (cursorId !== null && cursorId !== void 0) { return encodeDeltaCursor(cursorId); } const streamRows = await db.query(`SELECT max(cursorId) AS cursorId FROM feedback_events`); return encodeDeltaCursor(streamRows[0]?.cursorId); } async function getStreamHeadCursor(db) { const streamRows = await db.query(`SELECT max(cursorId) AS cursorId FROM feedback_events`); return encodeDeltaCursor(streamRows[0]?.cursorId); } async function getFeedbackAggregate(db, args) { const aggSql = getAggregationSql(args.aggregation); const { clause, params } = buildFeedbackWhereClause(args, true); const rows = await db.query( `SELECT ${aggSql} AS value FROM feedback_events ${clause}`, params ); const value = rows[0]?.value === null || rows[0]?.value === void 0 ? null : Number(rows[0]?.value); if (args.comparePeriod && args.filters?.timestamp) { const previousTimestamp = getComparisonDateRange(args.comparePeriod, args.filters.timestamp); if (previousTimestamp) { const previousWhere = buildFeedbackWhereClause( { ...args, filters: { ...args.filters ?? {}, timestamp: previousTimestamp } }, true ); const prevRows = await db.query( `SELECT ${aggSql} AS value FROM feedback_events ${previousWhere.clause}`, previousWhere.params ); const previousValue = prevRows[0]?.value === null || prevRows[0]?.value === void 0 ? null : Number(prevRows[0]?.value); let changePercent = null; if (previousValue !== null && previousValue !== 0 && value !== null) { changePercent = (value - previousValue) / Math.abs(previousValue) * 100; } return { value, previousValue, changePercent }; } } return { value }; } async function getFeedbackBreakdown(db, args) { const aggSql = getAggregationSql(args.aggregation); const { clause, params } = buildFeedbackWhereClause(args, true); const resolvedGroupBy = resolveFeedbackGroupBy(args.groupBy); 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`; const rows = await db.query(sql, params); return { groups: rows.map((row) => ({ dimensions: Object.fromEntries( resolvedGroupBy.map((entry, index) => { const value = row[`group_by_${index}`]; return [entry.key, value === null || value === void 0 ? null : String(value)]; }) ), value: Number(row.value ?? 0) })) }; } async function getFeedbackTimeSeries(db, args) { const aggSql = getAggregationSql(args.aggregation); const intervalSql = getIntervalSql(args.interval); const { clause, params } = buildFeedbackWhereClause(args, true); if (args.groupBy && args.groupBy.length > 0) { const resolvedGroupBy = resolveFeedbackGroupBy(args.groupBy); const sql = ` SELECT time_bucket(INTERVAL '${intervalSql}', timestamp) AS bucket, ${resolvedGroupBy.map((entry) => entry.selectSql).join(", ")}, ${aggSql} AS value FROM feedback_events ${clause} GROUP BY bucket, ${resolvedGroupBy.map((entry) => entry.groupSql).join(", ")} ORDER BY bucket `; const rows2 = await db.query(sql, params); const seriesMap = /* @__PURE__ */ new Map(); for (const row of rows2) { const groupValues = resolvedGroupBy.map((_, index) => row[`group_by_${index}`]); const key = JSON.stringify(groupValues); if (!seriesMap.has(key)) { seriesMap.set(key, { name: toSeriesName(groupValues), points: [] }); } seriesMap.get(key).points.push({ timestamp: row.bucket instanceof Date ? row.bucket : new Date(String(row.bucket)), value: Number(row.value ?? 0) }); } return { series: Array.from(seriesMap.values()) }; } const rows = await db.query( ` SELECT time_bucket(INTERVAL '${intervalSql}', timestamp) AS bucket, ${aggSql} AS value FROM feedback_events ${clause} GROUP BY bucket ORDER BY bucket `, params ); return { series: [ { name: args.feedbackSource ? `${args.feedbackType}|${args.feedbackSource}` : args.feedbackType, points: rows.map((row) => ({ timestamp: row.bucket instanceof Date ? row.bucket : new Date(String(row.bucket)), value: Number(row.value ?? 0) })) } ] }; } async function getFeedbackPercentiles(db, args) { const intervalSql = getIntervalSql(args.interval); const { clause, params } = buildFeedbackWhereClause(args, true); const percentiles = getValidatedPercentiles(args.percentiles); const series = []; for (const percentile of percentiles) { const rows = await db.query( ` SELECT time_bucket(INTERVAL '${intervalSql}', timestamp) AS bucket, percentile_cont(${percentile}) WITHIN GROUP (ORDER BY TRY_CAST(value AS DOUBLE)) AS pvalue FROM feedback_events ${clause} GROUP BY bucket ORDER BY bucket `, params ); series.push({ percentile, points: rows.map((row) => ({ timestamp: row.bucket instanceof Date ? row.bucket : new Date(String(row.bucket)), value: Number(row.pvalue ?? 0) })) }); } return { series }; } var COLUMNS = [ "logId", "timestamp", "cursorId", "level", "message", "data", "traceId", "spanId", "entityType", "entityId", "entityName", "entityVersionId", "parentEntityVersionId", "parentEntityType", "parentEntityId", "parentEntityName", "rootEntityVersionId", "rootEntityType", "rootEntityId", "rootEntityName", "userId", "organizationId", "resourceId", "runId", "sessionId", "threadId", "requestId", "environment", "executionSource", "serviceName", "experimentId", "tags", "metadata", "scope" ]; var COLUMNS_SQL = COLUMNS.join(", "); function rowToLogRecord(row) { return { logId: row.logId, timestamp: toDate(row.timestamp), level: row.level, message: row.message, data: parseJson(row.data), traceId: row.traceId ?? null, spanId: row.spanId ?? null, entityType: row.entityType ?? null, entityId: row.entityId ?? null, entityName: row.entityName ?? null, entityVersionId: row.entityVersionId ?? null, parentEntityVersionId: row.parentEntityVersionId ?? null, parentEntityType: row.parentEntityType ?? null, parentEntityId: row.parentEntityId ?? null, parentEntityName: row.parentEntityName ?? null, rootEntityVersionId: row.rootEntityVersionId ?? null, rootEntityType: row.rootEntityType ?? null, rootEntityId: row.rootEntityId ?? null, rootEntityName: row.rootEntityName ?? null, userId: row.userId ?? null, organizationId: row.organizationId ?? null, resourceId: row.resourceId ?? null, runId: row.runId ?? null, sessionId: row.sessionId ?? null, threadId: row.threadId ?? null, requestId: row.requestId ?? null, environment: row.environment ?? null, executionSource: row.executionSource ?? null, serviceName: row.serviceName ?? null, experimentId: row.experimentId ?? null, tags: parseJsonArray(row.tags), metadata: parseJson(row.metadata), scope: parseJson(row.scope) }; } async function batchCreateLogs(db, args) { if (args.logs.length === 0) return; const tuples = args.logs.map((log) => { return `(${[ v(log.logId), v(log.timestamp), "nextval('log_events_cursor_id_seq')", v(log.level), v(log.message), jsonV(log.data), v(log.traceId ?? null), v(log.spanId ?? null), v(log.entityType ?? null), v(log.entityId ?? null), v(log.entityName ?? null), v(log.entityVersionId ?? null), v(log.parentEntityVersionId ?? null), v(log.parentEntityType ?? null), v(log.parentEntityId ?? null), v(log.parentEntityName ?? null), v(log.rootEntityVersionId ?? null), v(log.rootEntityType ?? null), v(log.rootEntityId ?? null), v(log.rootEntityName ?? null), v(log.userId ?? null), v(log.organizationId ?? null), v(log.resourceId ?? null), v(log.runId ?? null), v(log.sessionId ?? null), v(log.threadId ?? null), v(log.requestId ?? null), v(log.environment ?? null), v(log.executionSource ?? null), v(log.serviceName ?? null), v(log.experimentId ?? null), jsonV(log.tags), jsonV(log.metadata), jsonV(log.scope) ].join(", ")})`; }); await db.execute(`INSERT INTO log_events (${COLUMNS_SQL}) VALUES ${tuples.join(",\n")} ON CONFLICT DO NOTHING`); } async function listLogs(db, args) { const { mode, filters, pagination, orderBy, after, limit } = storage.listLogsArgsSchema.parse(args); const filterRecord = filters; const page = Number(pagination.page); const perPage = Number(pagination.perPage); const { clause: filterClause, params: filterParams } = buildWhereClause(filterRecord); if (mode === "delta") { assertDeltaPollingEnabled(); const streamHeadCursor = await getStreamHeadCursor2(db); if (after === void 0) { return { logs: [], delta: { limit, hasMore: false }, deltaCursor: streamHeadCursor }; } const afterCursorId = validateCursorId(after); const deltaWhereClause = extendWhereClause(filterClause, ["cursorId IS NOT NULL", `cursorId > CAST(? AS BIGINT)`]); const rows2 = await db.query( `SELECT * FROM log_events ${deltaWhereClause} ORDER BY cursorId ASC LIMIT ?`, [...filterParams, afterCursorId, limit + 1] ); const visibleRows = rows2.slice(0, limit).map((row) => ({ cursorId: row.cursorId, log: rowToLogRecord(row) })); return { logs: visibleRows.map((row) => row.log), delta: { limit, hasMore: rows2.length > limit }, deltaCursor: visibleRows.length > 0 ? encodeDeltaCursor(visibleRows[visibleRows.length - 1]?.cursorId) : streamHeadCursor }; } const orderByClause = buildOrderByClause(orderBy); const { clause: paginationClause, params: paginationParams } = buildPaginationClause({ page, perPage }); const currentDeltaCursor = deltaPollingFeatureEnabled() ? await getDeltaCursor2(db, filterClause, filterParams) : void 0; const countResult = await db.query( `SELECT COUNT(*) as total FROM log_events ${filterClause}`, filterParams ); const total = Number(countResult[0]?.total ?? 0); const rows = await db.query(`SELECT * FROM log_events ${filterClause} ${orderByClause} ${paginationClause}`, [ ...filterParams, ...paginationParams ]); const logs = rows.map((row) => rowToLogRecord(row)); return { pagination: { total, page, perPage, hasMore: (page + 1) * perPage < total }, logs, ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {} }; } async function getDeltaCursor2(db, filterClause, filterParams) { const rows = await db.query( `SELECT max(cursorId) AS cursorId FROM log_events ${filterClause}`, filterParams ); const cursorId = rows[0]?.cursorId; if (cursorId !== null && cursorId !== void 0) { return encodeDeltaCursor(cursorId); } const streamRows = await db.query(`SELECT max(cursorId) AS cursorId FROM log_events`); return encodeDeltaCursor(streamRows[0]?.cursorId); } async function getStreamHeadCursor2(db) { const streamRows = await db.query(`SELECT max(cursorId) AS cursorId FROM log_events`); return encodeDeltaCursor(streamRows[0]?.cursorId); } function resolveDistinctColumnSql(distinctColumn) { if (!distinctColumn) { throw new Error(`count_distinct aggregation requires a 'distinctColumn' argument`); } if (!storage.METRIC_DISTINCT_COLUMNS.includes(distinctColumn)) { throw new Error(`Invalid distinctColumn: ${distinctColumn}`); } return utils.parseFieldKey(distinctColumn); } function getAggregationSql2(aggregation, measure = "value", distinctColumn) { switch (aggregation) { case "sum": return `SUM(${measure})`; case "avg": return `AVG(${measure})`; case "min": return `MIN(${measure})`; case "max": return `MAX(${measure})`; case "count": return `CAST(COUNT(${measure}) AS DOUBLE)`; case "count_distinct": { return `CAST(approx_count_distinct(${resolveDistinctColumnSql(distinctColumn)}) AS DOUBLE)`; } case "last": return `arg_max(${measure}, timestamp)`; default: return `SUM(${measure})`; } } function getIntervalSql2(interval) { switch (interval) { case "1m": return "1 minute"; case "5m": return "5 minutes"; case "15m": return "15 minutes"; case "1h": return "1 hour"; case "1d": return "1 day"; default: return "1 hour"; } } function buildMetricNameFilter(name) { if (Array.isArray(name)) { const placeholders = name.map(() => "?").join(", "); return { clause: `name IN (${placeholders})`, params: name }; } return { clause: `name = ?`, params: [name] }; } var METRIC_COLUMNS = [ "metricId", "timestamp", "cursorId", "name", "value", "traceId", "spanId", "entityType", "entityId", "entityName", "entityVersionId", "parentEntityVersionId", "parentEntityType", "parentEntityId", "parentEntityName", "rootEntityVersionId", "rootEntityType", "rootEntityId", "rootEntityName", "userId", "organizationId", "resourceId", "runId", "sessionId", "threadId", "requestId", "environment", "executionSource", "serviceName", "experimentId", "provider", "model", "estimatedCost", "costUnit", "tags", "labels", "costMetadata", "metadata", "scope" ]; var METRIC_COLUMNS_SQL = METRIC_COLUMNS.join(", "); var METRIC_COLUMN_SET = new Set(METRIC_COLUMNS); var METRIC_LABEL_ONLY_GROUP_BY_EXCLUDED = /* @__PURE__ */ new Set(["metadata", "scope", "costMetadata", "tags"]); function buildGroupByAlias(index) { return `group_by_${index}`; } function toSeriesDisplayValue(value) { return value === null || value === void 0 ? "" : String(value); } function getCostSummarySelect(prefix = "") { const ref = (column) => `${prefix}${column}`; return [ `SUM(${ref("estimatedCost")}) FILTER (WHERE ${ref("estimatedCost")} IS NOT NULL) AS estimatedCost`, `,`, `CASE`, ` WHEN COUNT(DISTINCT ${ref("costUnit")}) FILTER (WHERE ${ref("costUnit")} IS NOT NULL) = 1`, ` THEN MIN(${ref("costUnit")}) FILTER (WHERE ${ref("costUnit")} IS NOT NULL)`, ` ELSE NULL`, `END AS costUnit` ].join(" "); } function normalizeCostSummaryRow(row) { return { estimatedCost: row.estimatedCost === null || row.estimatedCost === void 0 ? null : Number(row.estimatedCost), costUnit: row.costUnit === null || row.costUnit === void 0 ? null : String(row.costUnit) }; } function buildCombinedWhereClause(nameClause, nameParams, filterClause, filterParams) { const conditions = [nameClause]; const params = [...nameParams]; if (filterClause) { conditions.push(filterClause.replace("WHERE ", "")); params.push(...filterParams); } return { clause: `WHERE ${conditions.join(" AND ")}`, params }; } function resolveGroupBy(groupBy) { return groupBy.map((key, index) => { if (METRIC_COLUMN_SET.has(key)) { const parsed = utils.parseFieldKey(key); if (METRIC_LABEL_ONLY_GROUP_BY_EXCLUDED.has(parsed)) { throw new Error(`Invalid groupBy column(s): ${key}`); } return { kind: "column", key, selectSql: `${parsed} AS "${key}"`, groupSql: parsed, resultKey: key }; } const labelPath = buildJsonPath(key).replace(/'/g, "''"); const labelExpr = `json_extract_string(labels, '${labelPath}')`; const alias = buildGroupByAlias(index); return { kind: "label", key, selectSql: `${labelExpr} AS ${alias}`, groupSql: alias, resultKey: alias }; }); } function rowToMetricRecord(row) { return { metricId: row.metricId, timestamp: toDate(row.timestamp), name: row.name, value: Number(row.value), traceId: row.traceId ?? null, spanId: row.spanId ?? null, entityType: row.entityType ?? null, entityId: row.entityId ?? null, entityName: row.entityName ?? null, entityVersionId: row.entityVersionId ?? null, parentEntityVersionId: row.parentEntityVersionId ?? null, parentEntityType: row.parentEntityType ?? null, parentEntityId: row.parentEntityId ?? null, parentEntityName: row.parentEntityName ?? null, rootEntityVersionId: row.rootEntityVersionId ?? null, rootEntityType: row.rootEntityType ?? null, rootEntityId: row.rootEntityId ?? null, rootEntityName: row.rootEntityName ?? null, userId: row.userId ?? null, organizationId: row.organizationId ?? null, resourceId: row.resourceId ?? null, runId: row.runId ?? null, sessionId: row.sessionId ?? null, threadId: row.threadId ?? null, requestId: row.requestId ?? null, environment: row.environment ?? null, executionSource: row.executionSource ?? null, serviceName: row.serviceName ?? null, experimentId: row.experimentId ?? null, provider: row.provider ?? null, model: row.model ?? null, estimatedCost: row.estimatedCost === null || row.estimatedCost === void 0 ? null : Number(row.estimatedCost), costUnit: row.costUnit ?? null, costMetadata: parseJson(row.costMetadata), tags: parseJsonArray(row.tags), labels: parseJson(row.labels) ?? {}, metadata: parseJson(row.metadata), scope: parseJson(row.scope) }; } async function batchCreateMetrics(db, args) { if (args.metrics.length === 0) return; const tuples = args.metrics.map((m) => { return `(${[ v(m.metricId), v(m.timestamp), "nextval('metric_events_cursor_id_seq')", v(m.name), v(m.value), v(m.traceId ?? null), v(m.spanId ?? null), v(m.entityType ?? null), v(m.entityId ?? null), v(m.entityName ?? null), v(m.entityVersionId ?? null), v(m.parentEntityVersionId ?? null), v(m.parentEntityType ?? null), v(m.parentEntityId ?? null), v(m.parentEntityName ?? null), v(m.rootEntityVersionId ?? null), v(m.rootEntityType ?? null), v(m.rootEntityId ?? null), v(m.rootEntityName ?? null), v(m.userId ?? null), v(m.organizationId ?? null), v(m.resourceId ?? null), v(m.runId ?? null), v(m.sessionId ?? null), v(m.threadId ?? null), v(m.requestId ?? null), v(m.environment ?? null), v(m.executionSource ?? null), v(m.serviceName ?? null), v(m.experimentId ?? null), v(m.provider ?? null), v(m.model ?? null), v(m.estimatedCost ?? null), v(m.costUnit ?? null), jsonV(m.tags ?? null), v(JSON.stringify(m.labels ?? {})), jsonV(m.costMetadata ?? null), jsonV(m.metadata ?? null), jsonV(m.scope ?? null) ].join(", ")})`; }); await db.execute( `INSERT INTO metric_events (${METRIC_COLUMNS_SQL}) VALUES ${tuples.join(",\n")} ON CONFLICT DO NOTHING` ); } async function listMetrics(db, args) { const { mode, filters, pagination, orderBy, after, limit } = storage.listMetricsArgsSchema.parse(args); const filterRecord = filters; const page = Number(pagination.page); const perPage = Number(pagination.perPage); const { clause: filterClause, params: filterParams } = buildWhereClause(filterRecord); if (mode === "delta") { assertDeltaPollingEnabled(); const streamHeadCursor = await getStreamHeadCursor3(db); if (after === void 0) { return { metrics: [], delta: { limit, hasMore: false }, deltaCursor: streamHeadCursor }; } const afterCursorId = validateCursorId(after); const deltaWhereClause = extendWhereClause(filterClause, ["cursorId IS NOT NULL", `cursorId > CAST(? AS BIGINT)`]); const rows2 = await db.query( `SELECT * FROM metric_events ${deltaWhereClause} ORDER BY cursorId ASC LIMIT ?`, [...filterParams, afterCursorId, limit + 1] ); const visibleRows = rows2.slice(0, limit).map((row) => ({ cursorId: row.cursorId, metric: rowToMetricRecord(row) })); return { metrics: visibleRows.map((row) => row.metric), delta: { limit, hasMore: rows2.length > limit }, deltaCursor: visibleRows.length > 0 ? encodeDeltaCursor(visibleRows[visibleRows.length - 1]?.cursorId) : streamHeadCursor }; } const orderByClause = buildOrderByClause(orderBy); const { clause: paginationClause, params: paginationParams } = buildPaginationClause({ page, perPage }); const currentDeltaCursor = deltaPollingFeatureEnabled() ? await getDeltaCursor3(db, filterClause, filterParams) : void 0; const countResult = await db.query( `SELECT COUNT(*) AS total FROM metric_events ${filterClause}`, filterParams ); const total = Number(countResult[0]?.total ?? 0); const rows = await db.query( `SELECT * FROM metric_events ${filterClause} ${orderByClause} ${paginationClause}`, [...filterParams, ...paginationParams] ); return { pagination: { total, page, perPage, hasMore: (page + 1) * perPage < total }, metrics: rows.map((row) => rowToMetricRecord(row)), ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {} }; } async function getDeltaCursor3(db, filterClause, filterParams) { const rows = await db.query( `SELECT max(cursorId) AS cursorId FROM metric_events ${filterClause}`, filterParams ); const cursorId = rows[0]?.cursorId; if (cursorId !== null && cursorId !== void 0) { return encodeDeltaCursor(cursorId); } const streamRows = await db.query(`SELECT max(cursorId) AS cursorId FROM metric_events`); return encodeDeltaCursor(streamRows[0]?.cursorId); } async function getStreamHeadCursor3(db) { const streamRows = await db.query(`SELECT max(cursorId) AS cursorId FROM metric_events`); return encodeDeltaCursor(streamRows[0]?.cursorId); } async function getMetricAggregate(db, args) { const aggSql = getAggregationSql2(args.aggregation, "value", args.distinctColumn); const { clause: nameClause, params: nameParams } = buildMetricNameFilter(args.name); const { clause: filterClause, params: filterParams } = buildWhereClause( args.filters ); const { clause: whereClause, params: allParams } = buildCombinedWhereClause( nameClause, nameParams, filterClause, filterParams ); const sql = `SELECT ${aggSql} AS value, ${getCostSummarySelect()} FROM metric_events ${whereClause}`; const result = await db.query(sql, allParams); const row = result[0] ?? {}; const value = row.value === null || row.value === void 0 ? null : Number(row.value); const costSummary = normalizeCostSummaryRow(row); if (args.comparePeriod && args.filters?.timestamp) { const ts = args.filters.timestamp; if (ts.start && ts.end) { const duration = ts.end.getTime() - ts.start.getTime(); let prevStart; let prevEnd; switch (args.comparePeriod) { case "previous_period": prevStart = new Date(ts.start.getTime() - duration); prevEnd = new Date(ts.end.getTime() - duration); break; case "previous_day": prevStart = new Date(ts.start.getTime() - 864e5); prevEnd = new Date(ts.end.getTime() - 864e5); break; case "previous_week": prevStart = new Date(ts.start.getTime() - 6048e5); prevEnd = new Date(ts.end.getTime() - 6048e5); break; default: prevStart = new Date(ts.start.getTime() - duration); prevEnd = new Date(ts.end.getTime() - duration); } const prevFilters = { ...args.filters ?? {}, timestamp: { start: prevStart, end: prevEnd, startExclusive: ts.startExclusive, endExclusive: ts.endExclusive } }; const { clause: prevFilterClause, params: prevFilterParams } = buildWhereClause( prevFilters ); const { clause: prevWhereClause, params: prevParams } = buildCombinedWhereClause( nameClause, nameParams, prevFilterClause, prevFilterParams ); const prevSql = `SELECT ${aggSql} AS value, ${getCostSummarySelect()} FROM metric_events ${prevWhereClause}`; const prevResult = await db.query(prevSql, prevParams); const prevRow = prevResult[0] ?? {}; const previousValue = prevRow.value === null || prevRow.value === void 0 ? null : Number(prevRow.value); const previousCostSummary = normalizeCostSummaryRow(prevRow); let changePercent = null; if (previousValue !== null && previousValue !== 0 && value !== null) { changePercent = (value - previousValue) / Math.abs(previousValue) * 100; } let costChangePercent = null; if (previousCostSummary.estimatedCost !== null && previousCostSummary.estimatedCost !== 0 && costSummary.estimatedCost !== null) { costChangePercent = (costSummary.estimatedCost - previousCostSummary.estimatedCost) / Math.abs(previousCostSummary.estimatedCost) * 100; } return { value, estimatedCost: costSummary.estimatedCost, costUnit: costSummary.costUnit, previousValue, previousEstimatedCost: previousCostSummary.estimatedCost, changePercent, costChangePercent }; } } return { value, estimatedCost: costSummary.estimatedCost, costUnit: costSummary.costUnit }; } async function getMetricBreakdown(db, args) { const aggSql = getAggregationSql2(args.aggregation, "value", args.distinctColumn); const { clause: nameClause, params: nameParams } = buildMetricNameFilter(args.name); const { clause: filterClause, params: filterParams } = buildWhereClause( args.filters ); const { clause: whereClause, params: allParams } = buildCombinedWhereClause( nameClause, nameParams, filterClause, filterParams ); const resolvedGroupBy = resolveGroupBy(args.groupBy); const selectGroupBy = resolvedGroupBy.map((entry) => entry.selectSql).join(", "); const groupByCols = resolvedGroupBy.map((entry) => entry.groupSql).join(", "); const orderDirection = args.orderDirection === "ASC" ? "ASC" : "DESC"; const limitClause = typeof args.limit === "number" ? `LIMIT ?` : ""; const limitParams = typeof args.limit === "number" ? [args.limit] : []; const sql = `SELECT ${selectGroupBy}, ${aggSql} AS value, ${getCostSummarySelect()} FROM metric_events ${whereClause} GROUP BY ${groupByCols} ORDER BY value ${orderDirection} ${limitClause}`; const rows = await db.query(sql, [...allParams, ...limitParams]); const groups = rows.map((row) => { const dimensions = {}; for (const entry of resolvedGroupBy) { const value = row[entry.resultKey]; dimensions[entry.key] = value === null || value === void 0 ? null : String(value); } const costSummary = normalizeCostSummaryRow(row); return { dimensions, value: Number(row.value ?? 0), estimatedCost: costSummary.estimatedCost, costUnit: costSummary.costUnit }; }); return { groups }; } async function getMetricTimeSeries(db, args) { const aggSql = getAggregationSql2(args.aggregation, "value", args.distinctColumn); const intervalSql = getIntervalSql2(args.interval); const { clause: nameClause, params: nameParams } = buildMetricNameFilter(args.name); const { clause: filterClause, params: filterParams } = buildWhereClause( args.filters ); const { clause: whereClause, params: allParams } = buildCombinedWhereClause( nameClause, nameParams, filterClause, filterParams ); if (args.groupBy && args.groupBy.length > 0) { const resolvedGroupBy = resolveGroupBy(args.groupBy); const selectGroupBy = resolvedGroupBy.map((entry) => entry.selectSql).join(", "); const groupByCols = resolvedGroupBy.map((entry) => entry.groupSql).join(", "); const sql2 = ` SELECT time_bucket(INTERVAL '${intervalSql}', timestamp) AS bucket, ${selectGroupBy}, ${aggSql} AS value, ${getCostSummarySelect()} FROM metric_events ${whereClause} GROUP BY bucket, ${groupByCols} ORDER BY bucket `; const rows2 = await db.query(sql2, allParams); const seriesMap = /* @__PURE__ */ new Map(); for (const row of rows2) { const dimensionValues = resolvedGroupBy.map((entry) => row[entry.resultKey]); const seriesKey = JSON.stringify(dimensionValues); const name = dimensionValues.map(toSeriesDisplayValue).join("|"); const costSummary = normalizeCostSummaryRow(row); if (!seriesMap.has(seriesKey)) { seriesMap.set(seriesKey, { name, costUnits: /* @__PURE__ */ new Set(), points: [] }); } if (costSummary.costUnit) { seriesMap.get(seriesKey).costUnits.add(costSummary.costUnit); } seriesMap.get(seriesKey).points.push({ timestamp: row.bucket instanceof Date ? row.bucket : new Date(String(row.bucket)), value: Number(row.value ?? 0), estimatedCost: costSummary.estimatedCost }); } return { series: Array.from(seriesMap.values()).map((series) => ({ name: series.name, costUnit: series.costUnits.size === 1 ? Array.from(series.costUnits)[0] : null, points: series.points })) }; } const sql = ` SELECT time_bucket(INTERVAL '${intervalSql}', timestamp) AS bucket, ${aggSql} AS value, ${getCostSummarySelect()} FROM metric_events ${whereClause} GROUP BY bucket ORDER BY bucket `; const rows = await db.query(sql, allParams); const metricName = Array.isArray(args.name) ? args.name.join(",") : args.name; const overallCostUnits = new Set( rows.map((row) => row.costUnit).filter((value) => typeof value === "string") ); return { series: [ { name: metricName, costUnit: overallCostUnits.size === 1 ? Array.from(overallCostUnits)[0] : null, points: rows.map((row) => { const costSummary = normalizeCostSummaryRow(row); return { timestamp: row.bucket instanceof Date ? row.bucket : new Date(String(row.bucket)), value: Number(row.value ?? 0), estimatedCost: costSummary.estimatedCost }; }) } ] }; } async function getMetricPercentiles(db, args) { const intervalSql = getIntervalSql2(args.interval); const { clause: filterClause, params: filterParams } = buildWhereClause( args.filters ); const allConditions = [`name = ?`]; const allParams = [args.name]; if (filterClause) { allConditions.push(filterClause.replace("WHERE ", "")); allParams.push(...filterParams); } const whereClause = `WHERE ${allConditions.join(" AND ")}`; const series = []; for (const p of args.percentiles) { const sql = ` SELECT time_bucket(INTERVAL '${intervalSql}', timestamp) AS bucket, percentile_cont(${p}) WITHIN GROUP (ORDER BY value) AS pvalue FROM metric_events ${whereClause} GROUP BY bucket ORDER BY bucket `; const rows = await db.query(sql, allParams); series.push({ percentile: p, points: rows.map((row) => ({ timestamp: row.bucket instanceof Date ? row.bucket : new Date(String(row.bucket)), value: Number(row.pvalue ?? 0) })) }); } return { series }; } async function getMetricNames(db, args) { const conditions = []; const params = []; if (args.prefix) { conditions.push(`name LIKE ?`); params.push(`${args.prefix}%`); } const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : ""; const limitClause = args.limit ? `LIMIT ?` : ""; if (args.limit) params.push(args.limit); const rows = await db.query( `SELECT DISTINCT name FROM metric_events ${whereClause} ORDER BY name ${limitClause}`, params ); return { names: rows.map((r) => r.name) }; } async function getMetricLabelKeys(db, args) { const rows = await db.query( `SELECT DISTINCT unnest(json_keys(labels)) AS key FROM metric_events WHERE name = ? AND labels IS NOT NULL`, [args.metricName] ); return { keys: rows.map((r) => r.key) }; } async function getMetricLabelValues(db, args) { const labelPath = buildJsonPath(args.labelKey); const conditions = [`name = ?`, `json_extract_string(labels, ?) IS NOT NULL`]; const params = [args.metricName, labelPath]; if (args.prefix) { conditions.push(`json_extract_string(labels, ?) LIKE ?`); params.push(labelPath, `${args.prefix}%`); } const limitClause = args.limit ? `LIMIT ?` : ""; if (args.limit) params.push(args.limit); const rows = await db.query( `SELECT DISTINCT json_extract_string(labels, ?) AS val FROM metric_events WHERE ${conditions.join(" AND ")} ORDER BY val ${limitClause}`, [labelPath, ...params] ); return { values: rows.map((r) => r.val) }; } var CURSOR_ID_TABLES = ["span_events", "metric_events", "log_events", "score_events", "feedback_events"]; async function dropLegacyCursorIdDefaults(db) { const rows = await db.query( `SELECT table_name FROM information_schema.columns WHERE column_name = 'cursorId' AND column_default IS NOT NULL AND table_name IN (${CURSOR_ID_TABLES.map((t) => `'${t}'`).join(", ")})` ); if (rows.length === 0) return; await db.executeBatch(rows.map((row) => `ALTER TABLE ${row.table_name} ALTER COLUMN cursorId DROP DEFAULT`)); } var SIGNAL_MIGRATIONS = [ { table: "metric_events", createDDL: METRIC_EVENTS_DDL, idColumn: "metricId", cursorSequenceDDL: METRIC_EVENTS_CURSOR_SEQUENCE_DDL }, { table: "log_events", createDDL: LOG_EVENTS_DDL, idColumn: "logId", cursorSequenceDDL: LOG_EVENTS_CURSOR_SEQUENCE_DDL }, { table: "score_events", createDDL: SCORE_EVENTS_DDL, idColumn: "scoreId", cursorSequenceDDL: SCORE_EVENTS_CURSOR_SEQUENCE_DDL }, { table: "feedback_events", createDDL: FEEDBACK_EVENTS_DDL, idColumn: "feedbackId", cursorSequenceDDL: FEEDBACK_EVENTS_CURSOR_SEQUENCE_DDL } ]; async function tableExists(db, table) { const rows = await db.query( `SELECT table_name FROM information_schema.tables WHERE table_name = ?`, [table] ); return rows.length > 0; } async function hasPrimaryKey(db, table) { const rows = await db.query( `SELECT constraint_type FROM information_schema.table_constraints WHERE table_name = ? AND constraint_type = 'PRIMARY KEY'`, [table] ); return rows.length > 0; } async function getColumns(db, table) { const rows = await db.query( `SELECT column_name FROM information_schema.columns WHERE table_name = ?`, [table] ); return rows.map((r) => r.column_name); } function buildTemporaryTableDDL(createDDL, table, tempTable) { return createDDL.replace(`CREATE TABLE IF NOT EXISTS ${table}`, `CREATE TABLE ${tempTable}`); } async function dropTableIfExists(db, table) { if (await tableExists(db, table)) { await db.execute(`DROP TABLE ${table}`); } } function createMigrationError(args, error$1) { return new error.MastraError( { id: storage.createStorageErrorId("DUCKDB", "MIGRATE_SIGNAL_TABLES", "FAILED"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.THIRD_PARTY, details: args }, error$1 ); } async function checkSignalTablesMigrationStatus(db) { const tables = []; for (const { table, idColumn } of SIGNAL_MIGRATIONS) { if (!await tableExists(db, table)) { continue; } if (await hasPrimaryKey(db, table)) { continue; } tables.push({ table, idColumn }); } return { needsMigration: tables.length > 0, tables }; } async function migrateSignalTables(db, logger) { for (const { table, createDDL, idColumn, cursorSequenceDDL } of SIGNAL_MIGRATIONS) { if (!await tableExists(db, table)) continue; if (await hasPrimaryKey(db, table)) continue; logger?.info?.(`Migrating ${table} to schema with ${idColumn} PRIMARY KEY`); const temp = `${table}_migrating_${Date.now()}`; const backup = `${table}_backup_${Date.now()}`; let originalRenamed = false; let swapCompleted = false; try { await db.execute(cursorSequenceDDL); await db.execute(buildTemporaryTableDDL(createDDL, table, temp)); const newColumns = await getColumns(db, temp); const currentColumns = new Set(await getColumns(db, table)); const columnList = newColumns.map((c) => `"${c}"`).join(", "); const selectExprs = newColumns.map((c) => { if (c === idColumn) { return currentColumns.has(c) ? `COALESCE(NULLIF("${c}", ''), CAST(uuid() AS VARCHAR)) AS "${c}"` : `CAST(uuid() AS VARCHAR) AS "${c}"`; } return currentColumns.has(c) ? `"${c}"` : `NULL AS "${c}"`; }).join(", "); await db.execute(`INSERT INTO ${temp} (${columnList}) SELECT ${selectExprs} FROM ${table}`); await db.execute(`ALTER TABLE ${table} RENAME TO ${backup}`); originalRenamed = true; await db.execute(`ALTER TABLE ${temp} RENAME TO ${table}`); swapCompleted = true; try { await db.execute(`DROP TABLE ${backup}`); } catch (cleanupError) { logger?.warn?.( `Migration of ${table} completed, but failed to drop backup ${backup}: ${cleanupError.message}` ); } logger?.info?.(`Successfully migrated ${table}`); } catch (error) { logger?.error?.(`Migration of ${table} failed: ${error.message}`); try { await dropTableIfExists(db, temp); } catch (restoreError) { logger?.error?.(`Failed to clean up temporary table ${temp}: ${restoreError.message}`); } if (originalRenamed && !swapCompleted) { try { await db.execute(`ALTER TABLE ${backup} RENAME TO ${table}`); } catch (restoreError) { logger?.error?.( `Failed to restore original table ${table} from backup ${backup}: ${restoreError.message}` ); } } throw createMigrationError({ table, idColumn }, error); } } } var SCORE_GROUP_BY_COLUMNS = /* @__PURE__ */ new Set([ "timestamp", "traceId", "spanId", "experimentId", "scoreTraceId", "entityType", "entityId", "entityName", "entityVersionId", "parentEntityVersionId", "parentEntityType", "parentEntityId", "parentEntityName", "rootEntityVersionId", "rootEntityType", "rootEntityId", "rootEntityName", "userId", "organizationId", "resourceId", "runId", "sessionId", "threadId", "requestId", "environment", "executionSource", "serviceName", "scorerId", "scorerVersion", "scoreSource", "score", "reason" ]); function getAggregationSql3(aggregation, measure = "score") { switch (aggregation) { case "sum": return `SUM(${measure})`; case "avg": return `AVG(${measure})`; case "min": return `MIN(${measure})`; case "max": return `MAX(${measure})`; case "count": return `CAST(COUNT(${measure}) AS DOUBLE)`; case "last": return `arg_max(${measure}, timestamp)`; default: return `SUM(${measure})`; } } function getIntervalSql3(interval) { switch (interval) { case "1m": return "1 minute"; case "5m": return "5 minutes"; case "15m": return "15 minutes"; case "1h": return "1 hour"; case "1d": return "1 day"; default: return "1 hour"; } } function getValidatedPercentiles2(percentiles) { if (!Array.isArray(percentiles) || percentiles.length === 0) { throw new Error("Percentiles must include at least one value between 0 and 1."); } return percentiles.map((percentile) => { if (!Number.isFinite(percentile) || percentile < 0 || percentile > 1) { throw new Error("Percentiles must be finite numbers between 0 and 1."); } return percentile; }); } function buildScoreWhereClause(args) { const conditions = ["scorerId = ?"]; const params = [args.scorerId]; if (args.scoreSource !== void 0) { conditions.push("scoreSource = ?"); params.push(args.scoreSource); } const { clause: filterClause, params: filterParams } = buildWhereClause( args.filters, { source: "scoreSource" } ); if (filterClause) { conditions.push(filterClause.replace("WHERE ", "")); params.push(...filterParams); } return { clause: `WHERE ${conditions.join(" AND ")}`, params }; } function resolveScoreGroupBy(groupBy) { return groupBy.map((key, index) => { const column = utils.parseFieldKey(key); if (!SCORE_GROUP_BY_COLUMNS.has(column)) { throw new Error(`Invalid groupBy column(s): ${key}`); } const alias = `group_by_${index}`; return { key, selectSql: `${column} AS ${alias}`, groupSql: alias }; }); } function toSeriesName2(values) { return values.map((value) => value === null || value === void 0 ? "" : String(value)).join("|"); } function rowToScoreRecord(row) { return { scoreId: row.scoreId, timestamp: toDate(row.timestamp), traceId: row.traceId ?? null, spanId: row.spanId ?? null, experimentId: row.experimentId ?? null, scoreTraceId: row.scoreTraceId ?? null, entityType: row.entityType ?? null, entityId: row.entityId ?? null, entityName: row.entityName ?? null, entityVersionId: row.entityVersionId ?? null, parentEntityVersionId: row.parentEntityVersionId ?? null, parentEntityType: row.parentEntityType ?? null, parentEntityId: row.parentEntityId ?? null, parentEntityName: row.parentEntityName ?? null, rootEntityVersionId: row.rootEntityVersionId ?? null, rootEntityType: row.rootEntityType ?? null, rootEntityId: row.rootEntityId ?? null, rootEntityName: row.rootEntityName ?? null, userId: row.userId ?? null, organizationId: row.organizationId ?? null, resourceId: row.resourceId ?? null, runId: row.runId ?? null, sessionId: row.sessionId ?? null, threadId: row.threadId ?? null, requestId: row.requestId ?? null, environment: row.environment ?? null, executionSource: row.executionSource ?? null, serviceName: row.serviceName ?? null, scorerId: row.scorerId, scorerVersion: row.scorerVersion ?? null, source: row.scoreSource ?? null, scoreSource: row.scoreSource ?? null, score: Number(row.score), reason: row.reason ?? null, tags: parseJsonArray(row.tags), metadata: parseJson(row.metadata), scope: parseJson(row.scope) }; } function getComparisonDateRange2(comparePeriod, timestamp) { if (!timestamp.start || !timestamp.end) return null; const duration = timestamp.end.getTime() - timestamp.start.getTime(); switch (comparePeriod) { case "previous_period": return { start: new Date(timestamp.start.getTime() - duration), end: new Date(timestamp.end.getTime() - duration), startExclusive: timestamp.startExclusive, endExclusive: timestamp.endExclusive }; case "previous_day": return { start: new Date(timestamp.start.getTime() - 864e5), end: new Date(timestamp.end.getTime() - 864e5), startExclusive: timestamp.startExclusive, endExclusive: timestamp.endExclusive }; case "previous_week": return { start: new Date(timestamp.start.getTime() - 6048e5), end: new Date(timestamp.end.getTime() - 6048e5), startExclusive: timestamp.startExclusive, endExclusive: timestamp.endExclusive }; } } async function createScore(db, args) { const s = args.score; const scoreSource = s.scoreSource ?? s.source ?? null; await db.execute( `INSERT INTO score_events ( scoreId, timestamp, cursorId, traceId, spanId, experimentId, scoreTraceId, entityType, entityId, entityName, entityVersionId, parentEntityVersionId, parentEntityType, parentEntityId, parentEntityName, rootEntityVersionId, rootEntityType, rootEntityId, rootEntityName, userId, organizationId, resourceId, runId, sessionId, threadId, requestId, environment, executionSource, serviceName, scorerId, scorerVersion, scoreSource, score, reason, tags, metadata, scope ) VALUES (${[ v(s.scoreId), v(s.timestamp), "nextval('score_events_cursor_id_seq')", v(s.traceId), v(s.spanId ?? null), v(s.experimentId ?? null), v(s.scoreTraceId ?? null), v(s.entityType ?? null), v(s.entityId ?? null), v(s.entityName ?? null), v(s.entityVersionId ?? null), v(s.parentEntityVersionId ?? null), v(s.parentEntityType ?? null), v(s.parentEntityId ?? null), v(s.parentEntityName ?? null), v(s.rootEntityVersionId ?? null), v(s.rootEntityType ?? null), v(s.rootEntityId ?? null), v(s.rootEntityName ?? null), v(s.userId ?? null), v(s.organizationId ?? null), v(s.resourceId ?? null), v(s.runId ?? null), v(s.sessionId ?? null), v(s.threadId ?? null), v(s.requestId ?? null), v(s.environment ?? null), v(s.executionSource ?? null), v(s.serviceName ?? null), v(s.scorerId), v(s.scorerVersion ?? null), v(scoreSource), v(s.score), v(s.reason ?? null), jsonV(s.tags ?? null), jsonV(s.metadata), jsonV(s.scope ?? null) ].join(", ")}) ON CONFLICT DO NOTHING` ); } async function batchCreateScores(db, args) { if (args.scores.length === 0) return; const tuples = args.scores.map((s) => { const legacyScore = s; const scoreSource = legacyScore.scoreSource ?? legacyScore.source ?? null; return `(${[ v(legacyScore.scoreId), v(legacyScore.timestamp), "nextval('score_events_cursor_id_seq')", v(legacyScore.traceId), v(legacyScore.spanId ?? null), v(legacyScore.experimentId ?? null), v(legacyScore.scoreTraceId ?? null), v(legacyScore.entityType ?? null), v(legacyScore.entityId ?? null), v(legacyScore.entityName ?? null), v(legacyScore.entityVersionId ?? null), v(legacyScore.parentEntityVersionId ?? null), v(legacyScore.parentEntityType ?? null), v(legacyScore.parentEntityId ?? null), v(legacyScore.parentEntityName ?? null), v(legacyScore.rootEntityVersionId ?? null), v(legacyScore.rootEntityType ?? null), v(legacyScore.rootEntityId ?? null), v(legacyScore.rootEntityName ?? null), v(legacyScore.userId ?? null), v(legacyScore.organizationId ?? null), v(legacyScore.resourceId ?? null), v(legacyScore.runId ?? null), v(legacyScore.sessionId ?? null), v(legacyScore.threadId ?? null), v(legacyScore.requestId ?? null), v(legacyScore.environment ?? null), v(legacyScore.executionSource ?? null), v(legacyScore.serviceName ?? null), v(legacyScore.scorerId), v(legacyScore.scorerVersion ?? null), v(scoreSource), v(legacyScore.score), v(legacyScore.reason ?? null), jsonV(legacyScore.tags ?? null), jsonV(legacyScore.metadata), jsonV(legacyScore.scope ?? null) ].join(", ")})`; }); await db.execute( `INSERT INTO score_events ( scoreId, timestamp, cursorId, traceId, spanId, experimentId, scoreTraceId, entityType, entityId, entityName, entityVersionId, parentEntityVersionId, parentEntityType, parentEntityId, parentEntityName, rootEntityVersionId, rootEntityType, rootEntityId, rootEntityName, userId, organizationId, resourceId, runId, sessionId, threadId, requestId, environment, executionSource, serviceName, scorerId, scorerVersion, scoreSource, score, reason, tags, metadata, scope ) VALUES ${tuples.join(",\n ")} ON CONFLICT DO NOTHING` ); } async function listScores(db, args) { const { mode, filters, pagination, orderBy, after, limit } = storage.listScoresArgsSchema.parse(args); const page = Number(pagination.page); const perPage = Number(pagination.perPage); const { clause: filterClause, params: filterParams } = buildWhereClause(filters, { source: "scoreSource" }); if (mode === "delta") { assertDeltaPollingEnabled(); const streamHeadCursor = await getStreamHeadCursor4(db); if (after === void 0) { return { scores: [], delta: { limit, hasMore: false }, deltaCursor: streamHeadCursor }; } const afterCursorId = validateCursorId(after); const deltaWhereClause = extendWhereClause(filterClause, ["cursorId IS NOT NULL", `cursorId > CAST(? AS BIGINT)`]); const rows2 = await db.query( `SELECT * FROM score_events ${deltaWhereClause} ORDER BY cursorId ASC LIMIT ?`, [...filterParams, afterCursorId, limit + 1] ); const visibleRows = rows2.slice(0, limit).map((row) => ({ cursorId: row.cursorId, score: rowToScoreRecord(row) })); return { scores: visibleRows.map((row) => row.score), delta: { limit, hasMore: rows2.length > limit }, deltaCursor: visibleRows.length > 0 ? encodeDeltaCursor(visibleRows[visibleRows.length - 1]?.cursorId) : streamHeadCursor }; } const orderByClause = buildOrderByClause(orderBy); const { clause: paginationClause, params: paginationParams } = buildPaginationClause({ page, perPage }); const currentDeltaCursor = deltaPollingFeatureEnabled() ? await getDeltaCursor4(db, filterClause, filterParams) : void 0; const countResult = await db.query( `SELECT COUNT(*) as total FROM score_events ${filterClause}`, filterParams ); const total = Number(countResult[0]?.total ?? 0); const rows = await db.query( `SELECT * FROM score_events ${filterClause} ${orderByClause} ${paginationClause}`, [...filterParams, ...paginationParams] ); return { pagination: { total, page, perPage, hasMore: (page + 1) * perPage < total }, scores: rows.map((row) => rowToScoreRecord(row)), ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {} }; } async function getDeltaCursor4(db, filterClause, filterParams) { const rows = await db.query( `SELECT max(cursorId) AS cursorId FROM score_events ${filterClause}`, filterParams ); const cursorId = rows[0]?.cursorId; if (cursorId !== null && cursorId !== void 0) { return encodeDeltaCursor(cursorId); } const streamRows = await db.query(`SELECT max(cursorId) AS cursorId FROM score_events`); return encodeDeltaCursor(streamRows[0]?.cursorId); } async function getStreamHeadCursor4(db) { const streamRows = await db.query(`SELECT max(cursorId) AS cursorId FROM score_events`); return encodeDeltaCursor(streamRows[0]?.cursorId); } async function getScoreById(db, scoreId) { const rows = await db.query(`SELECT * FROM score_events WHERE scoreId = ? LIMIT 1`, [ scoreId ]); return rows[0] ? rowToScoreRecord(rows[0]) : null; } async function getScoreAggregate(db, args) { const aggSql = getAggregationSql3(args.aggregation); const { clause, params } = buildScoreWhereClause(args); const rows = await db.query(`SELECT ${aggSql} AS value FROM score_events ${clause}`, params); const value = rows[0]?.value === null || rows[0]?.value === void 0 ? null : Number(rows[0]?.value); if (args.comparePeriod && args.filters?.timestamp) { const previousTimestamp = getComparisonDateRange2(args.comparePeriod, args.filters.timestamp); if (previousTimestamp) { const prevRows = await db.query( `SELECT ${aggSql} AS value FROM score_events ${buildScoreWhereClause({ ...args, filters: { ...args.filters ?? {}, timestamp: previousTimestamp } }).clause}`, buildScoreWhereClause({ ...args, filters: { ...args.filters ?? {}, timestamp: previousTimestamp } }).params ); const previousValue = prevRows[0]?.value === null || prevRows[0]?.value === void 0 ? null : Number(prevRows[0]?.value); let changePercent = null; if (previousValue !== null && previousValue !== 0 && value !== null) { changePercent = (value - previousValue) / Math.abs(previousValue) * 100; } return { value, previousValue, changePercent }; } } return { value }; } async function getScoreBreakdown(db, args) { const aggSql = getAggregationSql3(args.aggregation); const { clause, params } = buildScoreWhereClause(args); const resolvedGroupBy = resolveScoreGroupBy(args.groupBy); 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`; const rows = await db.query(sql, params); return { groups: rows.map((row) => ({ dimensions: Object.fromEntries( resolvedGroupBy.map((entry, index) => { const value = row[`group_by_${index}`]; return [entry.key, value === null || value === void 0 ? null : String(value)]; }) ), value: Number(row.value ?? 0) })) }; } async function getScoreTimeSeries(db, args) { const aggSql = getAggregationSql3(args.aggregation); const intervalSql = getIntervalSql3(args.interval); const { clause, params } = buildScoreWhereClause(args); if (args.groupBy && args.groupBy.length > 0) { const resolvedGroupBy = resolveScoreGroupBy(args.groupBy); const sql = ` SELECT time_bucket(INTERVAL '${intervalSql}', timestamp) AS bucket, ${resolvedGroupBy.map((entry) => entry.selectSql).join(", ")}, ${aggSql} AS value FROM score_events ${clause} GROUP BY bucket, ${resolvedGroupBy.map((entry) => entry.groupSql).join(", ")} ORDER BY bucket `; const rows2 = await db.query(sql, params); const seriesMap = /* @__PURE__ */ new Map(); for (const row of rows2) { const groupValues = resolvedGroupBy.map((_, index) => row[`group_by_${index}`]); const key = JSON.stringify(groupValues); if (!seriesMap.has(key)) { seriesMap.set(key, { name: toSeriesName2(groupValues), points: [] }); } seriesMap.get(key).points.push({ timestamp: row.bucket instanceof Date ? row.bucket : new Date(String(row.bucket)), value: Number(row.value ?? 0) }); } return { series: Array.from(seriesMap.values()) }; } const rows = await db.query( ` SELECT time_bucket(INTERVAL '${intervalSql}', timestamp) AS bucket, ${aggSql} AS value FROM score_events ${clause} GROUP BY bucket ORDER BY bucket `, params ); return { series: [ { name: args.scoreSource ? `${args.scorerId}|${args.scoreSource}` : args.scorerId, points: rows.map((row) => ({ timestamp: row.bucket instanceof Date ? row.bucket : new Date(String(row.bucket)), value: Number(row.value ?? 0) })) } ] }; } async function getScorePercentiles(db, args) { const intervalSql = getIntervalSql3(args.interval); const { clause, params } = buildScoreWhereClause(args); const percentiles = getValidatedPercentiles2(args.percentiles); const series = []; for (const percentile of percentiles) { const rows = await db.query( ` SELECT time_bucket(INTERVAL '${intervalSql}', timestamp) AS bucket, percentile_cont(${percentile}) WITHIN GROUP (ORDER BY score) AS pvalue FROM score_events ${clause} GROUP BY bucket ORDER BY bucket `, params ); series.push({ percentile, points: rows.map((row) => ({ timestamp: row.bucket instanceof Date ? row.bucket : new Date(String(row.bucket)), value: Number(row.pvalue ?? 0) })) }); } return { series }; } var COLUMNS2 = [ "eventType", "timestamp", "cursorId", "traceId", "spanId", "parentSpanId", "name", "spanType", "isEvent", "endedAt", "experimentId", "entityType", "entityId", "entityName", "entityVersionId", "userId", "organizationId", "resourceId", "runId", "sessionId", "threadId", "requestId", "environment", "source", "serviceName", "attributes", "metadata", "tags", "scope", "links", "input", "output", "error", "requestContext" ]; var COLUMNS_SQL2 = COLUMNS2.join(", "); function argMaxNonNull(col) { return `arg_max(${col}, timestamp) FILTER (WHERE ${col} IS NOT NULL) as ${col}`; } var SPAN_RECONSTRUCT_SELECT = ` SELECT traceId, spanId, ${argMaxNonNull("name")}, ${argMaxNonNull("spanType")}, ${argMaxNonNull("parentSpanId")}, ${argMaxNonNull("isEvent")}, coalesce(min(timestamp) FILTER (WHERE eventType = 'start'), min(timestamp)) as startedAt, ${argMaxNonNull("endedAt")}, ${argMaxNonNull("experimentId")}, ${argMaxNonNull("entityType")}, ${argMaxNonNull("entityId")}, ${argMaxNonNull("entityName")}, ${argMaxNonNull("entityVersionId")}, ${argMaxNonNull("userId")}, ${argMaxNonNull("organizationId")}, ${argMaxNonNull("resourceId")}, ${argMaxNonNull("runId")}, ${argMaxNonNull("sessionId")}, ${argMaxNonNull("threadId")}, ${argMaxNonNull("requestId")}, ${argMaxNonNull("environment")}, ${argMaxNonNull("source")}, ${argMaxNonNull("serviceName")}, ${argMaxNonNull("attributes")}, ${argMaxNonNull("metadata")}, ${argMaxNonNull("tags")}, ${argMaxNonNull("scope")}, ${argMaxNonNull("links")}, ${argMaxNonNull("input")}, ${argMaxNonNull("output")}, ${argMaxNonNull("error")}, ${argMaxNonNull("requestContext")} FROM span_events `; var SPAN_RECONSTRUCT_SELECT_LIGHT = ` SELECT traceId, spanId, ${argMaxNonNull("name")}, ${argMaxNonNull("spanType")}, ${argMaxNonNull("parentSpanId")}, ${argMaxNonNull("isEvent")}, coalesce(min(timestamp) FILTER (WHERE eventType = 'start'), min(timestamp)) as startedAt, ${argMaxNonNull("endedAt")}, ${argMaxNonNull("entityType")}, ${argMaxNonNull("entityId")}, ${argMaxNonNull("entityName")}, ${argMaxNonNull("error")} FROM span_events `; function rowToLightSpanRecord(row) { return { traceId: row.traceId, spanId: row.spanId, name: row.name, spanType: row.spanType, parentSpanId: row.parentSpanId ?? null, isEvent: row.isEvent, startedAt: toDate(row.startedAt), endedAt: toDateOrNull(row.endedAt), entityType: row.entityType ?? null, entityId: row.entityId ?? null, entityName: row.entityName ?? null, error: parseJson(row.error), createdAt: toDate(row.startedAt), // DuckDB event-sourced — use startedAt as proxy updatedAt: toDateOrNull(row.endedAt) }; } function rowToSpanRecord(row) { return { traceId: row.traceId, spanId: row.spanId, name: row.name, spanType: row.spanType, parentSpanId: row.parentSpanId ?? null, isEvent: row.isEvent, startedAt: toDate(row.startedAt), endedAt: toDateOrNull(row.endedAt), experimentId: row.experimentId ?? null, entityType: row.entityType ?? null, entityId: row.entityId ?? null, entityName: row.entityName ?? null, entityVersionId: row.entityVersionId ?? null, userId: row.userId ?? null, organizationId: row.organizationId ?? null, resourceId: row.resourceId ?? null, runId: row.runId ?? null, sessionId: row.sessionId ?? null, threadId: row.threadId ?? null, requestId: row.requestId ?? null, environment: row.environment ?? null, source: row.source ?? null, serviceName: row.serviceName ?? null, attributes: parseJson(row.attributes), metadata: parseJson(row.metadata), tags: parseJsonArray(row.tags), scope: parseJson(row.scope), links: parseJsonArray(row.links), input: parseJson(row.input), output: parseJson(row.output), error: parseJson(row.error), requestContext: parseJson(row.requestContext), createdAt: toDate(row.startedAt), updatedAt: null }; } function buildHasChildErrorClause(hasChildError, rootAlias) { if (hasChildError === void 0) return ""; const base = `SELECT 1 FROM span_events c WHERE c.traceId = ${rootAlias}.traceId AND c.spanId != ${rootAlias}.spanId AND c.error IS NOT NULL`; return hasChildError ? `EXISTS (${base})` : `NOT EXISTS (${base})`; } var PREFILTER_KEYS = /* @__PURE__ */ new Set([ "traceId", "spanId", "parentSpanId", "name", "spanType", "source", "entityType", "entityId", "entityName", "entityVersionId", "experimentId", "userId", "organizationId", "resourceId", "runId", "sessionId", "threadId", "requestId", "environment", "serviceName" ]); var SAFE_PREFILTER_ORDER_FIELDS = /* @__PURE__ */ new Set(["startedAt"]); function intersectTimestampRange(existing, incoming) { if (!existing) return { ...incoming }; const merged = { ...existing }; if (incoming.start !== void 0) { if (merged.start === void 0 || incoming.start.getTime() > merged.start.getTime()) { merged.start = incoming.start; merged.startExclusive = incoming.startExclusive; } else if (incoming.start.getTime() === merged.start.getTime()) { merged.startExclusive = (merged.startExclusive ?? false) || (incoming.startExclusive ?? false); } } if (incoming.end !== void 0) { if (merged.end === void 0 || incoming.end.getTime() < merged.end.getTime()) { merged.end = incoming.end; merged.endExclusive = incoming.endExclusive; } else if (incoming.end.getTime() === merged.end.getTime()) { merged.endExclusive = (merged.endExclusive ?? false) || (incoming.endExclusive ?? false); } } return merged; } function partitionAnchorFilters(filters) { const prefilter = {}; const postAgg = {}; let hasChildError; for (const [key, value] of Object.entries(filters)) { if (value === void 0 || value === null) continue; if (key === "hasChildError") { if (typeof value === "boolean") hasChildError = value; continue; } if (key === "startedAt") { prefilter.timestamp = intersectTimestampRange( prefilter.timestamp, value ); continue; } if (key === "endedAt") { postAgg.endedAt = value; const dateRange = value; if (dateRange?.end) { prefilter.timestamp = intersectTimestampRange(prefilter.timestamp, { end: dateRange.end, endExclusive: dateRange.endExclusive }); } continue; } if (PREFILTER_KEYS.has(key)) { prefilter[key] = value; continue; } postAgg[key] = value; } return { prefilter, postAgg, hasChildError }; } function toValuesTuple(row) { return [ v(row.eventType), v(row.timestamp), "nextval('span_events_cursor_id_seq')", v(row.traceId), v(row.spanId), v(row.parentSpanId), v(row.name), v(row.spanType), v(row.isEvent), v(row.endedAt), v(row.experimentId), v(row.entityType), v(row.entityId), v(row.entityName), v(row.entityVersionId), v(row.userId), v(row.organizationId), v(row.resourceId), v(row.runId), v(row.sessionId), v(row.threadId), v(row.requestId), v(row.environment), v(row.source), v(row.serviceName), jsonV(row.attributes), jsonV(row.metadata), jsonV(row.tags), jsonV(row.scope), jsonV(row.links), jsonV(row.input), jsonV(row.output), jsonV(row.error), jsonV(row.requestContext) ].join(", "); } async function insertSpanEvents(db, rows) { if (rows.length === 0) return; const tuples = rows.map((row) => `(${toValuesTuple(row)})`).join(",\n"); await db.execute(`INSERT INTO span_events (${COLUMNS_SQL2}) VALUES ${tuples}`); } function createStartSpanRow(s) { return { eventType: "start", timestamp: s.startedAt, traceId: s.traceId, spanId: s.spanId, parentSpanId: s.parentSpanId ?? null, name: s.name, spanType: s.spanType, isEvent: s.isEvent, endedAt: null, experimentId: s.experimentId ?? null, entityType: s.entityType ?? null, entityId: s.entityId ?? null, entityName: s.entityName ?? null, entityVersionId: s.entityVersionId ?? null, userId: s.userId ?? null, organizationId: s.organizationId ?? null, resourceId: s.resourceId ?? null, runId: s.runId ?? null, sessionId: s.sessionId ?? null, threadId: s.threadId ?? null, requestId: s.requestId ?? null, environment: s.environment ?? null, source: s.source ?? null, serviceName: s.serviceName ?? null, attributes: s.attributes ?? null, metadata: s.metadata ?? null, tags: s.tags ?? null, scope: s.scope ?? null, links: null, input: s.input ?? null, output: null, error: null, requestContext: s.requestContext ?? null }; } function createEndSpanRow(s) { return { eventType: "end", timestamp: s.endedAt, traceId: s.traceId, spanId: s.spanId, parentSpanId: s.parentSpanId ?? null, name: s.name, spanType: s.spanType, isEvent: s.isEvent, endedAt: s.endedAt ?? null, experimentId: s.experimentId ?? null, entityType: s.entityType ?? null, entityId: s.entityId ?? null, entityName: s.entityName ?? null, entityVersionId: s.entityVersionId ?? null, userId: s.userId ?? null, organizationId: s.organizationId ?? null, resourceId: s.resourceId ?? null, runId: s.runId ?? null, sessionId: s.sessionId ?? null, threadId: s.threadId ?? null, requestId: s.requestId ?? null, environment: s.environment ?? null, source: s.source ?? null, serviceName: s.serviceName ?? null, attributes: s.attributes ?? null, metadata: s.metadata ?? null, tags: s.tags ?? null, scope: s.scope ?? null, links: s.links ?? null, input: s.input ?? null, output: s.output ?? null, error: s.error ?? null, requestContext: s.requestContext ?? null }; } async function createSpan(db, args) { const rows = [createStartSpanRow(args.span)]; if (args.span.endedAt) { rows.push(createEndSpanRow(args.span)); } await insertSpanEvents(db, rows); } async function batchCreateSpans(db, args) { if (args.records.length === 0) return; const rows = args.records.flatMap((record) => { const events = [createStartSpanRow(record)]; if (record.endedAt) { events.push(createEndSpanRow(record)); } return events; }); await insertSpanEvents(db, rows); } async function batchDeleteTraces(db, args) { if (args.traceIds.length === 0) return; const placeholders = args.traceIds.map(() => "?").join(", "); await db.execute(`DELETE FROM span_events WHERE traceId IN (${placeholders})`, args.traceIds); } async function getSpan(db, args) { const rows = await db.query(`${SPAN_RECONSTRUCT_SELECT} WHERE traceId = ? AND spanId = ? GROUP BY traceId, spanId`, [ args.traceId, args.spanId ]); if (rows.length === 0) return null; return { span: rowToSpanRecord(rows[0]) }; } async function getRootSpan(db, args) { 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] ); if (rows.length === 0) return null; return { span: rowToSpanRecord(rows[0]) }; } async function getTrace(db, args) { const rows = await db.query(`${SPAN_RECONSTRUCT_SELECT} WHERE traceId = ? GROUP BY traceId, spanId`, [args.traceId]); if (rows.length === 0) return null; return { traceId: args.traceId, spans: rows.map((row) => rowToSpanRecord(row)) }; } async function getTraceLight(db, args) { const rows = await db.query(`${SPAN_RECONSTRUCT_SELECT_LIGHT} WHERE traceId = ? GROUP BY traceId, spanId`, [ args.traceId ]); if (rows.length === 0) return null; return { traceId: args.traceId, spans: rows.map((row) => rowToLightSpanRecord(row)) }; } async function listTraceRows(db, args, reconstructSelect, mapRow, toSpans) { const filters = args.filters ?? {}; const page = Number(args.pagination?.page ?? 0); const perPage = Number(args.pagination?.perPage ?? 10); const orderBy = { field: args.orderBy?.field ?? "startedAt", direction: args.orderBy?.direction ?? "DESC" }; const { prefilter, postAgg, hasChildError } = partitionAnchorFilters(filters); const { clause: prefilterClause, params: prefilterParams } = buildWhereClause(prefilter); const prefilterParts = [`eventType = 'start'`, `parentSpanId IS NULL`]; if (prefilterClause) prefilterParts.push(prefilterClause.replace(/^WHERE\s+/i, "")); const prefilterWhere = `WHERE ${prefilterParts.join(" AND ")}`; const outerAlias = "outer_root"; const orderDir = orderBy.direction.toUpperCase(); if (orderDir !== "ASC" && orderDir !== "DESC") { throw new Error(`Invalid sort direction: ${orderBy.direction}`); } const canOrderInPrefilter = SAFE_PREFILTER_ORDER_FIELDS.has(orderBy.field); const hasPostAggFilters = Object.keys(postAgg).length > 0 || hasChildError !== void 0 || !canOrderInPrefilter; if (!hasPostAggFilters) { const prefilterOrderBy = `ORDER BY timestamp ${orderDir}`; const offset = page * perPage; const countSql2 = ` SELECT COUNT(*) as total FROM span_events AS ${outerAlias} ${prefilterWhere} `; const countResult2 = await db.query(countSql2, prefilterParams); const total2 = Number(countResult2[0]?.total ?? 0); const pageSql = ` WITH page_roots AS ( SELECT traceId, spanId FROM span_events AS ${outerAlias} ${prefilterWhere} ${prefilterOrderBy} LIMIT ? OFFSET ? ) ${reconstructSelect} WHERE (traceId, spanId) IN (SELECT traceId, spanId FROM page_roots) GROUP BY traceId, spanId ${buildOrderByClause(orderBy)} `; const rows2 = await db.query(pageSql, [...prefilterParams, perPage, offset]); const spans2 = rows2.map((row) => mapRow(row)); return { pagination: { total: total2, page, perPage, hasMore: (page + 1) * perPage < total2 }, spans: toSpans(spans2) }; } const { clause: postAggClause, params: postAggParams } = buildWhereClause(postAgg); const postAggParts = []; if (postAggClause) postAggParts.push(postAggClause.replace(/^WHERE\s+/i, "")); const childErrorClause = buildHasChildErrorClause(hasChildError, "root_spans"); if (childErrorClause) postAggParts.push(childErrorClause); const postAggWhere = postAggParts.length > 0 ? `WHERE ${postAggParts.join(" AND ")}` : ""; const cteSql = ` WITH candidate_roots AS ( SELECT traceId, spanId FROM span_events AS ${outerAlias} ${prefilterWhere} ), root_spans AS ( ${reconstructSelect} WHERE (traceId, spanId) IN (SELECT traceId, spanId FROM candidate_roots) GROUP BY traceId, spanId ) `; const orderByClause = buildOrderByClause(orderBy); const { clause: paginationClause, params: paginationParams } = buildPaginationClause({ page, perPage }); const countSql = ` ${cteSql} SELECT COUNT(*) as total FROM root_spans ${postAggWhere} `; const countResult = await db.query(countSql, [...prefilterParams, ...postAggParams]); const total = Number(countResult[0]?.total ?? 0); const dataSql = ` ${cteSql} SELECT * FROM root_spans ${postAggWhere} ${orderByClause} ${paginationClause} `; const rows = await db.query(dataSql, [...prefilterParams, ...postAggParams, ...paginationParams]); const spans = rows.map((row) => mapRow(row)); return { pagination: { total, page, perPage, hasMore: (page + 1) * perPage < total }, spans: toSpans(spans) }; } async function listTraces(db, args) { const { mode, filters, pagination, orderBy, after, limit } = storage.listTracesArgsSchema.parse(args); const filterRecord = filters ?? {}; const page = Number(pagination.page); const perPage = Number(pagination.perPage); if (mode === "delta") { assertDeltaPollingEnabled(); const streamHeadCursor = await getTraceStreamHeadCursor(db); if (after === void 0) { return { spans: [], delta: { limit, hasMore: false }, deltaCursor: streamHeadCursor }; } const afterCursorId = validateCursorId(after); const { prefilter: prefilter2, postAgg: postAgg2, hasChildError: hasChildError2 } = partitionAnchorFilters(filterRecord); const { clause: prefilterClause2, params: prefilterParams2 } = buildWhereClause(prefilter2); const prefilterParts2 = [ `eventType = 'start'`, `parentSpanId IS NULL`, `cursorId IS NOT NULL`, `cursorId > CAST(? AS BIGINT)` ]; if (prefilterClause2) prefilterParts2.push(prefilterClause2.replace(/^WHERE\s+/i, "")); const prefilterWhere2 = `WHERE ${prefilterParts2.join(" AND ")}`; const { clause: postAggClause2, params: postAggParams2 } = buildWhereClause(postAgg2); const postAggParts2 = []; if (postAggClause2) postAggParts2.push(postAggClause2.replace(/^WHERE\s+/i, "")); const childErrorClause2 = buildHasChildErrorClause(hasChildError2, "root_spans"); if (childErrorClause2) postAggParts2.push(childErrorClause2); const postAggWhere2 = postAggParts2.length > 0 ? `WHERE ${postAggParts2.join(" AND ")}` : ""; const outerAlias2 = "outer_root"; const dataSql2 = ` WITH candidate_roots AS ( SELECT traceId, spanId, cursorId FROM span_events AS ${outerAlias2} ${prefilterWhere2} ), root_spans AS ( SELECT reconstructed.*, candidate_roots.cursorId AS anchorCursorId FROM ( ${SPAN_RECONSTRUCT_SELECT} WHERE (traceId, spanId) IN (SELECT traceId, spanId FROM candidate_roots) GROUP BY traceId, spanId ) AS reconstructed INNER JOIN candidate_roots USING (traceId, spanId) ) SELECT * FROM root_spans ${postAggWhere2} ORDER BY anchorCursorId ASC LIMIT ? `; const rows2 = await db.query(dataSql2, [ afterCursorId, ...prefilterParams2, ...postAggParams2, limit + 1 ]); const visibleRows = rows2.slice(0, limit).map((row) => ({ cursorId: row.anchorCursorId, span: rowToSpanRecord(row) })); return { spans: storage.toTraceSpans(visibleRows.map((row) => row.span)), delta: { limit, hasMore: rows2.length > limit }, deltaCursor: visibleRows.length > 0 ? encodeDeltaCursor(visibleRows[visibleRows.length - 1]?.cursorId) : streamHeadCursor }; } const { prefilter, postAgg, hasChildError } = partitionAnchorFilters(filterRecord); const { clause: prefilterClause, params: prefilterParams } = buildWhereClause(prefilter); const prefilterParts = [`eventType = 'start'`, `parentSpanId IS NULL`]; if (prefilterClause) prefilterParts.push(prefilterClause.replace(/^WHERE\s+/i, "")); const prefilterWhere = `WHERE ${prefilterParts.join(" AND ")}`; const outerAlias = "outer_root"; const orderDir = orderBy.direction.toUpperCase(); if (orderDir !== "ASC" && orderDir !== "DESC") { throw new Error(`Invalid sort direction: ${orderBy.direction}`); } const currentDeltaCursor = deltaPollingFeatureEnabled() ? await getTraceDeltaCursor(db, filters) : void 0; const canOrderInPrefilter = SAFE_PREFILTER_ORDER_FIELDS.has(orderBy.field); const hasPostAggFilters = Object.keys(postAgg).length > 0 || hasChildError !== void 0 || !canOrderInPrefilter; if (!hasPostAggFilters) { const prefilterOrderBy = `ORDER BY timestamp ${orderDir}`; const offset = page * perPage; const countSql2 = ` SELECT COUNT(*) as total FROM span_events AS ${outerAlias} ${prefilterWhere} `; const countResult2 = await db.query(countSql2, prefilterParams); const total2 = Number(countResult2[0]?.total ?? 0); const pageSql = ` WITH page_roots AS ( SELECT traceId, spanId FROM span_events AS ${outerAlias} ${prefilterWhere} ${prefilterOrderBy} LIMIT ? OFFSET ? ) ${SPAN_RECONSTRUCT_SELECT} WHERE (traceId, spanId) IN (SELECT traceId, spanId FROM page_roots) GROUP BY traceId, spanId ${buildOrderByClause(orderBy)} `; const rows2 = await db.query(pageSql, [...prefilterParams, perPage, offset]); const spans2 = rows2.map((row) => rowToSpanRecord(row)); return { pagination: { total: total2, page, perPage, hasMore: (page + 1) * perPage < total2 }, spans: storage.toTraceSpans(spans2), ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {} }; } const { clause: postAggClause, params: postAggParams } = buildWhereClause(postAgg); const postAggParts = []; if (postAggClause) postAggParts.push(postAggClause.replace(/^WHERE\s+/i, "")); const childErrorClause = buildHasChildErrorClause(hasChildError, "root_spans"); if (childErrorClause) postAggParts.push(childErrorClause); const postAggWhere = postAggParts.length > 0 ? `WHERE ${postAggParts.join(" AND ")}` : ""; const cteSql = ` WITH candidate_roots AS ( SELECT traceId, spanId FROM span_events AS ${outerAlias} ${prefilterWhere} ), root_spans AS ( ${SPAN_RECONSTRUCT_SELECT} WHERE (traceId, spanId) IN (SELECT traceId, spanId FROM candidate_roots) GROUP BY traceId, spanId ) `; const orderByClause = buildOrderByClause(orderBy); const { clause: paginationClause, params: paginationParams } = buildPaginationClause({ page, perPage }); const countSql = ` ${cteSql} SELECT COUNT(*) as total FROM root_spans ${postAggWhere} `; const countResult = await db.query(countSql, [...prefilterParams, ...postAggParams]); const total = Number(countResult[0]?.total ?? 0); const dataSql = ` ${cteSql} SELECT * FROM root_spans ${postAggWhere} ${orderByClause} ${paginationClause} `; const rows = await db.query(dataSql, [...prefilterParams, ...postAggParams, ...paginationParams]); const spans = rows.map((row) => rowToSpanRecord(row)); return { pagination: { total, page, perPage, hasMore: (page + 1) * perPage < total }, spans: storage.toTraceSpans(spans), ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {} }; } async function listTracesLight(db, args) { return listTraceRows( db, args, SPAN_RECONSTRUCT_SELECT_LIGHT, rowToLightSpanRecord, (spans) => spans ); } var BRANCH_SPAN_TYPE_PLACEHOLDERS = storage.BRANCH_SPAN_TYPES.map(() => "?").join(", "); async function getSpans(db, args) { if (args.spanIds.length === 0) { return { traceId: args.traceId, spans: [] }; } const placeholders = args.spanIds.map(() => "?").join(", "); const rows = await db.query( `${SPAN_RECONSTRUCT_SELECT} WHERE traceId = ? AND spanId IN (${placeholders}) GROUP BY traceId, spanId`, [args.traceId, ...args.spanIds] ); return { traceId: args.traceId, spans: rows.map((row) => rowToSpanRecord(row)) }; } async function listBranches(db, args) { const { mode, filters, pagination, orderBy, after, limit } = storage.listBranchesArgsSchema.parse(args); const filterRecord = filters ?? {}; const page = Number(pagination.page); const perPage = Number(pagination.perPage); const userSpanType = filterRecord.spanType; if (typeof userSpanType === "string" && !storage.BRANCH_SPAN_TYPES.includes(userSpanType)) { const currentDeltaCursor2 = deltaPollingFeatureEnabled() ? await getBranchDeltaCursor(db, filters) : void 0; if (mode === "delta") { assertDeltaPollingEnabled(); return { branches: [], delta: { limit, hasMore: false }, deltaCursor: currentDeltaCursor2 }; } return { pagination: { total: 0, page, perPage, hasMore: false }, branches: [], ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor2 } : {} }; } if (mode === "delta") { assertDeltaPollingEnabled(); const streamHeadCursor = await getBranchStreamHeadCursor( db, typeof userSpanType === "string" ? userSpanType : null ); if (after === void 0) { return { branches: [], delta: { limit, hasMore: false }, deltaCursor: streamHeadCursor }; } const afterCursorId = validateCursorId(after); const { spanType: _spanType2, ...rest2 } = filterRecord; const { prefilter: prefilter2, postAgg: postAgg2} = partitionAnchorFilters(rest2); const { clause: prefilterClause2, params: prefilterFilterParams2 } = buildWhereClause(prefilter2); const prefilterParts2 = [`eventType = 'start'`, `cursorId IS NOT NULL`, `cursorId > CAST(? AS BIGINT)`]; let spanTypeParams2; if (typeof userSpanType === "string") { prefilterParts2.push(`spanType = ?`); spanTypeParams2 = [userSpanType]; } else { prefilterParts2.push(`spanType IN (${BRANCH_SPAN_TYPE_PLACEHOLDERS})`); spanTypeParams2 = [...storage.BRANCH_SPAN_TYPES]; } if (prefilterClause2) prefilterParts2.push(prefilterClause2.replace(/^WHERE\s+/i, "")); const prefilterWhere2 = `WHERE ${prefilterParts2.join(" AND ")}`; const prefilterParams2 = [afterCursorId, ...spanTypeParams2, ...prefilterFilterParams2]; const { clause: postAggClause2, params: postAggParams2 } = buildWhereClause(postAgg2); const postAggWhere2 = postAggClause2 ? postAggClause2 : ""; const outerAlias2 = "outer_anchor"; const dataSql2 = ` WITH candidate_anchors AS ( SELECT traceId, spanId, cursorId FROM span_events AS ${outerAlias2} ${prefilterWhere2} ), branch_anchors AS ( SELECT reconstructed.*, candidate_anchors.cursorId AS anchorCursorId FROM ( ${SPAN_RECONSTRUCT_SELECT} WHERE (traceId, spanId) IN (SELECT traceId, spanId FROM candidate_anchors) GROUP BY traceId, spanId ) AS reconstructed INNER JOIN candidate_anchors USING (traceId, spanId) ) SELECT * FROM branch_anchors ${postAggWhere2} ORDER BY anchorCursorId ASC LIMIT ? `; const rows2 = await db.query(dataSql2, [...prefilterParams2, ...postAggParams2, limit + 1]); const visibleRows = rows2.slice(0, limit).map((row) => ({ cursorId: row.anchorCursorId, branch: rowToSpanRecord(row) })); return { branches: storage.toTraceSpans(visibleRows.map((row) => row.branch)), delta: { limit, hasMore: rows2.length > limit }, deltaCursor: visibleRows.length > 0 ? encodeDeltaCursor(visibleRows[visibleRows.length - 1]?.cursorId) : streamHeadCursor }; } const { spanType: _spanType, ...rest } = filterRecord; const { prefilter, postAgg} = partitionAnchorFilters(rest); const { clause: prefilterClause, params: prefilterFilterParams } = buildWhereClause(prefilter); const prefilterParts = [`eventType = 'start'`]; let spanTypeParams; if (typeof userSpanType === "string") { prefilterParts.push(`spanType = ?`); spanTypeParams = [userSpanType]; } else { prefilterParts.push(`spanType IN (${BRANCH_SPAN_TYPE_PLACEHOLDERS})`); spanTypeParams = [...storage.BRANCH_SPAN_TYPES]; } if (prefilterClause) prefilterParts.push(prefilterClause.replace(/^WHERE\s+/i, "")); const prefilterWhere = `WHERE ${prefilterParts.join(" AND ")}`; const prefilterParams = [...spanTypeParams, ...prefilterFilterParams]; const outerAlias = "outer_anchor"; const orderDir = orderBy.direction.toUpperCase(); if (orderDir !== "ASC" && orderDir !== "DESC") { throw new Error(`Invalid sort direction: ${orderBy.direction}`); } const currentDeltaCursor = deltaPollingFeatureEnabled() ? await getBranchDeltaCursor(db, filters) : void 0; const canOrderInPrefilter = SAFE_PREFILTER_ORDER_FIELDS.has(orderBy.field); const hasPostAggFilters = Object.keys(postAgg).length > 0 || !canOrderInPrefilter; if (!hasPostAggFilters) { const prefilterOrderBy = `ORDER BY timestamp ${orderDir}`; const offset = page * perPage; const countSql2 = ` SELECT COUNT(*) as total FROM span_events AS ${outerAlias} ${prefilterWhere} `; const countResult2 = await db.query(countSql2, prefilterParams); const total2 = Number(countResult2[0]?.total ?? 0); if (total2 === 0) { return { pagination: { total: 0, page, perPage, hasMore: false }, branches: [], ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {} }; } const pageSql = ` WITH page_anchors AS ( SELECT traceId, spanId FROM span_events AS ${outerAlias} ${prefilterWhere} ${prefilterOrderBy} LIMIT ? OFFSET ? ) ${SPAN_RECONSTRUCT_SELECT} WHERE (traceId, spanId) IN (SELECT traceId, spanId FROM page_anchors) GROUP BY traceId, spanId ${buildOrderByClause(orderBy)} `; const rows2 = await db.query(pageSql, [...prefilterParams, perPage, offset]); const spans2 = rows2.map((row) => rowToSpanRecord(row)); return { pagination: { total: total2, page, perPage, hasMore: (page + 1) * perPage < total2 }, branches: storage.toTraceSpans(spans2), ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {} }; } const { clause: postAggClause, params: postAggParams } = buildWhereClause(postAgg); const postAggWhere = postAggClause ? postAggClause : ""; const orderByClause = buildOrderByClause(orderBy); const { clause: paginationClause, params: paginationParams } = buildPaginationClause({ page, perPage }); const cteSql = ` WITH candidate_anchors AS ( SELECT traceId, spanId FROM span_events AS ${outerAlias} ${prefilterWhere} ), branch_anchors AS ( ${SPAN_RECONSTRUCT_SELECT} WHERE (traceId, spanId) IN (SELECT traceId, spanId FROM candidate_anchors) GROUP BY traceId, spanId ) `; const countSql = ` ${cteSql} SELECT COUNT(*) as total FROM branch_anchors ${postAggWhere} `; const countResult = await db.query(countSql, [...prefilterParams, ...postAggParams]); const total = Number(countResult[0]?.total ?? 0); if (total === 0) { return { pagination: { total: 0, page, perPage, hasMore: false }, branches: [], ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {} }; } const dataSql = ` ${cteSql} SELECT * FROM branch_anchors ${postAggWhere} ${orderByClause} ${paginationClause} `; const rows = await db.query(dataSql, [...prefilterParams, ...postAggParams, ...paginationParams]); const spans = rows.map((row) => rowToSpanRecord(row)); return { pagination: { total, page, perPage, hasMore: (page + 1) * perPage < total }, branches: storage.toTraceSpans(spans), ...deltaPollingFeatureEnabled() ? { deltaCursor: currentDeltaCursor } : {} }; } async function getTraceDeltaCursor(db, filters) { const { prefilter, postAgg, hasChildError } = partitionAnchorFilters(filters ?? {}); const { clause: prefilterClause, params: prefilterParams } = buildWhereClause(prefilter); const prefilterParts = [`eventType = 'start'`, `parentSpanId IS NULL`, `cursorId IS NOT NULL`]; if (prefilterClause) prefilterParts.push(prefilterClause.replace(/^WHERE\s+/i, "")); const prefilterWhere = `WHERE ${prefilterParts.join(" AND ")}`; const outerAlias = "outer_root"; const { clause: postAggClause, params: postAggParams } = buildWhereClause(postAgg); const postAggParts = []; if (postAggClause) postAggParts.push(postAggClause.replace(/^WHERE\s+/i, "")); const childErrorClause = buildHasChildErrorClause(hasChildError, "root_spans"); if (childErrorClause) postAggParts.push(childErrorClause); const postAggWhere = postAggParts.length > 0 ? `WHERE ${postAggParts.join(" AND ")}` : ""; if (postAggWhere === "") { const rows2 = await db.query( `SELECT max(cursorId) AS cursorId FROM span_events AS ${outerAlias} ${prefilterWhere}`, prefilterParams ); const cursorId2 = rows2[0]?.cursorId; if (cursorId2 !== null && cursorId2 !== void 0) { return encodeDeltaCursor(cursorId2); } const streamRows2 = await db.query( `SELECT max(cursorId) AS cursorId FROM span_events WHERE eventType = 'start' AND parentSpanId IS NULL AND cursorId IS NOT NULL` ); return encodeDeltaCursor(streamRows2[0]?.cursorId); } const cteSql = ` WITH candidate_roots AS ( SELECT traceId, spanId, cursorId FROM span_events AS ${outerAlias} ${prefilterWhere} ), root_spans AS ( SELECT reconstructed.*, candidate_roots.cursorId AS anchorCursorId FROM ( ${SPAN_RECONSTRUCT_SELECT} WHERE (traceId, spanId) IN (SELECT traceId, spanId FROM candidate_roots) GROUP BY traceId, spanId ) AS reconstructed INNER JOIN candidate_roots USING (traceId, spanId) ) `; const rows = await db.query( `${cteSql} SELECT max(anchorCursorId) AS cursorId FROM root_spans ${postAggWhere}`, [...prefilterParams, ...postAggParams] ); const cursorId = rows[0]?.cursorId; if (cursorId !== null && cursorId !== void 0) { return encodeDeltaCursor(cursorId); } const streamRows = await db.query( `SELECT max(cursorId) AS cursorId FROM span_events WHERE eventType = 'start' AND parentSpanId IS NULL AND cursorId IS NOT NULL` ); return encodeDeltaCursor(streamRows[0]?.cursorId); } async function getTraceStreamHeadCursor(db) { const streamRows = await db.query( `SELECT max(cursorId) AS cursorId FROM span_events WHERE eventType = 'start' AND parentSpanId IS NULL AND cursorId IS NOT NULL` ); return encodeDeltaCursor(streamRows[0]?.cursorId); } async function getBranchDeltaCursor(db, filters) { const filterRecord = filters ?? {}; const userSpanType = filterRecord.spanType; const { spanType: _spanType, ...rest } = filterRecord; const { prefilter, postAgg } = partitionAnchorFilters(rest); const { clause: prefilterClause, params: prefilterFilterParams } = buildWhereClause(prefilter); const prefilterParts = [`eventType = 'start'`, `cursorId IS NOT NULL`]; let spanTypeParams; if (typeof userSpanType === "string") { prefilterParts.push(`spanType = ?`); spanTypeParams = [userSpanType]; } else { prefilterParts.push(`spanType IN (${BRANCH_SPAN_TYPE_PLACEHOLDERS})`); spanTypeParams = [...storage.BRANCH_SPAN_TYPES]; } if (prefilterClause) prefilterParts.push(prefilterClause.replace(/^WHERE\s+/i, "")); const prefilterWhere = `WHERE ${prefilterParts.join(" AND ")}`; const prefilterParams = [...spanTypeParams, ...prefilterFilterParams]; const outerAlias = "outer_anchor"; const { clause: postAggClause, params: postAggParams } = buildWhereClause(postAgg); if (!postAggClause) { const rows2 = await db.query( `SELECT max(cursorId) AS cursorId FROM span_events AS ${outerAlias} ${prefilterWhere}`, prefilterParams ); const cursorId2 = rows2[0]?.cursorId; if (cursorId2 !== null && cursorId2 !== void 0) { return encodeDeltaCursor(cursorId2); } const streamRows2 = 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`, [...storage.BRANCH_SPAN_TYPES] ); return encodeDeltaCursor(streamRows2[0]?.cursorId); } const cteSql = ` WITH candidate_anchors AS ( SELECT traceId, spanId, cursorId FROM span_events AS ${outerAlias} ${prefilterWhere} ), branch_anchors AS ( SELECT reconstructed.*, candidate_anchors.cursorId AS anchorCursorId FROM ( ${SPAN_RECONSTRUCT_SELECT} WHERE (traceId, spanId) IN (SELECT traceId, spanId FROM candidate_anchors) GROUP BY traceId, spanId ) AS reconstructed INNER JOIN candidate_anchors USING (traceId, spanId) ) `; const rows = await db.query( `${cteSql} SELECT max(anchorCursorId) AS cursorId FROM branch_anchors ${postAggClause}`, [...prefilterParams, ...postAggParams] ); const cursorId = rows[0]?.cursorId; if (cursorId !== null && cursorId !== void 0) { return encodeDeltaCursor(cursorId); } const streamRows = 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`, [...storage.BRANCH_SPAN_TYPES] ); return encodeDeltaCursor(streamRows[0]?.cursorId); } async function getBranchStreamHeadCursor(db, userSpanType) { if (userSpanType) { const rows2 = await db.query( `SELECT max(cursorId) AS cursorId FROM span_events WHERE eventType = 'start' AND spanType = ? AND cursorId IS NOT NULL`, [userSpanType] ); return encodeDeltaCursor(rows2[0]?.cursorId); } const rows = 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`, [...storage.BRANCH_SPAN_TYPES] ); return encodeDeltaCursor(rows[0]?.cursorId); } // src/storage/domains/observability/index.ts function buildSignalMigrationRequiredMessage(args) { const tableList = args.tables.map((table) => ` - ${table.table}`).join("\n"); return ` =========================================================================== MIGRATION REQUIRED: DuckDB observability signal tables need signal IDs =========================================================================== The following signal tables still use the legacy schema and must be migrated before observability storage can initialize: ${tableList} To fix this, run the manual migration command: npx mastra migrate This command will: 1. Create replacement signal tables with signal-ID primary keys 2. Backfill missing signal IDs for legacy rows 3. Swap the migrated tables into place WARNING: This migration recreates the signal tables and may take significant time for large databases. Please ensure you have a backup before proceeding. =========================================================================== `; } var ObservabilityStorageDuckDB = class extends storage.ObservabilityStorage { db; constructor(config) { super(); this.db = config.db; } /** Create all observability tables if they don't exist. */ async init() { const migrationStatus = await checkSignalTablesMigrationStatus(this.db); if (migrationStatus.needsMigration) { throw new error.MastraError({ id: storage.createStorageErrorId("DUCKDB", "MIGRATION_REQUIRED", "SIGNAL_TABLES"), domain: error.ErrorDomain.STORAGE, category: error.ErrorCategory.USER, text: buildSignalMigrationRequiredMessage({ tables: migrationStatus.tables.map(({ table }) => ({ table })) }) }); } await this.db.executeBatch([...ALL_DDL, ...ALL_MIGRATIONS]); await dropLegacyCursorIdDefaults(this.db); } /** * Manually migrate legacy signal tables to the signal-ID primary-key schema. * The public method name is historical; the CLI still calls `migrateSpans()` * for observability migrations even though this now also migrates signal tables. */ async migrateSpans() { const migrationStatus = await checkSignalTablesMigrationStatus(this.db); if (!migrationStatus.needsMigration) { return { success: true, alreadyMigrated: true, duplicatesRemoved: 0, message: "Migration already complete. Signal tables already use signal-ID primary keys." }; } await migrateSignalTables(this.db, this.logger); return { success: true, alreadyMigrated: false, duplicatesRemoved: 0, message: `Migration complete. Migrated signal tables: ${migrationStatus.tables.map((t) => t.table).join(", ")}.` }; } /** Delete all rows from every observability table. Use with caution. */ async dangerouslyClearAll() { for (const table of ["span_events", "metric_events", "log_events", "score_events", "feedback_events"]) { await this.db.execute(`TRUNCATE TABLE ${table}`); } } get observabilityStrategy() { return { preferred: "event-sourced", supported: ["event-sourced"] }; } getFeatures() { if (!deltaPollingFeatureEnabled()) { return void 0; } return ["delta-polling"]; } // Tracing async createSpan(args) { return createSpan(this.db, args); } async batchCreateSpans(args) { return batchCreateSpans(this.db, args); } async batchDeleteTraces(args) { return batchDeleteTraces(this.db, args); } async getSpan(args) { return getSpan(this.db, args); } async getSpans(args) { return getSpans(this.db, args); } async getRootSpan(args) { return getRootSpan(this.db, args); } async getTrace(args) { return getTrace(this.db, args); } async getTraceLight(args) { return getTraceLight(this.db, args); } async listTraces(args) { return listTraces(this.db, args); } async listTracesLight(args) { return listTracesLight(this.db, args); } async listBranches(args) { return listBranches(this.db, args); } // Logs async batchCreateLogs(args) { return batchCreateLogs(this.db, args); } async listLogs(args) { return listLogs(this.db, args); } // Metrics async batchCreateMetrics(args) { return batchCreateMetrics(this.db, args); } async listMetrics(args) { return listMetrics(this.db, args); } async getMetricAggregate(args) { return getMetricAggregate(this.db, args); } async getMetricBreakdown(args) { return getMetricBreakdown(this.db, args); } async getMetricTimeSeries(args) { return getMetricTimeSeries(this.db, args); } async getMetricPercentiles(args) { return getMetricPercentiles(this.db, args); } // Metric Discovery async getMetricNames(args) { return getMetricNames(this.db, args); } async getMetricLabelKeys(args) { return getMetricLabelKeys(this.db, args); } async getMetricLabelValues(args) { return getMetricLabelValues(this.db, args); } // Span Discovery async getEntityTypes(args) { return getEntityTypes(this.db); } async getEntityNames(args) { return getEntityNames(this.db, args); } async getServiceNames(args) { return getServiceNames(this.db); } async getEnvironments(args) { return getEnvironments(this.db); } async getTags(args) { return getTags(this.db, args); } // Scores async createScore(args) { return createScore(this.db, args); } async batchCreateScores(args) { return batchCreateScores(this.db, args); } async listScores(args) { return listScores(this.db, args); } async getScoreById(scoreId) { return getScoreById(this.db, scoreId); } async getScoreAggregate(args) { return getScoreAggregate(this.db, args); } async getScoreBreakdown(args) { return getScoreBreakdown(this.db, args); } async getScoreTimeSeries(args) { return getScoreTimeSeries(this.db, args); } async getScorePercentiles(args) { return getScorePercentiles(this.db, args); } // Feedback async createFeedback(args) { return createFeedback(this.db, args); } async batchCreateFeedback(args) { return batchCreateFeedback(this.db, args); } async listFeedback(args) { return listFeedback(this.db, args); } async getFeedbackAggregate(args) { return getFeedbackAggregate(this.db, args); } async getFeedbackBreakdown(args) { return getFeedbackBreakdown(this.db, args); } async getFeedbackTimeSeries(args) { return getFeedbackTimeSeries(this.db, args); } async getFeedbackPercentiles(args) { return getFeedbackPercentiles(this.db, args); } }; exports.ObservabilityStorageDuckDB = ObservabilityStorageDuckDB; //# sourceMappingURL=observability-V2KYD7UF.cjs.map //# sourceMappingURL=observability-V2KYD7UF.cjs.map