@mastra/clickhouse 1.17.0 → 1.18.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
@@ -4,9 +4,9 @@ import * as coreStorage from "@mastra/core/storage";
4
4
  import { BRANCH_SPAN_TYPES, EntityType, METRIC_DISTINCT_COLUMNS, MastraCompositeStore, MemoryStorage, ObservabilityStorage, SCORERS_SCHEMA, SPAN_SCHEMA, ScoresStorage, TABLE_AGENT_VERSIONS, TABLE_DATASETS, TABLE_DATASET_ITEMS, TABLE_DATASET_VERSIONS, TABLE_EXPERIMENTS, TABLE_EXPERIMENT_RESULTS, TABLE_FAVORITES, TABLE_HARNESS_SESSIONS, TABLE_KNOWLEDGE_ACTIVITY, TABLE_KNOWLEDGE_CURSORS, TABLE_KNOWLEDGE_MENTIONS, TABLE_KNOWLEDGE_NODES, TABLE_KNOWLEDGE_RECORDS, TABLE_KNOWLEDGE_SEMANTIC_OUTBOX, 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";
5
5
  import { MessageList } from "@mastra/core/agent";
6
6
  import { MastraBase } from "@mastra/core/base";
7
+ import { randomUUID } from "crypto";
7
8
  import { parseFieldKey } from "@mastra/core/utils";
8
9
  import { coreFeatures } from "@mastra/core/features";
9
- import { randomUUID } from "crypto";
10
10
  import { saveScorePayloadSchema } from "@mastra/core/evals";
11
11
  //#region src/storage/db/replication.ts
12
12
  const DEFAULT_ZOOKEEPER_PATH = "/clickhouse/tables/{shard}/{database}/{table}";
@@ -4645,18 +4645,79 @@ async function batchCreateFeedback(client, args) {
4645
4645
  clickhouse_settings: CH_INSERT_SETTINGS
4646
4646
  });
4647
4647
  }
