@mastra/clickhouse 1.16.1 → 1.17.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,10 +1,12 @@
1
1
  import { createClient } from "@clickhouse/client";
2
2
  import { ErrorCategory, ErrorDomain, MastraError } from "@mastra/core/error";
3
+ import * as coreStorage from "@mastra/core/storage";
3
4
  import { BRANCH_SPAN_TYPES, EntityType, METRIC_DISTINCT_COLUMNS, MastraCompositeStore, MemoryStorage, ObservabilityStorage, SCORERS_SCHEMA, SPAN_SCHEMA, ScoresStorage, TABLE_AGENT_VERSIONS, TABLE_DATASETS, TABLE_DATASET_ITEMS, TABLE_DATASET_VERSIONS, TABLE_EXPERIMENTS, TABLE_EXPERIMENT_RESULTS, TABLE_FAVORITES, TABLE_HARNESS_SESSIONS, TABLE_KNOWLEDGE_ACTIVITY, TABLE_KNOWLEDGE_CURSORS, TABLE_KNOWLEDGE_MENTIONS, TABLE_KNOWLEDGE_NODES, TABLE_KNOWLEDGE_RECORDS, TABLE_KNOWLEDGE_SEMANTIC_OUTBOX, TABLE_MCP_CLIENTS, TABLE_MCP_CLIENT_VERSIONS, TABLE_MCP_SERVERS, TABLE_MCP_SERVER_VERSIONS, TABLE_MESSAGES, TABLE_NOTIFICATIONS, TABLE_PROMPT_BLOCKS, TABLE_PROMPT_BLOCK_VERSIONS, TABLE_RESOURCES, TABLE_SCHEDULES, TABLE_SCHEDULE_TRIGGERS, TABLE_SCHEMAS, TABLE_SCORERS, TABLE_SCORER_DEFINITIONS, TABLE_SCORER_DEFINITION_VERSIONS, TABLE_SKILLS, TABLE_SKILL_BLOBS, TABLE_SKILL_VERSIONS, TABLE_SPANS, TABLE_THREADS, TABLE_THREAD_STATE, TABLE_TOOL_PROVIDER_CONNECTIONS, TABLE_TRACES, TABLE_WORKFLOW_DEFINITIONS, TABLE_WORKFLOW_SNAPSHOT, TABLE_WORKSPACES, TABLE_WORKSPACE_VERSIONS, TraceStatus, WorkflowsStorage, buildInputPreview, calculatePagination, computeTraceStatus, createStorageErrorId, getDefaultValue, getSqlType, listBranchesArgsSchema, listFeedbackArgsSchema, listLogsArgsSchema, listMetricsArgsSchema, listScoresArgsSchema, listTracesArgsSchema, normalizePerPage, safelyParseJSON, toTraceSpans, transformScoreRow, validateStorageMetadataFilter } from "@mastra/core/storage";
4
5
  import { MessageList } from "@mastra/core/agent";
5
6
  import { MastraBase } from "@mastra/core/base";
6
7
  import { parseFieldKey } from "@mastra/core/utils";
7
8
  import { coreFeatures } from "@mastra/core/features";
9
+ import { randomUUID } from "crypto";
8
10
  import { saveScorePayloadSchema } from "@mastra/core/evals";
9
11
  //#region src/storage/db/replication.ts
10
12
  const DEFAULT_ZOOKEEPER_PATH = "/clickhouse/tables/{shard}/{database}/{table}";
@@ -70,12 +72,22 @@ function getEngineNameAndArgs(engine) {
70
72
  }
