@mastra/clickhouse 1.2.3 → 1.3.0-alpha.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @mastra/clickhouse
2
2
 
3
+ ## 1.3.0-alpha.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Added `requestContext` column to the spans table. Request context data from tracing is now persisted alongside other span data. ([#14020](https://github.com/mastra-ai/mastra/pull/14020))
8
+
9
+ ### Patch Changes
10
+
11
+ - Added resilient column handling to insert and update operations. Unknown columns in records are now silently dropped instead of causing SQL errors, ensuring forward compatibility when newer domain packages add fields that haven't been migrated yet. ([#14021](https://github.com/mastra-ai/mastra/pull/14021))
12
+
13
+ For example, calling `db.insert({ tableName, record: { id: '1', title: 'Hello', futureField: 'value' } })` will silently ignore `futureField` if it doesn't exist in the database table, rather than throwing. The same applies to `update` — unknown fields in the data payload are dropped before building the SQL statement.
14
+
15
+ - Fixed slow semantic recall in the libsql, Cloudflare D1, and ClickHouse storage adapters. Recall performance no longer degrades as threads grow larger. (Fixes #11702) ([#14022](https://github.com/mastra-ai/mastra/pull/14022))
16
+
17
+ - Updated dependencies [[`4f71b43`](https://github.com/mastra-ai/mastra/commit/4f71b436a4a6b8839842d8da47b57b84509af56c), [`a070277`](https://github.com/mastra-ai/mastra/commit/a07027766ce195ba74d0783116d894cbab25d44c), [`b628b91`](https://github.com/mastra-ai/mastra/commit/b628b9128b372c0f54214d902b07279f03443900), [`332c014`](https://github.com/mastra-ai/mastra/commit/332c014e076b81edf7fe45b58205882726415e90), [`6b63153`](https://github.com/mastra-ai/mastra/commit/6b63153878ea841c0f4ce632ba66bb33e57e9c1b), [`4246e34`](https://github.com/mastra-ai/mastra/commit/4246e34cec9c26636d0965942268e6d07c346671), [`b8837ee`](https://github.com/mastra-ai/mastra/commit/b8837ee77e2e84197609762bfabd8b3da326d30c), [`5d950f7`](https://github.com/mastra-ai/mastra/commit/5d950f7bf426a215a1808f0abef7de5c8336ba1c), [`28c85b1`](https://github.com/mastra-ai/mastra/commit/28c85b184fc32b40f7f160483c982da6d388ecbd), [`e9a08fb`](https://github.com/mastra-ai/mastra/commit/e9a08fbef1ada7e50e961e2f54f55e8c10b4a45c), [`631ffd8`](https://github.com/mastra-ai/mastra/commit/631ffd82fed108648b448b28e6a90e38c5f53bf5), [`aae2295`](https://github.com/mastra-ai/mastra/commit/aae2295838a2d329ad6640829e87934790ffe5b8), [`aa61f29`](https://github.com/mastra-ai/mastra/commit/aa61f29ff8095ce46a4ae16e46c4d8c79b2b685b), [`7ff3714`](https://github.com/mastra-ai/mastra/commit/7ff37148515439bb3be009a60e02c3e363299760), [`41d79a1`](https://github.com/mastra-ai/mastra/commit/41d79a14bd8cb6de1e2565fd0a04786bae2f211b), [`e673376`](https://github.com/mastra-ai/mastra/commit/e6733763ad1321aa7e5ae15096b9c2104f93b1f3), [`b2204c9`](https://github.com/mastra-ai/mastra/commit/b2204c98a42848bbfb6f0440f005dc2b6354f1cd), [`a1bf1e3`](https://github.com/mastra-ai/mastra/commit/a1bf1e385ed4c0ef6f11b56c5887442970d127f2), [`b6f647a`](https://github.com/mastra-ai/mastra/commit/b6f647ae2388e091f366581595feb957e37d5b40), [`0c57b8b`](https://github.com/mastra-ai/mastra/commit/0c57b8b0a69a97b5a4ae3f79be6c610f29f3cf7b), [`b081f27`](https://github.com/mastra-ai/mastra/commit/b081f272cf411716e1d6bd72ceac4bcee2657b19), [`0c09eac`](https://github.com/mastra-ai/mastra/commit/0c09eacb1926f64cfdc9ae5c6d63385cf8c9f72c), [`6b9b93d`](https://github.com/mastra-ai/mastra/commit/6b9b93d6f459d1ba6e36f163abf62a085ddb3d64), [`31b6067`](https://github.com/mastra-ai/mastra/commit/31b6067d0cc3ab10e1b29c36147f3b5266bc714a), [`797ac42`](https://github.com/mastra-ai/mastra/commit/797ac4276de231ad2d694d9aeca75980f6cd0419), [`0bc289e`](https://github.com/mastra-ai/mastra/commit/0bc289e2d476bf46c5b91c21969e8d0c6864691c), [`9b75a06`](https://github.com/mastra-ai/mastra/commit/9b75a06e53ebb0b950ba7c1e83a0142047185f46), [`4c3a1b1`](https://github.com/mastra-ai/mastra/commit/4c3a1b122ea083e003d71092f30f3b31680b01c0), [`85cc3b3`](https://github.com/mastra-ai/mastra/commit/85cc3b3b6f32ae4b083c26498f50d5b250ba944b), [`97ea28c`](https://github.com/mastra-ai/mastra/commit/97ea28c746e9e4147d56047bbb1c4a92417a3fec), [`d567299`](https://github.com/mastra-ai/mastra/commit/d567299cf81e02bd9d5221d4bc05967d6c224161), [`716ffe6`](https://github.com/mastra-ai/mastra/commit/716ffe68bed81f7c2690bc8581b9e140f7bf1c3d), [`8296332`](https://github.com/mastra-ai/mastra/commit/8296332de21c16e3dfc3d0b2d615720a6dc88f2f), [`4df2116`](https://github.com/mastra-ai/mastra/commit/4df211619dd922c047d396ca41cd7027c8c4c8e7), [`2219c1a`](https://github.com/mastra-ai/mastra/commit/2219c1acbd21da116da877f0036ffb985a9dd5a3), [`17c4145`](https://github.com/mastra-ai/mastra/commit/17c4145166099354545582335b5252bdfdfd908b)]:
18
+ - @mastra/core@1.11.0-alpha.0
19
+
3
20
  ## 1.2.3
4
21
 
5
22
  ### Patch Changes
@@ -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.2.3"
6
+ version: "1.3.0-alpha.0"
7
7
  ---
8
8
 
9
9
  ## When to use
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.2.3",
2
+ "version": "1.3.0-alpha.0",
3
3
  "package": "@mastra/clickhouse",
4
4
  "exports": {},
5
5
  "modules": {}
@@ -120,23 +120,23 @@ export const mastra = new Mastra({
120
120
 
121
121
  ## Options
122
122
 
123
- **id:** (`string`): Unique identifier for this storage instance.
123
+ **id** (`string`): Unique identifier for this storage instance.
124
124
 
125
- **default?:** (`MastraCompositeStore`): Default storage adapter. Domains not explicitly specified in \`domains\` will use this storage's domains as fallbacks.
125
+ **default** (`MastraCompositeStore`): Default storage adapter. Domains not explicitly specified in \`domains\` will use this storage's domains as fallbacks.
126
126
 
127
- **domains?:** (`object`): Individual domain overrides. Each domain can come from a different storage adapter. These take precedence over the default storage.
127
+ **domains** (`object`): Individual domain overrides. Each domain can come from a different storage adapter. These take precedence over the default storage.
128
128
 
129
- **domains.memory?:** (`MemoryStorage`): Storage for threads, messages, and resources.
129
+ **domains.memory** (`MemoryStorage`): Storage for threads, messages, and resources.
130
130
 
131
- **domains.workflows?:** (`WorkflowsStorage`): Storage for workflow snapshots.
131
+ **domains.workflows** (`WorkflowsStorage`): Storage for workflow snapshots.
132
132
 
133
- **domains.scores?:** (`ScoresStorage`): Storage for evaluation scores.
133
+ **domains.scores** (`ScoresStorage`): Storage for evaluation scores.
134
134
 
135
- **domains.observability?:** (`ObservabilityStorage`): Storage for traces and spans.
135
+ **domains.observability** (`ObservabilityStorage`): Storage for traces and spans.
136
136
 
137
- **domains.agents?:** (`AgentsStorage`): Storage for stored agent configurations.
137
+ **domains.agents** (`AgentsStorage`): Storage for stored agent configurations.
138
138
 
139
- **disableInit?:** (`boolean`): When true, automatic initialization is disabled. You must call init() explicitly.
139
+ **disableInit** (`boolean`): When true, automatic initialization is disabled. You must call init() explicitly.
140
140
 
141
141
  ## Initialization
142
142
 
package/dist/index.cjs CHANGED
@@ -105,6 +105,8 @@ function resolveClickhouseConfig(config) {
105
105
  var ClickhouseDB = class extends base.MastraBase {
106
106
  ttl;
107
107
  client;
108
+ /** Cache of actual table columns: tableName -> Promise<Set<columnName>> (stores in-flight promise to coalesce concurrent calls) */
109
+ tableColumnsCache = /* @__PURE__ */ new Map();
108
110
  constructor({ client, ttl }) {
109
111
  super({
110
112
  name: "CLICKHOUSE_DB"
@@ -112,6 +114,48 @@ var ClickhouseDB = class extends base.MastraBase {
112
114
  this.ttl = ttl;
113
115
  this.client = client;
114
116
  }
117
+ /**
118
+ * Gets the set of column names that actually exist in the database table.
119
+ * Results are cached; the cache is invalidated when alterTable() adds new columns.
120
+ */
121
+ async getTableColumns(tableName) {
122
+ const cached = this.tableColumnsCache.get(tableName);
123
+ if (cached) return cached;
124
+ const promise = (async () => {
125
+ try {
126
+ const result = await this.client.query({
127
+ query: `DESCRIBE TABLE ${tableName}`,
128
+ format: "JSONEachRow"
129
+ });
130
+ const rows = await result.json();
131
+ const columns = new Set(rows.map((r) => r.name));
132
+ if (columns.size === 0) {
133
+ this.tableColumnsCache.delete(tableName);
134
+ }
135
+ return columns;
136
+ } catch {
137
+ this.tableColumnsCache.delete(tableName);
138
+ return /* @__PURE__ */ new Set();
139
+ }
140
+ })();
141
+ this.tableColumnsCache.set(tableName, promise);
142
+ return promise;
143
+ }
144
+ /**
145
+ * Filters a record to only include columns that exist in the actual database table.
146
+ * Unknown columns are silently dropped to ensure forward compatibility.
147
+ */
148
+ async filterRecordToKnownColumns(tableName, record) {
149
+ const knownColumns = await this.getTableColumns(tableName);
150
+ if (knownColumns.size === 0) return record;
151
+ const filtered = {};
152
+ for (const [key, value] of Object.entries(record)) {
153
+ if (knownColumns.has(key)) {
154
+ filtered[key] = value;
155
+ }
156
+ }
157
+ return filtered;
158
+ }
115
159
  async hasColumn(table, column) {
116
160
  const result = await this.client.query({
117
161
  query: `DESCRIBE TABLE ${table}`,
@@ -423,6 +467,8 @@ var ClickhouseDB = class extends base.MastraBase {
423
467
  },
424
468
  error$1
425
469
  );
470
+ } finally {
471
+ this.tableColumnsCache.delete(tableName);
426
472
  }
427
473
  }
428
474
  async alterTable({
@@ -462,6 +508,8 @@ var ClickhouseDB = class extends base.MastraBase {
462
508
  },
463
509
  error$1
464
510
  );
511
+ } finally {
512
+ this.tableColumnsCache.delete(tableName);
465
513
  }
466
514
  }
467
515
  async clearTable({ tableName }) {
@@ -489,25 +537,39 @@ var ClickhouseDB = class extends base.MastraBase {
489
537
  }
490
538
  }
491
539
  async dropTable({ tableName }) {
492
- await this.client.query({
493
- query: `DROP TABLE IF EXISTS ${tableName}`
494
- });
540
+ try {
541
+ await this.client.query({
542
+ query: `DROP TABLE IF EXISTS ${tableName}`
543
+ });
544
+ } catch (error$1) {
545
+ throw new error.MastraError(
546
+ {
547
+ id: storage.createStorageErrorId("CLICKHOUSE", "DROP_TABLE", "FAILED"),
548
+ domain: error.ErrorDomain.STORAGE,
549
+ category: error.ErrorCategory.THIRD_PARTY,
550
+ details: { tableName }
551
+ },
552
+ error$1
553
+ );
554
+ } finally {
555
+ this.tableColumnsCache.delete(tableName);
556
+ }
495
557
  }
496
558
  async insert({ tableName, record }) {
497
- const rawCreatedAt = record.createdAt || record.created_at || /* @__PURE__ */ new Date();
498
- const rawUpdatedAt = record.updatedAt || /* @__PURE__ */ new Date();
499
- const createdAt = rawCreatedAt instanceof Date ? rawCreatedAt.toISOString() : rawCreatedAt;
500
- const updatedAt = rawUpdatedAt instanceof Date ? rawUpdatedAt.toISOString() : rawUpdatedAt;
501
559
  try {
560
+ const filteredRecord = await this.filterRecordToKnownColumns(tableName, record);
561
+ if (Object.keys(filteredRecord).length === 0) return;
562
+ const rawCreatedAt = filteredRecord.createdAt || filteredRecord.created_at || /* @__PURE__ */ new Date();
563
+ const rawUpdatedAt = filteredRecord.updatedAt || /* @__PURE__ */ new Date();
564
+ if ("createdAt" in filteredRecord || (await this.getTableColumns(tableName)).has("createdAt")) {
565
+ filteredRecord.createdAt = rawCreatedAt instanceof Date ? rawCreatedAt.toISOString() : rawCreatedAt;
566
+ }
567
+ if ("updatedAt" in filteredRecord || (await this.getTableColumns(tableName)).has("updatedAt")) {
568
+ filteredRecord.updatedAt = rawUpdatedAt instanceof Date ? rawUpdatedAt.toISOString() : rawUpdatedAt;
569
+ }
502
570
  await this.client.insert({
503
571
  table: tableName,
504
- values: [
505
- {
506
- ...record,
507
- createdAt,
508
- updatedAt
509
- }
510
- ],
572
+ values: [filteredRecord],
511
573
  format: "JSONEachRow",
512
574
  clickhouse_settings: {
513
575
  // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
@@ -529,7 +591,7 @@ var ClickhouseDB = class extends base.MastraBase {
529
591
  }
530
592
  }
531
593
  async batchInsert({ tableName, records }) {
532
- const recordsToBeInserted = records.map((record) => ({
594
+ const processedRecords = records.map((record) => ({
533
595
  ...Object.fromEntries(
534
596
  Object.entries(record).map(([key, value]) => [
535
597
  key,
@@ -539,10 +601,15 @@ var ClickhouseDB = class extends base.MastraBase {
539
601
  ])
540
602
  )
541
603
  }));
604
+ const recordsToBeInserted = await Promise.all(
605
+ processedRecords.map((r) => this.filterRecordToKnownColumns(tableName, r))
606
+ );
607
+ const nonEmptyRecords = recordsToBeInserted.filter((r) => Object.keys(r).length > 0);
608
+ if (nonEmptyRecords.length === 0) return;
542
609
  try {
543
610
  await this.client.insert({
544
611
  table: tableName,
545
- values: recordsToBeInserted,
612
+ values: nonEmptyRecords,
546
613
  format: "JSONEachRow",
547
614
  clickhouse_settings: {
548
615
  // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
@@ -808,6 +875,20 @@ var MemoryStorageClickhouse = class extends storage.MemoryStorage {
808
875
  }
809
876
  const { field, direction } = this.parseOrderBy(orderBy, "ASC");
810
877
  dataQuery += ` ORDER BY "${field}" ${direction}`;
878
+ if (perPageForQuery === 0 && (!include || include.length === 0)) {
879
+ return { messages: [], total: 0, page, perPage: perPageForResponse, hasMore: false };
880
+ }
881
+ if (perPageForQuery === 0 && include && include.length > 0) {
882
+ const includeResult = await this._getIncludedMessages({ include });
883
+ const list2 = new agent.MessageList().add(includeResult, "memory");
884
+ return {
885
+ messages: this._sortMessages(list2.get.all.db(), field, direction),
886
+ total: 0,
887
+ page,
888
+ perPage: perPageForResponse,
889
+ hasMore: false
890
+ };
891
+ }
811
892
  if (perPageForResponse === false) ; else {
812
893
  dataQuery += ` LIMIT {limit:Int64} OFFSET {offset:Int64}`;
813
894
  dataParams.limit = perPageForQuery;
@@ -869,92 +950,17 @@ var MemoryStorageClickhouse = class extends storage.MemoryStorage {
869
950
  };
870
951
  }
871
952
  const messageIds = new Set(paginatedMessages.map((m) => m.id));
872
- let includeMessages = [];
873
953
  if (include && include.length > 0) {
874
- const includesNeedingThread = include.filter((inc) => !inc.threadId);
875
- const threadByMessageId = /* @__PURE__ */ new Map();
876
- if (includesNeedingThread.length > 0) {
877
- const { messages: includeLookup } = await this.listMessagesById({
878
- messageIds: includesNeedingThread.map((inc) => inc.id)
879
- });
880
- for (const msg of includeLookup) {
881
- if (msg.threadId) {
882
- threadByMessageId.set(msg.id, msg.threadId);
883
- }
884
- }
885
- }
886
- const unionQueries = [];
887
- const params = [];
888
- let paramIdx = 1;
889
- for (const inc of include) {
890
- const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
891
- const searchThreadId = inc.threadId ?? threadByMessageId.get(id);
892
- if (!searchThreadId) continue;
893
- unionQueries.push(`
894
- SELECT * FROM (
895
- WITH numbered_messages AS (
896
- SELECT
897
- id, content, role, type, "createdAt", thread_id, "resourceId",
898
- ROW_NUMBER() OVER (ORDER BY "createdAt" ASC) as row_num
899
- FROM "${storage.TABLE_MESSAGES}"
900
- WHERE thread_id = {var_thread_id_${paramIdx}:String}
901
- ),
902
- target_positions AS (
903
- SELECT row_num as target_pos
904
- FROM numbered_messages
905
- WHERE id = {var_include_id_${paramIdx}:String}
906
- )
907
- SELECT DISTINCT m.id, m.content, m.role, m.type, m."createdAt", m.thread_id AS "threadId", m."resourceId"
908
- FROM numbered_messages m
909
- CROSS JOIN target_positions t
910
- WHERE m.row_num BETWEEN (t.target_pos - {var_withPreviousMessages_${paramIdx}:Int64}) AND (t.target_pos + {var_withNextMessages_${paramIdx}:Int64})
911
- ) AS query_${paramIdx}
912
- `);
913
- params.push(
914
- { [`var_thread_id_${paramIdx}`]: searchThreadId },
915
- { [`var_include_id_${paramIdx}`]: id },
916
- { [`var_withPreviousMessages_${paramIdx}`]: withPreviousMessages },
917
- { [`var_withNextMessages_${paramIdx}`]: withNextMessages }
918
- );
919
- paramIdx++;
920
- }
921
- if (unionQueries.length > 0) {
922
- const finalQuery = unionQueries.join(" UNION ALL ") + ' ORDER BY "createdAt" ASC';
923
- const mergedParams = params.reduce((acc, paramObj) => ({ ...acc, ...paramObj }), {});
924
- const includeResult = await this.client.query({
925
- query: finalQuery,
926
- query_params: mergedParams,
927
- clickhouse_settings: {
928
- date_time_input_format: "best_effort",
929
- date_time_output_format: "iso",
930
- use_client_time_zone: 1,
931
- output_format_json_quote_64bit_integers: 0
932
- }
933
- });
934
- const includeRows = await includeResult.json();
935
- includeMessages = transformRows(includeRows.data);
936
- for (const includeMsg of includeMessages) {
937
- if (!messageIds.has(includeMsg.id)) {
938
- paginatedMessages.push(includeMsg);
939
- messageIds.add(includeMsg.id);
940
- }
954
+ const includeMessages = await this._getIncludedMessages({ include });
955
+ for (const includeMsg of includeMessages) {
956
+ if (!messageIds.has(includeMsg.id)) {
957
+ paginatedMessages.push(includeMsg);
958
+ messageIds.add(includeMsg.id);
941
959
  }
942
960
  }
943
961
  }
944
962
  const list = new agent.MessageList().add(paginatedMessages, "memory");
945
- let finalMessages = list.get.all.db();
946
- finalMessages = finalMessages.sort((a, b) => {
947
- const isDateField = field === "createdAt" || field === "updatedAt";
948
- const aValue = isDateField ? new Date(a[field]).getTime() : a[field];
949
- const bValue = isDateField ? new Date(b[field]).getTime() : b[field];
950
- if (aValue === bValue) {
951
- return a.id.localeCompare(b.id);
952
- }
953
- if (typeof aValue === "number" && typeof bValue === "number") {
954
- return direction === "ASC" ? aValue - bValue : bValue - aValue;
955
- }
956
- return direction === "ASC" ? String(aValue).localeCompare(String(bValue)) : String(bValue).localeCompare(String(aValue));
957
- });
963
+ const finalMessages = this._sortMessages(list.get.all.db(), field, direction);
958
964
  const threadIdSet = new Set(threadIds);
959
965
  const returnedThreadMessageIds = new Set(
960
966
  finalMessages.filter((m) => m.threadId && threadIdSet.has(m.threadId)).map((m) => m.id)
@@ -992,6 +998,95 @@ var MemoryStorageClickhouse = class extends storage.MemoryStorage {
992
998
  };
993
999
  }
994
1000
  }
1001
+ _sortMessages(messages, field, direction) {
1002
+ return messages.sort((a, b) => {
1003
+ const isDateField = field === "createdAt" || field === "updatedAt";
1004
+ const aValue = isDateField ? new Date(a[field]).getTime() : a[field];
1005
+ const bValue = isDateField ? new Date(b[field]).getTime() : b[field];
1006
+ if (aValue === bValue) {
1007
+ return a.id.localeCompare(b.id);
1008
+ }
1009
+ if (typeof aValue === "number" && typeof bValue === "number") {
1010
+ return direction === "ASC" ? aValue - bValue : bValue - aValue;
1011
+ }
1012
+ return direction === "ASC" ? String(aValue).localeCompare(String(bValue)) : String(bValue).localeCompare(String(aValue));
1013
+ });
1014
+ }
1015
+ async _getIncludedMessages({
1016
+ include
1017
+ }) {
1018
+ if (!include || include.length === 0) return [];
1019
+ const targetIds = include.map((inc) => inc.id).filter(Boolean);
1020
+ if (targetIds.length === 0) return [];
1021
+ const { messages: targetDocs } = await this.listMessagesById({ messageIds: targetIds });
1022
+ const targetMap = new Map(
1023
+ targetDocs.map((msg) => [msg.id, { threadId: msg.threadId, createdAt: msg.createdAt }])
1024
+ );
1025
+ const unionQueries = [];
1026
+ const params = {};
1027
+ let paramIdx = 1;
1028
+ for (const inc of include) {
1029
+ const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
1030
+ const target = targetMap.get(id);
1031
+ if (!target) continue;
1032
+ const threadParam = `var_thread_${paramIdx}`;
1033
+ const createdAtParam = `var_createdAt_${paramIdx}`;
1034
+ const limitParam = `var_limit_${paramIdx}`;
1035
+ unionQueries.push(`
1036
+ SELECT id, content, role, type, "createdAt", thread_id AS "threadId", "resourceId"
1037
+ FROM "${storage.TABLE_MESSAGES}"
1038
+ WHERE thread_id = {${threadParam}:String}
1039
+ AND createdAt <= parseDateTime64BestEffort({${createdAtParam}:String}, 3)
1040
+ ORDER BY createdAt DESC, id DESC
1041
+ LIMIT {${limitParam}:Int64}
1042
+ `);
1043
+ params[threadParam] = target.threadId;
1044
+ params[createdAtParam] = target.createdAt;
1045
+ params[limitParam] = withPreviousMessages + 1;
1046
+ paramIdx++;
1047
+ if (withNextMessages > 0) {
1048
+ const threadParam2 = `var_thread_${paramIdx}`;
1049
+ const createdAtParam2 = `var_createdAt_${paramIdx}`;
1050
+ const limitParam2 = `var_limit_${paramIdx}`;
1051
+ unionQueries.push(`
1052
+ SELECT id, content, role, type, "createdAt", thread_id AS "threadId", "resourceId"
1053
+ FROM "${storage.TABLE_MESSAGES}"
1054
+ WHERE thread_id = {${threadParam2}:String}
1055
+ AND createdAt > parseDateTime64BestEffort({${createdAtParam2}:String}, 3)
1056
+ ORDER BY createdAt ASC, id ASC
1057
+ LIMIT {${limitParam2}:Int64}
1058
+ `);
1059
+ params[threadParam2] = target.threadId;
1060
+ params[createdAtParam2] = target.createdAt;
1061
+ params[limitParam2] = withNextMessages;
1062
+ paramIdx++;
1063
+ }
1064
+ }
1065
+ if (unionQueries.length === 0) return [];
1066
+ let finalQuery;
1067
+ if (unionQueries.length === 1) {
1068
+ finalQuery = unionQueries[0];
1069
+ } else {
1070
+ finalQuery = `SELECT * FROM (${unionQueries.join(" UNION ALL ")}) ORDER BY "createdAt" ASC, id ASC`;
1071
+ }
1072
+ const includeResult = await this.client.query({
1073
+ query: finalQuery,
1074
+ query_params: params,
1075
+ clickhouse_settings: {
1076
+ date_time_input_format: "best_effort",
1077
+ date_time_output_format: "iso",
1078
+ use_client_time_zone: 1,
1079
+ output_format_json_quote_64bit_integers: 0
1080
+ }
1081
+ });
1082
+ const includeRows = await includeResult.json();
1083
+ const seen = /* @__PURE__ */ new Set();
1084
+ return transformRows(includeRows.data).filter((row) => {
1085
+ if (seen.has(row.id)) return false;
1086
+ seen.add(row.id);
1087
+ return true;
1088
+ });
1089
+ }
995
1090
  async saveMessages(args) {
996
1091
  const { messages } = args;
997
1092
  if (messages.length === 0) return { messages };
@@ -1912,6 +2007,11 @@ time for large tables. Please ensure you have a backup before proceeding.
1912
2007
  });
1913
2008
  }
1914
2009
  await this.#db.createTable({ tableName: storage.TABLE_SPANS, schema: storage.SPAN_SCHEMA });
2010
+ await this.#db.alterTable({
2011
+ tableName: storage.TABLE_SPANS,
2012
+ schema: storage.SPAN_SCHEMA,
2013
+ ifNotExists: ["requestContext"]
2014
+ });
1915
2015
  }
1916
2016
  async dangerouslyClearAll() {
1917
2017
  await this.#db.clearTable({ tableName: storage.TABLE_SPANS });
@@ -2184,7 +2284,8 @@ time for large tables. Please ensure you have a backup before proceeding.
2184
2284
  }
2185
2285
  async listTraces(args) {
2186
2286
  const { filters, pagination, orderBy } = storage.listTracesArgsSchema.parse(args);
2187
- const { page, perPage } = pagination;
2287
+ const page = pagination?.page ?? 0;
2288
+ const perPage = pagination?.perPage ?? 10;
2188
2289
  try {
2189
2290
  const conditions = [`(parentSpanId IS NULL OR parentSpanId = '')`];
2190
2291
  const values = {};
@@ -2331,8 +2432,8 @@ time for large tables. Please ensure you have a backup before proceeding.
2331
2432
  const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2332
2433
  const engine = TABLE_ENGINES[storage.TABLE_SPANS] ?? "MergeTree()";
2333
2434
  const finalClause = engine.startsWith("ReplacingMergeTree") ? "FINAL" : "";
2334
- const sortField = orderBy.field;
2335
- const sortDirection = orderBy.direction;
2435
+ const sortField = orderBy?.field ?? "startedAt";
2436
+ const sortDirection = orderBy?.direction ?? "DESC";
2336
2437
  let orderClause;
2337
2438
  if (sortField === "endedAt") {
2338
2439
  const nullSortValue = sortDirection === "DESC" ? 0 : 1;