@mastra/clickhouse 1.16.1 → 1.17.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
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";
@@ -6091,6 +6092,269 @@ async function getScorePercentiles(client, args) {
6091
6092
  return { series };
6092
6093
  }
6093
6094
  //#endregion
6095
+ //#region src/storage/domains/observability/v-next/trace-query.ts
6096
+ const TRACE_STATUS_SQL = `if(isNotNull(r.error), 'error', 'success')`;
6097
+ const TRACE_FIELDS = {
6098
+ traceId: {
6099
+ sql: "r.traceId",
6100
+ parameterType: "String"
6101
+ },
6102
+ threadId: {
6103
+ sql: "r.threadId",
6104
+ parameterType: "String"
6105
+ },
6106
+ resourceId: {
6107
+ sql: "r.resourceId",
6108
+ parameterType: "String"
6109
+ },
6110
+ startedAt: {
6111
+ sql: "r.startedAt",
6112
+ parameterType: "DateTime64(3, 'UTC')"
6113
+ },
6114
+ endedAt: {
6115
+ sql: "r.endedAt",
6116
+ parameterType: "DateTime64(3, 'UTC')"
6117
+ },
6118
+ entityName: {
6119
+ sql: "r.entityName",
6120
+ parameterType: "String"
6121
+ },
6122
+ entityType: {
6123
+ sql: "r.entityType",
6124
+ parameterType: "String"
6125
+ },
6126
+ environment: {
6127
+ sql: "r.environment",
6128
+ parameterType: "String"
6129
+ },
6130
+ status: {
6131
+ sql: TRACE_STATUS_SQL,
6132
+ parameterType: "String"
6133
+ }
6134
+ };
6135
+ const SPAN_FIELDS = {
6136
+ spanType: {
6137
+ sql: "s.spanType",
6138
+ parameterType: "String"
6139
+ },
6140
+ error: {
6141
+ sql: "s.error",
6142
+ parameterType: "String"
6143
+ }
6144
+ };
6145
+ const SCORE_FIELDS = {
6146
+ scorerId: {
6147
+ sql: "s.scorerId",
6148
+ parameterType: "String"
6149
+ },
6150
+ score: {
6151
+ sql: "s.score",
6152
+ parameterType: "Float64"
6153
+ }
6154
+ };
6155
+ const TRACE_SELECT = `
6156
+ r.traceId AS traceId,
6157
+ r.spanId AS rootSpanId,
6158
+ r.threadId AS threadId,
6159
+ r.resourceId AS resourceId,
6160
+ r.startedAt AS startedAt,
6161
+ r.endedAt AS endedAt,
6162
+ r.entityName AS entityName,
6163
+ r.entityType AS entityType,
6164
+ r.environment AS environment,
6165
+ ${TRACE_STATUS_SQL} AS status`;
6166
+ var ParameterBuilder = class {
6167
+ params = {};
6168
+ #next = 1;
6169
+ add(value, type) {
6170
+ const name = `trace_query_${this.#next++}`;
6171
+ this.params[name] = type === "DateTime64(3, 'UTC')" ? new Date(value).toISOString().replace("T", " ").replace(/Z$/, "") : value;
6172
+ return `{${name}:${type}}`;
6173
+ }
6174
+ };
6175
+ function fieldDefinition(registry, field) {
6176
+ const definition = registry[field];
6177
+ if (definition === void 0) throw new Error(`Unsupported trusted trace-query field: ${field}`);
6178
+ return definition;
6179
+ }
6180
+ function resolveOrderField(field) {
6181
+ if (field === "startedAt" || field === "endedAt") return field;
6182
+ throw new Error(`Unsupported trusted trace-query field: ${field}`);
6183
+ }
6184
+ function compileScalarPredicate(predicate, registry, parameters) {
6185
+ if (predicate.type === "boolean") return predicate.args.map((arg) => `(${compileScalarPredicate(arg, registry, parameters)})`).join(predicate.operator === "and" ? " AND " : " OR ");
6186
+ if (predicate.type === "not") return `NOT (${compileScalarPredicate(predicate.arg, registry, parameters)})`;
6187
+ const field = fieldDefinition(registry, predicate.field);
6188
+ if (predicate.type === "presence") return `${predicate.operator === "exists" ? "isNotNull" : "isNull"}(${field.sql})`;
6189
+ if (predicate.type === "membership") {
6190
+ const values = predicate.values.map((value) => parameters.add(value, field.parameterType)).join(", ");
6191
+ return `ifNull(${`${field.sql} ${predicate.operator === "in" ? "IN" : "NOT IN"} (${values})`}, ${predicate.operator === "in" ? "0" : "1"})`;
6192
+ }
6193
+ const parameter = parameters.add(predicate.value, field.parameterType);
6194
+ const operator = {
6195
+ eq: "=",
6196
+ ne: "!=",
6197
+ lt: "<",
6198
+ lte: "<=",
6199
+ gt: ">",
6200
+ gte: ">="
6201
+ }[predicate.operator];
6202
+ if (operator === void 0) throw new Error(`Unsupported trusted trace-query operator: ${predicate.operator}`);
6203
+ return `ifNull(${field.sql} ${operator} ${parameter}, ${predicate.operator === "ne" ? "1" : "0"})`;
6204
+ }
6205
+ function collectRelationCollections(predicate, collections = /* @__PURE__ */ new Set()) {
6206
+ if (!predicate) return collections;
6207
+ if (predicate.type === "relation") collections.add(predicate.collection);
6208
+ else if (predicate.type === "boolean") for (const arg of predicate.args) collectRelationCollections(arg, collections);
6209
+ else if (predicate.type === "not") collectRelationCollections(predicate.arg, collections);
6210
+ return collections;
6211
+ }
6212
+ function compilePredicate(predicate, parameters) {
6213
+ if (predicate.type === "relation") {
6214
+ const registry = predicate.collection === "spans" ? SPAN_FIELDS : SCORE_FIELDS;
6215
+ const existence = `EXISTS (
6216
+ SELECT 1 FROM ${predicate.collection === "spans" ? "current_spans" : "current_scores"} s
6217
+ WHERE s.traceId = r.traceId
6218
+ AND (${compileScalarPredicate(predicate.predicate, registry, parameters)})
6219
+ )`;
6220
+ return predicate.quantifier === "some" ? existence : `NOT ${existence}`;
6221
+ }
6222
+ if (predicate.type === "boolean") return predicate.args.map((arg) => `(${compilePredicate(arg, parameters)})`).join(predicate.operator === "and" ? " AND " : " OR ");
6223
+ if (predicate.type === "not") return `NOT (${compilePredicate(predicate.arg, parameters)})`;
6224
+ return compileScalarPredicate(predicate, TRACE_FIELDS, parameters);
6225
+ }
6226
+ function compileClickHouseTraceQuery(plan) {
6227
+ const parameters = new ParameterBuilder();
6228
+ const from = parameters.add(plan.timeRange.from, "DateTime64(3, 'UTC')");
6229
+ const to = parameters.add(plan.timeRange.to, "DateTime64(3, 'UTC')");
6230
+ const relationCollections = collectRelationCollections(plan.where);
6231
+ const ctes = [`current_roots AS (
6232
+ SELECT * FROM (
6233
+ SELECT *
6234
+ FROM ${TABLE_TRACE_ROOTS}
6235
+ ORDER BY dedupeKey
6236
+ LIMIT 1 BY dedupeKey
6237
+ )
6238
+ ORDER BY traceId, dedupeKey
6239
+ LIMIT 1 BY traceId
6240
+ )`, `root_scope AS (
6241
+ SELECT *
6242
+ FROM current_roots
6243
+ WHERE startedAt >= ${from}
6244
+ AND startedAt < ${to}
6245
+ )`];
6246
+ if (relationCollections.has("spans")) ctes.push(`current_spans AS (
6247
+ SELECT traceId, spanType, error
6248
+ FROM ${TABLE_SPAN_EVENTS}
6249
+ WHERE traceId IN (SELECT traceId FROM root_scope)
6250
+ ORDER BY dedupeKey
6251
+ LIMIT 1 BY dedupeKey
6252
+ )`);
6253
+ if (relationCollections.has("scores")) ctes.push(`current_scores AS (
6254
+ SELECT traceId, scorerId, score
6255
+ FROM ${TABLE_SCORE_EVENTS}
6256
+ WHERE isNotNull(traceId)
6257
+ AND traceId IN (SELECT traceId FROM root_scope)
6258
+ )`);
6259
+ const predicate = plan.where ? compilePredicate(plan.where, parameters) : "1";
6260
+ ctes.push(`candidates AS (
6261
+ SELECT ${TRACE_SELECT}
6262
+ FROM root_scope r
6263
+ WHERE ${predicate}
6264
+ )`);
6265
+ const candidates = `WITH ${ctes.join(",\n")}`;
6266
+ if (plan.result === "groups") return {
6267
+ query: `${candidates}
6268
+ SELECT threadId
6269
+ FROM candidates
6270
+ WHERE isNotNull(threadId) ${plan.cursor ? `AND threadId > ${parameters.add(plan.cursor.threadId, "String")}` : ""}
6271
+ GROUP BY threadId
6272
+ ORDER BY threadId ASC
6273
+ LIMIT ${parameters.add(plan.limit + 1, "UInt64")}`,
6274
+ query_params: parameters.params
6275
+ };
6276
+ const orderField = resolveOrderField(plan.orderBy.field);
6277
+ const direction = plan.orderBy.direction === "asc" ? "ASC" : "DESC";
6278
+ let pageCondition = "";
6279
+ if (plan.cursor) {
6280
+ const comparison = plan.orderBy.direction === "asc" ? ">" : "<";
6281
+ const sortValue = parameters.add(plan.cursor.sortValue, "DateTime64(3, 'UTC')");
6282
+ pageCondition = `WHERE (${orderField} ${comparison} ${sortValue} OR (${orderField} = ${sortValue} AND traceId > ${parameters.add(plan.cursor.traceId, "String")}))`;
6283
+ }
6284
+ const limit = parameters.add(plan.limit + 1, "UInt64");
6285
+ return {
6286
+ query: `${candidates}
6287
+ SELECT *
6288
+ FROM candidates
6289
+ ${pageCondition}
6290
+ ORDER BY ${orderField} ${direction}, traceId ASC
6291
+ LIMIT ${limit}`,
6292
+ query_params: parameters.params
6293
+ };
6294
+ }
6295
+ function asIsoTimestamp(value) {
6296
+ return new Date(value).toISOString();
6297
+ }
6298
+ function isClickHouseExecutionTimeout(error) {
6299
+ if (!error || typeof error !== "object") return false;
6300
+ const candidate = error;
6301
+ return String(candidate.code ?? "") === "159" || candidate.type === "TIMEOUT_EXCEEDED";
6302
+ }
6303
+ async function runWithClickHouseTraceQueryTimeout(client, timeoutMs, compiled, queryId) {
6304
+ const resolvedTimeoutMs = coreStorage.resolveTraceQueryTimeoutMs(timeoutMs);
6305
+ try {
6306
+ return await (await client.query({
6307
+ query: compiled.query,
6308
+ query_params: compiled.query_params,
6309
+ query_id: queryId,
6310
+ format: "JSONEachRow",
6311
+ clickhouse_settings: {
6312
+ ...CH_SETTINGS,
6313
+ max_execution_time: resolvedTimeoutMs / 1e3
6314
+ }
6315
+ })).json();
6316
+ } catch (error) {
6317
+ if (isClickHouseExecutionTimeout(error)) throw new coreStorage.TraceQueryExecutionError();
6318
+ throw error;
6319
+ }
6320
+ }
6321
+ async function queryTraces(client, plan, timeoutMs) {
6322
+ const rows = await runWithClickHouseTraceQueryTimeout(client, timeoutMs, compileClickHouseTraceQuery(plan));
6323
+ const visibleRows = rows.slice(0, plan.limit);
6324
+ if (plan.result === "groups") {
6325
+ const groups = visibleRows.map((row) => ({ threadId: String(row.threadId) }));
6326
+ const last = groups.at(-1);
6327
+ return coreStorage.traceQueryResponseSchema.parse({
6328
+ groups,
6329
+ page: { next: rows.length > plan.limit && last ? coreStorage.encodeTraceQueryCursor(plan, {
6330
+ result: "groups",
6331
+ threadId: last.threadId
6332
+ }) : null }
6333
+ });
6334
+ }
6335
+ const traces = visibleRows.map((row) => ({
6336
+ traceId: String(row.traceId),
6337
+ rootSpanId: String(row.rootSpanId),
6338
+ threadId: row.threadId == null ? null : String(row.threadId),
6339
+ resourceId: row.resourceId == null ? null : String(row.resourceId),
6340
+ startedAt: asIsoTimestamp(row.startedAt),
6341
+ endedAt: asIsoTimestamp(row.endedAt),
6342
+ entityName: row.entityName == null ? null : String(row.entityName),
6343
+ entityType: row.entityType == null ? null : String(row.entityType),
6344
+ environment: row.environment == null ? null : String(row.environment),
6345
+ status: row.status
6346
+ }));
6347
+ const last = traces.at(-1);
6348
+ return coreStorage.traceQueryResponseSchema.parse({
6349
+ traces,
6350
+ page: { next: rows.length > plan.limit && last ? coreStorage.encodeTraceQueryCursor(plan, {
6351
+ result: "traces",
6352
+ sortValue: last[plan.orderBy.field],
6353
+ traceId: last.traceId
6354
+ }) : null }
6355
+ });
6356
+ }
6357
+ //#endregion
6094
6358
  //#region src/storage/domains/observability/v-next/trace-roots.ts