71
73
  function hasBalancedParens(s) {
72
74
  let depth = 0;
73
- for (const c of s) if (c === "(") depth++;
74
- else if (c === ")") {
75
- depth--;
76
- if (depth < 0) return false;
75
+ let quote;
76
+ for (let i = 0; i < s.length; i++) {
77
+ const c = s[i];
78
+ if (quote) {
79
+ if (c === "\\") i++;
80
+ else if (c === quote) quote = void 0;
81
+ continue;
82
+ }
83
+ if (c === "`" || c === "\"" || c === "'") quote = c;
84
+ else if (c === "(") depth++;
85
+ else if (c === ")") {
86
+ depth--;
87
+ if (depth < 0) return false;
88
+ }
77
89
  }
78
- return depth === 0;
90
+ return depth === 0 && quote === void 0;
79
91
  }
80
92
  function buildReplicatedTableEngine(engine, replication) {
81
93
  if (!replication) return engine;
@@ -147,20 +159,51 @@ function addOnClusterToDDL(sql, replication) {
147
159
  return out;
148
160
  }
149
161
  function rewriteEngineClauses(sql, replication) {
150
- return sql.replace(/ENGINE\s*=\s*(\w+)\s*/gi, (match, engineName, offset, source) => {
151
- const argsStart = offset + match.length;
152
- if (source[argsStart] !== "(") return `ENGINE = ${buildReplicatedTableEngine(engineName, replication)}`;
153
- let depth = 0;
154
- for (let i = argsStart; i < source.length; i++) {
155
- const char = source[i];
156
- if (char === "(") depth++;
157
- else if (char === ")") {
158
- depth--;
159
- if (depth === 0) return `ENGINE = ${buildReplicatedTableEngine(`${engineName}${source.slice(argsStart, i + 1)}`, replication)}`;
162
+ const enginePattern = /ENGINE\s*=\s*(\w+)/gi;
163
+ let result = "";
164
+ let consumedUntil = 0;
165
+ for (let match = enginePattern.exec(sql); match; match = enginePattern.exec(sql)) {
166
+ const engineName = match[1];
167
+ if (!engineName) continue;
168
+ let engineEnd = enginePattern.lastIndex;
169
+ let argsStart = engineEnd;
170
+ while (/\s/.test(sql[argsStart] ?? "")) argsStart++;
171
+ let engine = engineName;
172
+ if (sql[argsStart] === "(") {
173
+ let depth = 0;
174
+ let closingParenEnd;
175
+ let quote;
176
+ for (let i = argsStart; i < sql.length; i++) {
177
+ const char = sql[i];
178
+ if (quote) {
179
+ if (char === "\\") i++;
180
+ else if (char === quote) quote = void 0;
181
+ continue;
182
+ }
183
+ if (char === "`" || char === "\"" || char === "'") quote = char;
184
+ else if (char === "(") depth++;
185
+ else if (char === ")") {
186
+ depth--;
187
+ if (depth === 0) {
188
+ closingParenEnd = i + 1;
189
+ break;
190
+ }
191
+ }
192
+ }
193
+ if (closingParenEnd === void 0) {
194
+ result += sql.slice(consumedUntil, enginePattern.lastIndex);
195
+ consumedUntil = enginePattern.lastIndex;
196
+ continue;
160
197
  }
198
+ engineEnd = closingParenEnd;
199
+ engine = `${engineName}${sql.slice(argsStart, engineEnd)}`;
161
200
  }
162
- return match;
163
- });
201
+ result += sql.slice(consumedUntil, match.index);
202
+ result += `ENGINE = ${buildReplicatedTableEngine(engine, replication)}`;
203
+ consumedUntil = engineEnd;
204
+ enginePattern.lastIndex = engineEnd;
205
+ }
206
+ return result + sql.slice(consumedUntil);
164
207
  }
165
208
  function applyReplicationToDDL(sql, replication) {
166
209
  return addOnClusterToDDL(replication ? rewriteEngineClauses(sql, replication) : sql, replication);
@@ -2477,6 +2520,7 @@ var ObservabilityStorageClickhouse = class extends ObservabilityStorage {
2477
2520
  }
2478
2521
  }
2479
2522
  async batchDeleteTraces(args) {
2523
+ this.assertUnscopedBatchDeleteTraces(args);
2480
2524
  try {
2481
2525
  if (args.traceIds.length === 0) return;
2482
2526
  await this.client.command({
@@ -2503,6 +2547,7 @@ const TABLE_METRIC_EVENTS = "mastra_metric_events";
2503
2547
  const TABLE_LOG_EVENTS = "mastra_log_events";
2504
2548
  const TABLE_SCORE_EVENTS = "mastra_score_events";
2505
2549
  const TABLE_FEEDBACK_EVENTS = "mastra_feedback_events";
2550
+ const TABLE_DELETION_REQUESTS = "mastra_deletion_requests";
2506
2551
  const TABLE_METRIC_EVENTS_DELTA = "mastra_metric_events_delta";
2507
2552
  const TABLE_LOG_EVENTS_DELTA = "mastra_log_events_delta";
2508
2553
  const TABLE_SCORE_EVENTS_DELTA = "mastra_score_events_delta";
@@ -3198,6 +3243,23 @@ PARTITION BY toDate(timestamp)
3198
3243
  ORDER BY (traceId, timestamp, feedbackId)
3199
3244
  SETTINGS allow_nullable_key = 1
3200
3245
  `;
3246
+ const DELETION_REQUESTS_DDL = `
3247
+ CREATE TABLE IF NOT EXISTS ${TABLE_DELETION_REQUESTS} (
3248
+ requestId String,
3249
+ organizationId String DEFAULT '',
3250
+ resourceId String DEFAULT '',
3251
+ signal LowCardinality(String),
3252
+ predicateType LowCardinality(String),
3253
+ predicateValues Array(String),
3254
+ requestedAt DateTime64(3),
3255
+ requestedBy String DEFAULT '',
3256
+ lastAppliedAt DateTime64(3) DEFAULT 0,
3257
+ purgeVerifiedAt DateTime64(3) DEFAULT 0,
3258
+ updatedAt DateTime64(3)
3259
+ )
3260
+ ENGINE = ReplacingMergeTree(updatedAt)
3261
+ ORDER BY (organizationId, resourceId, requestId)
3262
+ `;
3201
3263
  function buildFeedbackEventsDeltaDDL() {
3202
3264
  return `
3203
3265
  CREATE TABLE IF NOT EXISTS ${TABLE_FEEDBACK_EVENTS_DELTA} (
@@ -3347,6 +3409,7 @@ const BASE_TABLE_DDL = [
3347
3409
  LOG_EVENTS_DDL,
3348
3410
  SCORE_EVENTS_DDL,
3349
3411
  FEEDBACK_EVENTS_DDL,
3412
+ DELETION_REQUESTS_DDL,
3350
3413
  DISCOVERY_VALUES_DDL,
3351
3414
  DISCOVERY_PAIRS_DDL
3352
3415
  ];
@@ -3431,6 +3494,7 @@ const ALL_TABLE_NAMES = [
3431
3494
  TABLE_LOG_EVENTS,
3432
3495
  TABLE_SCORE_EVENTS,
3433
3496
  TABLE_FEEDBACK_EVENTS,
3497
+ TABLE_DELETION_REQUESTS,
3434
3498
  TABLE_METRIC_EVENTS_DELTA,
3435
3499
  TABLE_LOG_EVENTS_DELTA,
3436
3500
  TABLE_SCORE_EVENTS_DELTA,
@@ -4088,6 +4152,35 @@ function feedbackRecordToRow(feedback) {
4088
4152
  };
4089
4153
  }
4090
4154
  //#endregion
4155
+ //#region src/storage/domains/observability/v-next/deletion-requests.ts
4156
+ const EPOCH = "1970-01-01T00:00:00.000Z";
4157
+ async function recordDeletionRequest(client, args) {
4158
+ const row = {
4159
+ requestId: args.requestId,
4160
+ organizationId: args.organizationId ?? "",
4161
+ resourceId: args.resourceId ?? "",
4162
+ signal: args.signal,
4163
+ predicateType: args.predicateType,
4164
+ predicateValues: args.predicateValues,
4165
+ requestedAt: args.requestedAt,
4166
+ requestedBy: args.requestedBy ?? "",
4167
+ lastAppliedAt: EPOCH,
4168
+ purgeVerifiedAt: EPOCH,
4169
+ updatedAt: args.requestedAt
4170
+ };
4171
+ await client.insert({
4172
+ table: TABLE_DELETION_REQUESTS,
4173
+ values: [row],
4174
+ format: "JSONEachRow",
4175
+ clickhouse_settings: isReplicationConfigured(args.replication) ? {
4176
+ ...CH_INSERT_SETTINGS,
4177
+ insert_quorum: "auto",
4178
+ insert_quorum_parallel: 1
4179
+ } : CH_INSERT_SETTINGS
4180
+ });
4181
+ return row;
4182
+ }
4183
+ //#endregion
4091
4184
  //#region src/storage/domains/observability/v-next/discovery.ts
4092
4185
  async function queryJson$3(client, query, params = {}) {
4093
4186
  return await (await client.query({
@@ -6091,6 +6184,269 @@ async function getScorePercentiles(client, args) {
6091
6184
  return { series };
6092
6185
  }
6093
6186
  //#endregion
6187
+ //#region src/storage/domains/observability/v-next/trace-query.ts
6188
+ const TRACE_STATUS_SQL = `if(isNotNull(r.error), 'error', 'success')`;
6189
+ const TRACE_FIELDS = {
6190
+ traceId: {
6191
+ sql: "r.traceId",
6192
+ parameterType: "String"
6193
+ },
6194
+ threadId: {
6195
+ sql: "r.threadId",
6196
+ parameterType: "String"
6197
+ },
6198
+ resourceId: {
6199
+ sql: "r.resourceId",
6200
+ parameterType: "String"
6201
+ },
6202
+ startedAt: {
6203
+ sql: "r.startedAt",
6204
+ parameterType: "DateTime64(3, 'UTC')"
6205
+ },
6206
+ endedAt: {
6207
+ sql: "r.endedAt",
6208
+ parameterType: "DateTime64(3, 'UTC')"
6209
+ },
6210
+ entityName: {
6211
+ sql: "r.entityName",
6212
+ parameterType: "String"
6213
+ },
6214
+ entityType: {
6215
+ sql: "r.entityType",
6216
+ parameterType: "String"
6217
+ },
6218
+ environment: {
6219
+ sql: "r.environment",
6220
+ parameterType: "String"
6221
+ },
6222
+ status: {
6223
+ sql: TRACE_STATUS_SQL,
6224
+ parameterType: "String"
6225
+ }
6226
+ };
6227
+ const SPAN_FIELDS = {
6228
+ spanType: {
6229
+ sql: "s.spanType",
6230
+ parameterType: "String"
6231
+ },
6232
+ error: {
6233
+ sql: "s.error",
6234
+ parameterType: "String"
6235
+ }
6236
+ };
6237
+ const SCORE_FIELDS = {
6238
+ scorerId: {
6239
+ sql: "s.scorerId",
6240
+ parameterType: "String"
6241
+ },
6242
+ score: {
6243
+ sql: "s.score",
6244
+ parameterType: "Float64"
6245
+ }
6246
+ };
6247
+ const TRACE_SELECT = `
6248
+ r.traceId AS traceId,
6249
+ r.spanId AS rootSpanId,
6250
+ r.threadId AS threadId,
6251
+ r.resourceId AS resourceId,
6252
+ r.startedAt AS startedAt,
6253
+ r.endedAt AS endedAt,
6254
+ r.entityName AS entityName,
6255
+ r.entityType AS entityType,
6256
+ r.environment AS environment,
6257
+ ${TRACE_STATUS_SQL} AS status`;
6258
+ var ParameterBuilder = class {
6259
+ params = {};
6260
+ #next = 1;
6261
+ add(value, type) {
6262
+ const name = `trace_query_${this.#next++}`;
6263
+ this.params[name] = type === "DateTime64(3, 'UTC')" ? new Date(value).toISOString().replace("T", " ").replace(/Z$/, "") : value;
6264
+ return `{${name}:${type}}`;
6265
+ }
6266
+ };
6267
+ function fieldDefinition(registry, field) {
6268
+ const definition = registry[field];
6269
+ if (definition === void 0) throw new Error(`Unsupported trusted trace-query field: ${field}`);
6270
+ return definition;
6271
+ }
6272
+ function resolveOrderField(field) {
6273
+ if (field === "startedAt" || field === "endedAt") return field;
6274
+ throw new Error(`Unsupported trusted trace-query field: ${field}`);
6275
+ }
6276
+ function compileScalarPredicate(predicate, registry, parameters) {
6277
+ if (predicate.type === "boolean") return predicate.args.map((arg) => `(${compileScalarPredicate(arg, registry, parameters)})`).join(predicate.operator === "and" ? " AND " : " OR ");
6278
+ if (predicate.type === "not") return `NOT (${compileScalarPredicate(predicate.arg, registry, parameters)})`;
6279
+ const field = fieldDefinition(registry, predicate.field);
6280
+ if (predicate.type === "presence") return `${predicate.operator === "exists" ? "isNotNull" : "isNull"}(${field.sql})`;
6281
+ if (predicate.type === "membership") {
6282
+ const values = predicate.values.map((value) => parameters.add(value, field.parameterType)).join(", ");
6283
+ return `ifNull(${`${field.sql} ${predicate.operator === "in" ? "IN" : "NOT IN"} (${values})`}, ${predicate.operator === "in" ? "0" : "1"})`;
6284
+ }
6285
+ const parameter = parameters.add(predicate.value, field.parameterType);
6286
+ const operator = {
6287
+ eq: "=",
6288
+ ne: "!=",
6289
+ lt: "<",
6290
+ lte: "<=",
6291
+ gt: ">",
6292
+ gte: ">="
6293
+ }[predicate.operator];
6294
+ if (operator === void 0) throw new Error(`Unsupported trusted trace-query operator: ${predicate.operator}`);
6295
+ return `ifNull(${field.sql} ${operator} ${parameter}, ${predicate.operator === "ne" ? "1" : "0"})`;
6296
+ }
6297
+ function collectRelationCollections(predicate, collections = /* @__PURE__ */ new Set()) {
6298
+ if (!predicate) return collections;
6299
+ if (predicate.type === "relation") collections.add(predicate.collection);
6300
+ else if (predicate.type === "boolean") for (const arg of predicate.args) collectRelationCollections(arg, collections);
6301
+ else if (predicate.type === "not") collectRelationCollections(predicate.arg, collections);
6302
+ return collections;
6303
+ }
6304
+ function compilePredicate(predicate, parameters) {
6305
+ if (predicate.type === "relation") {
6306
+ const registry = predicate.collection === "spans" ? SPAN_FIELDS : SCORE_FIELDS;
6307
+ const existence = `EXISTS (
6308
+ SELECT 1 FROM ${predicate.collection === "spans" ? "current_spans" : "current_scores"} s
6309
+ WHERE s.traceId = r.traceId
6310
+ AND (${compileScalarPredicate(predicate.predicate, registry, parameters)})
6311
+ )`;
6312
+ return predicate.quantifier === "some" ? existence : `NOT ${existence}`;
6313
+ }
6314
+ if (predicate.type === "boolean") return predicate.args.map((arg) => `(${compilePredicate(arg, parameters)})`).join(predicate.operator === "and" ? " AND " : " OR ");
6315
+ if (predicate.type === "not") return `NOT (${compilePredicate(predicate.arg, parameters)})`;
6316
+ return compileScalarPredicate(predicate, TRACE_FIELDS, parameters);
6317
+ }
6318
+ function compileClickHouseTraceQuery(plan) {
6319
+ const parameters = new ParameterBuilder();
6320
+ const from = parameters.add(plan.timeRange.from, "DateTime64(3, 'UTC')");
6321
+ const to = parameters.add(plan.timeRange.to, "DateTime64(3, 'UTC')");
6322
+ const relationCollections = collectRelationCollections(plan.where);
6323
+ const ctes = [`current_roots AS (
6324
+ SELECT * FROM (
6325
+ SELECT *
6326
+ FROM ${TABLE_TRACE_ROOTS}
6327
+ ORDER BY dedupeKey
6328
+ LIMIT 1 BY dedupeKey
6329
+ )
6330
+ ORDER BY traceId, dedupeKey
6331
+ LIMIT 1 BY traceId
6332
+ )`, `root_scope AS (
6333
+ SELECT *
6334
+ FROM current_roots
6335
+ WHERE startedAt >= ${from}
6336
+ AND startedAt < ${to}
6337
+ )`];
6338
+ if (relationCollections.has("spans")) ctes.push(`current_spans AS (
6339
+ SELECT traceId, spanType, error
6340
+ FROM ${TABLE_SPAN_EVENTS}
6341
+ WHERE traceId IN (SELECT traceId FROM root_scope)
6342
+ ORDER BY dedupeKey
6343
+ LIMIT 1 BY dedupeKey
6344
+ )`);
6345
+ if (relationCollections.has("scores")) ctes.push(`current_scores AS (
6346
+ SELECT traceId, scorerId, score
6347
+ FROM ${TABLE_SCORE_EVENTS}
6348
+ WHERE isNotNull(traceId)
6349
+ AND traceId IN (SELECT traceId FROM root_scope)
6350
+ )`);
6351
+ const predicate = plan.where ? compilePredicate(plan.where, parameters) : "1";
6352
+ ctes.push(`candidates AS (
6353
+ SELECT ${TRACE_SELECT}
6354
+ FROM root_scope r
6355
+ WHERE ${predicate}
6356
+ )`);
6357
+ const candidates = `WITH ${ctes.join(",\n")}`;
6358
+ if (plan.result === "groups") return {
6359
+ query: `${candidates}
6360
+ SELECT threadId
6361
+ FROM candidates
6362
+ WHERE isNotNull(threadId) ${plan.cursor ? `AND threadId > ${parameters.add(plan.cursor.threadId, "String")}` : ""}
6363
+ GROUP BY threadId
6364
+ ORDER BY threadId ASC
6365
+ LIMIT ${parameters.add(plan.limit + 1, "UInt64")}`,
6366
+ query_params: parameters.params
6367
+ };
6368
+ const orderField = resolveOrderField(plan.orderBy.field);
6369
+ const direction = plan.orderBy.direction === "asc" ? "ASC" : "DESC";
6370
+ let pageCondition = "";
6371
+ if (plan.cursor) {
6372
+ const comparison = plan.orderBy.direction === "asc" ? ">" : "<";
6373
+ const sortValue = parameters.add(plan.cursor.sortValue, "DateTime64(3, 'UTC')");
6374
+ pageCondition = `WHERE (${orderField} ${comparison} ${sortValue} OR (${orderField} = ${sortValue} AND traceId > ${parameters.add(plan.cursor.traceId, "String")}))`;
6375
+ }
6376
+ const limit = parameters.add(plan.limit + 1, "UInt64");
6377
+ return {
6378
+ query: `${candidates}
6379
+ SELECT *
6380
+ FROM candidates
6381
+ ${pageCondition}
6382
+ ORDER BY ${orderField} ${direction}, traceId ASC
6383
+ LIMIT ${limit}`,
6384
+ query_params: parameters.params
6385
+ };
6386
+ }
6387
+ function asIsoTimestamp(value) {
6388
+ return new Date(value).toISOString();
6389
+ }
6390
+ function isClickHouseExecutionTimeout(error) {
6391
+ if (!error || typeof error !== "object") return false;
6392
+ const candidate = error;
6393
+ return String(candidate.code ?? "") === "159" || candidate.type === "TIMEOUT_EXCEEDED";
6394
+ }
6395
+ async function runWithClickHouseTraceQueryTimeout(client, timeoutMs, compiled, queryId) {
6396
+ const resolvedTimeoutMs = coreStorage.resolveTraceQueryTimeoutMs(timeoutMs);
6397
+ try {
6398
+ return await (await client.query({
6399
+ query: compiled.query,
6400
+ query_params: compiled.query_params,
6401
+ query_id: queryId,
6402
+ format: "JSONEachRow",
6403
+ clickhouse_settings: {
6404
+ ...CH_SETTINGS,
6405
+ max_execution_time: resolvedTimeoutMs / 1e3
6406
+ }
6407
+ })).json();
6408
+ } catch (error) {
6409
+ if (isClickHouseExecutionTimeout(error)) throw new coreStorage.TraceQueryExecutionError();
6410
+ throw error;
6411
+ }
6412
+ }
6413
+ async function queryTraces(client, plan, timeoutMs) {
6414
+ const rows = await runWithClickHouseTraceQueryTimeout(client, timeoutMs, compileClickHouseTraceQuery(plan));
6415
+ const visibleRows = rows.slice(0, plan.limit);
6416
+ if (plan.result === "groups") {
6417
+ const groups = visibleRows.map((row) => ({ threadId: String(row.threadId) }));
6418
+ const last = groups.at(-1);
6419
+ return coreStorage.traceQueryResponseSchema.parse({
6420
+ groups,
6421
+ page: { next: rows.length > plan.limit && last ? coreStorage.encodeTraceQueryCursor(plan, {
6422
+ result: "groups",
6423
+ threadId: last.threadId
6424
+ }) : null }
6425
+ });
6426
+ }
6427
+ const traces = visibleRows.map((row) => ({
6428
+ traceId: String(row.traceId),
6429
+ rootSpanId: String(row.rootSpanId),
6430
+ threadId: row.threadId == null ? null : String(row.threadId),
6431
+ resourceId: row.resourceId == null ? null : String(row.resourceId),
6432
+ startedAt: asIsoTimestamp(row.startedAt),
6433
+ endedAt: asIsoTimestamp(row.endedAt),
6434
+ entityName: row.entityName == null ? null : String(row.entityName),
6435
+ entityType: row.entityType == null ? null : String(row.entityType),
6436
+ environment: row.environment == null ? null : String(row.environment),
6437
+ status: row.status
6438
+ }));
6439
+ const last = traces.at(-1);
6440
+ return coreStorage.traceQueryResponseSchema.parse({
6441
+ traces,
6442
+ page: { next: rows.length > plan.limit && last ? coreStorage.encodeTraceQueryCursor(plan, {
6443
+ result: "traces",
6444
+ sortValue: last[plan.orderBy.field],
6445
+ traceId: last.traceId
6446
+ }) : null }
6447
+ });
6448
+ }
6449
+ //#endregion
6094
6450
  //#region src/storage/domains/observability/v-next/trace-roots.ts
6095
6451
  /**
6096
6452
  * Get the root span for a trace, reading from trace_roots as compatibility path.
@@ -6327,6 +6683,17 @@ function buildTraceCursor(row) {
6327
6683
  }
6328
6684
  //#endregion
6329
6685
  //#region src/storage/domains/observability/v-next/tracing.ts
6686
+ /**
6687
+ * Tracing operations for ClickHouse v-next observability.
6688
+ *
6689
+ * Owns: batchCreateSpans, getSpan, getSpans, getTrace, getTraceLight,
6690
+ * listBranches, batchDeleteTraces, dangerouslyClearSpanEvents.
6691
+ * Delegates to trace-roots.ts: listTraces, getRootSpan.
6692
+ *
6693
+ * `listBranches` reads from the MV-fed `mastra_trace_branches` table (one row
6694
+ * per branch anchor span). It lives here -- alongside the other read paths
6695
+ * over the trace data -- since branches are conceptually a subset of traces.
6696
+ */
6330
6697
  const BRANCH_SPAN_TYPE_SQL_LIST = BRANCH_SPAN_TYPES.map((t) => `'${t}'`).join(", ");
6331
6698
  /** Insert a single completed span. */
6332
6699
  async function createSpan(client, args) {
@@ -6466,16 +6833,40 @@ async function getTraceLight(client, args) {
6466
6833
  };
6467
6834
  }
6468
6835
  /**
6469
- * Delete traces by traceId.
6470
- * Issues lightweight DELETE against both span_events and trace_roots.
6836
+ * Delete traces by traceId, cascading to trace-derived tables and trace-linked
6837
+ * signal events (metrics, logs, scores, feedback). Signal rows with a NULL
6838
+ * traceId are never affected.
6839
+ *
6840
+ * On the tracing tables, rows are targeted by tracing identity: traceId +
6841
+ * dedupeKey (which starts with traceId). The dedupeKey condition is redundant
6842
+ * for correctness (dedupeKey = traceId:spanId) but satisfies the design-doc
6843
+ * requirement that trace deletes reference dedupeKey and helps the engine
6844
+ * narrow within the sorted ORDER BY key. Signal tables key on their own event
6845
+ * ids, so they are targeted by traceId alone.
6846
+ *
6847
+ * `trace_branches` must be deleted explicitly: its MV fires on insert only,
6848
+ * so span deletes never propagate to it. Delta tables self-expire via TTL and
6849
+ * discovery tables self-heal, so neither needs explicit deletes.
6471
6850
  *
6472
- * Targets rows by tracing identity: traceId + dedupeKey (which starts with traceId).
6473
- * The dedupeKey condition is redundant for correctness (dedupeKey = traceId:spanId)
6474
- * but satisfies the design-doc requirement that trace deletes reference dedupeKey
6475
- * and helps the engine narrow within the sorted ORDER BY key.
6851
+ * Records the predicate before using lightweight DELETE FROM on every table.
6852
+ * Lightweight deletes hide rows through ClickHouse's delete mask; physical
6853
+ * removal depends on the deployment's configured retention and merge policy.
6854
+ *
6855
+ * When the optional tenant scope (`organizationId` / `resourceId`) is set,
6856
+ * every DELETE additionally requires the row's tenant columns to match.
6476
6857
  */
6477
- async function batchDeleteTraces(client, args) {
6858
+ async function batchDeleteTraces(client, args, replication) {
6478
6859
  if (args.traceIds.length === 0) return;
6860
+ await recordDeletionRequest(client, {
6861
+ requestId: randomUUID(),
6862
+ organizationId: args.organizationId,
6863
+ resourceId: args.resourceId,
6864
+ signal: "traces",
6865
+ predicateType: "traceIds",
6866
+ predicateValues: [...args.traceIds],
6867
+ requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
6868
+ replication
6869
+ });
6479
6870
  const params = {};
6480
6871
  const traceInPlaceholders = [];
6481
6872
  const dedupeOrParts = [];
@@ -6489,13 +6880,35 @@ async function batchDeleteTraces(client, args) {
6489
6880
  }
6490
6881
  const traceInList = traceInPlaceholders.join(", ");
6491
6882
  const dedupeCondition = dedupeOrParts.length === 1 ? dedupeOrParts[0] : `(${dedupeOrParts.join(" OR ")})`;
6492
- await Promise.all([client.command({
6493
- query: `DELETE FROM ${TABLE_SPAN_EVENTS} WHERE traceId IN (${traceInList}) AND ${dedupeCondition}`,
6494
- query_params: params
6495
- }), client.command({
6496
- query: `DELETE FROM ${TABLE_TRACE_ROOTS} WHERE traceId IN (${traceInList}) AND ${dedupeCondition}`,
6497
- query_params: params
6498
- })]);
6883
+ let scopeCondition = "";
6884
+ if (args.organizationId !== void 0) {
6885
+ params.scope_org = args.organizationId;
6886
+ scopeCondition += ` AND organizationId = {scope_org:String}`;
6887
+ }
6888
+ if (args.resourceId !== void 0) {
6889
+ params.scope_res = args.resourceId;
6890
+ scopeCondition += ` AND resourceId = {scope_res:String}`;
6891
+ }
6892
+ const tracingTables = [
6893
+ TABLE_SPAN_EVENTS,
6894
+ TABLE_TRACE_ROOTS,
6895
+ TABLE_TRACE_BRANCHES
6896
+ ];
6897
+ const signalTables = [
6898
+ TABLE_METRIC_EVENTS,
6899
+ TABLE_LOG_EVENTS,
6900
+ TABLE_SCORE_EVENTS,
6901
+ TABLE_FEEDBACK_EVENTS
6902
+ ];
6903
+ await Promise.all([...tracingTables.map((table) => client.command({
6904
+ query: `DELETE FROM ${table} WHERE traceId IN (${traceInList}) AND ${dedupeCondition}${scopeCondition}`,
6905
+ query_params: params,
6906
+ clickhouse_settings: { lightweight_deletes_sync: "2" }
6907
+ })), ...signalTables.map((table) => client.command({
6908
+ query: `DELETE FROM ${table} WHERE traceId IN (${traceInList})${scopeCondition}`,
6909
+ query_params: params,
6910
+ clickhouse_settings: { lightweight_deletes_sync: "2" }
6911
+ }))]);
6499
6912
  }
6500
6913
  /**
6501
6914
  * List trace branches with optional filtering, pagination, and ordering.
@@ -7033,6 +7446,7 @@ var ObservabilityStorageClickhouseVNext = class extends ObservabilityStorage {
7033
7446
  #retention;
7034
7447
  #replication;
7035
7448
  #deltaCursorStrategyOverride;
7449
+ #traceQueryTimeoutMs;
7036
7450
  #deltaCursorStrategy = "fallback";
7037
7451
  constructor(config) {
7038
7452
  super();
@@ -7041,6 +7455,7 @@ var ObservabilityStorageClickhouseVNext = class extends ObservabilityStorage {
7041
7455
  this.#replication = replication;
7042
7456
  this.#retention = config.retention;
7043
7457
  this.#deltaCursorStrategyOverride = config.deltaCursorStrategy;
7458
+ this.#traceQueryTimeoutMs = coreStorage.resolveTraceQueryTimeoutMs(config.traceQueryTimeoutMs);
7044
7459
  }
7045
7460
  async init() {
7046
7461
  const migrationStatus = await checkSignalTablesMigrationStatus(this.#client);
@@ -7144,11 +7559,16 @@ var ObservabilityStorageClickhouseVNext = class extends ObservabilityStorage {
7144
7559
  };
7145
7560
  }
7146
7561
  getFeatures() {
7147
- if (!deltaPollingSupported(this.#deltaCursorStrategy)) return ["metrics", "logs"];
7562
+ if (!deltaPollingSupported(this.#deltaCursorStrategy)) return [
7563
+ "metrics",
7564
+ "logs",
7565
+ "trace-query"
7566
+ ];
7148
7567
  return [
7149
7568
  "metrics",
7150
7569
  "logs",
7151
- "delta-polling"
7570
+ "delta-polling",
7571
+ "trace-query"
7152
7572
  ];
7153
7573
  }
7154
7574
  async createSpan(args) {
@@ -7263,6 +7683,18 @@ var ObservabilityStorageClickhouseVNext = class extends ObservabilityStorage {
7263
7683
  }, error);
7264
7684
  }
7265
7685
  }
7686
+ async queryTraces(plan) {
7687
+ try {
7688
+ return await queryTraces(this.#client, plan, this.#traceQueryTimeoutMs);
7689
+ } catch (error) {
7690
+ if (error instanceof MastraError || error instanceof coreStorage.TraceQueryExecutionError) throw error;
7691
+ throw new MastraError({
7692
+ id: createStorageErrorId("CLICKHOUSE", "QUERY_TRACES", "FAILED"),
7693
+ domain: ErrorDomain.STORAGE,
7694
+ category: ErrorCategory.THIRD_PARTY
7695
+ }, error);
7696
+ }
7697
+ }
7266
7698
  async listTracesLight(args) {
7267
7699
  try {
7268
7700
  return await listTracesLight(this.#client, args, this.#deltaCursorStrategy);
@@ -7679,7 +8111,7 @@ var ObservabilityStorageClickhouseVNext = class extends ObservabilityStorage {
7679
8111
  }
7680
8112
  async batchDeleteTraces(args) {
7681
8113
  try {
7682
- await batchDeleteTraces(this.#client, args);
8114
+ await batchDeleteTraces(this.#client, args, this.#replication);
7683
8115
  } catch (error) {
7684
8116
  if (error instanceof MastraError) throw error;
7685
8117
  throw new MastraError({
@@ -8539,7 +8971,8 @@ var ClickhouseStoreVNext = class extends ClickhouseStore {
8539
8971
  this.name = "ClickhouseStoreVNext";
8540
8972
  const observability = new ObservabilityStorageClickhouseVNext({
8541
8973
  client: this.db,
8542
- replication: config.replication
8974
+ replication: config.replication,
8975
+ traceQueryTimeoutMs: config.traceQueryTimeoutMs
8543
8976
  });
8544
8977
  this.stores = {
8545
8978
  ...this.stores,
@@ -8548,6 +8981,6 @@ var ClickhouseStoreVNext = class extends ClickhouseStore {
8548
8981
  }
8549
8982
  };
8550
8983
  //#endregion
8551
- export { COLUMN_TYPES, ClickhouseStore, ClickhouseStoreVNext, MemoryStorageClickhouse, ObservabilityStorageClickhouse, ObservabilityStorageClickhouseVNext, ScoresStorageClickhouse, TABLE_ENGINES, WorkflowsStorageClickhouse };
8984
+ export { COLUMN_TYPES, ClickhouseStore, ClickhouseStoreVNext, MemoryStorageClickhouse, ObservabilityStorageClickhouse, ObservabilityStorageClickhouseVNext, ScoresStorageClickhouse, TABLE_DELETION_REQUESTS, TABLE_ENGINES, WorkflowsStorageClickhouse, recordDeletionRequest };
8552
8985
 
8553
8986
  //# sourceMappingURL=index.js.map