@mastra/clickhouse 1.26.0 → 1.27.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.
@@ -3,7 +3,7 @@ name: mastra-clickhouse
3
3
  description: Documentation for @mastra/clickhouse. Use when working with @mastra/clickhouse APIs, configuration, or implementation.
4
4
  metadata:
5
5
  package: "@mastra/clickhouse"
6
- version: "1.26.0"
6
+ version: "1.27.0-alpha.0"
7
7
  ---
8
8
 
9
9
  ## When to use
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.26.0",
2
+ "version": "1.27.0-alpha.0",
3
3
  "package": "@mastra/clickhouse",
4
4
  "exports": {},
5
5
  "modules": {}
package/dist/index.cjs CHANGED
@@ -3671,6 +3671,9 @@ const ALL_MIGRATIONS = [
3671
3671
  addColumn(TABLE_SPAN_EVENTS, "inputPreview", "Nullable(String)"),
3672
3672
  addColumn(TABLE_SPAN_EVENTS, "outputPreview", "Nullable(String)"),
3673
3673
  addColumn(TABLE_SPAN_EVENTS, "errorPreview", "Nullable(String)"),
3674
+ addColumn(TABLE_TRACE_BRANCHES, "entityVersionId", "Nullable(String)"),
3675
+ addColumn(TABLE_TRACE_BRANCHES, "parentEntityVersionId", "Nullable(String)"),
3676
+ addColumn(TABLE_TRACE_BRANCHES, "rootEntityVersionId", "Nullable(String)"),
3674
3677
  addColumn(TABLE_SPAN_EVENTS, "entityVersionId", "Nullable(String)"),
3675
3678
  addColumn(TABLE_SPAN_EVENTS, "parentEntityVersionId", "Nullable(String)"),
3676
3679
  addColumn(TABLE_SPAN_EVENTS, "rootEntityVersionId", "Nullable(String)"),
@@ -8012,8 +8015,7 @@ async function queryThreads(client, plan, timeoutMs) {
8012
8015
  });
8013
8016
  }
8014
8017
  /** Compile a direct span-query filter using its dedicated field mapping. */
