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