@mastra/clickhouse 1.27.0 → 1.28.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.27.0"
6
+ version: "1.28.0-alpha.0"
7
7
  ---
8
8
 
9
9
  ## When to use
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.27.0",
2
+ "version": "1.28.0-alpha.0",
3
3
  "package": "@mastra/clickhouse",
4
4
  "exports": {},
5
5
  "modules": {}
package/dist/index.cjs CHANGED
@@ -6928,7 +6928,17 @@ const TRACE_STATUS_SQL = `if(isNotNull(r.error), 'error', 'success')`;
6928
6928
  const timeToFirstChunkSupported = "timeToFirstChunkMs" in (_mastra_core_storage.TRACE_QUERY_FIELD_REGISTRY?.trace ?? {});
6929
6929
  /** Core before the sort rejects `orderBy` on the field, so the sort is only declared when core plans it. */
6930
6930
  const timeToFirstChunkSortSupported = _mastra_core_storage.TRACE_QUERY_ORDER_FIELDS?.includes("timeToFirstChunkMs") === true;
6931
- const timeToFirstChunkFeatures = timeToFirstChunkSupported ? timeToFirstChunkSortSupported ? ["trace-query-time-to-first-chunk", "trace-query-time-to-first-chunk-sort"] : ["trace-query-time-to-first-chunk"] : [];
6931
+ /** Same rule for time to first token: returned and sorted only when core knows the field. */
6932
+ const timeToFirstTokenSupported = "timeToFirstTokenMs" in (_mastra_core_storage.TRACE_QUERY_FIELD_REGISTRY?.trace ?? {});
6933
+ const timeToFirstTokenSortSupported = _mastra_core_storage.TRACE_QUERY_ORDER_FIELDS?.includes("timeToFirstTokenMs") === true;
6934
+ const timingFeatures = [
6935
+ ...timeToFirstChunkSupported ? ["trace-query-time-to-first-chunk"] : [],
6936
+ ...timeToFirstChunkSupported && timeToFirstChunkSortSupported ? ["trace-query-time-to-first-chunk-sort"] : [],
6937
+ ...timeToFirstTokenSupported ? ["trace-query-time-to-first-token"] : [],
6938
+ ...timeToFirstTokenSupported && timeToFirstTokenSortSupported ? ["trace-query-time-to-first-token-sort"] : []
6939
+ ];
6940
+ /** Order fields holding numbers that can be empty; the rest are timestamps. */
6941
+ const NUMERIC_ORDER_FIELDS = /* @__PURE__ */ new Set(["timeToFirstChunkMs", "timeToFirstTokenMs"]);
6932
6942
  function durationMsSql(startedAt, endedAt) {
6933
6943
  return `dateDiff('millisecond', ${startedAt}, ${endedAt})`;
6934
6944
  }
@@ -6981,6 +6991,10 @@ const TRACE_FIELDS = {
6981
6991
  sql: "r.timeToFirstChunkMs",
6982
6992
  parameterType: "Float64"
6983
6993
  },