8015
- function compileSpanQueryPredicate(predicate) {
8016
- const parameters = new ParameterBuilder();
8018
+ function compileSpanQueryPredicate(predicate, parameters = new ParameterBuilder()) {
8017
8019
  return {
8018
8020
  sql: compileScalarPredicate(predicate, SPAN_QUERY_FIELDS, parameters),
8019
8021
  params: parameters.params
@@ -8174,6 +8176,131 @@ function compileClickHouseSpanQuery(plan) {
8174
8176
  }
8175
8177
  };
8176
8178
  }
8179
+ const branchSpanTypes = new Set(BRANCH_SPAN_TYPE_VALUES);
8180
+ const sameColumn = (field) => [field];
8181
+ /**
8182
+ * Span fields `trace_branches` can serve, as the `trace_branches` columns each one reads. The two
8183
+ * tables are expected to diverge, so this list is explicit: a selection whose fields are not all
8184
+ * listed reads `span_events`. A new span-query field has to be classified here deliberately, and a
8185
+ * missing column then costs speed, not correctness.
8186
+ */
8187
+ const TRACE_BRANCHES_SPAN_FIELD_COLUMNS = {
8188
+ ...Object.fromEntries([
8189
+ "organizationId",
8190
+ "resourceId",
8191
+ "traceId",
8192
+ "spanId",
8193
+ "parentSpanId",
8194
+ "name",
8195
+ "spanType",
8196
+ "startedAt",
8197
+ "endedAt",
8198
+ "timeToFirstChunkMs",
8199
+ "threadId",
8200
+ "runId",
8201
+ "sessionId",
8202
+ "userId",
8203
+ "requestId",
8204
+ "serviceName",
8205
+ "experimentId",
8206
+ "environment",
8207
+ "executionSource",
8208
+ "entityType",
8209
+ "entityId",
8210
+ "entityName",
8211
+ "entityVersionId",
8212
+ "parentEntityType",
8213
+ "parentEntityId",
8214
+ "parentEntityName",
8215
+ "parentEntityVersionId",
8216
+ "rootEntityType",
8217
+ "rootEntityId",
8218
+ "rootEntityName",
8219
+ "rootEntityVersionId"
8220
+ ].map((field) => [field, sameColumn(field)])),
8221
+ status: ["error"],
8222
+ error: ["error"],
8223
+ durationMs: ["startedAt", "endedAt"]
8224
+ };
8225
+ /**
8226
+ * Whether a span selection reads `trace_branches`: `where` must restrict `spanType` to branch
8227
+ * types, and `trace_branches` must serve every selected field (`served`, for tests).
8228
+ */
8229
+ function readsTraceBranches(where, fields, served = TRACE_BRANCHES_SPAN_FIELD_COLUMNS) {
8230
+ return positiveSpanConjuncts(where).some(({ field, values }) => field === "spanType" && values.length > 0 && values.every((value) => branchSpanTypes.has(String(value)))) && [...fields].every((field) => Object.hasOwn(served, field));
8231
+ }
8232
+ /**
8233
+ * Top-level `spanType` / `name` equality or `in` conditions. Every span the predicate can match
8234
+ * satisfies them, and copies of a span never differ in either field, so they can be applied
8235
+ * before collapsing copies.
8236
+ */
8237
+ function positiveSpanConjuncts(where) {
8238
+ return (where?.type === "boolean" && where.operator === "and" ? where.args : where ? [where] : []).flatMap((conjunct) => {
8239
+ if (conjunct.type !== "comparison" && conjunct.type !== "membership") return [];
8240
+ if (conjunct.field !== "spanType" && conjunct.field !== "name") return [];
8241
+ if (conjunct.type === "comparison" && conjunct.operator === "eq") return [{
8242
+ field: conjunct.field,
8243
+ values: [conjunct.value]
8244
+ }];
8245
+ if (conjunct.type === "membership" && conjunct.operator === "in") return [{
8246
+ field: conjunct.field,
8247
+ values: conjunct.values
8248
+ }];
8249
+ return [];
8250
+ });
8251
+ }
8252
+ /**
8253
+ * The spans `querySpans()` selects, as CTEs ending in `selected` (alias each row as you like):
8254
+ * one row per current completed span with `startedAt` in the window, matching `where`, carrying
8255
+ * the identity, `startedAt`, `endedAt`, `status`, and `fields`.
8256
+ *
8257
+ * Copies of a span collapse to the copy with the latest `endedAt`. When `where` requires branch
8258
+ * span types (agent and workflow runs, tool calls, …) and `trace_branches` serves every selected
8259
+ * field, the spans are read from `trace_branches`, whose sort key starts with `spanType`, instead of
8260
+ * every span in the window. That path can differ from `querySpans()` in two ways:
8261
+ * - `trace_branches` is filled only by its materialized view, with no backfill, so an install that
8262
+ * upgraded from @mastra/clickhouse 1.4–1.6 has no branch rows for spans written before then.
8263
+ * - It is a `ReplacingMergeTree` without a version column, so once a background merge collapses a
8264
+ * retried span's copies it keeps the last-inserted copy, not the one with the latest `endedAt`.
8265
+ * Counts are unaffected; `status` and duration of an out-of-order retry can differ.
8266
+ *
8267
+ * Like `querySpans()`, the read has no upper bound on `endedAt` (`PREWHERE endedAt >= from` only,
8268
+ * since a span that starts in the window can end any time later), so a historical window reads
8269
+ * every span that ended since `from`.
8270
+ */
8271
+ function compileClickHouseSpanSelection(plan, fields, parameters) {
8272
+ const from = parameters.add(plan.timeRange.from, "DateTime64(3, 'UTC')");
8273
+ const to = parameters.add(plan.timeRange.to, "DateTime64(3, 'UTC')");
8274
+ const prefilter = positiveSpanConjuncts(plan.where).map(({ field, values }) => values.length ? `\n AND e.${field} IN (${values.map((value) => parameters.add(String(value), "String")).join(", ")})` : "\n AND 0").join("");
8275
+ const predicate = plan.where ? compileSpanQueryPredicate(plan.where, parameters).sql : "1";
8276
+ let tenant = "";
8277
+ if (plan.scope) {
8278
+ tenant += `\n AND e.organizationId = ${parameters.add(plan.scope.organizationId, "String")}`;
8279
+ if (plan.scope.resourceId !== void 0) tenant += `\n AND e.resourceId = ${parameters.add(plan.scope.resourceId, "String")}`;
8280
+ }
8281
+ const selectionFields = [.../* @__PURE__ */ new Set([
8282
+ "startedAt",
8283
+ "endedAt",
8284
+ "status",
8285
+ ...fields,
8286
+ ...predicateFields$1(plan.where)
8287
+ ])].filter((field) => !new Set(identityFields).has(field));
8288
+ const branches = readsTraceBranches(plan.where, selectionFields);
8289
+ const scan = `FROM ${branches ? TABLE_TRACE_BRANCHES : TABLE_SPAN_EVENTS} e
8290
+ PREWHERE e.endedAt >= ${from}
8291
+ WHERE e.startedAt >= ${from} AND e.startedAt < ${to}${tenant}${prefilter}`;
8292
+ return [`span_candidates AS (
8293
+ ${branches ? `SELECT ${identityFields.map((field) => `e.${field} AS ${field}`).join(", ")}, ${selectionFields.map((field) => field === "endedAt" ? "max(e.endedAt) AS endedAt" : `argMax(tuple(${fieldExpression(field)}), e.endedAt).1 AS ${field}`).join(", ")}
8294
+ ${scan}
8295
+ GROUP BY ${identityFields.map((field) => `e.${field}`).join(", ")}` : `SELECT ${identities}, ${selectionFields.map((field) => `${fieldExpression(field)} AS ${field}`).join(", ")}
8296
+ ${scan}
8297
+ ORDER BY ${identities}, e.endedAt DESC
8298
+ LIMIT 1 BY ${identities}`}
8299
+ )`, `selected AS (
8300
+ SELECT * FROM span_candidates s
8301
+ WHERE s.startedAt >= ${from} AND s.startedAt < ${to} AND (${predicate})
8302
+ )`];
8303
+ }
8177
8304
  const LEGACY_PAYLOAD_BATCH_ROWS = 32;
8178
8305
  function iso$1(value) {
8179
8306
  return new Date(value.includes("T") ? value : `${value.replace(" ", "T")}Z`).toISOString();
@@ -8577,6 +8704,28 @@ const DIMENSION_SQL = {
8577
8704
  organizationId: "r.organizationId",
8578
8705
  experimentId: "r.experimentId"
8579
8706
  };
8707
+ /** Span column expressions (alias `r`, a `selected` span row) for each groupable span dimension. */
8708
+ const SPAN_DIMENSION_SQL = Object.fromEntries([
8709
+ "name",
8710
+ "spanType",
8711
+ "entityType",
8712
+ "entityId",
8713
+ "entityName",
8714
+ "parentEntityType",
8715
+ "parentEntityName",
8716
+ "rootEntityType",
8717
+ "rootEntityName",
8718
+ "environment",
8719
+ "status",
8720
+ "serviceName",
8721
+ "executionSource",
8722
+ "threadId",
8723
+ "resourceId",
8724
+ "userId",
8725
+ "sessionId",
8726
+ "organizationId",
8727
+ "experimentId"
8728
+ ].map((field) => [field, `r.${field}`]));
8580
8729
  const PERCENTILES = {
8581
8730
  "duration.p50": .5,
8582
8731
  "duration.p90": .9,
@@ -8603,13 +8752,41 @@ const COMPARISON_SQL = {
8603
8752
  */
8604
8753
  function compileClickHouseTraceAggregate(plan) {
8605
8754
  const parameters = new ParameterBuilder();
8606
- const ctes = compileClickHouseTraceCandidates(plan, "*", parameters, { oneRootPerTrace: true });
8607
- const dimensionSql = (field) => {
8608
- if (field.startsWith("metadata.")) return traceMetadataValueSql(parameters.add(field.slice(9), "String"));
8609
- const sql = Object.hasOwn(DIMENSION_SQL, field) ? DIMENSION_SQL[field] : void 0;
8610
- if (sql === void 0) throw new Error(`Unsupported trusted trace-aggregate field: ${field}`);
8611
- return sql;
8612
- };
8755
+ return compileClickHouseAggregate(plan, parameters, {
8756
+ ctes: compileClickHouseTraceCandidates(plan, "*", parameters, { oneRootPerTrace: true }),
8757
+ from: "candidates",
8758
+ dimensionSql: (field) => {
8759
+ if (field.startsWith("metadata.")) return traceMetadataValueSql(parameters.add(field.slice(9), "String"));
8760
+ return lookupDimensionSql(DIMENSION_SQL, field);
8761
+ },
8762
+ isErrorSql: "isNotNull(r.error)",
8763
+ usageKey: ["traceId"],
8764
+ usageBound: "traceId IN (SELECT traceId FROM candidates)"
8765
+ });
8766
+ }
8767
+ /**
8768
+ * Compiles a trusted span aggregate plan over the spans `querySpans()` selects. Usage is
8769
+ * attributed by `(traceId, spanId)`, so only the span that recorded a metric row carries it.
8770
+ */
8771
+ function compileClickHouseSpanAggregate(plan) {
8772
+ const parameters = new ParameterBuilder();
8773
+ return compileClickHouseAggregate(plan, parameters, {
8774
+ ctes: compileClickHouseSpanSelection(plan, [...plan.dimensions, ...plan.measures.flatMap((measure) => measure.type === "countDistinct" && measure.field !== "traceId" ? [measure.field] : [])], parameters),
8775
+ from: "selected",
8776
+ dimensionSql: (field) => lookupDimensionSql(SPAN_DIMENSION_SQL, field),
8777
+ isErrorSql: "r.status = 'error'",
8778
+ usageKey: ["traceId", "spanId"],
8779
+ usageBound: "isNotNull(spanId)"
8780
+ });
8781
+ }
8782
+ function lookupDimensionSql(columns, field) {
8783
+ const sql = Object.hasOwn(columns, field) ? columns[field] : void 0;
8784
+ if (sql === void 0) throw new Error(`Unsupported trusted aggregate field: ${field}`);
8785
+ return sql;
8786
+ }
8787
+ function compileClickHouseAggregate(plan, parameters, source) {
8788
+ const { ctes } = source;
8789
+ const dimensionSql = source.dimensionSql;
8613
8790
  const distinctSql = (field) => field === "traceId" ? "r.traceId" : dimensionSql(field);
8614
8791
  const dimensionColumns = plan.dimensions.map((_, index) => `d${index}`);
8615
8792
  const distinctColumns = /* @__PURE__ */ new Map();
@@ -8620,7 +8797,7 @@ function compileClickHouseTraceAggregate(plan) {
8620
8797
  distinctColumns.set(measure.field, column);
8621
8798
  factColumns.push(`CAST(${distinctSql(measure.field)} AS Nullable(String)) AS ${column}`);
8622
8799
  }
8623
- factColumns.push(`toFloat64(${durationMsSql("r.startedAt", "r.endedAt")}) AS durationMs`, `isNotNull(r.error) AS isError`, `cityHash64(r.traceId) AS traceSeed`);
8800
+ factColumns.push(`toFloat64(${durationMsSql("r.startedAt", "r.endedAt")}) AS durationMs`, `${source.isErrorSql} AS isError`, `cityHash64(${source.usageKey.map((column) => `r.${column}`).join(", ")}) AS traceSeed`);
8624
8801
  if (plan.interval !== void 0) {
8625
8802
  const intervalMs = _mastra_core_storage.TRACE_AGGREGATE_INTERVAL_MS[plan.interval];
8626
8803
  if (intervalMs === void 0) throw new Error(`Unsupported trusted trace-aggregate interval: ${plan.interval}`);
@@ -8630,14 +8807,14 @@ function compileClickHouseTraceAggregate(plan) {
8630
8807
  const hasCost = measureKinds.has("cost");
8631
8808
  let usageJoin = "";
8632
8809
  if (hasCost || measureKinds.has("tokens")) {
8633
- ctes.push(compileUsageCte(plan, parameters));
8634
- usageJoin = "\n LEFT JOIN usage u ON u.traceId = r.traceId";
8810
+ ctes.push(compileUsageCte(plan, parameters, source));
8811
+ usageJoin = `\n LEFT JOIN usage u ON ${source.usageKey.map((column) => `u.${column} = r.${column}`).join(" AND ")}`;
8635
8812
  USAGE_METRIC_NAMES.forEach((_, index) => factColumns.push(`if(u.hasUsage = 1, u.t${index}, NULL) AS t${index}`));
8636
8813
  factColumns.push(`u.hasUsage = 1 AS usageBearing`, `if(u.hasUsage = 1 AND u.priced = 1, u.cost, NULL) AS traceCost`, `u.hasUsage = 1 AND u.priced = 1 AND u.pricingFailure = 0 AS covered`, `if(u.hasUsage = 1 AND u.priced = 1, u.unitMin, NULL) AS unitMin`, `if(u.hasUsage = 1 AND u.priced = 1, u.unitMax, NULL) AS unitMax`);
8637
8814
  }
8638
8815
  ctes.push(`facts AS (
8639
8816
  SELECT ${factColumns.join(",\n ")}
8640
- FROM candidates r${usageJoin}
8817
+ FROM ${source.from} r${usageJoin}
8641
8818
  )`);
8642
8819
  const mixedUnitsSql = "minOrNull(unitMin) != maxOrNull(unitMax)";
8643
8820
  const measureSql = (name) => {
@@ -8752,26 +8929,28 @@ function usageMetricIndex(name) {
8752
8929
  return index;
8753
8930
  }
8754
8931
  /**
8755
- * Builds `usage`: one row per candidate trace that has at least one token metric row, holding
8756
- * per-name token sums (`t<i>`, indexed by `TRACE_AGGREGATE_USAGE_METRIC_NAMES`) and the trace's
8757
- * pricing state. Metric rows are matched by traceId, usage metric name, tenant scope, and
8758
- * `timestamp >= from` only: a trace that starts in the window can emit metrics after `to`, and
8759
- * the candidates semi-join already bounds the scan.
8932
+ * Builds `usage`: one row per usage key (a trace, or a span) that has at least one token metric
8933
+ * row, holding per-name token sums (`t<i>`, indexed by `TRACE_AGGREGATE_USAGE_METRIC_NAMES`) and
8934
+ * its pricing state. Metric rows are matched by the source's usage bound, usage metric name,
8935
+ * tenant scope, and `timestamp >= from` only: a trace or span that starts in the window can emit
8936
+ * metrics after `to`. For spans that makes the read cost grow with the age of `from` (see
8937
+ * `usageBound`).
8760
8938
  *
8761
8939
  * Rows are deduplicated with `LIMIT 1 BY metricId` at query time. `ReplacingMergeTree` only
8762
8940
  * collapses rows with equal sort keys `(name, timestamp, metricId)`, so retried copies with
8763
8941
  * different timestamps never merge, and copies with equal keys stay separate until a background
8764
8942
  * merge runs.
8765
8943
  */
8766
- function compileUsageCte(plan, parameters) {
8944
+ function compileUsageCte(plan, parameters, source) {
8945
+ const key = source.usageKey.join(", ");
8767
8946
  const names = USAGE_METRIC_NAMES.map((name) => parameters.add(name, "String"));
8768
8947
  const costRow = `name IN (${_mastra_core_storage.TRACE_AGGREGATE_COST_METRIC_NAMES.map((name) => names[usageMetricIndex(name)]).join(", ")})`;
8769
8948
  const from = parameters.add(plan.timeRange.from, "DateTime64(3, 'UTC')");
8770
8949
  const tenant = compileTenantScope(plan.scope, parameters);
8771
- const priced = `${costRow} AND isNotNull(estimatedCost) AND isNotNull(costUnit) AND NOT hasError`;
8950
+ const priced = `${costRow} AND isNotNull(estimatedCost) AND isNotNull(costUnit) AND (NOT hasError OR partialCost)`;
8772
8951
  const failed = `${costRow} AND (hasError OR (isNotNull(estimatedCost) AND isNull(costUnit)))`;
8773
8952
  return `usage AS (
8774
- SELECT traceId,
8953
+ SELECT ${key},
8775
8954
  toUInt8(1) AS hasUsage,
8776
8955
  ${names.map((name, index) => `sumIf(value, name = ${name}) AS t${index}`).join(",\n ")},
8777
8956
  sumIf(assumeNotNull(estimatedCost), ${priced}) AS cost,
@@ -8780,34 +8959,42 @@ function compileUsageCte(plan, parameters) {
8780
8959
  minIf(assumeNotNull(costUnit), ${priced}) AS unitMin,
8781
8960
  maxIf(assumeNotNull(costUnit), ${priced}) AS unitMax
8782
8961
  FROM (
8783
- SELECT traceId,
8962
+ SELECT ${key},
8784
8963
  latest.1 AS name,
8785
8964
  latest.2 AS value,
8786
8965
  latest.3 AS estimatedCost,
8787
8966
  latest.4 AS costUnit,
8788
- latest.5 AS hasError
8967
+ latest.5 AS hasError,
8968
+ latest.6 AS partialCost
8789
8969
  FROM (
8790
- SELECT traceId,
8791
- argMax(tuple(name, value, estimatedCost, costUnit, hasError), timestamp) AS latest
8970
+ SELECT ${key},
8971
+ argMax(tuple(name, value, estimatedCost, costUnit, hasError, partialCost), timestamp) AS latest
8792
8972
  FROM (
8793
- SELECT traceId, metricId, timestamp, name, value, estimatedCost, costUnit,
8794
- if(${costRow}, ifNull(JSONHas(costMetadata, 'error') AND JSONType(costMetadata, 'error') != 'Null', 0), 0) AS hasError
8973
+ SELECT ${key}, metricId, timestamp, name, value, estimatedCost, costUnit,
8974
+ if(${costRow}, ifNull(JSONHas(costMetadata, 'error') AND JSONType(costMetadata, 'error') != 'Null', 0), 0) AS hasError,
8975
+ if(${costRow}, ifNull(JSONExtractString(costMetadata, 'error') = 'partial_cost', 0), 0) AS partialCost
8795
8976
  FROM ${TABLE_METRIC_EVENTS}
8796
- WHERE traceId IN (SELECT traceId FROM candidates)
8977
+ WHERE ${source.usageBound}
8797
8978
  AND name IN (${names.join(", ")})
8798
8979
  AND timestamp >= ${from}${tenant}
8799
8980
  )
8800
- GROUP BY traceId, metricId
8981
+ GROUP BY ${key}, metricId
8801
8982
  )
8802
8983
  )
8803
- GROUP BY traceId
8984
+ GROUP BY ${key}
8804
8985
  )`;
8805
8986
  }
8806
8987
  function nullableNumber(value) {
8807
8988
  return value === null || value === void 0 ? null : Number(value);
8808
8989
  }
8809
- async function aggregateTraces(client, plan, timeoutMs) {
8810
- const result = await runWithClickHouseTraceQueryTimeout(client, { timeoutMs }, compileClickHouseTraceAggregate(plan));
8990
+ function aggregateTraces(client, plan, timeoutMs) {
8991
+ return runAggregate(client, plan, compileClickHouseTraceAggregate(plan), timeoutMs);
8992
+ }
8993
+ async function aggregateSpans(client, plan, timeoutMs) {
8994
+ return await runAggregate(client, plan, compileClickHouseSpanAggregate(plan), timeoutMs);
8995
+ }
8996
+ async function runAggregate(client, plan, compiled, timeoutMs) {
8997
+ const result = await runWithClickHouseTraceQueryTimeout(client, { timeoutMs }, compiled);
8811
8998
  let truncated;
8812
8999
  let resultRows;
8813
9000
  if (plan.interval === void 0) {
@@ -9703,6 +9890,7 @@ function buildBranchCursor(row) {
9703
9890
  //#endregion
9704
9891
  //#region src/storage/domains/observability/v-next/index.ts
9705
9892
  const spanQueryFeatures = typeof _mastra_core_storage.planSpanQuery === "function" ? ["span-query", "span-query-context-fields"] : [];
9893
+ const spanAggregateFeatures = typeof _mastra_core_storage.planSpanAggregate === "function" ? ["span-aggregate"] : [];
9706
9894
  const threadQueryFeatures = typeof _mastra_core_storage.planThreadRowsQuery === "function" ? ["thread-query-rows"] : [];
9707
9895
  function buildSignalMigrationRequiredMessage(args) {
9708
9896
  const tableList = args.tables.map((table) => ` - ${table.table} (${table.engine})`).join("\n");
@@ -10146,6 +10334,7 @@ var ObservabilityStorageClickhouseVNext = class extends _mastra_core_storage.Obs
10146
10334
  "trace-query-root-entity-id",
10147
10335
  "trace-aggregate",
10148
10336
  ...spanQueryFeatures,
10337
+ ...spanAggregateFeatures,
10149
10338
  ...threadQueryFeatures
10150
10339
  ];
10151
10340
  return [
@@ -10170,6 +10359,7 @@ var ObservabilityStorageClickhouseVNext = class extends _mastra_core_storage.Obs
10170
10359
  "trace-query-root-entity-id",
10171
10360
  "trace-aggregate",
10172
10361
  ...spanQueryFeatures,
10362
+ ...spanAggregateFeatures,
10173
10363
  ...threadQueryFeatures
10174
10364
  ];
10175
10365
  }
@@ -10321,6 +10511,18 @@ var ObservabilityStorageClickhouseVNext = class extends _mastra_core_storage.Obs
10321
10511
  }, error);
10322
10512
  }
10323
10513
  }
10514
+ async aggregateSpans(plan) {
10515
+ try {
10516
+ return await aggregateSpans(this.#client, plan, this.#traceQueryTimeoutMs);
10517
+ } catch (error) {
10518
+ if (error instanceof _mastra_core_error.MastraError || error instanceof _mastra_core_storage.TraceQueryExecutionError || error instanceof _mastra_core_storage.TraceQueryResourceLimitError) throw error;
10519
+ throw new _mastra_core_error.MastraError({
10520
+ id: (0, _mastra_core_storage.createStorageErrorId)("CLICKHOUSE", "AGGREGATE_SPANS", "FAILED"),
10521
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
10522
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY
10523
+ }, error);
10524
+ }
10525
+ }
10324
10526
  async getTraceQueryObservedFields(plan) {
10325
10527
  try {
10326
10528
  return await getTraceQueryObservedFields(this.#client, plan, this.#traceQueryDiscoveryLimits);