@mastra/clickhouse 1.4.1 → 1.5.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,8 +1,8 @@
1
1
  import { createClient } from '@clickhouse/client';
2
2
  import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
3
- import { TABLE_SKILL_BLOBS, TABLE_SKILL_VERSIONS, TABLE_SKILLS, TABLE_WORKSPACE_VERSIONS, TABLE_WORKSPACES, TABLE_MCP_SERVER_VERSIONS, TABLE_MCP_SERVERS, TABLE_MCP_CLIENT_VERSIONS, TABLE_MCP_CLIENTS, TABLE_SCORER_DEFINITION_VERSIONS, TABLE_SCORER_DEFINITIONS, TABLE_PROMPT_BLOCK_VERSIONS, TABLE_PROMPT_BLOCKS, TABLE_EXPERIMENT_RESULTS, TABLE_EXPERIMENTS, TABLE_DATASET_VERSIONS, TABLE_DATASET_ITEMS, TABLE_DATASETS, TABLE_AGENT_VERSIONS, TABLE_SPANS, TABLE_RESOURCES, TABLE_SCORERS, TABLE_THREADS, TABLE_TRACES, TABLE_WORKFLOW_SNAPSHOT, TABLE_MESSAGES, MemoryStorage, TABLE_SCHEMAS, createStorageErrorId, normalizePerPage, calculatePagination, ObservabilityStorage, SPAN_SCHEMA, listTracesArgsSchema, toTraceSpans, ScoresStorage, transformScoreRow, SCORERS_SCHEMA, WorkflowsStorage, MastraCompositeStore, getSqlType, getDefaultValue, safelyParseJSON, listLogsArgsSchema, listMetricsArgsSchema, listScoresArgsSchema, listFeedbackArgsSchema, EntityType, TraceStatus } from '@mastra/core/storage';
4
- import { MessageList } from '@mastra/core/agent';
3
+ import { TABLE_SKILL_BLOBS, TABLE_SKILL_VERSIONS, TABLE_SKILLS, TABLE_WORKSPACE_VERSIONS, TABLE_WORKSPACES, TABLE_MCP_SERVER_VERSIONS, TABLE_MCP_SERVERS, TABLE_MCP_CLIENT_VERSIONS, TABLE_MCP_CLIENTS, TABLE_SCORER_DEFINITION_VERSIONS, TABLE_SCORER_DEFINITIONS, TABLE_PROMPT_BLOCK_VERSIONS, TABLE_PROMPT_BLOCKS, TABLE_EXPERIMENT_RESULTS, TABLE_EXPERIMENTS, TABLE_DATASET_VERSIONS, TABLE_DATASET_ITEMS, TABLE_DATASETS, TABLE_AGENT_VERSIONS, TABLE_SPANS, TABLE_RESOURCES, TABLE_SCORERS, TABLE_THREADS, TABLE_TRACES, TABLE_WORKFLOW_SNAPSHOT, TABLE_MESSAGES, BackgroundTasksStorage, TABLE_SCHEMAS, TABLE_BACKGROUND_TASKS, MemoryStorage, createStorageErrorId, normalizePerPage, calculatePagination, ObservabilityStorage, SPAN_SCHEMA, listTracesArgsSchema, toTraceSpans, ScoresStorage, transformScoreRow, SCORERS_SCHEMA, WorkflowsStorage, MastraCompositeStore, getSqlType, getDefaultValue, safelyParseJSON, listLogsArgsSchema, listMetricsArgsSchema, listScoresArgsSchema, listFeedbackArgsSchema, EntityType, TraceStatus } from '@mastra/core/storage';
5
4
  import { MastraBase } from '@mastra/core/base';
5
+ import { MessageList } from '@mastra/core/agent';
6
6
  import { parseFieldKey } from '@mastra/core/utils';
7
7
  import { saveScorePayloadSchema } from '@mastra/core/evals';
8
8
 
@@ -37,7 +37,8 @@ var TABLE_ENGINES = {
37
37
  [TABLE_WORKSPACE_VERSIONS]: `MergeTree()`,
38
38
  [TABLE_SKILLS]: `ReplacingMergeTree()`,
39
39
  [TABLE_SKILL_VERSIONS]: `MergeTree()`,
40
- [TABLE_SKILL_BLOBS]: `ReplacingMergeTree()`
40
+ [TABLE_SKILL_BLOBS]: `ReplacingMergeTree()`,
41
+ mastra_background_tasks: `ReplacingMergeTree()`
41
42
  };
42
43
  var COLUMN_TYPES = {
43
44
  text: "String",
@@ -682,7 +683,203 @@ var ClickhouseDB = class extends MastraBase {
682
683
  }
683
684
  };
684
685
 