6994
+ timeToFirstTokenMs: {
6995
+ sql: "r.timeToFirstTokenMs",
6996
+ parameterType: "Float64"
6997
+ },
6984
6998
  entityId: {
6985
6999
  sql: "r.entityId",
6986
7000
  parameterType: "String"
@@ -7047,6 +7061,10 @@ const SPAN_FIELDS = {
7047
7061
  sql: "s.timeToFirstChunkMs",
7048
7062
  parameterType: "Float64"
7049
7063
  },
7064
+ timeToFirstTokenMs: {
7065
+ sql: "s.timeToFirstTokenMs",
7066
+ parameterType: "Float64"
7067
+ },
7050
7068
  status: {
7051
7069
  sql: "s.status",
7052
7070
  parameterType: "String"
@@ -7259,6 +7277,7 @@ const TRACE_SELECT = `
7259
7277
  r.entityType AS entityType,
7260
7278
  r.environment AS environment,
7261
7279
  r.timeToFirstChunkMs AS timeToFirstChunkMs,
7280
+ r.timeToFirstTokenMs AS timeToFirstTokenMs,
7262
7281
  ${TRACE_STATUS_SQL} AS status`;
7263
7282
  var ParameterBuilder = class {
7264
7283
  params = {};
@@ -7275,7 +7294,7 @@ function fieldDefinition(registry, field) {
7275
7294
  return definition;
7276
7295
  }
7277
7296
  function resolveOrderField(field) {
7278
- if (field === "startedAt" || field === "endedAt" || field === "timeToFirstChunkMs") return field;
7297
+ if (field === "startedAt" || field === "endedAt" || NUMERIC_ORDER_FIELDS.has(field)) return field;
7279
7298
  throw new Error(`Unsupported trusted trace-query field: ${field}`);
7280
7299
  }
7281
7300
  function isMetadataField(field) {
@@ -7465,6 +7484,7 @@ function compileClickHouseTraceScope(selection, relationCollections, parameters,
7465
7484
  endedAt,
7466
7485
  ${durationMsSql("startedAt", "endedAt")} AS durationMs,
7467
7486
  timeToFirstChunkMs,
7487
+ timeToFirstTokenMs,
7468
7488
  if(isNotNull(error), 'error', 'success') AS status,
7469
7489
  error,
7470
7490
  entityType,
@@ -7580,7 +7600,7 @@ function traceOrder(plan) {
7580
7600
  function keysetCursorCondition(plan, order, parameters) {
7581
7601
  if (plan.paginationMode !== "keyset" || !plan.cursor) return "";
7582
7602
  if (plan.cursor.sortValue === null) return `(isNull(sortValue) AND traceId > ${parameters.add(plan.cursor.traceId, "String")})`;
7583
- const numeric = order.field === "timeToFirstChunkMs";
7603
+ const numeric = NUMERIC_ORDER_FIELDS.has(order.field);
7584
7604
  const comparison = order.direction === "ASC" ? ">" : "<";
7585
7605
  const sortValue = parameters.add(plan.cursor.sortValue, numeric ? "Float64" : "DateTime64(3, 'UTC')");
7586
7606
  return `(sortValue ${comparison} ${sortValue} OR (sortValue = ${sortValue} AND traceId > ${parameters.add(plan.cursor.traceId, "String")})${numeric ? " OR isNull(sortValue)" : ""})`;
@@ -7594,7 +7614,7 @@ function compileClickHouseTraceQueryKeys(plan) {
7594
7614
  if (plan.paginationMode === "delta") throw new Error("Delta trace queries have no keys statement");
7595
7615
  const parameters = new ParameterBuilder();
7596
7616
  const order = traceOrder(plan);
7597
- const ctes = compileClickHouseTraceCandidates(plan, "r.traceId AS traceId, r.dedupeKey AS dedupeKey, r.startedAt AS startedAt, r.endedAt AS endedAt, r.timeToFirstChunkMs AS timeToFirstChunkMs", parameters, QUERY_TRACES_SCOPE);
7617
+ const ctes = compileClickHouseTraceCandidates(plan, "r.traceId AS traceId, r.dedupeKey AS dedupeKey, r.startedAt AS startedAt, r.endedAt AS endedAt, r.timeToFirstChunkMs AS timeToFirstChunkMs, r.timeToFirstTokenMs AS timeToFirstTokenMs", parameters, QUERY_TRACES_SCOPE);
7598
7618
  const cursor = keysetCursorCondition(plan, order, parameters);
7599
7619
  const limit = plan.paginationMode === "page" ? `LIMIT ${parameters.add(plan.perPage, "UInt64")} OFFSET ${parameters.add(plan.page * plan.perPage, "UInt64")}` : `LIMIT ${parameters.add(plan.limit + 1, "UInt64")}`;
7600
7620
  return {
@@ -7995,7 +8015,8 @@ async function queryTraces(client, plan, timeoutMs, strategy = null) {
7995
8015
  entityType: row.entityType == null ? null : String(row.entityType),
7996
8016
  environment: row.environment == null ? null : String(row.environment),
7997
8017
  status: row.status,
7998
- ...timeToFirstChunkSupported && { timeToFirstChunkMs: row.timeToFirstChunkMs == null ? null : Number(row.timeToFirstChunkMs) }
8018
+ ...timeToFirstChunkSupported && { timeToFirstChunkMs: row.timeToFirstChunkMs == null ? null : Number(row.timeToFirstChunkMs) },
8019
+ ...timeToFirstTokenSupported && { timeToFirstTokenMs: row.timeToFirstTokenMs == null ? null : Number(row.timeToFirstTokenMs) }
7999
8020
  });
8000
8021
  if (plan.paginationMode === "delta") {
8001
8022
  const rows = await run(compileClickHouseTraceQuery(plan, deltaHead));
@@ -8018,7 +8039,7 @@ async function queryTraces(client, plan, timeoutMs, strategy = null) {
8018
8039
  });
8019
8040
  }
8020
8041
  const keys = await run(compileClickHouseTraceQueryKeys(plan));
8021
- const sortValue = (value) => plan.orderBy.field !== "timeToFirstChunkMs" ? asIsoTimestamp(value) : value == null ? null : Number(value);
8042
+ const sortValue = (value) => !NUMERIC_ORDER_FIELDS.has(plan.orderBy.field) ? asIsoTimestamp(value) : value == null ? null : Number(value);
8022
8043
  let total = Number(keys[0]?.__total ?? 0);
8023
8044
  if (plan.paginationMode === "page" && keys.length === 0 && plan.page > 0) {
8024
8045
  const [stats] = await run(compileClickHouseTraceQueryTotal(plan));
@@ -8121,6 +8142,7 @@ const displayFields = [
8121
8142
  "provider",
8122
8143
  "durationMs",
8123
8144
  "timeToFirstChunkMs",
8145
+ "timeToFirstTokenMs",
8124
8146
  "status"
8125
8147
  ];
8126
8148
  var Parameters = class {
@@ -8149,7 +8171,7 @@ function afterCursor$1(plan, parameters) {
8149
8171
  const equal = [];
8150
8172
  if (cursor.sortValue === null) equal.push(`isNull(${field})`);
8151
8173
  else {
8152
- const numeric = plan.orderBy.field === "timeToFirstChunkMs";
8174
+ const numeric = plan.orderBy.field === "timeToFirstChunkMs" || plan.orderBy.field === "timeToFirstTokenMs";
8153
8175
  const value = parameters.add(cursor.sortValue, numeric ? "Float64" : "DateTime64(3, 'UTC')");
8154
8176
  terms.push(`${field} ${plan.orderBy.direction === "asc" ? ">" : "<"} ${value}`);
8155
8177
  if (numeric) terms.push(`isNull(${field})`);
@@ -8245,6 +8267,7 @@ const TRACE_BRANCHES_SPAN_FIELD_COLUMNS = {
8245
8267
  "startedAt",
8246
8268
  "endedAt",
8247
8269
  "timeToFirstChunkMs",
8270
+ "timeToFirstTokenMs",
8248
8271
  "threadId",
8249
8272
  "runId",
8250
8273
  "sessionId",
@@ -8405,12 +8428,13 @@ async function querySpans(client, plan, timeoutMs) {
8405
8428
  const hydrated = page.flatMap((identity) => {
8406
8429
  const row = byIdentity.get(_mastra_core_storage.spanQueryIdentityKey(identity));
8407
8430
  if (!row) return [];
8408
- const { inputPreview: _input, outputPreview: _output, errorPreview: _error, timeToFirstChunkMs, ...metadata } = row;
8431
+ const { inputPreview: _input, outputPreview: _output, errorPreview: _error, timeToFirstChunkMs, timeToFirstTokenMs, ...metadata } = row;
8409
8432
  return [{
8410
8433
  ...metadata,
8411
8434
  ...identity,
8412
8435
  durationMs: Number(row.durationMs),
8413
- ...timeToFirstChunkSupported && { timeToFirstChunkMs: timeToFirstChunkMs == null ? null : Number(timeToFirstChunkMs) }
8436
+ ...timeToFirstChunkSupported && { timeToFirstChunkMs: timeToFirstChunkMs == null ? null : Number(timeToFirstChunkMs) },
8437
+ ...timeToFirstTokenSupported && { timeToFirstTokenMs: timeToFirstTokenMs == null ? null : Number(timeToFirstTokenMs) }
8414
8438
  }];
8415
8439
  });
8416
8440
  let metrics = [];
@@ -8533,7 +8557,11 @@ function compileClickHouseThreadRowsQuery(plan, strategy = "full") {
8533
8557
  const after = afterCursor(plan, parameters);
8534
8558
  const qualify = plan.timeRange.mode === "traces" ? "s.inWindow > 0" : `s.${plan.timeRange.field} >= ${from} AND s.${plan.timeRange.field} < ${to}`;
8535
8559
  const filtered = predicateFields(plan.where);
8536
- const firstContext = [...contextFields, "timeToFirstChunkMs"].filter((field) => field === "resourceId" || filtered.has(field));
8560
+ const firstContext = [
8561
+ ...contextFields,
8562
+ "timeToFirstChunkMs",
8563
+ "timeToFirstTokenMs"
8564
+ ].filter((field) => field === "resourceId" || filtered.has(field));
8537
8565
  const firstFields = [
8538
8566
  "startedAt",
8539
8567
  "traceId",
@@ -8557,7 +8585,7 @@ function compileClickHouseThreadRowsQuery(plan, strategy = "full") {
8557
8585
  ...firstContext,
8558
8586
  ...filtered.has("status") ? ["status"] : []
8559
8587
  ];
8560
- const rootExpression = (field) => field === "timeToFirstChunkMs" ? "if(spanType = 'agent_run', timeToFirstChunkMs, NULL)" : field === "status" ? "if(isNull(error), 'success', 'error')" : field;
8588
+ const rootExpression = (field) => field === "timeToFirstChunkMs" || field === "timeToFirstTokenMs" ? `if(spanType = 'agent_run', ${field}, NULL)` : field === "status" ? "if(isNull(error), 'success', 'error')" : field;
8561
8589
  const sortedRoots = `roots AS (
8562
8590
  SELECT organizationId, traceId, ${rootDataFields.map((field) => `${rootExpression(field)} AS ${field}`).join(", ")}
8563
8591
  FROM ${TABLE_TRACE_ROOTS} ${scope.length || strategy === "candidates" ? `WHERE ${[...scope, ...strategy === "candidates" ? [candidateTracesFilter] : []].join(" AND ")}` : ""}
@@ -8661,6 +8689,7 @@ async function queryThreadRows(client, plan, timeoutMs, strategy = "auto") {
8661
8689
  for (const batch of batches([...roots.values()])) stored.push(...await run({
8662
8690
  query: `SELECT organizationId, traceId, spanId, startedAt, endedAt,
8663
8691
  ${contextFields.join(", ")}, if(spanType = 'agent_run', timeToFirstChunkMs, NULL) AS timeToFirstChunkMs,
8692
+ if(spanType = 'agent_run', timeToFirstTokenMs, NULL) AS timeToFirstTokenMs,
8664
8693
  if(isNull(error), 'success', 'error') AS status, ${PREVIEW_COLUMNS.join(", ")}
8665
8694
  FROM ${TABLE_TRACE_ROOTS} WHERE ${rootWhere}
8666
8695
  ORDER BY ${ROOT_VERSION_ORDER} LIMIT 1 BY organizationId, traceId, spanId`,
@@ -8694,6 +8723,7 @@ async function queryThreadRows(client, plan, timeoutMs, strategy = "auto") {
8694
8723
  endedAt: iso(row.endedAt),
8695
8724
  traceCount: Number(row.traceCount),
8696
8725
  timeToFirstChunkMs: first.timeToFirstChunkMs,
8726
+ ...timeToFirstTokenSupported && { timeToFirstTokenMs: first.timeToFirstTokenMs },
8697
8727
  inputPreview: resolveStoredPreviews(first).inputPreview,
8698
8728
  outputPreview: resolveStoredPreviews(last).outputPreview,
8699
8729
  errorPreview: resolveStoredPreviews(last).errorPreview
@@ -10350,7 +10380,7 @@ var ObservabilityStorageClickhouseVNext = class extends _mastra_core_storage.Obs
10350
10380
  "trace-query-tenant-scope",
10351
10381
  "feedback",
10352
10382
  "trace-query-context-ids",
10353
- ...timeToFirstChunkFeatures,
10383
+ ...timingFeatures,
10354
10384
  "trace-query-root-span-fields",
10355
10385
  "trace-query-root-entity-id",
10356
10386
  "trace-aggregate",
@@ -10375,7 +10405,7 @@ var ObservabilityStorageClickhouseVNext = class extends _mastra_core_storage.Obs
10375
10405
  "trace-query-tenant-scope",
10376
10406
  "feedback",
10377
10407
  "trace-query-context-ids",
10378
- ...timeToFirstChunkFeatures,
10408
+ ...timingFeatures,
10379
10409
  "trace-query-root-span-fields",
10380
10410
  "trace-query-root-entity-id",
10381
10411
  "trace-aggregate",