@mastra/clickhouse 1.14.0 → 1.15.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.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createClient } from "@clickhouse/client";
2
2
  import { ErrorCategory, ErrorDomain, MastraError } from "@mastra/core/error";
3
- import { BRANCH_SPAN_TYPES, BackgroundTasksStorage, EntityType, METRIC_DISTINCT_COLUMNS, MastraCompositeStore, MemoryStorage, ObservabilityStorage, SCORERS_SCHEMA, SPAN_SCHEMA, ScoresStorage, TABLE_AGENT_VERSIONS, TABLE_BACKGROUND_TASKS, TABLE_DATASETS, TABLE_DATASET_ITEMS, TABLE_DATASET_VERSIONS, TABLE_EXPERIMENTS, TABLE_EXPERIMENT_RESULTS, TABLE_FAVORITES, TABLE_HARNESS_SESSIONS, 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, calculatePagination, createStorageErrorId, getDefaultValue, getSqlType, listBranchesArgsSchema, listFeedbackArgsSchema, listLogsArgsSchema, listMetricsArgsSchema, listScoresArgsSchema, listTracesArgsSchema, normalizePerPage, safelyParseJSON, toTraceSpans, transformScoreRow, validateStorageMetadataFilter } from "@mastra/core/storage";
3
+ import { BRANCH_SPAN_TYPES, BackgroundTasksStorage, EntityType, METRIC_DISTINCT_COLUMNS, MastraCompositeStore, MemoryStorage, ObservabilityStorage, SCORERS_SCHEMA, SPAN_SCHEMA, ScoresStorage, TABLE_AGENT_VERSIONS, TABLE_BACKGROUND_TASKS, TABLE_DATASETS, TABLE_DATASET_ITEMS, TABLE_DATASET_VERSIONS, TABLE_EXPERIMENTS, TABLE_EXPERIMENT_RESULTS, TABLE_FAVORITES, TABLE_HARNESS_SESSIONS, 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
4
  import { MastraBase } from "@mastra/core/base";
5
5
  import { MessageList } from "@mastra/core/agent";
6
6
  import { parseFieldKey } from "@mastra/core/utils";
@@ -1184,7 +1184,10 @@ var MemoryStorageClickhouse = class extends MemoryStorage {
1184
1184
  hasMore: false
1185
1185
  };
1186
1186
  if (perPageForQuery === 0 && include && include.length > 0) {
1187
- const includeResult = await this._getIncludedMessages({ include });
1187
+ const includeResult = await this._getIncludedMessages({
1188
+ include,
1189
+ resourceId
1190
+ });
1188
1191
  const list = new MessageList().add(includeResult, "memory");
1189
1192
  return {
1190
1193
  messages: this._sortMessages(list.get.all.db(), field, direction),
@@ -1251,7 +1254,10 @@ var MemoryStorageClickhouse = class extends MemoryStorage {
1251
1254
  };
1252
1255
  const messageIds = new Set(paginatedMessages.map((m) => m.id));
1253
1256
  if (include && include.length > 0) {
1254
- const includeMessages = await this._getIncludedMessages({ include });
1257
+ const includeMessages = await this._getIncludedMessages({
1258
+ include,
1259
+ resourceId
1260
+ });
1255
1261
  for (const includeMsg of includeMessages) if (!messageIds.has(includeMsg.id)) {
1256
1262
  paginatedMessages.push(includeMsg);
1257
1263
  messageIds.add(includeMsg.id);
@@ -1269,6 +1275,7 @@ var MemoryStorageClickhouse = class extends MemoryStorage {
1269
1275
  hasMore: metadataFilter && perPageForResponse !== false ? offset + paginatedCount < total : perPageForResponse === false ? false : allThreadMessagesReturned ? false : offset + paginatedCount < total
1270
1276
  };
1271
1277
  } catch (error) {
1278
+ if (error instanceof MastraError && error.category === ErrorCategory.USER) throw error;
1272
1279
  const mastraError = new MastraError({
1273
1280
  id: createStorageErrorId("CLICKHOUSE", "LIST_MESSAGES", "FAILED"),
1274
1281
  domain: ErrorDomain.STORAGE,
@@ -1280,13 +1287,7 @@ var MemoryStorageClickhouse = class extends MemoryStorage {
1280
1287
  }, error);
1281
1288
  this.logger?.error?.(mastraError.toString());
1282
1289
  this.logger?.trackException?.(mastraError);
1283
- return {
1284
- messages: [],
1285
- total: 0,
1286
- page,
1287
- perPage: perPageForResponse,
1288
- hasMore: false
1289
- };
1290
+ throw mastraError;
1290
1291
  }
1291
1292
  }
1292
1293
  _sortMessages(messages, field, direction) {
@@ -1299,15 +1300,24 @@ var MemoryStorageClickhouse = class extends MemoryStorage {
1299
1300
  return direction === "ASC" ? String(aValue).localeCompare(String(bValue)) : String(bValue).localeCompare(String(aValue));
1300
1301
  });
1301
1302
  }
1302
- async _getIncludedMessages({ include }) {
1303
+ /**
1304
+ * Fetches the messages named by `include` together with their surrounding context.
1305
+ *
1306
+ * @param include - Message ids to pin, each with an optional before/after window.
1307
+ * @param resourceId - When set, restricts both the pinned messages and their context
1308
+ * to that resource so an id from another resource returns nothing.
1309
+ */
1310
+ async _getIncludedMessages({ include, resourceId }) {
1303
1311
  if (!include || include.length === 0) return [];
1304
1312
  const targetIds = include.map((inc) => inc.id).filter(Boolean);
1305
1313
  if (targetIds.length === 0) return [];
1306
1314
  const { messages: targetDocs } = await this.listMessagesById({ messageIds: targetIds });
1307
- const targetMap = new Map(targetDocs.map((msg) => [msg.id, {
1315
+ const scopedTargetDocs = resourceId ? targetDocs.filter((msg) => msg.resourceId === resourceId) : targetDocs;
1316
+ const targetMap = new Map(scopedTargetDocs.map((msg) => [msg.id, {
1308
1317
  threadId: msg.threadId,
1309
1318
  createdAt: msg.createdAt
1310
1319
  }]));
1320
+ const resourceCondition = resourceId ? ` AND "resourceId" = {var_resource:String}` : "";
1311
1321
  const unionQueries = [];
1312
1322
  const params = {};
1313
1323
  let paramIdx = 1;
@@ -1322,7 +1332,7 @@ var MemoryStorageClickhouse = class extends MemoryStorage {
1322
1332
  SELECT id, content, role, type, "createdAt", thread_id AS "threadId", "resourceId"
1323
1333
  FROM "${TABLE_MESSAGES}"
1324
1334
  WHERE thread_id = {${threadParam}:String}
1325
- AND createdAt <= parseDateTime64BestEffort({${createdAtParam}:String}, 3)
1335
+ AND createdAt <= parseDateTime64BestEffort({${createdAtParam}:String}, 3)${resourceCondition}
1326
1336
  ORDER BY createdAt DESC, id DESC
1327
1337
  LIMIT {${limitParam}:Int64}
1328
1338
  `);
@@ -1338,7 +1348,7 @@ var MemoryStorageClickhouse = class extends MemoryStorage {
1338
1348
  SELECT id, content, role, type, "createdAt", thread_id AS "threadId", "resourceId"
1339
1349
  FROM "${TABLE_MESSAGES}"
1340
1350
  WHERE thread_id = {${threadParam2}:String}
1341
- AND createdAt > parseDateTime64BestEffort({${createdAtParam2}:String}, 3)
1351
+ AND createdAt > parseDateTime64BestEffort({${createdAtParam2}:String}, 3)${resourceCondition}
1342
1352
  ORDER BY createdAt ASC, id ASC
1343
1353
  LIMIT {${limitParam2}:Int64}
1344
1354
  `);
@@ -1349,6 +1359,7 @@ var MemoryStorageClickhouse = class extends MemoryStorage {
1349
1359
  }
1350
1360
  }
1351
1361
  if (unionQueries.length === 0) return [];
1362
+ if (resourceId) params.var_resource = resourceId;
1352
1363
  let finalQuery;
1353
1364
  if (unionQueries.length === 1) finalQuery = unionQueries[0];
1354
1365
  else finalQuery = `SELECT * FROM (${unionQueries.join(" UNION ALL ")}) ORDER BY "createdAt" ASC, id ASC`;
@@ -3872,6 +3883,7 @@ function rowToSpanRecord(row) {
3872
3883
  function rowToLightSpanRecord(row) {
3873
3884
  const startedAt = toDate(row.startedAt);
3874
3885
  const endedAt = row.isEvent ? startedAt : toDateOrNull(row.endedAt);
3886
+ const error = parseJson(row.error) ?? void 0;
3875
3887
  return {
3876
3888
  traceId: row.traceId,
3877
3889
  spanId: row.spanId,
@@ -3884,7 +3896,13 @@ function rowToLightSpanRecord(row) {
3884
3896
  entityType: nullableEntityType(row.entityType),
3885
3897
  entityId: nullableString(row.entityId),
3886
3898
  entityName: nullableString(row.entityName),
3887
- error: parseJson(row.error) ?? void 0,
3899
+ error,
3900
+ status: computeTraceStatus({
3901
+ error,
3902
+ endedAt
3903
+ }),
3904
+ metadata: parseJson(row.metadataRaw) ?? void 0,
3905
+ inputPreview: buildInputPreview(row.input ?? null),
3888
3906
  createdAt: startedAt,
3889
3907
  updatedAt: null
3890
3908
  };
@@ -6207,93 +6225,48 @@ async function getRootSpan(client, args) {
6207
6225
  return { span: rowToSpanRecord(rows[0]) };
6208
6226
  }
6209
6227
  /**
6210
- * Shared page-mode helper used by listTracesLight.
6228
+ * Columns a trace list renders. `input` is selected only so the row mapper can
6229
+ * derive a short `inputPreview` at read time (see `rowToLightSpanRecord`); the
6230
+ * raw blob itself never leaves the store.
6211
6231
  */
6212
- async function listTraceRows(client, args, selectClause, mapRows) {
6213
- const { filters, pagination, orderBy } = listTracesArgsSchema.parse(args);
6214
- const page = pagination?.page ?? 0;
6215
- const perPage = pagination?.perPage ?? 10;
6216
- const { conditions, params } = buildTraceFilterConditions(filters, "r");
6217
- if (filters?.hasChildError != null) if (filters.hasChildError) conditions.push(`EXISTS (
6218
- SELECT 1 FROM ${TABLE_SPAN_EVENTS} c
6219
- WHERE c.traceId = r.traceId
6220
- AND c.parentSpanId IS NOT NULL
6221
- AND c.error IS NOT NULL
6222
- )`);
6223
- else conditions.push(`NOT EXISTS (
6224
- SELECT 1 FROM ${TABLE_SPAN_EVENTS} c
6225
- WHERE c.traceId = r.traceId
6226
- AND c.parentSpanId IS NOT NULL
6227
- AND c.error IS NOT NULL
6228
- )`);
6229
- const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
6230
- const orderClause = buildTraceOrderByClause(orderBy);
6231
- const countRows = await (await client.query({
6232
- query: `
6233
- SELECT count() as cnt FROM (
6234
- SELECT dedupeKey
6235
- FROM ${TABLE_TRACE_ROOTS} r
6236
- ${whereClause}
6237
- ORDER BY dedupeKey
6238
- LIMIT 1 BY dedupeKey
6239
- )
6240
- `,
6241
- query_params: params,
6242
- format: "JSONEachRow",
6243
- clickhouse_settings: CH_SETTINGS
6244
- })).json();
6245
- const total = Number(countRows[0]?.cnt ?? 0);
6246
- if (total === 0) return {
6247
- pagination: {
6248
- total: 0,
6249
- page,
6250
- perPage,
6251
- hasMore: false
6252
- },
6253
- spans: []
6254
- };
6255
- const rows = await (await client.query({
6256
- query: `
6257
- SELECT ${selectClause} FROM (
6258
- SELECT ${selectClause}
6259
- FROM ${TABLE_TRACE_ROOTS} r
6260
- ${whereClause}
6261
- ORDER BY dedupeKey
6262
- LIMIT 1 BY dedupeKey
6263
- )
6264
- ORDER BY ${orderClause}
6265
- LIMIT {limit:UInt32}
6266
- OFFSET {offset:UInt32}
6267
- `,
6268
- query_params: {
6269
- ...params,
6270
- limit: perPage,
6271
- offset: page * perPage
6272
- },
6273
- format: "JSONEachRow",
6274
- clickhouse_settings: CH_SETTINGS
6275
- })).json();
6276
- return {
6277
- pagination: {
6278
- total,
6279
- page,
6280
- perPage,
6281
- hasMore: (page + 1) * perPage < total
6282
- },
6283
- spans: mapRows(rows)
6284
- };
6285
- }
6232
+ const LIGHT_TRACE_ROOT_FIELDS = [
6233
+ "traceId",
6234
+ "spanId",
6235
+ "parentSpanId",
6236
+ "name",
6237
+ "spanType",
6238
+ "isEvent",
6239
+ "startedAt",
6240
+ "endedAt",
6241
+ "entityType",
6242
+ "entityId",
6243
+ "entityName",
6244
+ "error",
6245
+ "metadataRaw",
6246
+ "input"
6247
+ ];
6248
+ const FULL_PROJECTION = {
6249
+ innerSelect: "*",
6250
+ outerSelect: "*",
6251
+ deltaSelect: "r.* EXCEPT(startedAt, traceId, dedupeKey)"
6252
+ };
6253
+ const LIGHT_PROJECTION = {
6254
+ innerSelect: ["dedupeKey", ...LIGHT_TRACE_ROOT_FIELDS].join(", "),
6255
+ outerSelect: LIGHT_TRACE_ROOT_FIELDS.join(", "),
6256
+ deltaSelect: LIGHT_TRACE_ROOT_FIELDS.filter((field) => field !== "startedAt" && field !== "traceId").map((field) => `r.${field}`).join(", ")
6257
+ };
6286
6258
  /**
6287
- * List traces with optional filtering, pagination, and ordering.
6259
+ * Shared implementation behind listTraces and listTracesLight.
6288
6260
  *
6289
6261
  * Reads from trace_roots (root spans only).
6290
- * Uses two-stage query for ReplacingMergeTree deduplication:
6262
+ * Page mode uses a two-stage query for ReplacingMergeTree deduplication:
6291
6263
  * Inner: filter + deterministic ORDER BY + LIMIT 1 BY dedupeKey
6292
6264
  * Outer: final ordering + pagination
6265
+ * Delta mode joins the append-only delta table and returns only rows past the cursor.
6293
6266
  *
6294
6267
  * hasChildError is handled via EXISTS subquery against span_events.
6295
6268
  */
6296
- async function listTraces(client, args, strategy) {
6269
+ async function listTraceRows(client, args, strategy, projection, mapRows) {
6297
6270
  const { mode, filters, pagination, orderBy, after, limit } = listTracesArgsSchema.parse(args);
6298
6271
  const page = pagination?.page ?? 0;
6299
6272
  const perPage = pagination?.perPage ?? 10;
@@ -6323,10 +6296,11 @@ async function listTraces(client, args, strategy) {
6323
6296
  },
6324
6297
  deltaCursor: streamHeadCursor
6325
6298
  };
6326
- const rows = await queryTracesAfterCursor(client, whereClause, params, limit, validateCursorId(after));
6299
+ const afterCursor = validateCursorId(after);
6300
+ const rows = await queryTracesAfterCursor(client, projection.deltaSelect, whereClause, params, limit, afterCursor);
6327
6301
  const visibleRows = rows.slice(0, limit);
6328
6302
  return {
6329
- spans: toTraceSpans(visibleRows.map(rowToSpanRecord)),
6303
+ spans: mapRows(visibleRows),
6330
6304
  delta: {
6331
6305
  limit,
6332
6306
  hasMore: rows.length > limit
@@ -6361,10 +6335,10 @@ async function listTraces(client, args, strategy) {
6361
6335
  spans: [],
6362
6336
  ...deltaCursorEnabled ? { deltaCursor: currentDeltaCursor } : {}
6363
6337
  };
6364
- const spans = (await (await client.query({
6338
+ const rows = await (await client.query({
6365
6339
  query: `
6366
- SELECT * FROM (
6367
- SELECT *
6340
+ SELECT ${projection.outerSelect} FROM (
6341
+ SELECT ${projection.innerSelect}
6368
6342
  FROM ${TABLE_TRACE_ROOTS} r
6369
6343
  ${whereClause}
6370
6344
  ORDER BY dedupeKey
@@ -6381,7 +6355,7 @@ async function listTraces(client, args, strategy) {
6381
6355
  },
6382
6356
  format: "JSONEachRow",
6383
6357
  clickhouse_settings: CH_SETTINGS
6384
- })).json()).map(rowToSpanRecord);
6358
+ })).json();
6385
6359
  return {
6386
6360
  pagination: {
6387
6361
  total,
@@ -6389,32 +6363,27 @@ async function listTraces(client, args, strategy) {
6389
6363
  perPage,
6390
6364
  hasMore: (page + 1) * perPage < total
6391
6365
  },
6392
- spans: toTraceSpans(spans),
6366
+ spans: mapRows(rows),
6393
6367
  ...deltaCursorEnabled ? { deltaCursor: currentDeltaCursor } : {}
6394
6368
  };
6395
6369
  }
6396
- const LIGHT_TRACE_ROOT_COLUMNS = [
6397
- "traceId",
6398
- "spanId",
6399
- "parentSpanId",
6400
- "name",
6401
- "spanType",
6402
- "isEvent",
6403
- "startedAt",
6404
- "endedAt",
6405
- "entityType",
6406
- "entityId",
6407
- "entityName",
6408
- "error"
6409
- ].join(", ");
6410
- async function listTracesLight(client, args) {
6411
- return listTraceRows(client, args, LIGHT_TRACE_ROOT_COLUMNS, (rows) => rows.map(rowToLightSpanRecord));
6370
+ /** List traces with optional filtering, pagination, and ordering. */
6371
+ async function listTraces(client, args, strategy) {
6372
+ return listTraceRows(client, args, strategy, FULL_PROJECTION, (rows) => toTraceSpans(rows.map(rowToSpanRecord)));
6373
+ }
6374
+ /**
6375
+ * List traces projecting only the columns a trace list renders.
6376
+ * Skips the attributes/output blobs and reduces `input` to a short preview in
6377
+ * the mapper, so the response payload stays flat as traces grow.
6378
+ */
6379
+ async function listTracesLight(client, args, strategy) {
6380
+ return listTraceRows(client, args, strategy, LIGHT_PROJECTION, (rows) => rows.map(rowToLightSpanRecord));
6412
6381
  }
6413
- async function queryTracesAfterCursor(client, whereClause, params, limit, cursorId) {
6382
+ async function queryTracesAfterCursor(client, deltaSelect, whereClause, params, limit, cursorId) {
6414
6383
  return await (await client.query({
6415
6384
  query: `
6416
6385
  SELECT
6417
- r.* EXCEPT(startedAt, traceId, dedupeKey),
6386
+ ${deltaSelect},
6418
6387
  r.startedAt AS startedAt,
6419
6388
  r.traceId AS traceId,
6420
6389
  r.dedupeKey AS dedupeKey,
@@ -7391,7 +7360,7 @@ var ObservabilityStorageClickhouseVNext = class extends ObservabilityStorage {
7391
7360
  }
7392
7361
  async listTracesLight(args) {
7393
7362
  try {
7394
- return await listTracesLight(this.#client, args);
7363
+ return await listTracesLight(this.#client, args, this.#deltaCursorStrategy);
7395
7364
  } catch (error) {
7396
7365
  if (error instanceof MastraError) throw error;
7397
7366
  throw new MastraError({