@mastra/clickhouse 1.26.0 → 1.27.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.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- //#region \0rolldown/runtime.js
2
+ // \0rolldown/runtime.js
3
3
  var __create = Object.create;
4
4
  var __defProp = Object.defineProperty;
5
5
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -20,7 +20,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
20
20
  value: mod,
21
21
  enumerable: true
22
22
  }) : target, mod));
23
- //#endregion
24
23
  let _clickhouse_client = require("@clickhouse/client");
25
24
  let _mastra_core_error = require("@mastra/core/error");
26
25
  let _mastra_core_storage = require("@mastra/core/storage");
@@ -31,7 +30,7 @@ let _mastra_core_utils = require("@mastra/core/utils");
31
30
  let _mastra_core_features = require("@mastra/core/features");
32
31
  let zod_v4 = require("zod/v4");
33
32
  let _mastra_core_evals = require("@mastra/core/evals");
34
- //#region src/storage/db/replication.ts
33
+ // src/storage/db/replication.ts
35
34
  const DEFAULT_ZOOKEEPER_PATH = "/clickhouse/tables/{shard}/{database}/{table}";
36
35
  const DEFAULT_REPLICA_NAME = "{replica}";
37
36
  const SUPPORTED_ENGINE_NAMES = /* @__PURE__ */ new Set([
@@ -231,8 +230,7 @@ function rewriteEngineClauses(sql, replication) {
231
230
  function applyReplicationToDDL(sql, replication) {
232
231
  return addOnClusterToDDL(replication ? rewriteEngineClauses(sql, replication) : sql, replication);
233
232
  }
234
- //#endregion
235
- //#region src/storage/db/utils.ts
233
+ // src/storage/db/utils.ts
236
234
  const TABLE_ENGINES = {
237
235
  [_mastra_core_storage.TABLE_MESSAGES]: `MergeTree()`,
238
236
  [_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT]: `ReplacingMergeTree()`,
@@ -341,8 +339,7 @@ function transformRow(row) {
341
339
  function transformRows(rows) {
342
340
  return rows.map((row) => transformRow(row));
343
341
  }
344
- //#endregion
345
- //#region src/storage/db/index.ts
342
+ // src/storage/db/index.ts
346
343
  /**
347
344
  * Resolves ClickhouseDomainConfig to a ClickHouse client and ttl config.
348
345
  * Handles creating a new client if config is provided.
@@ -868,8 +865,7 @@ var ClickhouseDB = class extends _mastra_core_base.MastraBase {
868
865
  }
869
866
  }
870
867
  };
871
- //#endregion
872
- //#region src/storage/domains/memory/index.ts
868
+ // src/storage/domains/memory/index.ts
873
869
  /**
874
870
  * Serialize metadata object to JSON string for storage in ClickHouse.
875
871
  * Ensures we always store valid JSON, defaulting to '{}' for null/undefined.
@@ -2012,8 +2008,7 @@ var MemoryStorageClickhouse = class extends _mastra_core_storage.MemoryStorage {
2012
2008
  }
2013
2009
  }
2014
2010
  };
2015
- //#endregion
2016
- //#region src/storage/domains/observability/index.ts
2011
+ // src/storage/domains/observability/index.ts
2017
2012
  var ObservabilityStorageClickhouse = class extends _mastra_core_storage.ObservabilityStorage {
2018
2013
  client;
2019
2014
  #db;
@@ -2618,8 +2613,7 @@ var ObservabilityStorageClickhouse = class extends _mastra_core_storage.Observab
2618
2613
  }
2619
2614
  }
2620
2615
  };
2621
- //#endregion
2622
- //#region src/storage/domains/observability/v-next/ddl.ts
2616
+ // src/storage/domains/observability/v-next/ddl.ts
2623
2617
  const TABLE_SPAN_EVENTS = "mastra_span_events";
2624
2618
  const TABLE_TRACE_ROOTS = "mastra_trace_roots";
2625
2619
  const TABLE_TRACE_BRANCHES = "mastra_trace_branches";
@@ -3671,6 +3665,9 @@ const ALL_MIGRATIONS = [
3671
3665
  addColumn(TABLE_SPAN_EVENTS, "inputPreview", "Nullable(String)"),
3672
3666
  addColumn(TABLE_SPAN_EVENTS, "outputPreview", "Nullable(String)"),
3673
3667
  addColumn(TABLE_SPAN_EVENTS, "errorPreview", "Nullable(String)"),
3668
+ addColumn(TABLE_TRACE_BRANCHES, "entityVersionId", "Nullable(String)"),
3669
+ addColumn(TABLE_TRACE_BRANCHES, "parentEntityVersionId", "Nullable(String)"),
3670
+ addColumn(TABLE_TRACE_BRANCHES, "rootEntityVersionId", "Nullable(String)"),
3674
3671
  addColumn(TABLE_SPAN_EVENTS, "entityVersionId", "Nullable(String)"),
3675
3672
  addColumn(TABLE_SPAN_EVENTS, "parentEntityVersionId", "Nullable(String)"),
3676
3673
  addColumn(TABLE_SPAN_EVENTS, "rootEntityVersionId", "Nullable(String)"),
@@ -3819,8 +3816,7 @@ function parseTtlExpression(expr) {
3819
3816
  days
3820
3817
  };
3821
3818
  }
3822
- //#endregion
3823
- //#region src/storage/domains/observability/v-next/preview-compat.ts
3819
+ // src/storage/domains/observability/v-next/preview-compat.ts
3824
3820
  const generator = _mastra_core_storage.buildStoredPreview;
3825
3821
  const canGeneratePreviews = typeof generator === "function";
3826
3822
  function needsPreview(value) {
@@ -3873,8 +3869,7 @@ function buildSpanQueryResponse(...args) {
3873
3869
  }
3874
3870
  return response;
3875
3871
  }
3876
- //#endregion
3877
- //#region src/storage/domains/observability/v-next/previews.ts
3872
+ // src/storage/domains/observability/v-next/previews.ts
3878
3873
  /**
3879
3874
  * Stored span previews (`inputPreview` / `outputPreview` / `errorPreview`).
3880
3875
  *
@@ -3909,8 +3904,7 @@ function resolveStoredPreviews(row, payloads = row) {
3909
3904
  }
3910
3905
  return previews;
3911
3906
  }
3912
- //#endregion
3913
- //#region src/storage/domains/observability/v-next/review-status.ts
3907
+ // src/storage/domains/observability/v-next/review-status.ts
3914
3908
  const FEEDBACK_REVIEW_STATUSES = ["needs-review", "reviewed"];
3915
3909
  function isFeedbackReviewStatus(value) {
3916
3910
  return FEEDBACK_REVIEW_STATUSES.some((status) => status === value);
@@ -3934,8 +3928,7 @@ function parseUpdateFeedbackReviewStatusArgs(args) {
3934
3928
  reviewStatus: args.reviewStatus
3935
3929
  };
3936
3930
  }
3937
- //#endregion
3938
- //#region src/storage/domains/observability/v-next/helpers.ts
3931
+ // src/storage/domains/observability/v-next/helpers.ts
3939
3932
  const CH_SETTINGS = {
3940
3933
  date_time_input_format: "best_effort",
3941
3934
  date_time_output_format: "iso",
@@ -4505,8 +4498,7 @@ function feedbackRecordToRow(feedback) {
4505
4498
  scope: jsonEncode(feedback.scope)
4506
4499
  };
4507
4500
  }
4508
- //#endregion
4509
- //#region src/storage/domains/observability/v-next/deletion-requests.ts
4501
+ // src/storage/domains/observability/v-next/deletion-requests.ts
4510
4502
  const EPOCH = "1970-01-01T00:00:00.000Z";
4511
4503
  async function recordDeletionRequest(client, args) {
4512
4504
  const row = {
@@ -4563,8 +4555,7 @@ async function insertDeletionRequest(client, row, replication) {
4563
4555
  } : CH_INSERT_SETTINGS
4564
4556
  });
4565
4557
  }
4566
- //#endregion
4567
- //#region src/storage/domains/observability/v-next/discovery.ts
4558
+ // src/storage/domains/observability/v-next/discovery.ts
4568
4559
  async function queryJson$3(client, query, params = {}) {
4569
4560
  return await (await client.query({
4570
4561
  query,
@@ -4604,8 +4595,7 @@ async function getTags(client, args) {
4604
4595
  }
4605
4596
  return { tags: (await queryJson$3(client, `SELECT DISTINCT value FROM ${TABLE_DISCOVERY_VALUES} WHERE ${conditions.join(" AND ")} ORDER BY value`, params)).map((r) => r.value) };
4606
4597
  }
4607
- //#endregion
4608
- //#region src/storage/domains/observability/v-next/filters.ts
4598
+ // src/storage/domains/observability/v-next/filters.ts
4609
4599
  /**
4610
4600
  * ClickHouse v-next filter builders.
4611
4601
  *
@@ -4877,8 +4867,7 @@ function buildPaginationClause(pagination) {
4877
4867
  offset: page * perPage
4878
4868
  };
4879
4869
  }
4880
- //#endregion
4881
- //#region src/storage/domains/observability/v-next/polling.ts
4870
+ // src/storage/domains/observability/v-next/polling.ts
4882
4871
  const OBSERVABILITY_DELTA_POLLING_FEATURE = "observability-delta-polling";
4883
4872
  function deltaPollingFeatureEnabled() {
4884
4873
  return _mastra_core_features.coreFeatures.has(OBSERVABILITY_DELTA_POLLING_FEATURE);
@@ -4909,8 +4898,7 @@ function appendWhere(whereClause, ...conditions) {
4909
4898
  const parts = [whereClause.replace(/^\s*WHERE\s+/i, ""), ...conditions].filter(Boolean);
4910
4899
  return parts.length > 0 ? `WHERE ${parts.join(" AND ")}` : "";
4911
4900
  }
4912
- //#endregion
4913
- //#region src/storage/domains/observability/v-next/feedback.ts
4901
+ // src/storage/domains/observability/v-next/feedback.ts
4914
4902
  const FEEDBACK_TYPED_COLUMNS = /* @__PURE__ */ new Set([
4915
4903
  "timestamp",
4916
4904
  "traceId",
@@ -5412,8 +5400,7 @@ async function getFeedbackPercentiles(client, args) {
5412
5400
  }))
5413
5401
  })) };
5414
5402
  }
