@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.
@@ -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.17.0"
6
+ version: "1.18.0-alpha.1"
7
7
  ---
8
8
 
9
9
  ## When to use
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.17.0",
2
+ "version": "1.18.0-alpha.1",
3
3
  "package": "@mastra/clickhouse",
4
4
  "exports": {},
5
5
  "modules": {}
package/dist/index.cjs CHANGED
@@ -27,9 +27,9 @@ let _mastra_core_storage = require("@mastra/core/storage");
27
27
  _mastra_core_storage = __toESM(_mastra_core_storage, 1);
28
28
  let _mastra_core_agent = require("@mastra/core/agent");
29
29
  let _mastra_core_base = require("@mastra/core/base");
30
+ let crypto$1 = require("crypto");
30
31
  let _mastra_core_utils = require("@mastra/core/utils");
31
32
  let _mastra_core_features = require("@mastra/core/features");
32
- let crypto$1 = require("crypto");
33
33
  let _mastra_core_evals = require("@mastra/core/evals");
34
34
  //#region src/storage/db/replication.ts
35
35
  const DEFAULT_ZOOKEEPER_PATH = "/clickhouse/tables/{shard}/{database}/{table}";
@@ -4668,18 +4668,79 @@ async function batchCreateFeedback(client, args) {
4668
4668
  clickhouse_settings: CH_INSERT_SETTINGS
4669
4669
  });
4670
4670
  }