4648
- async function updateFeedbackReviewStatus(client, args) {
4649
- const { feedbackId, reviewStatus } = parseUpdateFeedbackReviewStatusArgs(args);
4650
- const existing = await queryJson$2(client, `SELECT * FROM ${TABLE_FEEDBACK_EVENTS} FINAL WHERE feedbackId = {feedbackId:String} LIMIT 1`, { feedbackId });
4651
- if (!existing[0]) throw new MastraError({
4648
+ /**
4649
+ * Delete feedback events by feedbackId via lightweight DELETE. Optional
4650
+ * `organizationId` and `resourceId` values are ANDed into the predicate to
4651
+ * restrict deletion to records with matching scope fields.
4652
+ *
4653
+ * A durable deletion request is recorded before the lightweight delete. The
4654
+ * delete is immediately visible to subsequent reads; physical purge depends on
4655
+ * the table's configured retention TTL. The delta table is intentionally not
4656
+ * touched and expires through its fixed two-day TTL.
4657
+ */
4658
+ async function deleteFeedback(client, args, replication) {
4659
+ if (args.feedbackIds.length === 0) return;
4660
+ await recordDeletionRequest(client, {
4661
+ requestId: randomUUID(),
4662
+ organizationId: args.organizationId,
4663
+ resourceId: args.resourceId,
4664
+ signal: "feedback",
4665
+ predicateType: "itemIds",
4666
+ predicateValues: [...args.feedbackIds],
4667
+ requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
4668
+ replication
4669
+ });
4670
+ const params = {};
4671
+ const idPlaceholders = [];
4672
+ for (let i = 0; i < args.feedbackIds.length; i++) {
4673
+ const name = `fid_${i}`;
4674
+ params[name] = args.feedbackIds[i];
4675
+ idPlaceholders.push(`{${name}:String}`);
4676
+ }
4677
+ const conditions = [`feedbackId IN (${idPlaceholders.join(", ")})`];
4678
+ if (args.organizationId !== void 0) {
4679
+ conditions.push("organizationId = {delOrganizationId:String}");
4680
+ params.delOrganizationId = args.organizationId;
4681
+ }
4682
+ if (args.resourceId !== void 0) {
4683
+ conditions.push("resourceId = {delResourceId:String}");
4684
+ params.delResourceId = args.resourceId;
4685
+ }
4686
+ await client.command({
4687
+ query: `DELETE FROM ${TABLE_FEEDBACK_EVENTS} WHERE ${conditions.join(" AND ")}`,
4688
+ query_params: params,
4689
+ clickhouse_settings: { lightweight_deletes_sync: isReplicationConfigured(replication) ? "2" : "1" }
4690
+ });
4691
+ }
4692
+ function feedbackNotFoundError(feedbackId) {
4693
+ return new MastraError({
4652
4694
  id: "OBSERVABILITY_UPDATE_FEEDBACK_REVIEW_STATUS_NOT_FOUND",
4653
4695
  domain: ErrorDomain.MASTRA_OBSERVABILITY,
4654
4696
  category: ErrorCategory.USER,
4655
4697
  text: "Feedback record not found",
4656
4698
  details: { feedbackId }
4657
4699
  });
4700
+ }
4701
+ async function hasFeedbackDeletionRequest(client, feedbackId, organizationId, resourceId) {
4702
+ return (await queryJson$2(client, `SELECT 1 AS found FROM ${TABLE_DELETION_REQUESTS} FINAL
4703
+ WHERE signal = 'feedback'
4704
+ AND predicateType = 'itemIds'
4705
+ AND has(predicateValues, {feedbackId:String})
4706
+ AND (organizationId = '' OR organizationId = {organizationId:String})
4707
+ AND (resourceId = '' OR resourceId = {resourceId:String})
4708
+ LIMIT 1`, {
4709
+ feedbackId,
4710
+ organizationId: organizationId ?? "",
4711
+ resourceId: resourceId ?? ""
4712
+ })).length > 0;
4713
+ }
4714
+ async function updateFeedbackReviewStatus(client, args, replication) {
4715
+ const { feedbackId, reviewStatus } = parseUpdateFeedbackReviewStatusArgs(args);
4716
+ const existingRow = (await queryJson$2(client, `SELECT * FROM ${TABLE_FEEDBACK_EVENTS} FINAL WHERE feedbackId = {feedbackId:String} LIMIT 1`, { feedbackId }))[0];
4717
+ if (!existingRow) throw feedbackNotFoundError(feedbackId);
4718
+ if (await hasFeedbackDeletionRequest(client, feedbackId, existingRow.organizationId, existingRow.resourceId)) throw feedbackNotFoundError(feedbackId);
4658
4719
  const updated = rowToFeedbackRecord({
4659
- ...existing[0],
4720
+ ...existingRow,
4660
4721
  reviewStatus
4661
4722
  });
4662
4723
  await client.insert({
@@ -4665,6 +4726,14 @@ async function updateFeedbackReviewStatus(client, args) {
4665
4726
  format: "JSONEachRow",
4666
4727
  clickhouse_settings: CH_INSERT_SETTINGS
4667
4728
  });
4729
+ if (await hasFeedbackDeletionRequest(client, feedbackId, existingRow.organizationId, existingRow.resourceId)) {
4730
+ await deleteFeedback(client, {
4731
+ feedbackIds: [feedbackId],
4732
+ organizationId: existingRow.organizationId ?? void 0,
4733
+ resourceId: existingRow.resourceId ?? void 0
4734
+ }, replication);
4735
+ throw feedbackNotFoundError(feedbackId);
4736
+ }
4668
4737
  return updated;
4669
4738
  }
4670
4739
  async function listFeedback(client, args, strategy) {
@@ -5954,6 +6023,50 @@ async function batchCreateScores(client, args) {
5954
6023
  clickhouse_settings: CH_INSERT_SETTINGS
5955
6024
  });
5956
6025
  }
6026
+ /**
6027
+ * Delete score events by scoreId via lightweight DELETE. Optional
6028
+ * `organizationId` and `resourceId` values are ANDed into the predicate to
6029
+ * restrict deletion to records with matching scope fields.
6030
+ *
6031
+ * A durable deletion request is recorded before the lightweight delete. The
6032
+ * delete is immediately visible to subsequent reads; physical purge depends on
6033
+ * the table's configured retention TTL. The delta table is intentionally not
6034
+ * touched and expires through its fixed two-day TTL.
6035
+ */
6036
+ async function deleteScores(client, args, replication) {
6037
+ if (args.scoreIds.length === 0) return;
6038
+ await recordDeletionRequest(client, {
6039
+ requestId: randomUUID(),
6040
+ organizationId: args.organizationId,
6041
+ resourceId: args.resourceId,
6042
+ signal: "scores",
6043
+ predicateType: "itemIds",
6044
+ predicateValues: [...args.scoreIds],
6045
+ requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
6046
+ replication
6047
+ });
6048
+ const params = {};
6049
+ const idPlaceholders = [];
6050
+ for (let i = 0; i < args.scoreIds.length; i++) {
6051
+ const name = `sid_${i}`;
6052
+ params[name] = args.scoreIds[i];
6053
+ idPlaceholders.push(`{${name}:String}`);
6054
+ }
6055
+ const conditions = [`scoreId IN (${idPlaceholders.join(", ")})`];
6056
+ if (args.organizationId !== void 0) {
6057
+ conditions.push("organizationId = {delOrganizationId:String}");
6058
+ params.delOrganizationId = args.organizationId;
6059
+ }
6060
+ if (args.resourceId !== void 0) {
6061
+ conditions.push("resourceId = {delResourceId:String}");
6062
+ params.delResourceId = args.resourceId;
6063
+ }
6064
+ await client.command({
6065
+ query: `DELETE FROM ${TABLE_SCORE_EVENTS} WHERE ${conditions.join(" AND ")}`,
6066
+ query_params: params,
6067
+ clickhouse_settings: { lightweight_deletes_sync: isReplicationConfigured(replication) ? "2" : "1" }
6068
+ });
6069
+ }
5957
6070
  async function listScores(client, args, strategy) {
5958
6071
  const parsed = listScoresArgsSchema.parse(args);
5959
6072
  const deltaCursorEnabled = deltaPollingSupported(strategy);
@@ -6225,13 +6338,65 @@ const TRACE_FIELDS = {
6225
6338
  }
6226
6339
  };
6227
6340
  const SPAN_FIELDS = {
6341
+ name: {
6342
+ sql: "s.name",
6343
+ parameterType: "String"
6344
+ },
6228
6345
  spanType: {
6229
6346
  sql: "s.spanType",
6230
6347
  parameterType: "String"
6231
6348
  },
6349
+ model: {
6350
+ sql: "s.model",
6351
+ parameterType: "String"
6352
+ },
6353
+ provider: {
6354
+ sql: "s.provider",
6355
+ parameterType: "String"
6356
+ },
6357
+ startedAt: {
6358
+ sql: "s.startedAt",
6359
+ parameterType: "DateTime64(3, 'UTC')"
6360
+ },
6361
+ endedAt: {
6362
+ sql: "s.endedAt",
6363
+ parameterType: "DateTime64(3, 'UTC')"
6364
+ },
6365
+ durationMs: {
6366
+ sql: "s.durationMs",
6367
+ parameterType: "Float64"
6368
+ },
6369
+ status: {
6370
+ sql: "s.status",
6371
+ parameterType: "String"
6372
+ },
6232
6373
  error: {
6233
6374
  sql: "s.error",
6234
6375
  parameterType: "String"
6376
+ },
6377
+ entityType: {
6378
+ sql: "s.entityType",
6379
+ parameterType: "String"
6380
+ },
6381
+ entityId: {
6382
+ sql: "s.entityId",
6383
+ parameterType: "String"
6384
+ },
6385
+ entityName: {
6386
+ sql: "s.entityName",
6387
+ parameterType: "String"
6388
+ },
6389
+ entityVersionId: {
6390
+ sql: "s.entityVersionId",
6391
+ parameterType: "String"
6392
+ },
6393
+ parentEntityVersionId: {
6394
+ sql: "s.parentEntityVersionId",
6395
+ parameterType: "String"
6396
+ },
6397
+ rootEntityVersionId: {
6398
+ sql: "s.rootEntityVersionId",
6399
+ parameterType: "String"
6235
6400
  }
6236
6401
  };
6237
6402
  const SCORE_FIELDS = {
@@ -6239,9 +6404,37 @@ const SCORE_FIELDS = {
6239
6404
  sql: "s.scorerId",
6240
6405
  parameterType: "String"
6241
6406
  },
6407
+ scorerVersion: {
6408
+ sql: "s.scorerVersion",
6409
+ parameterType: "String"
6410
+ },
6411
+ scoreSource: {
6412
+ sql: "s.scoreSource",
6413
+ parameterType: "String"
6414
+ },
6242
6415
  score: {
6243
6416
  sql: "s.score",
6244
6417
  parameterType: "Float64"
6418
+ },
6419
+ timestamp: {
6420
+ sql: "s.timestamp",
6421
+ parameterType: "DateTime64(3, 'UTC')"
6422
+ },
6423
+ spanId: {
6424
+ sql: "s.spanId",
6425
+ parameterType: "String"
6426
+ },
6427
+ entityVersionId: {
6428
+ sql: "s.entityVersionId",
6429
+ parameterType: "String"
6430
+ },
6431
+ parentEntityVersionId: {
6432
+ sql: "s.parentEntityVersionId",
6433
+ parameterType: "String"
6434
+ },
6435
+ rootEntityVersionId: {
6436
+ sql: "s.rootEntityVersionId",
6437
+ parameterType: "String"
6245
6438
  }
6246
6439
  };
6247
6440
  const TRACE_SELECT = `
@@ -6273,10 +6466,20 @@ function resolveOrderField(field) {
6273
6466
  if (field === "startedAt" || field === "endedAt") return field;
6274
6467
  throw new Error(`Unsupported trusted trace-query field: ${field}`);
6275
6468
  }
6276
- function compileScalarPredicate(predicate, registry, parameters) {
6277
- if (predicate.type === "boolean") return predicate.args.map((arg) => `(${compileScalarPredicate(arg, registry, parameters)})`).join(predicate.operator === "and" ? " AND " : " OR ");
6278
- if (predicate.type === "not") return `NOT (${compileScalarPredicate(predicate.arg, registry, parameters)})`;
6279
- const field = fieldDefinition(registry, predicate.field);
6469
+ function isMetadataField(field) {
6470
+ return field.startsWith("metadata.");
6471
+ }
6472
+ function compileScalarPredicate(predicate, registry, parameters, allowMetadata = false) {
6473
+ if (predicate.type === "boolean") return predicate.args.map((arg) => `(${compileScalarPredicate(arg, registry, parameters, allowMetadata)})`).join(predicate.operator === "and" ? " AND " : " OR ");
6474
+ if (predicate.type === "not") return `NOT (${compileScalarPredicate(predicate.arg, registry, parameters, allowMetadata)})`;
6475
+ const field = isMetadataField(predicate.field) ? (() => {
6476
+ if (!allowMetadata) throw new Error(`Unsupported trusted trace-query field: ${predicate.field}`);
6477
+ const key = parameters.add(predicate.field.slice(9), "String");
6478
+ return {
6479
+ sql: `coalesce(if(mapContains(r.metadataSearch, ${key}), r.metadataSearch[${key}], NULL), nullIf(trim(JSONExtractString(r.metadataRaw, ${key})), ''))`,
6480
+ parameterType: "String"
6481
+ };
6482
+ })() : fieldDefinition(registry, predicate.field);
6280
6483
  if (predicate.type === "presence") return `${predicate.operator === "exists" ? "isNotNull" : "isNull"}(${field.sql})`;
6281
6484
  if (predicate.type === "membership") {
6282
6485
  const values = predicate.values.map((value) => parameters.add(value, field.parameterType)).join(", ");
@@ -6313,7 +6516,7 @@ function compilePredicate(predicate, parameters) {
6313
6516
  }
6314
6517
  if (predicate.type === "boolean") return predicate.args.map((arg) => `(${compilePredicate(arg, parameters)})`).join(predicate.operator === "and" ? " AND " : " OR ");
6315
6518
  if (predicate.type === "not") return `NOT (${compilePredicate(predicate.arg, parameters)})`;
6316
- return compileScalarPredicate(predicate, TRACE_FIELDS, parameters);
6519
+ return compileScalarPredicate(predicate, TRACE_FIELDS, parameters, true);
6317
6520
  }
6318
6521
  function compileClickHouseTraceQuery(plan) {
6319
6522
  const parameters = new ParameterBuilder();
@@ -6336,17 +6539,46 @@ function compileClickHouseTraceQuery(plan) {
6336
6539
  AND startedAt < ${to}
6337
6540
  )`];
6338
6541
  if (relationCollections.has("spans")) ctes.push(`current_spans AS (
6339
- SELECT traceId, spanType, error
6542
+ SELECT
6543
+ traceId,
6544
+ name,
6545
+ spanType,
6546
+ if(JSONType(attributes, 'model') = 'String', JSONExtractString(attributes, 'model'), NULL) AS model,
6547
+ if(JSONType(attributes, 'provider') = 'String', JSONExtractString(attributes, 'provider'), NULL) AS provider,
6548
+ startedAt,
6549
+ endedAt,
6550
+ dateDiff('millisecond', startedAt, endedAt) AS durationMs,
6551
+ if(isNotNull(error), 'error', 'success') AS status,
6552
+ error,
6553
+ entityType,
6554
+ entityId,
6555
+ entityName,
6556
+ entityVersionId,
6557
+ parentEntityVersionId,
6558
+ rootEntityVersionId
6340
6559
  FROM ${TABLE_SPAN_EVENTS}
6341
- WHERE traceId IN (SELECT traceId FROM root_scope)
6560
+ WHERE isNotNull(traceId)
6561
+ AND traceId IN (SELECT traceId FROM root_scope)
6342
6562
  ORDER BY dedupeKey
6343
6563
  LIMIT 1 BY dedupeKey
6344
6564
  )`);
6345
6565
  if (relationCollections.has("scores")) ctes.push(`current_scores AS (
6346
- SELECT traceId, scorerId, score
6566
+ SELECT
6567
+ traceId,
6568
+ spanId,
6569
+ timestamp,
6570
+ scorerId,
6571
+ scorerVersion,
6572
+ scoreSource,
6573
+ score,
6574
+ entityVersionId,
6575
+ parentEntityVersionId,
6576
+ rootEntityVersionId
6347
6577
  FROM ${TABLE_SCORE_EVENTS}
6348
6578
  WHERE isNotNull(traceId)
6349
6579
  AND traceId IN (SELECT traceId FROM root_scope)
6580
+ ORDER BY scoreId, timestamp DESC
6581
+ LIMIT 1 BY scoreId
6350
6582
  )`);
6351
6583
  const predicate = plan.where ? compilePredicate(plan.where, parameters) : "1";
6352
6584
  ctes.push(`candidates AS (
@@ -7806,6 +8038,19 @@ var ObservabilityStorageClickhouseVNext = class extends ObservabilityStorage {
7806
8038
  }, error);
7807
8039
  }
7808
8040
  }
8041
+ async deleteScores(args) {
8042
+ try {
8043
+ await deleteScores(this.#client, args, this.#replication);
8044
+ } catch (error) {
8045
+ if (error instanceof MastraError) throw error;
8046
+ throw new MastraError({
8047
+ id: createStorageErrorId("CLICKHOUSE", "DELETE_SCORES", "FAILED"),
8048
+ domain: ErrorDomain.STORAGE,
8049
+ category: ErrorCategory.THIRD_PARTY,
8050
+ details: { count: args.scoreIds.length }
8051
+ }, error);
8052
+ }
8053
+ }
7809
8054
  async getScoreById(scoreId) {
7810
8055
  try {
7811
8056
  return await getScoreById(this.#client, scoreId);
@@ -7844,9 +8089,22 @@ var ObservabilityStorageClickhouseVNext = class extends ObservabilityStorage {
7844
8089
  }, error);
7845
8090
  }
7846
8091
  }
8092
+ async deleteFeedback(args) {
8093
+ try {
8094
+ await deleteFeedback(this.#client, args, this.#replication);
8095
+ } catch (error) {
8096
+ if (error instanceof MastraError) throw error;
8097
+ throw new MastraError({
8098
+ id: createStorageErrorId("CLICKHOUSE", "DELETE_FEEDBACK", "FAILED"),
8099
+ domain: ErrorDomain.STORAGE,
8100
+ category: ErrorCategory.THIRD_PARTY,
8101
+ details: { count: args.feedbackIds.length }
8102
+ }, error);
8103
+ }
8104
+ }
7847
8105
  async updateFeedbackReviewStatus(args) {
7848
8106
  try {
7849
- return await updateFeedbackReviewStatus(this.#client, args);
8107
+ return await updateFeedbackReviewStatus(this.#client, args, this.#replication);
7850
8108
  } catch (error) {
7851
8109
  if (error instanceof MastraError) throw error;
7852
8110
  throw new MastraError({