685
- // src/storage/domains/memory/index.ts
686
+ // src/storage/domains/background-tasks/index.ts
687
+ function serializeJson(v) {
688
+ if (typeof v === "object" && v != null) return JSON.stringify(v);
689
+ return v ?? "";
690
+ }
691
+ function rowToTask(row) {
692
+ const parseJson2 = (val) => {
693
+ if (val == null || val === "") return void 0;
694
+ if (typeof val === "string") {
695
+ try {
696
+ return JSON.parse(val);
697
+ } catch {
698
+ return val;
699
+ }
700
+ }
701
+ return val;
702
+ };
703
+ return {
704
+ id: row.id,
705
+ status: row.status,
706
+ toolName: row.tool_name,
707
+ toolCallId: row.tool_call_id,
708
+ args: parseJson2(row.args) ?? {},
709
+ agentId: row.agent_id,
710
+ threadId: row.thread_id || void 0,
711
+ resourceId: row.resource_id || void 0,
712
+ runId: row.run_id ?? "",
713
+ result: parseJson2(row.result),
714
+ error: parseJson2(row.error),
715
+ retryCount: Number(row.retry_count ?? 0),
716
+ maxRetries: Number(row.max_retries ?? 0),
717
+ timeoutMs: Number(row.timeout_ms ?? 3e5),
718
+ createdAt: new Date(row.createdAt),
719
+ startedAt: row.startedAt ? new Date(row.startedAt) : void 0,
720
+ completedAt: row.completedAt ? new Date(row.completedAt) : void 0
721
+ };
722
+ }
723
+ var BackgroundTasksStorageClickhouse = class extends BackgroundTasksStorage {
724
+ client;
725
+ #db;
726
+ constructor(config) {
727
+ super();
728
+ const { client, ttl } = resolveClickhouseConfig(config);
729
+ this.client = client;
730
+ this.#db = new ClickhouseDB({ client, ttl });
731
+ }
732
+ async init() {
733
+ await this.#db.createTable({ tableName: TABLE_BACKGROUND_TASKS, schema: TABLE_SCHEMAS[TABLE_BACKGROUND_TASKS] });
734
+ }
735
+ async dangerouslyClearAll() {
736
+ await this.#db.clearTable({ tableName: TABLE_BACKGROUND_TASKS });
737
+ }
738
+ async createTask(task) {
739
+ await this.client.insert({
740
+ table: TABLE_BACKGROUND_TASKS,
741
+ values: [
742
+ {
743
+ id: task.id,
744
+ tool_call_id: task.toolCallId,
745
+ tool_name: task.toolName,
746
+ agent_id: task.agentId,
747
+ thread_id: task.threadId ?? "",
748
+ resource_id: task.resourceId ?? "",
749
+ run_id: task.runId,
750
+ status: task.status,
751
+ args: serializeJson(task.args),
752
+ result: serializeJson(task.result),
753
+ error: serializeJson(task.error),
754
+ retry_count: task.retryCount,
755
+ max_retries: task.maxRetries,
756
+ timeout_ms: task.timeoutMs,
757
+ createdAt: task.createdAt.toISOString(),
758
+ startedAt: task.startedAt?.toISOString() ?? "1970-01-01T00:00:00.000Z",
759
+ completedAt: task.completedAt?.toISOString() ?? "1970-01-01T00:00:00.000Z"
760
+ }
761
+ ],
762
+ format: "JSONEachRow",
763
+ clickhouse_settings: { date_time_input_format: "best_effort" }
764
+ });
765
+ }
766
+ async updateTask(taskId, update) {
767
+ const existing = await this.getTask(taskId);
768
+ if (!existing) return;
769
+ const merged = { ...existing };
770
+ if ("status" in update) merged.status = update.status;
771
+ if ("result" in update) merged.result = update.result;
772
+ if ("error" in update) merged.error = update.error;
773
+ if ("retryCount" in update) merged.retryCount = update.retryCount;
774
+ if ("startedAt" in update) merged.startedAt = update.startedAt;
775
+ if ("completedAt" in update) merged.completedAt = update.completedAt;
776
+ await this.createTask(merged);
777
+ }
778
+ async getTask(taskId) {
779
+ const result = await this.client.query({
780
+ query: `SELECT * FROM ${TABLE_BACKGROUND_TASKS} FINAL WHERE id = {var_id:String} LIMIT 1`,
781
+ query_params: { var_id: taskId },
782
+ format: "JSONEachRow",
783
+ clickhouse_settings: { date_time_input_format: "best_effort" }
784
+ });
785
+ const rows = await result.json();
786
+ return rows.length > 0 ? rowToTask(rows[0]) : null;
787
+ }
788
+ async listTasks(filter) {
789
+ const conditions = [];
790
+ const params = {};
791
+ if (filter.status) {
792
+ const statuses = Array.isArray(filter.status) ? filter.status : [filter.status];
793
+ conditions.push(`status IN ({var_statuses:Array(String)})`);
794
+ params.var_statuses = statuses;
795
+ }
796
+ if (filter.agentId) {
797
+ conditions.push(`agent_id = {var_agent:String}`);
798
+ params.var_agent = filter.agentId;
799
+ }
800
+ if (filter.threadId) {
801
+ conditions.push(`thread_id = {var_thread:String}`);
802
+ params.var_thread = filter.threadId;
803
+ }
804
+ if (filter.runId) {
805
+ conditions.push(`run_id = {var_run:String}`);
806
+ params.var_run = filter.runId;
807
+ }
808
+ if (filter.toolName) {
809
+ conditions.push(`tool_name = {var_tool:String}`);
810
+ params.var_tool = filter.toolName;
811
+ }
812
+ const dateCol = filter.dateFilterBy === "startedAt" ? "startedAt" : filter.dateFilterBy === "completedAt" ? "completedAt" : "createdAt";
813
+ if (filter.fromDate) {
814
+ conditions.push(`${dateCol} >= parseDateTimeBestEffort({var_from_date:String})`);
815
+ params.var_from_date = filter.fromDate.toISOString();
816
+ }
817
+ if (filter.toDate) {
818
+ conditions.push(`${dateCol} < parseDateTimeBestEffort({var_to_date:String})`);
819
+ params.var_to_date = filter.toDate.toISOString();
820
+ }
821
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
822
+ const countResult = await this.client.query({
823
+ query: `SELECT count() as count FROM ${TABLE_BACKGROUND_TASKS} FINAL ${where}`,
824
+ query_params: params,
825
+ format: "JSONEachRow",
826
+ clickhouse_settings: { date_time_input_format: "best_effort" }
827
+ });
828
+ const countRows = await countResult.json();
829
+ const total = Number(countRows[0]?.count ?? 0);
830
+ const orderCol = filter.orderBy === "startedAt" ? "startedAt" : filter.orderBy === "completedAt" ? "completedAt" : "createdAt";
831
+ const direction = filter.orderDirection === "desc" ? "DESC" : "ASC";
832
+ let sql = `SELECT * FROM ${TABLE_BACKGROUND_TASKS} FINAL ${where} ORDER BY ${orderCol} ${direction}`;
833
+ if (filter.perPage != null) {
834
+ sql += ` LIMIT {var_limit:UInt32}`;
835
+ params.var_limit = filter.perPage;
836
+ if (filter.page != null) {
837
+ sql += ` OFFSET {var_offset:UInt32}`;
838
+ params.var_offset = filter.page * filter.perPage;
839
+ }
840
+ }
841
+ const result = await this.client.query({
842
+ query: sql,
843
+ query_params: params,
844
+ format: "JSONEachRow",
845
+ clickhouse_settings: { date_time_input_format: "best_effort" }
846
+ });
847
+ const tasks = (await result.json()).map(rowToTask);
848
+ return { tasks, total };
849
+ }
850
+ async deleteTask(taskId) {
851
+ await this.client.query({
852
+ query: `ALTER TABLE ${TABLE_BACKGROUND_TASKS} DELETE WHERE id = {var_id:String}`,
853
+ query_params: { var_id: taskId }
854
+ });
855
+ }
856
+ async deleteTasks(filter) {
857
+ const { tasks } = await this.listTasks(filter);
858
+ for (const task of tasks) {
859
+ await this.client.query({
860
+ query: `ALTER TABLE ${TABLE_BACKGROUND_TASKS} DELETE WHERE id = {var_id:String}`,
861
+ query_params: { var_id: task.id }
862
+ });
863
+ }
864
+ }
865
+ async getRunningCount() {
866
+ const result = await this.client.query({
867
+ query: `SELECT count() as count FROM ${TABLE_BACKGROUND_TASKS} FINAL WHERE status = 'running'`,
868
+ format: "JSONEachRow"
869
+ });
870
+ const rows = await result.json();
871
+ return Number(rows[0]?.count ?? 0);
872
+ }
873
+ async getRunningCountByAgent(agentId) {
874
+ const result = await this.client.query({
875
+ query: `SELECT count() as count FROM ${TABLE_BACKGROUND_TASKS} FINAL WHERE status = 'running' AND agent_id = {var_agent:String}`,
876
+ query_params: { var_agent: agentId },
877
+ format: "JSONEachRow"
878
+ });
879
+ const rows = await result.json();
880
+ return Number(rows[0]?.count ?? 0);
881
+ }
882
+ };
686
883
  function serializeMetadata(metadata) {
687
884
  if (!metadata || Object.keys(metadata).length === 0) {
688
885
  return "{}";
@@ -2231,6 +2428,49 @@ time for large tables. Please ensure you have a backup before proceeding.
2231
2428
  );
2232
2429
  }