5415
- //#endregion
5416
- //#region src/storage/domains/observability/v-next/logs.ts
5403
+ // src/storage/domains/observability/v-next/logs.ts
5417
5404
  async function batchCreateLogs(client, args) {
5418
5405
  if (args.logs.length === 0) return;
5419
5406
  await client.insert({
@@ -5552,8 +5539,7 @@ async function getStreamHeadCursor$4(client) {
5552
5539
  function buildLogsCursor(row) {
5553
5540
  return row.cursorId ?? "0";
5554
5541
  }
5555
- //#endregion
5556
- //#region src/storage/domains/observability/v-next/metrics.ts
5542
+ // src/storage/domains/observability/v-next/metrics.ts
5557
5543
  /** Map typed columns to their ClickHouse column names. */
5558
5544
  const METRIC_TYPED_COLUMNS = /* @__PURE__ */ new Set([
5559
5545
  "timestamp",
@@ -6050,8 +6036,7 @@ async function getMetricLabelValues(client, args) {
6050
6036
  if (args.limit) params.valLimit = args.limit;
6051
6037
  return { values: (await queryJson$1(client, `SELECT DISTINCT value FROM ${TABLE_DISCOVERY_PAIRS} WHERE ${conditions.join(" AND ")} ORDER BY value ${limitClause}`, params)).map((r) => r.value) };
6052
6038
  }
6053
- //#endregion
6054
- //#region src/storage/domains/observability/v-next/migration.ts
6039
+ // src/storage/domains/observability/v-next/migration.ts
6055
6040
  const SIGNAL_MIGRATIONS = [
6056
6041
  {
6057
6042
  table: TABLE_METRIC_EVENTS,
@@ -6367,8 +6352,7 @@ async function migrateSignalTables(client, logger) {
6367
6352
  }
6368
6353
  }
6369
6354
  }
6370
- //#endregion
6371
- //#region src/storage/domains/observability/v-next/score-current.ts
6355
+ // src/storage/domains/observability/v-next/score-current.ts
6372
6356
  const SCORE_CURRENT_BACKFILL_MARKER = "v1";
6373
6357
  async function backfillCurrentScores(client) {
6374
6358
  const markerRows = await (await client.query({
@@ -6397,8 +6381,7 @@ async function backfillCurrentScores(client) {
6397
6381
  query_params: { marker: SCORE_CURRENT_BACKFILL_MARKER }
6398
6382
  });
6399
6383
  }
6400
- //#endregion
6401
- //#region src/storage/domains/observability/v-next/scores.ts
6384
+ // src/storage/domains/observability/v-next/scores.ts
6402
6385
  const SCORE_TYPED_COLUMNS = /* @__PURE__ */ new Set([
6403
6386
  "timestamp",
6404
6387
  "traceId",
@@ -6865,8 +6848,7 @@ async function getScorePercentiles(client, args) {
6865
6848
  }))
6866
6849
  })) };
6867
6850
  }
6868
- //#endregion
6869
- //#region src/storage/domains/observability/v-next/trace-query.ts
6851
+ // src/storage/domains/observability/v-next/trace-query.ts
6870
6852
  const TRACE_STATUS_SQL = `if(isNotNull(r.error), 'error', 'success')`;
6871
6853
  /**
6872
6854
  * Core versions without the field reject it in strict query results, so it is only returned when core knows it.
@@ -8012,8 +7994,7 @@ async function queryThreads(client, plan, timeoutMs) {
8012
7994
  });
8013
7995
  }
8014
7996
  /** Compile a direct span-query filter using its dedicated field mapping. */
8015
- function compileSpanQueryPredicate(predicate) {
8016
- const parameters = new ParameterBuilder();
7997
+ function compileSpanQueryPredicate(predicate, parameters = new ParameterBuilder()) {
8017
7998
  return {
8018
7999
  sql: compileScalarPredicate(predicate, SPAN_QUERY_FIELDS, parameters),
8019
8000
  params: parameters.params
@@ -8029,8 +8010,7 @@ function compileThreadQueryPredicate(predicate, parameters = new ParameterBuilde
8029
8010
  params: parameters.params
8030
8011
  };
8031
8012
  }
8032
- //#endregion
8033
- //#region src/storage/domains/observability/v-next/span-query.ts
8013
+ // src/storage/domains/observability/v-next/span-query.ts
8034
8014
  const identityFields = [
8035
8015
  "organizationId",
8036
8016
  "resourceId",
@@ -8174,6 +8154,131 @@ function compileClickHouseSpanQuery(plan) {
8174
8154
  }
8175
8155
  };
8176
8156
  }
8157
+ const branchSpanTypes = new Set(BRANCH_SPAN_TYPE_VALUES);
8158
+ const sameColumn = (field) => [field];
8159
+ /**
8160
+ * Span fields `trace_branches` can serve, as the `trace_branches` columns each one reads. The two
8161
+ * tables are expected to diverge, so this list is explicit: a selection whose fields are not all
8162
+ * listed reads `span_events`. A new span-query field has to be classified here deliberately, and a
8163
+ * missing column then costs speed, not correctness.
8164
+ */
8165
+ const TRACE_BRANCHES_SPAN_FIELD_COLUMNS = {
8166
+ ...Object.fromEntries([
8167
+ "organizationId",
8168
+ "resourceId",
8169
+ "traceId",
8170
+ "spanId",
8171
+ "parentSpanId",
8172
+ "name",
8173
+ "spanType",
8174
+ "startedAt",
8175
+ "endedAt",
8176
+ "timeToFirstChunkMs",
8177
+ "threadId",
8178
+ "runId",
8179
+ "sessionId",
8180
+ "userId",
8181
+ "requestId",
8182
+ "serviceName",
8183
+ "experimentId",
8184
+ "environment",
8185
+ "executionSource",
8186
+ "entityType",
8187
+ "entityId",
8188
+ "entityName",
8189
+ "entityVersionId",
8190
+ "parentEntityType",
8191
+ "parentEntityId",
8192
+ "parentEntityName",
8193
+ "parentEntityVersionId",
8194
+ "rootEntityType",
8195
+ "rootEntityId",
8196
+ "rootEntityName",
8197
+ "rootEntityVersionId"
8198
+ ].map((field) => [field, sameColumn(field)])),
8199
+ status: ["error"],
8200
+ error: ["error"],
8201
+ durationMs: ["startedAt", "endedAt"]
8202
+ };
8203
+ /**
8204
+ * Whether a span selection reads `trace_branches`: `where` must restrict `spanType` to branch
8205
+ * types, and `trace_branches` must serve every selected field (`served`, for tests).
8206
+ */
8207
+ function readsTraceBranches(where, fields, served = TRACE_BRANCHES_SPAN_FIELD_COLUMNS) {
8208
+ 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));
8209
+ }
8210
+ /**
8211
+ * Top-level `spanType` / `name` equality or `in` conditions. Every span the predicate can match
8212
+ * satisfies them, and copies of a span never differ in either field, so they can be applied
8213
+ * before collapsing copies.
8214
+ */
8215
+ function positiveSpanConjuncts(where) {
8216
+ return (where?.type === "boolean" && where.operator === "and" ? where.args : where ? [where] : []).flatMap((conjunct) => {
8217
+ if (conjunct.type !== "comparison" && conjunct.type !== "membership") return [];
8218
+ if (conjunct.field !== "spanType" && conjunct.field !== "name") return [];
8219
+ if (conjunct.type === "comparison" && conjunct.operator === "eq") return [{
8220
+ field: conjunct.field,
8221
+ values: [conjunct.value]
8222
+ }];
8223
+ if (conjunct.type === "membership" && conjunct.operator === "in") return [{
8224
+ field: conjunct.field,
8225
+ values: conjunct.values
8226
+ }];
8227
+ return [];
8228
+ });
8229
+ }
8230
+ /**
8231
+ * The spans `querySpans()` selects, as CTEs ending in `selected` (alias each row as you like):
8232
+ * one row per current completed span with `startedAt` in the window, matching `where`, carrying
8233
+ * the identity, `startedAt`, `endedAt`, `status`, and `fields`.
8234
+ *
8235
+ * Copies of a span collapse to the copy with the latest `endedAt`. When `where` requires branch
8236
+ * span types (agent and workflow runs, tool calls, …) and `trace_branches` serves every selected
8237
+ * field, the spans are read from `trace_branches`, whose sort key starts with `spanType`, instead of
8238
+ * every span in the window. That path can differ from `querySpans()` in two ways:
8239
+ * - `trace_branches` is filled only by its materialized view, with no backfill, so an install that
8240
+ * upgraded from @mastra/clickhouse 1.4–1.6 has no branch rows for spans written before then.
8241
+ * - It is a `ReplacingMergeTree` without a version column, so once a background merge collapses a
8242
+ * retried span's copies it keeps the last-inserted copy, not the one with the latest `endedAt`.
8243
+ * Counts are unaffected; `status` and duration of an out-of-order retry can differ.
8244
+ *
8245
+ * Like `querySpans()`, the read has no upper bound on `endedAt` (`PREWHERE endedAt >= from` only,
8246
+ * since a span that starts in the window can end any time later), so a historical window reads
8247
+ * every span that ended since `from`.
8248
+ */
8249
+ function compileClickHouseSpanSelection(plan, fields, parameters) {
8250
+ const from = parameters.add(plan.timeRange.from, "DateTime64(3, 'UTC')");
8251
+ const to = parameters.add(plan.timeRange.to, "DateTime64(3, 'UTC')");
8252
+ 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("");
8253
+ const predicate = plan.where ? compileSpanQueryPredicate(plan.where, parameters).sql : "1";
8254
+ let tenant = "";
8255
+ if (plan.scope) {
8256
+ tenant += `\n AND e.organizationId = ${parameters.add(plan.scope.organizationId, "String")}`;
8257
+ if (plan.scope.resourceId !== void 0) tenant += `\n AND e.resourceId = ${parameters.add(plan.scope.resourceId, "String")}`;
8258
+ }
8259
+ const selectionFields = [.../* @__PURE__ */ new Set([
8260
+ "startedAt",
8261
+ "endedAt",
8262
+ "status",
8263
+ ...fields,
8264
+ ...predicateFields$1(plan.where)
8265
+ ])].filter((field) => !new Set(identityFields).has(field));
8266
+ const branches = readsTraceBranches(plan.where, selectionFields);
8267
+ const scan = `FROM ${branches ? TABLE_TRACE_BRANCHES : TABLE_SPAN_EVENTS} e
8268
+ PREWHERE e.endedAt >= ${from}
8269
+ WHERE e.startedAt >= ${from} AND e.startedAt < ${to}${tenant}${prefilter}`;
8270
+ return [`span_candidates AS (
8271
+ ${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(", ")}
8272
+ ${scan}
8273
+ GROUP BY ${identityFields.map((field) => `e.${field}`).join(", ")}` : `SELECT ${identities}, ${selectionFields.map((field) => `${fieldExpression(field)} AS ${field}`).join(", ")}
8274
+ ${scan}
8275
+ ORDER BY ${identities}, e.endedAt DESC
8276
+ LIMIT 1 BY ${identities}`}
8277
+ )`, `selected AS (
8278
+ SELECT * FROM span_candidates s
8279
+ WHERE s.startedAt >= ${from} AND s.startedAt < ${to} AND (${predicate})
8280
+ )`];
8281
+ }
8177
8282
  const LEGACY_PAYLOAD_BATCH_ROWS = 32;
8178
8283
  function iso$1(value) {
8179
8284
  return new Date(value.includes("T") ? value : `${value.replace(" ", "T")}Z`).toISOString();
@@ -8262,8 +8367,7 @@ async function querySpans(client, plan, timeoutMs) {
8262
8367
  }) : null;
8263
8368
  return result;
8264
8369
  }
8265
- //#endregion
8266
- //#region src/storage/domains/observability/v-next/thread-query.ts
8370
+ // src/storage/domains/observability/v-next/thread-query.ts
8267
8371
  const contextFields = [
8268
8372
  "resourceId",
8269
8373
  "userId",
@@ -8557,8 +8661,7 @@ async function queryThreadRows(client, plan, timeoutMs, strategy = "auto") {
8557
8661
  }) : null }
8558
8662
  };
8559
8663
  }
8560
- //#endregion
8561
- //#region src/storage/domains/observability/v-next/trace-aggregate.ts
8664
+ // src/storage/domains/observability/v-next/trace-aggregate.ts
8562
8665
  /** Trace-root column expressions (alias `r`) for each groupable dimension; same values `where` sees. */
8563
8666
  const DIMENSION_SQL = {
8564
8667
  entityType: "r.entityType",
@@ -8577,6 +8680,28 @@ const DIMENSION_SQL = {
8577
8680
  organizationId: "r.organizationId",
8578
8681
  experimentId: "r.experimentId"
8579
8682
  };
8683
+ /** Span column expressions (alias `r`, a `selected` span row) for each groupable span dimension. */
8684
+ const SPAN_DIMENSION_SQL = Object.fromEntries([
8685
+ "name",
8686
+ "spanType",
8687
+ "entityType",
8688
+ "entityId",
8689
+ "entityName",
8690
+ "parentEntityType",
8691
+ "parentEntityName",
8692
+ "rootEntityType",
8693
+ "rootEntityName",
8694
+ "environment",
8695
+ "status",
8696
+ "serviceName",
8697
+ "executionSource",
8698
+ "threadId",
8699
+ "resourceId",
8700
+ "userId",
8701
+ "sessionId",
8702
+ "organizationId",
8703
+ "experimentId"
8704
+ ].map((field) => [field, `r.${field}`]));
8580
8705
  const PERCENTILES = {
8581
8706
  "duration.p50": .5,
8582
8707
  "duration.p90": .9,
@@ -8603,13 +8728,41 @@ const COMPARISON_SQL = {
8603
8728
  */
8604
8729
  function compileClickHouseTraceAggregate(plan) {
8605
8730
  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
- };
8731
+ return compileClickHouseAggregate(plan, parameters, {
8732
+ ctes: compileClickHouseTraceCandidates(plan, "*", parameters, { oneRootPerTrace: true }),
8733
+ from: "candidates",
8734
+ dimensionSql: (field) => {
8735
+ if (field.startsWith("metadata.")) return traceMetadataValueSql(parameters.add(field.slice(9), "String"));
8736
+ return lookupDimensionSql(DIMENSION_SQL, field);
8737
+ },
8738
+ isErrorSql: "isNotNull(r.error)",
8739
+ usageKey: ["traceId"],
8740
+ usageBound: "traceId IN (SELECT traceId FROM candidates)"
8741
+ });
8742
+ }
8743
+ /**
8744
+ * Compiles a trusted span aggregate plan over the spans `querySpans()` selects. Usage is
8745
+ * attributed by `(traceId, spanId)`, so only the span that recorded a metric row carries it.
8746
+ */
8747
+ function compileClickHouseSpanAggregate(plan) {
8748
+ const parameters = new ParameterBuilder();
8749
+ return compileClickHouseAggregate(plan, parameters, {
8750
+ ctes: compileClickHouseSpanSelection(plan, [...plan.dimensions, ...plan.measures.flatMap((measure) => measure.type === "countDistinct" && measure.field !== "traceId" ? [measure.field] : [])], parameters),
8751
+ from: "selected",
8752
+ dimensionSql: (field) => lookupDimensionSql(SPAN_DIMENSION_SQL, field),
8753
+ isErrorSql: "r.status = 'error'",
8754
+ usageKey: ["traceId", "spanId"],
8755
+ usageBound: "isNotNull(spanId)"
8756
+ });
8757
+ }
8758
+ function lookupDimensionSql(columns, field) {
8759
+ const sql = Object.hasOwn(columns, field) ? columns[field] : void 0;
8760
+ if (sql === void 0) throw new Error(`Unsupported trusted aggregate field: ${field}`);
8761
+ return sql;
8762
+ }
8763
+ function compileClickHouseAggregate(plan, parameters, source) {
8764
+ const { ctes } = source;
8765
+ const dimensionSql = source.dimensionSql;
8613
8766
  const distinctSql = (field) => field === "traceId" ? "r.traceId" : dimensionSql(field);
8614
8767
  const dimensionColumns = plan.dimensions.map((_, index) => `d${index}`);
8615
8768
  const distinctColumns = /* @__PURE__ */ new Map();
@@ -8620,7 +8773,7 @@ function compileClickHouseTraceAggregate(plan) {
8620
8773
  distinctColumns.set(measure.field, column);
8621
8774
  factColumns.push(`CAST(${distinctSql(measure.field)} AS Nullable(String)) AS ${column}`);
8622
8775
  }
8623
- factColumns.push(`toFloat64(${durationMsSql("r.startedAt", "r.endedAt")}) AS durationMs`, `isNotNull(r.error) AS isError`, `cityHash64(r.traceId) AS traceSeed`);
8776
+ 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
8777
  if (plan.interval !== void 0) {
8625
8778
  const intervalMs = _mastra_core_storage.TRACE_AGGREGATE_INTERVAL_MS[plan.interval];
8626
8779
  if (intervalMs === void 0) throw new Error(`Unsupported trusted trace-aggregate interval: ${plan.interval}`);
@@ -8630,14 +8783,14 @@ function compileClickHouseTraceAggregate(plan) {
8630
8783
  const hasCost = measureKinds.has("cost");
8631
8784
  let usageJoin = "";
8632
8785
  if (hasCost || measureKinds.has("tokens")) {
8633
- ctes.push(compileUsageCte(plan, parameters));
8634
- usageJoin = "\n LEFT JOIN usage u ON u.traceId = r.traceId";
8786
+ ctes.push(compileUsageCte(plan, parameters, source));
8787
+ usageJoin = `\n LEFT JOIN usage u ON ${source.usageKey.map((column) => `u.${column} = r.${column}`).join(" AND ")}`;
8635
8788
  USAGE_METRIC_NAMES.forEach((_, index) => factColumns.push(`if(u.hasUsage = 1, u.t${index}, NULL) AS t${index}`));
8636
8789
  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
8790
  }
8638
8791
  ctes.push(`facts AS (
8639
8792
  SELECT ${factColumns.join(",\n ")}
8640
- FROM candidates r${usageJoin}
8793
+ FROM ${source.from} r${usageJoin}
8641
8794
  )`);
8642
8795
  const mixedUnitsSql = "minOrNull(unitMin) != maxOrNull(unitMax)";
8643
8796
  const measureSql = (name) => {
@@ -8752,26 +8905,28 @@ function usageMetricIndex(name) {
8752
8905
  return index;
8753
8906
  }
8754
8907
  /**
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.
8908
+ * Builds `usage`: one row per usage key (a trace, or a span) that has at least one token metric
8909
+ * row, holding per-name token sums (`t<i>`, indexed by `TRACE_AGGREGATE_USAGE_METRIC_NAMES`) and
8910
+ * its pricing state. Metric rows are matched by the source's usage bound, usage metric name,
8911
+ * tenant scope, and `timestamp >= from` only: a trace or span that starts in the window can emit
8912
+ * metrics after `to`. For spans that makes the read cost grow with the age of `from` (see
8913
+ * `usageBound`).
8760
8914
  *
8761
8915
  * Rows are deduplicated with `LIMIT 1 BY metricId` at query time. `ReplacingMergeTree` only
8762
8916
  * collapses rows with equal sort keys `(name, timestamp, metricId)`, so retried copies with
8763
8917
  * different timestamps never merge, and copies with equal keys stay separate until a background
8764
8918
  * merge runs.
8765
8919
  */
8766
- function compileUsageCte(plan, parameters) {
8920
+ function compileUsageCte(plan, parameters, source) {
8921
+ const key = source.usageKey.join(", ");
8767
8922
  const names = USAGE_METRIC_NAMES.map((name) => parameters.add(name, "String"));
8768
8923
  const costRow = `name IN (${_mastra_core_storage.TRACE_AGGREGATE_COST_METRIC_NAMES.map((name) => names[usageMetricIndex(name)]).join(", ")})`;
8769
8924
  const from = parameters.add(plan.timeRange.from, "DateTime64(3, 'UTC')");
8770
8925
  const tenant = compileTenantScope(plan.scope, parameters);
8771
- const priced = `${costRow} AND isNotNull(estimatedCost) AND isNotNull(costUnit) AND NOT hasError`;
8926
+ const priced = `${costRow} AND isNotNull(estimatedCost) AND isNotNull(costUnit) AND (NOT hasError OR partialCost)`;
8772
8927
  const failed = `${costRow} AND (hasError OR (isNotNull(estimatedCost) AND isNull(costUnit)))`;
8773
8928
  return `usage AS (
8774
- SELECT traceId,
8929
+ SELECT ${key},
8775
8930
  toUInt8(1) AS hasUsage,
8776
8931
  ${names.map((name, index) => `sumIf(value, name = ${name}) AS t${index}`).join(",\n ")},
8777
8932
  sumIf(assumeNotNull(estimatedCost), ${priced}) AS cost,
@@ -8780,34 +8935,42 @@ function compileUsageCte(plan, parameters) {
8780
8935
  minIf(assumeNotNull(costUnit), ${priced}) AS unitMin,
8781
8936
  maxIf(assumeNotNull(costUnit), ${priced}) AS unitMax
8782
8937
  FROM (
8783
- SELECT traceId,
8938
+ SELECT ${key},
8784
8939
  latest.1 AS name,
8785
8940
  latest.2 AS value,
8786
8941
  latest.3 AS estimatedCost,
8787
8942
  latest.4 AS costUnit,
8788
- latest.5 AS hasError
8943
+ latest.5 AS hasError,
8944
+ latest.6 AS partialCost
8789
8945
  FROM (
8790
- SELECT traceId,
8791
- argMax(tuple(name, value, estimatedCost, costUnit, hasError), timestamp) AS latest
8946
+ SELECT ${key},
8947
+ argMax(tuple(name, value, estimatedCost, costUnit, hasError, partialCost), timestamp) AS latest
8792
8948
  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
8949
+ SELECT ${key}, metricId, timestamp, name, value, estimatedCost, costUnit,
8950
+ if(${costRow}, ifNull(JSONHas(costMetadata, 'error') AND JSONType(costMetadata, 'error') != 'Null', 0), 0) AS hasError,
8951
+ if(${costRow}, ifNull(JSONExtractString(costMetadata, 'error') = 'partial_cost', 0), 0) AS partialCost
8795
8952
  FROM ${TABLE_METRIC_EVENTS}
8796
- WHERE traceId IN (SELECT traceId FROM candidates)
8953
+ WHERE ${source.usageBound}
8797
8954
  AND name IN (${names.join(", ")})
8798
8955
  AND timestamp >= ${from}${tenant}
8799
8956
  )
8800
- GROUP BY traceId, metricId
8957
+ GROUP BY ${key}, metricId
8801
8958
  )
8802
8959
  )
8803
- GROUP BY traceId
8960
+ GROUP BY ${key}
8804
8961
  )`;
8805
8962
  }
8806
8963
  function nullableNumber(value) {
8807
8964
  return value === null || value === void 0 ? null : Number(value);
8808
8965
  }
8809
- async function aggregateTraces(client, plan, timeoutMs) {
8810
- const result = await runWithClickHouseTraceQueryTimeout(client, { timeoutMs }, compileClickHouseTraceAggregate(plan));
8966
+ function aggregateTraces(client, plan, timeoutMs) {
8967
+ return runAggregate(client, plan, compileClickHouseTraceAggregate(plan), timeoutMs);
8968
+ }
8969
+ async function aggregateSpans(client, plan, timeoutMs) {
8970
+ return await runAggregate(client, plan, compileClickHouseSpanAggregate(plan), timeoutMs);
8971
+ }
8972
+ async function runAggregate(client, plan, compiled, timeoutMs) {
8973
+ const result = await runWithClickHouseTraceQueryTimeout(client, { timeoutMs }, compiled);
8811
8974
  let truncated;
8812
8975
  let resultRows;
8813
8976
  if (plan.interval === void 0) {
@@ -8831,8 +8994,7 @@ async function aggregateTraces(client, plan, timeoutMs) {
8831
8994
  truncated
8832
8995
  };
8833
8996
  }
8834
- //#endregion
8835
- //#region src/storage/domains/observability/v-next/trace-roots.ts
8997
+ // src/storage/domains/observability/v-next/trace-roots.ts
8836
8998
  /**
8837
8999
  * Get the root span for a trace, reading from trace_roots as compatibility path.
8838
9000
  * Uses ordinary LIMIT 1 (duplicates are byte-identical per design).
@@ -9109,8 +9271,7 @@ async function getStreamHeadCursor$1(client) {
9109
9271
  function buildTraceCursor(row) {
9110
9272
  return row.cursorId ?? "0";
9111
9273
  }
9112
- //#endregion
9113
- //#region src/storage/domains/observability/v-next/tracing.ts
9274
+ // src/storage/domains/observability/v-next/tracing.ts
9114
9275
  const BRANCH_SPAN_TYPE_SQL_LIST = _mastra_core_storage.BRANCH_SPAN_TYPES.map((t) => `'${t}'`).join(", ");
9115
9276
  /** Insert a single completed span. */
9116
9277
  async function createSpan(client, args) {
@@ -9700,9 +9861,9 @@ async function getStreamHeadCursor(client) {
9700
9861
  function buildBranchCursor(row) {
9701
9862
  return row.cursorId ?? "0";
9702
9863
  }
9703
- //#endregion
9704
- //#region src/storage/domains/observability/v-next/index.ts
9864
+ // src/storage/domains/observability/v-next/index.ts
9705
9865
  const spanQueryFeatures = typeof _mastra_core_storage.planSpanQuery === "function" ? ["span-query", "span-query-context-fields"] : [];
9866
+ const spanAggregateFeatures = typeof _mastra_core_storage.planSpanAggregate === "function" ? ["span-aggregate"] : [];
9706
9867
  const threadQueryFeatures = typeof _mastra_core_storage.planThreadRowsQuery === "function" ? ["thread-query-rows"] : [];
9707
9868
  function buildSignalMigrationRequiredMessage(args) {
9708
9869
  const tableList = args.tables.map((table) => ` - ${table.table} (${table.engine})`).join("\n");
@@ -10146,6 +10307,7 @@ var ObservabilityStorageClickhouseVNext = class extends _mastra_core_storage.Obs
10146
10307
  "trace-query-root-entity-id",
10147
10308
  "trace-aggregate",
10148
10309
  ...spanQueryFeatures,
10310
+ ...spanAggregateFeatures,
10149
10311
  ...threadQueryFeatures
10150
10312
  ];
10151
10313
  return [
@@ -10170,6 +10332,7 @@ var ObservabilityStorageClickhouseVNext = class extends _mastra_core_storage.Obs
10170
10332
  "trace-query-root-entity-id",
10171
10333
  "trace-aggregate",
10172
10334
  ...spanQueryFeatures,
10335
+ ...spanAggregateFeatures,
10173
10336
  ...threadQueryFeatures
10174
10337
  ];
10175
10338
  }
@@ -10321,6 +10484,18 @@ var ObservabilityStorageClickhouseVNext = class extends _mastra_core_storage.Obs
10321
10484
  }, error);
10322
10485
  }
10323
10486
  }
10487
+ async aggregateSpans(plan) {
10488
+ try {
10489
+ return await aggregateSpans(this.#client, plan, this.#traceQueryTimeoutMs);
10490
+ } catch (error) {
10491
+ if (error instanceof _mastra_core_error.MastraError || error instanceof _mastra_core_storage.TraceQueryExecutionError || error instanceof _mastra_core_storage.TraceQueryResourceLimitError) throw error;
10492
+ throw new _mastra_core_error.MastraError({
10493
+ id: (0, _mastra_core_storage.createStorageErrorId)("CLICKHOUSE", "AGGREGATE_SPANS", "FAILED"),
10494
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
10495
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY
10496
+ }, error);
10497
+ }
10498
+ }
10324
10499
  async getTraceQueryObservedFields(plan) {
10325
10500
  try {
10326
10501
  return await getTraceQueryObservedFields(this.#client, plan, this.#traceQueryDiscoveryLimits);
@@ -10824,8 +10999,7 @@ var ObservabilityStorageClickhouseVNext = class extends _mastra_core_storage.Obs
10824
10999
  }
10825
11000
  }
10826
11001
  };
10827
- //#endregion
10828
- //#region src/storage/domains/scores/index.ts
11002
+ // src/storage/domains/scores/index.ts
10829
11003
  /**
10830
11004
  * Builds a multi-tenant scope SQL fragment and its named query params.
10831
11005
  * Returns a clause prefixed with ` AND ` (empty when no filters) plus params.
@@ -11246,8 +11420,7 @@ var ScoresStorageClickhouse = class extends _mastra_core_storage.ScoresStorage {
11246
11420
  }
11247
11421
  }
11248
11422
  };
11249
- //#endregion
11250
- //#region src/storage/domains/workflows/index.ts
11423
+ // src/storage/domains/workflows/index.ts
11251
11424
  var WorkflowsStorageClickhouse = class extends _mastra_core_storage.WorkflowsStorage {
11252
11425
  client;
11253
11426
  #db;
@@ -11514,8 +11687,7 @@ var WorkflowsStorageClickhouse = class extends _mastra_core_storage.WorkflowsSto
11514
11687
  }
11515
11688
  }
11516
11689
  };
11517
- //#endregion
11518
- //#region src/storage/index.ts
11690
+ // src/storage/index.ts
11519
11691
  /**
11520
11692
  * Type guard for pre-configured client config
11521
11693
  */
@@ -11678,7 +11850,6 @@ var ClickhouseStoreVNext = class extends ClickhouseStore {
11678
11850
  };
11679
11851
  }
11680
11852
  };
11681
- //#endregion
11682
11853
  exports.COLUMN_TYPES = COLUMN_TYPES;
11683
11854
  exports.ClickhouseStore = ClickhouseStore;
11684
11855
  exports.ClickhouseStoreVNext = ClickhouseStoreVNext;