4671
- async function updateFeedbackReviewStatus(client, args) {
4672
- const { feedbackId, reviewStatus } = parseUpdateFeedbackReviewStatusArgs(args);
4673
- const existing = await queryJson$2(client, `SELECT * FROM ${TABLE_FEEDBACK_EVENTS} FINAL WHERE feedbackId = {feedbackId:String} LIMIT 1`, { feedbackId });
4674
- if (!existing[0]) throw new _mastra_core_error.MastraError({
4671
+ /**
4672
+ * Delete feedback events by feedbackId via lightweight DELETE. Optional
4673
+ * `organizationId` and `resourceId` values are ANDed into the predicate to
4674
+ * restrict deletion to records with matching scope fields.
4675
+ *
4676
+ * A durable deletion request is recorded before the lightweight delete. The
4677
+ * delete is immediately visible to subsequent reads; physical purge depends on
4678
+ * the table's configured retention TTL. The delta table is intentionally not
4679
+ * touched and expires through its fixed two-day TTL.
4680
+ */
4681
+ async function deleteFeedback(client, args, replication) {
4682
+ if (args.feedbackIds.length === 0) return;
4683
+ await recordDeletionRequest(client, {
4684
+ requestId: (0, crypto$1.randomUUID)(),
4685
+ organizationId: args.organizationId,
4686
+ resourceId: args.resourceId,
4687
+ signal: "feedback",
4688
+ predicateType: "itemIds",
4689
+ predicateValues: [...args.feedbackIds],
4690
+ requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
4691
+ replication
4692
+ });
4693
+ const params = {};
4694
+ const idPlaceholders = [];
4695
+ for (let i = 0; i < args.feedbackIds.length; i++) {
4696
+ const name = `fid_${i}`;
4697
+ params[name] = args.feedbackIds[i];
4698
+ idPlaceholders.push(`{${name}:String}`);
4699
+ }
4700
+ const conditions = [`feedbackId IN (${idPlaceholders.join(", ")})`];
4701
+ if (args.organizationId !== void 0) {
4702
+ conditions.push("organizationId = {delOrganizationId:String}");
4703
+ params.delOrganizationId = args.organizationId;
4704
+ }
4705
+ if (args.resourceId !== void 0) {
4706
+ conditions.push("resourceId = {delResourceId:String}");
4707
+ params.delResourceId = args.resourceId;
4708
+ }
4709
+ await client.command({
4710
+ query: `DELETE FROM ${TABLE_FEEDBACK_EVENTS} WHERE ${conditions.join(" AND ")}`,
4711
+ query_params: params,
4712
+ clickhouse_settings: { lightweight_deletes_sync: isReplicationConfigured(replication) ? "2" : "1" }
4713
+ });
4714
+ }
4715
+ function feedbackNotFoundError(feedbackId) {
4716
+ return new _mastra_core_error.MastraError({
4675
4717
  id: "OBSERVABILITY_UPDATE_FEEDBACK_REVIEW_STATUS_NOT_FOUND",
4676
4718
  domain: _mastra_core_error.ErrorDomain.MASTRA_OBSERVABILITY,
4677
4719
  category: _mastra_core_error.ErrorCategory.USER,
4678
4720
  text: "Feedback record not found",
4679
4721
  details: { feedbackId }
4680
4722
  });
4723
+ }
4724
+ async function hasFeedbackDeletionRequest(client, feedbackId, organizationId, resourceId) {
4725
+ return (await queryJson$2(client, `SELECT 1 AS found FROM ${TABLE_DELETION_REQUESTS} FINAL
4726
+ WHERE signal = 'feedback'
4727
+ AND predicateType = 'itemIds'
4728
+ AND has(predicateValues, {feedbackId:String})
4729
+ AND (organizationId = '' OR organizationId = {organizationId:String})
4730
+ AND (resourceId = '' OR resourceId = {resourceId:String})
4731
+ LIMIT 1`, {
4732
+ feedbackId,
4733
+ organizationId: organizationId ?? "",
4734
+ resourceId: resourceId ?? ""
4735
+ })).length > 0;
4736
+ }
4737
+ async function updateFeedbackReviewStatus(client, args, replication) {
4738
+ const { feedbackId, reviewStatus } = parseUpdateFeedbackReviewStatusArgs(args);
4739
+ const existingRow = (await queryJson$2(client, `SELECT * FROM ${TABLE_FEEDBACK_EVENTS} FINAL WHERE feedbackId = {feedbackId:String} LIMIT 1`, { feedbackId }))[0];
4740
+ if (!existingRow) throw feedbackNotFoundError(feedbackId);
4741
+ if (await hasFeedbackDeletionRequest(client, feedbackId, existingRow.organizationId, existingRow.resourceId)) throw feedbackNotFoundError(feedbackId);
4681
4742
  const updated = rowToFeedbackRecord({
4682
- ...existing[0],
4743
+ ...existingRow,
4683
4744
  reviewStatus
4684
4745
  });
4685
4746
  await client.insert({
@@ -4688,6 +4749,14 @@ async function updateFeedbackReviewStatus(client, args) {
4688
4749
  format: "JSONEachRow",
4689
4750
  clickhouse_settings: CH_INSERT_SETTINGS
4690
4751
  });
4752
+ if (await hasFeedbackDeletionRequest(client, feedbackId, existingRow.organizationId, existingRow.resourceId)) {
4753
+ await deleteFeedback(client, {
4754
+ feedbackIds: [feedbackId],
4755
+ organizationId: existingRow.organizationId ?? void 0,
4756
+ resourceId: existingRow.resourceId ?? void 0
4757
+ }, replication);
4758
+ throw feedbackNotFoundError(feedbackId);
4759
+ }
4691
4760
  return updated;
4692
4761
  }
4693
4762
  async function listFeedback(client, args, strategy) {
@@ -5977,6 +6046,50 @@ async function batchCreateScores(client, args) {
5977
6046
  clickhouse_settings: CH_INSERT_SETTINGS
5978
6047
  });
5979
6048
  }
6049
+ /**
6050
+ * Delete score events by scoreId via lightweight DELETE. Optional
6051
+ * `organizationId` and `resourceId` values are ANDed into the predicate to
6052
+ * restrict deletion to records with matching scope fields.
6053
+ *
6054
+ * A durable deletion request is recorded before the lightweight delete. The
6055
+ * delete is immediately visible to subsequent reads; physical purge depends on
6056
+ * the table's configured retention TTL. The delta table is intentionally not
6057
+ * touched and expires through its fixed two-day TTL.
6058
+ */
6059
+ async function deleteScores(client, args, replication) {
6060
+ if (args.scoreIds.length === 0) return;
6061
+ await recordDeletionRequest(client, {
6062
+ requestId: (0, crypto$1.randomUUID)(),
6063
+ organizationId: args.organizationId,
6064
+ resourceId: args.resourceId,
6065
+ signal: "scores",
6066
+ predicateType: "itemIds",
6067
+ predicateValues: [...args.scoreIds],
6068
+ requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
6069
+ replication
6070
+ });
6071
+ const params = {};
6072
+ const idPlaceholders = [];
6073
+ for (let i = 0; i < args.scoreIds.length; i++) {
6074
+ const name = `sid_${i}`;
6075
+ params[name] = args.scoreIds[i];
6076
+ idPlaceholders.push(`{${name}:String}`);
6077
+ }
6078
+ const conditions = [`scoreId IN (${idPlaceholders.join(", ")})`];
6079
+ if (args.organizationId !== void 0) {
6080
+ conditions.push("organizationId = {delOrganizationId:String}");
6081
+ params.delOrganizationId = args.organizationId;
6082
+ }
6083
+ if (args.resourceId !== void 0) {
6084
+ conditions.push("resourceId = {delResourceId:String}");
6085
+ params.delResourceId = args.resourceId;
6086
+ }
6087
+ await client.command({
6088
+ query: `DELETE FROM ${TABLE_SCORE_EVENTS} WHERE ${conditions.join(" AND ")}`,
6089
+ query_params: params,
6090
+ clickhouse_settings: { lightweight_deletes_sync: isReplicationConfigured(replication) ? "2" : "1" }
6091
+ });
6092
+ }
5980
6093
  async function listScores(client, args, strategy) {
5981
6094
  const parsed = _mastra_core_storage.listScoresArgsSchema.parse(args);
5982
6095
  const deltaCursorEnabled = deltaPollingSupported(strategy);
@@ -6248,13 +6361,65 @@ const TRACE_FIELDS = {
6248
6361
  }
6249
6362
  };
6250
6363
  const SPAN_FIELDS = {
6364
+ name: {
6365
+ sql: "s.name",
6366
+ parameterType: "String"
6367
+ },
6251
6368
  spanType: {
6252
6369
  sql: "s.spanType",
6253
6370
  parameterType: "String"
6254
6371
  },
6372
+ model: {
6373
+ sql: "s.model",
6374
+ parameterType: "String"
6375
+ },
6376
+ provider: {
6377
+ sql: "s.provider",
6378
+ parameterType: "String"
6379
+ },
6380
+ startedAt: {
6381
+ sql: "s.startedAt",
6382
+ parameterType: "DateTime64(3, 'UTC')"
6383
+ },
6384
+ endedAt: {
6385
+ sql: "s.endedAt",
6386
+ parameterType: "DateTime64(3, 'UTC')"
6387
+ },
6388
+ durationMs: {
6389
+ sql: "s.durationMs",
6390
+ parameterType: "Float64"
6391
+ },
6392
+ status: {
6393
+ sql: "s.status",
6394
+ parameterType: "String"
6395
+ },
6255
6396
  error: {
6256
6397
  sql: "s.error",
6257
6398
  parameterType: "String"
6399
+ },
6400
+ entityType: {
6401
+ sql: "s.entityType",
6402
+ parameterType: "String"
6403
+ },
6404
+ entityId: {
6405
+ sql: "s.entityId",
6406
+ parameterType: "String"
6407
+ },
6408
+ entityName: {
6409
+ sql: "s.entityName",
6410
+ parameterType: "String"
6411
+ },
6412
+ entityVersionId: {
6413
+ sql: "s.entityVersionId",
6414
+ parameterType: "String"
6415
+ },
6416
+ parentEntityVersionId: {
6417
+ sql: "s.parentEntityVersionId",
6418
+ parameterType: "String"
6419
+ },
6420
+ rootEntityVersionId: {
6421
+ sql: "s.rootEntityVersionId",
6422
+ parameterType: "String"
6258
6423
  }
6259
6424
  };
6260
6425
  const SCORE_FIELDS = {
@@ -6262,9 +6427,37 @@ const SCORE_FIELDS = {
6262
6427
  sql: "s.scorerId",
6263
6428
  parameterType: "String"
6264
6429
  },
6430
+ scorerVersion: {
6431
+ sql: "s.scorerVersion",
6432
+ parameterType: "String"
6433
+ },
6434
+ scoreSource: {
6435
+ sql: "s.scoreSource",
6436
+ parameterType: "String"
6437
+ },
6265
6438
  score: {
6266
6439
  sql: "s.score",
6267
6440
  parameterType: "Float64"
6441
+ },
6442
+ timestamp: {
6443
+ sql: "s.timestamp",
6444
+ parameterType: "DateTime64(3, 'UTC')"
6445
+ },
6446
+ spanId: {
6447
+ sql: "s.spanId",
6448
+ parameterType: "String"
6449
+ },
6450
+ entityVersionId: {
6451
+ sql: "s.entityVersionId",
6452
+ parameterType: "String"
6453
+ },
6454
+ parentEntityVersionId: {
6455
+ sql: "s.parentEntityVersionId",
6456
+ parameterType: "String"
6457
+ },
6458
+ rootEntityVersionId: {
6459
+ sql: "s.rootEntityVersionId",
6460
+ parameterType: "String"
6268
6461
  }
6269
6462
  };
6270
6463
  const TRACE_SELECT = `
@@ -6296,10 +6489,20 @@ function resolveOrderField(field) {
6296
6489
  if (field === "startedAt" || field === "endedAt") return field;
6297
6490
  throw new Error(`Unsupported trusted trace-query field: ${field}`);
6298
6491
  }
6299
- function compileScalarPredicate(predicate, registry, parameters) {
6300
- if (predicate.type === "boolean") return predicate.args.map((arg) => `(${compileScalarPredicate(arg, registry, parameters)})`).join(predicate.operator === "and" ? " AND " : " OR ");
6301
- if (predicate.type === "not") return `NOT (${compileScalarPredicate(predicate.arg, registry, parameters)})`;
6302
- const field = fieldDefinition(registry, predicate.field);
6492
+ function isMetadataField(field) {
6493
+ return field.startsWith("metadata.");
6494
+ }
6495
+ function compileScalarPredicate(predicate, registry, parameters, allowMetadata = false) {
6496
+ if (predicate.type === "boolean") return predicate.args.map((arg) => `(${compileScalarPredicate(arg, registry, parameters, allowMetadata)})`).join(predicate.operator === "and" ? " AND " : " OR ");
6497
+ if (predicate.type === "not") return `NOT (${compileScalarPredicate(predicate.arg, registry, parameters, allowMetadata)})`;
6498
+ const field = isMetadataField(predicate.field) ? (() => {
6499
+ if (!allowMetadata) throw new Error(`Unsupported trusted trace-query field: ${predicate.field}`);
6500
+ const key = parameters.add(predicate.field.slice(9), "String");
6501
+ return {
6502
+ sql: `coalesce(if(mapContains(r.metadataSearch, ${key}), r.metadataSearch[${key}], NULL), nullIf(trim(JSONExtractString(r.metadataRaw, ${key})), ''))`,
6503
+ parameterType: "String"
6504
+ };
6505
+ })() : fieldDefinition(registry, predicate.field);
6303
6506
  if (predicate.type === "presence") return `${predicate.operator === "exists" ? "isNotNull" : "isNull"}(${field.sql})`;
6304
6507
  if (predicate.type === "membership") {
6305
6508
  const values = predicate.values.map((value) => parameters.add(value, field.parameterType)).join(", ");
@@ -6336,7 +6539,7 @@ function compilePredicate(predicate, parameters) {
6336
6539
  }
6337
6540
  if (predicate.type === "boolean") return predicate.args.map((arg) => `(${compilePredicate(arg, parameters)})`).join(predicate.operator === "and" ? " AND " : " OR ");
6338
6541
  if (predicate.type === "not") return `NOT (${compilePredicate(predicate.arg, parameters)})`;
6339
- return compileScalarPredicate(predicate, TRACE_FIELDS, parameters);
6542
+ return compileScalarPredicate(predicate, TRACE_FIELDS, parameters, true);
6340
6543
  }
6341
6544
  function compileClickHouseTraceQuery(plan) {
6342
6545
  const parameters = new ParameterBuilder();
@@ -6359,17 +6562,46 @@ function compileClickHouseTraceQuery(plan) {
6359
6562
  AND startedAt < ${to}
6360
6563
  )`];
6361
6564
  if (relationCollections.has("spans")) ctes.push(`current_spans AS (
6362
- SELECT traceId, spanType, error
6565
+ SELECT
6566
+ traceId,
6567
+ name,
6568
+ spanType,
6569
+ if(JSONType(attributes, 'model') = 'String', JSONExtractString(attributes, 'model'), NULL) AS model,
6570
+ if(JSONType(attributes, 'provider') = 'String', JSONExtractString(attributes, 'provider'), NULL) AS provider,
6571
+ startedAt,
6572
+ endedAt,
6573
+ dateDiff('millisecond', startedAt, endedAt) AS durationMs,
6574
+ if(isNotNull(error), 'error', 'success') AS status,
6575
+ error,
6576
+ entityType,
6577
+ entityId,
6578
+ entityName,
6579
+ entityVersionId,
6580
+ parentEntityVersionId,
6581
+ rootEntityVersionId
6363
6582
  FROM ${TABLE_SPAN_EVENTS}
6364
- WHERE traceId IN (SELECT traceId FROM root_scope)
6583
+ WHERE isNotNull(traceId)
6584
+ AND traceId IN (SELECT traceId FROM root_scope)
6365
6585
  ORDER BY dedupeKey
6366
6586
  LIMIT 1 BY dedupeKey
6367
6587
  )`);
6368
6588
  if (relationCollections.has("scores")) ctes.push(`current_scores AS (
6369
- SELECT traceId, scorerId, score
6589
+ SELECT
6590
+ traceId,
6591
+ spanId,
6592
+ timestamp,
6593
+ scorerId,
6594
+ scorerVersion,
6595
+ scoreSource,
6596
+ score,
6597
+ entityVersionId,
6598
+ parentEntityVersionId,
6599
+ rootEntityVersionId
6370
6600
  FROM ${TABLE_SCORE_EVENTS}
6371
6601
  WHERE isNotNull(traceId)
6372
6602
  AND traceId IN (SELECT traceId FROM root_scope)
6603
+ ORDER BY scoreId, timestamp DESC
6604
+ LIMIT 1 BY scoreId
6373
6605
  )`);
6374
6606
  const predicate = plan.where ? compilePredicate(plan.where, parameters) : "1";
6375
6607
  ctes.push(`candidates AS (
@@ -7829,6 +8061,19 @@ var ObservabilityStorageClickhouseVNext = class extends _mastra_core_storage.Obs
7829
8061
  }, error);
7830
8062
  }
7831
8063
  }
8064
+ async deleteScores(args) {
8065
+ try {
8066
+ await deleteScores(this.#client, args, this.#replication);
8067
+ } catch (error) {
8068
+ if (error instanceof _mastra_core_error.MastraError) throw error;
8069
+ throw new _mastra_core_error.MastraError({
8070
+ id: (0, _mastra_core_storage.createStorageErrorId)("CLICKHOUSE", "DELETE_SCORES", "FAILED"),
8071
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
8072
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
8073
+ details: { count: args.scoreIds.length }
8074
+ }, error);
8075
+ }
8076
+ }
7832
8077
  async getScoreById(scoreId) {
7833
8078
  try {
7834
8079
  return await getScoreById(this.#client, scoreId);
@@ -7867,9 +8112,22 @@ var ObservabilityStorageClickhouseVNext = class extends _mastra_core_storage.Obs
7867
8112
  }, error);
7868
8113
  }
7869
8114
  }
8115
+ async deleteFeedback(args) {
8116
+ try {
8117
+ await deleteFeedback(this.#client, args, this.#replication);
8118
+ } catch (error) {
8119
+ if (error instanceof _mastra_core_error.MastraError) throw error;
8120
+ throw new _mastra_core_error.MastraError({
8121
+ id: (0, _mastra_core_storage.createStorageErrorId)("CLICKHOUSE", "DELETE_FEEDBACK", "FAILED"),
8122
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
8123
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
8124
+ details: { count: args.feedbackIds.length }
8125
+ }, error);
8126
+ }
8127
+ }
7870
8128
  async updateFeedbackReviewStatus(args) {
7871
8129
  try {
7872
- return await updateFeedbackReviewStatus(this.#client, args);
8130
+ return await updateFeedbackReviewStatus(this.#client, args, this.#replication);
7873
8131
  } catch (error) {
7874
8132
  if (error instanceof _mastra_core_error.MastraError) throw error;
7875
8133
  throw new _mastra_core_error.MastraError({