2233
2430
  }
2431
+ async getTraceLight(args) {
2432
+ const { traceId } = args;
2433
+ try {
2434
+ const engine = TABLE_ENGINES[TABLE_SPANS] ?? "MergeTree()";
2435
+ const result = await this.client.query({
2436
+ query: `
2437
+ SELECT traceId, spanId, parentSpanId, name,
2438
+ entityType, entityId, entityName,
2439
+ spanType, error, isEvent,
2440
+ startedAt, endedAt, createdAt, updatedAt
2441
+ FROM ${TABLE_SPANS} ${engine.startsWith("ReplacingMergeTree") ? "FINAL" : ""}
2442
+ WHERE traceId = {traceId:String}
2443
+ ORDER BY startedAt ASC
2444
+ `,
2445
+ query_params: { traceId },
2446
+ format: "JSONEachRow",
2447
+ clickhouse_settings: {
2448
+ date_time_input_format: "best_effort",
2449
+ date_time_output_format: "iso",
2450
+ use_client_time_zone: 1,
2451
+ output_format_json_quote_64bit_integers: 0
2452
+ }
2453
+ });
2454
+ const rows = await result.json();
2455
+ if (!rows || rows.length === 0) {
2456
+ return null;
2457
+ }
2458
+ return {
2459
+ traceId,
2460
+ spans: transformRows(rows)
2461
+ };
2462
+ } catch (error) {
2463
+ throw new MastraError(
2464
+ {
2465
+ id: createStorageErrorId("CLICKHOUSE", "GET_TRACE_LIGHT", "FAILED"),
2466
+ domain: ErrorDomain.STORAGE,
2467
+ category: ErrorCategory.THIRD_PARTY,
2468
+ details: { traceId }
2469
+ },
2470
+ error
2471
+ );
2472
+ }
2473
+ }
2234
2474
  async updateSpan(args) {
2235
2475
  const { traceId, spanId, updates } = args;
2236
2476
  try {
@@ -2745,6 +2985,7 @@ CREATE TABLE IF NOT EXISTS ${TABLE_METRIC_EVENTS} (
2745
2985
  timestamp DateTime64(3, 'UTC'),
2746
2986
 
2747
2987
  -- IDs
2988
+ metricId String,
2748
2989
  traceId Nullable(String),
2749
2990
  spanId Nullable(String),
2750
2991
  experimentId Nullable(String),
@@ -2792,9 +3033,9 @@ CREATE TABLE IF NOT EXISTS ${TABLE_METRIC_EVENTS} (
2792
3033
  metadata Nullable(String),
2793
3034
  scope Nullable(String)
2794
3035
  )
2795
- ENGINE = MergeTree
3036
+ ENGINE = ReplacingMergeTree
2796
3037
  PARTITION BY toDate(timestamp)
2797
- ORDER BY (name, timestamp)
3038
+ ORDER BY (name, timestamp, metricId)
2798
3039
  `;
2799
3040
  var LOG_EVENTS_DDL = `
2800
3041
  CREATE TABLE IF NOT EXISTS ${TABLE_LOG_EVENTS} (
@@ -2802,6 +3043,7 @@ CREATE TABLE IF NOT EXISTS ${TABLE_LOG_EVENTS} (
2802
3043
  timestamp DateTime64(3, 'UTC'),
2803
3044
 
2804
3045
  -- IDs
3046
+ logId String,
2805
3047
  traceId Nullable(String),
2806
3048
  spanId Nullable(String),
2807
3049
  experimentId Nullable(String),
@@ -2844,10 +3086,9 @@ CREATE TABLE IF NOT EXISTS ${TABLE_LOG_EVENTS} (
2844
3086
  metadata Nullable(String),
2845
3087
  scope Nullable(String)
2846
3088
  )
2847
- ENGINE = MergeTree
3089
+ ENGINE = ReplacingMergeTree
2848
3090
  PARTITION BY toDate(timestamp)
2849
- ORDER BY (timestamp, traceId)
2850
- SETTINGS allow_nullable_key = 1
3091
+ ORDER BY (timestamp, logId)
2851
3092
  `;
2852
3093
  var SCORE_EVENTS_DDL = `
2853
3094
  CREATE TABLE IF NOT EXISTS ${TABLE_SCORE_EVENTS} (
@@ -2855,6 +3096,7 @@ CREATE TABLE IF NOT EXISTS ${TABLE_SCORE_EVENTS} (
2855
3096
  timestamp DateTime64(3, 'UTC'),
2856
3097
 
2857
3098
  -- IDs
3099
+ scoreId String,
2858
3100
  traceId Nullable(String),
2859
3101
  spanId Nullable(String),
2860
3102
  experimentId Nullable(String),
@@ -2904,9 +3146,9 @@ CREATE TABLE IF NOT EXISTS ${TABLE_SCORE_EVENTS} (
2904
3146
  metadata Nullable(String),
2905
3147
  scope Nullable(String)
2906
3148
  )
2907
- ENGINE = MergeTree
3149
+ ENGINE = ReplacingMergeTree
2908
3150
  PARTITION BY toDate(timestamp)
2909
- ORDER BY (traceId, timestamp)
3151
+ ORDER BY (traceId, timestamp, scoreId)
2910
3152
  SETTINGS allow_nullable_key = 1
2911
3153
  `;
2912
3154
  var FEEDBACK_EVENTS_DDL = `
@@ -2915,6 +3157,7 @@ CREATE TABLE IF NOT EXISTS ${TABLE_FEEDBACK_EVENTS} (
2915
3157
  timestamp DateTime64(3, 'UTC'),
2916
3158
 
2917
3159
  -- IDs
3160
+ feedbackId String,
2918
3161
  traceId Nullable(String),
2919
3162
  spanId Nullable(String),
2920
3163
  experimentId Nullable(String),
@@ -2967,9 +3210,9 @@ CREATE TABLE IF NOT EXISTS ${TABLE_FEEDBACK_EVENTS} (
2967
3210
  metadata Nullable(String),
2968
3211
  scope Nullable(String)
2969
3212
  )
2970
- ENGINE = MergeTree
3213
+ ENGINE = ReplacingMergeTree
2971
3214
  PARTITION BY toDate(timestamp)
2972
- ORDER BY (traceId, timestamp)
3215
+ ORDER BY (traceId, timestamp, feedbackId)
2973
3216
  SETTINGS allow_nullable_key = 1
2974
3217
  `;
2975
3218
  var DISCOVERY_VALUES_DDL = `
@@ -3324,6 +3567,7 @@ function spanRecordToRow(span) {
3324
3567
  }
3325
3568
  function rowToLogRecord(row) {
3326
3569
  return {
3570
+ logId: row.logId,
3327
3571
  timestamp: toDate(row.timestamp),
3328
3572
  level: row.level,
3329
3573
  message: row.message,
@@ -3360,6 +3604,7 @@ function rowToLogRecord(row) {
3360
3604
  }
3361
3605
  function logRecordToRow(log) {
3362
3606
  return {
3607
+ logId: log.logId,
3363
3608
  timestamp: toISOString(log.timestamp),
3364
3609
  level: log.level,
3365
3610
  message: log.message,
@@ -3396,6 +3641,7 @@ function logRecordToRow(log) {
3396
3641
  }
3397
3642
  function rowToMetricRecord(row) {
3398
3643
  return {
3644
+ metricId: row.metricId,
3399
3645
  timestamp: toDate(row.timestamp),
3400
3646
  name: row.name,
3401
3647
  value: Number(row.value),
@@ -3437,6 +3683,7 @@ function rowToMetricRecord(row) {
3437
3683
  }
3438
3684
  function metricRecordToRow(metric) {
3439
3685
  return {
3686
+ metricId: metric.metricId,
3440
3687
  timestamp: toISOString(metric.timestamp),
3441
3688
  name: metric.name,
3442
3689
  value: metric.value,
@@ -3478,6 +3725,7 @@ function metricRecordToRow(metric) {
3478
3725
  }
3479
3726
  function rowToScoreRecord(row) {
3480
3727
  return {
3728
+ scoreId: row.scoreId,
3481
3729
  timestamp: toDate(row.timestamp),
3482
3730
  // Core score/feedback shapes still type traceId as required for now.
3483
3731
  traceId: nullableString(row.traceId),
@@ -3520,6 +3768,7 @@ function scoreRecordToRow(score) {
3520
3768
  const metadata = score.metadata ?? null;
3521
3769
  const scoreSource = score.scoreSource ?? score.source ?? null;
3522
3770
  return {
3771
+ scoreId: score.scoreId,
3523
3772
  timestamp: toISOString(score.timestamp),
3524
3773
  traceId: score.traceId ?? null,
3525
3774
  spanId: score.spanId ?? null,
@@ -3562,6 +3811,7 @@ function rowToFeedbackRecord(row) {
3562
3811
  const feedbackSource = nullableString(row.feedbackSource);
3563
3812
  const feedbackUserId = nullableString(row.feedbackUserId) ?? nullableString(row.userId);
3564
3813
  return {
3814
+ feedbackId: row.feedbackId,
3565
3815
  timestamp: toDate(row.timestamp),
3566
3816
  // Core score/feedback shapes still type traceId as required for now.
3567
3817
  traceId: nullableString(row.traceId),
@@ -3605,6 +3855,7 @@ function feedbackRecordToRow(feedback) {
3605
3855
  const feedbackSource = feedback.feedbackSource ?? feedback.source ?? "";
3606
3856
  const feedbackUserId = feedback.feedbackUserId ?? feedback.userId ?? null;
3607
3857
  return {
3858
+ feedbackId: feedback.feedbackId,
3608
3859
  timestamp: toISOString(feedback.timestamp),
3609
3860
  traceId: feedback.traceId ?? null,
3610
3861
  spanId: feedback.spanId ?? null,
@@ -4724,6 +4975,93 @@ async function getMetricLabelValues(client, args) {
4724
4975
  );
4725
4976
  return { values: rows.map((r) => r.value) };
4726
4977
  }
4978
+ var SIGNAL_MIGRATIONS = [
4979
+ { table: TABLE_METRIC_EVENTS, createDDL: METRIC_EVENTS_DDL, idColumn: "metricId" },
4980
+ { table: TABLE_LOG_EVENTS, createDDL: LOG_EVENTS_DDL, idColumn: "logId" },
4981
+ { table: TABLE_SCORE_EVENTS, createDDL: SCORE_EVENTS_DDL, idColumn: "scoreId" },
4982
+ { table: TABLE_FEEDBACK_EVENTS, createDDL: FEEDBACK_EVENTS_DDL, idColumn: "feedbackId" }
4983
+ ];
4984
+ async function getTableEngine(client, table) {
4985
+ const result = await client.query({
4986
+ query: `SELECT engine FROM system.tables WHERE database = currentDatabase() AND name = {table:String}`,
4987
+ query_params: { table },
4988
+ format: "JSONEachRow"
4989
+ });
4990
+ const rows = await result.json();
4991
+ return rows[0]?.engine ?? null;
4992
+ }
4993
+ async function getTableColumns(client, table) {
4994
+ const result = await client.query({ query: `DESCRIBE TABLE ${table}`, format: "JSONEachRow" });
4995
+ const rows = await result.json();
4996
+ return rows.map((r) => r.name);
4997
+ }
4998
+ function buildTemporaryTableDDL(createDDL, table, tempTable) {
4999
+ return createDDL.replace(`CREATE TABLE IF NOT EXISTS ${table}`, `CREATE TABLE ${tempTable}`);
5000
+ }
5001
+ async function dropTableIfExists(client, table) {
5002
+ if (await getTableEngine(client, table) !== null) {
5003
+ await client.command({ query: `DROP TABLE ${table}` });
5004
+ }
5005
+ }
5006
+ function createMigrationError(args, error) {
5007
+ return new MastraError(
5008
+ {
5009
+ id: createStorageErrorId("CLICKHOUSE", "MIGRATE_SIGNAL_TABLES", "FAILED"),
5010
+ domain: ErrorDomain.STORAGE,
5011
+ category: ErrorCategory.THIRD_PARTY,
5012
+ details: args
5013
+ },
5014
+ error
5015
+ );
5016
+ }
5017
+ async function checkSignalTablesMigrationStatus(client) {
5018
+ const tables = [];
5019
+ for (const { table, idColumn } of SIGNAL_MIGRATIONS) {
5020
+ const engine = await getTableEngine(client, table);
5021
+ if (!engine || engine === "ReplacingMergeTree") {
5022
+ continue;
5023
+ }
5024
+ tables.push({ table, engine, idColumn });
5025
+ }
5026
+ return {
5027
+ needsMigration: tables.length > 0,
5028
+ tables
5029
+ };
5030
+ }
5031
+ async function migrateSignalTables(client, logger) {
5032
+ for (const { table, createDDL, idColumn } of SIGNAL_MIGRATIONS) {
5033
+ const engine = await getTableEngine(client, table);
5034
+ if (!engine || engine === "ReplacingMergeTree") continue;
5035
+ logger?.info?.(`Migrating ${table} from ${engine} to ReplacingMergeTree with ${idColumn} column`);
5036
+ const temp = `${table}_migrating_${Date.now()}`;
5037
+ try {
5038
+ await client.command({ query: buildTemporaryTableDDL(createDDL, table, temp) });
5039
+ const newColumns = await getTableColumns(client, temp);
5040
+ const currentColumns = new Set(await getTableColumns(client, table));
5041
+ const columnList = newColumns.map((c) => `"${c}"`).join(", ");
5042
+ const selectExprs = newColumns.map((c) => {
5043
+ if (c === idColumn) {
5044
+ return currentColumns.has(c) ? `COALESCE(nullIf("${c}", ''), toString(generateUUIDv4())) AS "${c}"` : `toString(generateUUIDv4()) AS "${c}"`;
5045
+ }
5046
+ return currentColumns.has(c) ? `"${c}"` : `NULL AS "${c}"`;
5047
+ }).join(", ");
5048
+ await client.command({
5049
+ query: `INSERT INTO ${temp} (${columnList}) SELECT ${selectExprs} FROM ${table}`
5050
+ });
5051
+ await client.command({ query: `EXCHANGE TABLES ${temp} AND ${table}` });
5052
+ await client.command({ query: `DROP TABLE ${temp}` });
5053
+ logger?.info?.(`Successfully migrated ${table}`);
5054
+ } catch (error) {
5055
+ logger?.error?.(`Migration of ${table} failed: ${error.message}`);
5056
+ try {
5057
+ await dropTableIfExists(client, temp);
5058
+ } catch (restoreError) {
5059
+ logger?.error?.(`Failed to clean up temporary table ${temp}: ${restoreError.message}`);
5060
+ }
5061
+ throw createMigrationError({ table, idColumn }, error);
5062
+ }
5063
+ }
5064
+ }
4727
5065
  var SCORE_TYPED_COLUMNS = /* @__PURE__ */ new Set([
4728
5066
  "timestamp",
4729
5067
  "traceId",
@@ -5187,6 +5525,31 @@ async function getTrace(client, args) {
5187
5525
  const spans = rows.map(rowToSpanRecord);
5188
5526
  return { traceId: args.traceId, spans };
5189
5527
  }
5528
+ async function getTraceLight(client, args) {
5529
+ const result = await client.query({
5530
+ query: `
5531
+ SELECT traceId, spanId, parentSpanId, name,
5532
+ entityType, entityId, entityName,
5533
+ spanType, error, isEvent,
5534
+ startedAt, endedAt
5535
+ FROM (
5536
+ SELECT *
5537
+ FROM ${TABLE_SPAN_EVENTS}
5538
+ WHERE traceId = {traceId:String}
5539
+ ORDER BY dedupeKey, endedAt DESC
5540
+ LIMIT 1 BY dedupeKey
5541
+ )
5542
+ ORDER BY startedAt ASC
5543
+ `,
5544
+ query_params: { traceId: args.traceId },
5545
+ format: "JSONEachRow",
5546
+ clickhouse_settings: CH_SETTINGS
5547
+ });
5548
+ const rows = await result.json();
5549
+ if (!rows || rows.length === 0) return null;
5550
+ const spans = rows.map(rowToSpanRecord);
5551
+ return { traceId: args.traceId, spans };
5552
+ }
5190
5553
  async function batchDeleteTraces(client, args) {
5191
5554
  if (args.traceIds.length === 0) return;
5192
5555
  const params = {};
@@ -5215,6 +5578,32 @@ async function batchDeleteTraces(client, args) {
5215
5578
  }
5216
5579
 
5217
5580
  // src/storage/domains/observability/v-next/index.ts
5581
+ function buildSignalMigrationRequiredMessage(args) {
5582
+ const tableList = args.tables.map((table) => ` - ${table.table} (${table.engine})`).join("\n");
5583
+ return `
5584
+ ===========================================================================
5585
+ MIGRATION REQUIRED: ${args.store} observability signal tables need signal IDs
5586
+ ===========================================================================
5587
+
5588
+ The following signal tables still use the legacy schema and must be migrated
5589
+ before observability storage can initialize:
5590
+
5591
+ ${tableList}
5592
+
5593
+ To fix this, run the manual migration command:
5594
+
5595
+ npx mastra migrate
5596
+
5597
+ This command will:
5598
+ 1. Create replacement signal tables with signal-ID dedupe keys
5599
+ 2. Backfill missing signal IDs for legacy rows
5600
+ 3. Swap the migrated tables into place
5601
+
5602
+ WARNING: This migration recreates the signal tables and may take significant
5603
+ time for large databases. Please ensure you have a backup before proceeding.
5604
+ ===========================================================================
5605
+ `;
5606
+ }
5218
5607
  var ObservabilityStorageClickhouseVNext = class extends ObservabilityStorage {
5219
5608
  #client;
5220
5609
  #retention;
@@ -5228,6 +5617,18 @@ var ObservabilityStorageClickhouseVNext = class extends ObservabilityStorage {
5228
5617
  // Initialization
5229
5618
  // -------------------------------------------------------------------------
5230
5619
  async init() {
5620
+ const migrationStatus = await checkSignalTablesMigrationStatus(this.#client);
5621
+ if (migrationStatus.needsMigration) {
5622
+ throw new MastraError({
5623
+ id: createStorageErrorId("CLICKHOUSE", "MIGRATION_REQUIRED", "SIGNAL_TABLES"),
5624
+ domain: ErrorDomain.STORAGE,
5625
+ category: ErrorCategory.USER,
5626
+ text: buildSignalMigrationRequiredMessage({
5627
+ store: "ClickHouse",
5628
+ tables: migrationStatus.tables.map(({ table, engine }) => ({ table, engine }))
5629
+ })
5630
+ });
5631
+ }
5231
5632
  try {
5232
5633
  for (const ddl of [...ALL_TABLE_DDL, ...ALL_MV_DDL]) {
5233
5634
  await this.#client.command({ query: ddl });
@@ -5242,6 +5643,9 @@ var ObservabilityStorageClickhouseVNext = class extends ObservabilityStorage {
5242
5643
  }
5243
5644
  }
5244
5645
  } catch (error) {
5646
+ if (error instanceof MastraError) {
5647
+ throw error;
5648
+ }
5245
5649
  throw new MastraError(
5246
5650
  {
5247
5651
  id: createStorageErrorId("CLICKHOUSE", "VNEXT_INIT", "FAILED"),
@@ -5263,6 +5667,29 @@ var ObservabilityStorageClickhouseVNext = class extends ObservabilityStorage {
5263
5667
  } catch {
5264
5668
  }
5265
5669
  }
5670
+ /**
5671
+ * Manually migrate legacy signal tables to the signal-ID ReplacingMergeTree schema.
5672
+ * The public method name is historical; the CLI still calls `migrateSpans()`
5673
+ * for observability migrations even though this now also migrates signal tables.
5674
+ */
5675
+ async migrateSpans() {
5676
+ const migrationStatus = await checkSignalTablesMigrationStatus(this.#client);
5677
+ if (!migrationStatus.needsMigration) {
5678
+ return {
5679
+ success: true,
5680
+ alreadyMigrated: true,
5681
+ duplicatesRemoved: 0,
5682
+ message: "Migration already complete. Signal tables already use signal-ID dedupe keys."
5683
+ };
5684
+ }
5685
+ await migrateSignalTables(this.#client, this.logger);
5686
+ return {
5687
+ success: true,
5688
+ alreadyMigrated: false,
5689
+ duplicatesRemoved: 0,
5690
+ message: `Migration complete. Migrated signal tables: ${migrationStatus.tables.map((t) => t.table).join(", ")}.`
5691
+ };
5692
+ }
5266
5693
  // -------------------------------------------------------------------------
5267
5694
  // Strategy
5268
5695
  // -------------------------------------------------------------------------
@@ -5358,6 +5785,22 @@ var ObservabilityStorageClickhouseVNext = class extends ObservabilityStorage {
5358
5785
  );
5359
5786
  }
5360
5787
  }
5788
+ async getTraceLight(args) {
5789
+ try {
5790
+ return await getTraceLight(this.#client, args);
5791
+ } catch (error) {
5792
+ if (error instanceof MastraError) throw error;
5793
+ throw new MastraError(
5794
+ {
5795
+ id: createStorageErrorId("CLICKHOUSE", "GET_TRACE_LIGHT", "FAILED"),
5796
+ domain: ErrorDomain.STORAGE,
5797
+ category: ErrorCategory.THIRD_PARTY,
5798
+ details: { traceId: args.traceId }
5799
+ },
5800
+ error
5801
+ );
5802
+ }
5803
+ }
5361
5804
  async listTraces(args) {
5362
5805
  try {
5363
5806
  return await listTraces(this.#client, args);
@@ -6646,7 +7089,8 @@ var ClickhouseStore = class extends MastraCompositeStore {
6646
7089
  workflows,
6647
7090
  scores,
6648
7091
  memory,
6649
- observability
7092
+ observability,
7093
+ backgroundTasks: new BackgroundTasksStorageClickhouse(domainConfig)
6650
7094
  };
6651
7095
  }
6652
7096
  async optimizeTable({ tableName }) {
@@ -6705,6 +7149,6 @@ var ClickhouseStore = class extends MastraCompositeStore {
6705
7149
  }
6706
7150
  };
6707
7151
 
6708
- export { COLUMN_TYPES, ClickhouseStore, MemoryStorageClickhouse, ObservabilityStorageClickhouse, ObservabilityStorageClickhouseVNext, ScoresStorageClickhouse, TABLE_ENGINES, WorkflowsStorageClickhouse };
7152
+ export { BackgroundTasksStorageClickhouse, COLUMN_TYPES, ClickhouseStore, MemoryStorageClickhouse, ObservabilityStorageClickhouse, ObservabilityStorageClickhouseVNext, ScoresStorageClickhouse, TABLE_ENGINES, WorkflowsStorageClickhouse };
6709
7153
  //# sourceMappingURL=index.js.map
6710
7154
  //# sourceMappingURL=index.js.map