6095
6359
  /**
6096
6360
  * Get the root span for a trace, reading from trace_roots as compatibility path.
@@ -7033,6 +7297,7 @@ var ObservabilityStorageClickhouseVNext = class extends ObservabilityStorage {
7033
7297
  #retention;
7034
7298
  #replication;
7035
7299
  #deltaCursorStrategyOverride;
7300
+ #traceQueryTimeoutMs;
7036
7301
  #deltaCursorStrategy = "fallback";
7037
7302
  constructor(config) {
7038
7303
  super();
@@ -7041,6 +7306,7 @@ var ObservabilityStorageClickhouseVNext = class extends ObservabilityStorage {
7041
7306
  this.#replication = replication;
7042
7307
  this.#retention = config.retention;
7043
7308
  this.#deltaCursorStrategyOverride = config.deltaCursorStrategy;
7309
+ this.#traceQueryTimeoutMs = coreStorage.resolveTraceQueryTimeoutMs(config.traceQueryTimeoutMs);
7044
7310
  }
7045
7311
  async init() {
7046
7312
  const migrationStatus = await checkSignalTablesMigrationStatus(this.#client);
@@ -7144,11 +7410,16 @@ var ObservabilityStorageClickhouseVNext = class extends ObservabilityStorage {
7144
7410
  };
7145
7411
  }
7146
7412
  getFeatures() {
7147
- if (!deltaPollingSupported(this.#deltaCursorStrategy)) return ["metrics", "logs"];
7413
+ if (!deltaPollingSupported(this.#deltaCursorStrategy)) return [
7414
+ "metrics",
7415
+ "logs",
7416
+ "trace-query"
7417
+ ];
7148
7418
  return [
7149
7419
  "metrics",
7150
7420
  "logs",
7151
- "delta-polling"
7421
+ "delta-polling",
7422
+ "trace-query"
7152
7423
  ];
7153
7424
  }
7154
7425
  async createSpan(args) {
@@ -7263,6 +7534,18 @@ var ObservabilityStorageClickhouseVNext = class extends ObservabilityStorage {
7263
7534
  }, error);
7264
7535
  }
7265
7536
  }
7537
+ async queryTraces(plan) {
7538
+ try {
7539
+ return await queryTraces(this.#client, plan, this.#traceQueryTimeoutMs);
7540
+ } catch (error) {
7541
+ if (error instanceof MastraError || error instanceof coreStorage.TraceQueryExecutionError) throw error;
7542
+ throw new MastraError({
7543
+ id: createStorageErrorId("CLICKHOUSE", "QUERY_TRACES", "FAILED"),
7544
+ domain: ErrorDomain.STORAGE,
7545
+ category: ErrorCategory.THIRD_PARTY
7546
+ }, error);
7547
+ }
7548
+ }
7266
7549
  async listTracesLight(args) {
7267
7550
  try {
7268
7551
  return await listTracesLight(this.#client, args, this.#deltaCursorStrategy);
@@ -8539,7 +8822,8 @@ var ClickhouseStoreVNext = class extends ClickhouseStore {
8539
8822
  this.name = "ClickhouseStoreVNext";
8540
8823
  const observability = new ObservabilityStorageClickhouseVNext({
8541
8824
  client: this.db,
8542
- replication: config.replication
8825
+ replication: config.replication,
8826
+ traceQueryTimeoutMs: config.traceQueryTimeoutMs
8543
8827
  });
8544
8828
  this.stores = {
8545
8829
  ...this.stores,