@mastra/clickhouse 0.0.0-vnext-inngest-20250508131921 → 0.0.0-vnext-20251104230439

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,20 +1,19 @@
1
1
  import { createClient } from '@clickhouse/client';
2
- import { TABLE_EVALS, TABLE_THREADS, TABLE_TRACES, TABLE_WORKFLOW_SNAPSHOT, TABLE_MESSAGES, MastraStorage, TABLE_SCHEMAS } from '@mastra/core/storage';
2
+ import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
3
+ import { TABLE_AI_SPANS, TABLE_RESOURCES, TABLE_SCORERS, TABLE_THREADS, TABLE_TRACES, TABLE_WORKFLOW_SNAPSHOT, TABLE_MESSAGES, MastraStorage, StoreOperations, TABLE_SCHEMAS, WorkflowsStorage, normalizePerPage, ScoresStorage, safelyParseJSON, calculatePagination, MemoryStorage, resolveMessageLimit } from '@mastra/core/storage';
4
+ import { MessageList } from '@mastra/core/agent';
5
+ import { saveScorePayloadSchema } from '@mastra/core/evals';
3
6
 
4
7
  // src/storage/index.ts
5
- function safelyParseJSON(jsonString) {
6
- try {
7
- return JSON.parse(jsonString);
8
- } catch {
9
- return {};
10
- }
11
- }
12
8
  var TABLE_ENGINES = {
13
9
  [TABLE_MESSAGES]: `MergeTree()`,
14
10
  [TABLE_WORKFLOW_SNAPSHOT]: `ReplacingMergeTree()`,
15
11
  [TABLE_TRACES]: `MergeTree()`,
16
12
  [TABLE_THREADS]: `ReplacingMergeTree()`,
17
- [TABLE_EVALS]: `MergeTree()`
13
+ [TABLE_SCORERS]: `MergeTree()`,
14
+ [TABLE_RESOURCES]: `ReplacingMergeTree()`,
15
+ // TODO: verify this is the correct engine for ai spans when implementing clickhouse storage
16
+ [TABLE_AI_SPANS]: `ReplacingMergeTree()`
18
17
  };
19
18
  var COLUMN_TYPES = {
20
19
  text: "String",
@@ -22,11 +21,10 @@ var COLUMN_TYPES = {
22
21
  uuid: "String",
23
22
  jsonb: "String",
24
23
  integer: "Int64",
25
- bigint: "Int64"
24
+ float: "Float64",
25
+ bigint: "Int64",
26
+ boolean: "Bool"
26
27
  };
27
- function transformRows(rows) {
28
- return rows.map((row) => transformRow(row));
29
- }
30
28
  function transformRow(row) {
31
29
  if (!row) {
32
30
  return row;
@@ -37,54 +35,824 @@ function transformRow(row) {
37
35
  if (row.updatedAt) {
38
36
  row.updatedAt = new Date(row.updatedAt);
39
37
  }
38
+ if (row.content && typeof row.content === "string") {
39
+ row.content = safelyParseJSON(row.content);
40
+ }
40
41
  return row;
41
42
  }
42
- var ClickhouseStore = class extends MastraStorage {
43
- db;
44
- ttl = {};
45
- constructor(config) {
46
- super({ name: "ClickhouseStore" });
47
- this.db = createClient({
48
- url: config.url,
49
- username: config.username,
50
- password: config.password,
51
- clickhouse_settings: {
52
- date_time_input_format: "best_effort",
53
- date_time_output_format: "iso",
54
- // This is crucial
55
- use_client_time_zone: 1,
56
- output_format_json_quote_64bit_integers: 0
43
+ function transformRows(rows) {
44
+ return rows.map((row) => transformRow(row));
45
+ }
46
+
47
+ // src/storage/domains/memory/index.ts
48
+ var MemoryStorageClickhouse = class extends MemoryStorage {
49
+ client;
50
+ operations;
51
+ constructor({ client, operations }) {
52
+ super();
53
+ this.client = client;
54
+ this.operations = operations;
55
+ }
56
+ async getMessages({
57
+ threadId,
58
+ resourceId,
59
+ selectBy
60
+ }) {
61
+ try {
62
+ if (!threadId.trim()) throw new Error("threadId must be a non-empty string");
63
+ const messages = [];
64
+ const limit = resolveMessageLimit({ last: selectBy?.last, defaultLimit: 40 });
65
+ const include = selectBy?.include || [];
66
+ if (include.length) {
67
+ const unionQueries = [];
68
+ const params = [];
69
+ let paramIdx = 1;
70
+ for (const inc of include) {
71
+ const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
72
+ const searchId = inc.threadId || threadId;
73
+ unionQueries.push(`
74
+ SELECT * FROM (
75
+ WITH numbered_messages AS (
76
+ SELECT
77
+ id, content, role, type, "createdAt", thread_id, "resourceId",
78
+ ROW_NUMBER() OVER (ORDER BY "createdAt" ASC) as row_num
79
+ FROM "${TABLE_MESSAGES}"
80
+ WHERE thread_id = {var_thread_id_${paramIdx}:String}
81
+ ),
82
+ target_positions AS (
83
+ SELECT row_num as target_pos
84
+ FROM numbered_messages
85
+ WHERE id = {var_include_id_${paramIdx}:String}
86
+ )
87
+ SELECT DISTINCT m.id, m.content, m.role, m.type, m."createdAt", m.thread_id AS "threadId"
88
+ FROM numbered_messages m
89
+ CROSS JOIN target_positions t
90
+ WHERE m.row_num BETWEEN (t.target_pos - {var_withPreviousMessages_${paramIdx}:Int64}) AND (t.target_pos + {var_withNextMessages_${paramIdx}:Int64})
91
+ ) AS query_${paramIdx}
92
+ `);
93
+ params.push(
94
+ { [`var_thread_id_${paramIdx}`]: searchId },
95
+ { [`var_include_id_${paramIdx}`]: id },
96
+ { [`var_withPreviousMessages_${paramIdx}`]: withPreviousMessages },
97
+ { [`var_withNextMessages_${paramIdx}`]: withNextMessages }
98
+ );
99
+ paramIdx++;
100
+ }
101
+ const finalQuery = unionQueries.join(" UNION ALL ") + ' ORDER BY "createdAt" DESC';
102
+ const mergedParams = params.reduce((acc, paramObj) => ({ ...acc, ...paramObj }), {});
103
+ const includeResult = await this.client.query({
104
+ query: finalQuery,
105
+ query_params: mergedParams,
106
+ clickhouse_settings: {
107
+ date_time_input_format: "best_effort",
108
+ date_time_output_format: "iso",
109
+ use_client_time_zone: 1,
110
+ output_format_json_quote_64bit_integers: 0
111
+ }
112
+ });
113
+ const rows2 = await includeResult.json();
114
+ const includedMessages = transformRows(rows2.data);
115
+ const seen = /* @__PURE__ */ new Set();
116
+ const dedupedMessages = includedMessages.filter((message) => {
117
+ if (seen.has(message.id)) return false;
118
+ seen.add(message.id);
119
+ return true;
120
+ });
121
+ messages.push(...dedupedMessages);
57
122
  }
58
- });
59
- this.ttl = config.ttl;
123
+ let whereClause = "WHERE thread_id = {threadId:String}";
124
+ const queryParams = {
125
+ threadId,
126
+ exclude: messages.map((m) => m.id),
127
+ limit
128
+ };
129
+ if (resourceId) {
130
+ whereClause += ' AND "resourceId" = {resourceId:String}';
131
+ queryParams.resourceId = resourceId;
132
+ }
133
+ const result = await this.client.query({
134
+ query: `
135
+ SELECT
136
+ id,
137
+ content,
138
+ role,
139
+ type,
140
+ toDateTime64(createdAt, 3) as createdAt,
141
+ thread_id AS "threadId"
142
+ FROM "${TABLE_MESSAGES}"
143
+ ${whereClause}
144
+ AND id NOT IN ({exclude:Array(String)})
145
+ ORDER BY "createdAt" DESC
146
+ LIMIT {limit:Int64}
147
+ `,
148
+ query_params: queryParams,
149
+ clickhouse_settings: {
150
+ // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
151
+ date_time_input_format: "best_effort",
152
+ date_time_output_format: "iso",
153
+ use_client_time_zone: 1,
154
+ output_format_json_quote_64bit_integers: 0
155
+ }
156
+ });
157
+ const rows = await result.json();
158
+ messages.push(...transformRows(rows.data));
159
+ messages.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
160
+ messages.forEach((message) => {
161
+ if (typeof message.content === "string") {
162
+ try {
163
+ message.content = JSON.parse(message.content);
164
+ } catch {
165
+ }
166
+ }
167
+ });
168
+ const list = new MessageList({ threadId, resourceId }).add(
169
+ messages,
170
+ "memory"
171
+ );
172
+ return { messages: list.get.all.db() };
173
+ } catch (error) {
174
+ throw new MastraError(
175
+ {
176
+ id: "CLICKHOUSE_STORAGE_GET_MESSAGES_FAILED",
177
+ domain: ErrorDomain.STORAGE,
178
+ category: ErrorCategory.THIRD_PARTY,
179
+ details: { threadId, resourceId: resourceId ?? "" }
180
+ },
181
+ error
182
+ );
183
+ }
184
+ }
185
+ async listMessagesById({ messageIds }) {
186
+ if (messageIds.length === 0) return { messages: [] };
187
+ try {
188
+ const result = await this.client.query({
189
+ query: `
190
+ SELECT
191
+ id,
192
+ content,
193
+ role,
194
+ type,
195
+ toDateTime64(createdAt, 3) as createdAt,
196
+ thread_id AS "threadId",
197
+ "resourceId"
198
+ FROM "${TABLE_MESSAGES}"
199
+ WHERE id IN {messageIds:Array(String)}
200
+ ORDER BY "createdAt" DESC
201
+ `,
202
+ query_params: {
203
+ messageIds
204
+ },
205
+ clickhouse_settings: {
206
+ // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
207
+ date_time_input_format: "best_effort",
208
+ date_time_output_format: "iso",
209
+ use_client_time_zone: 1,
210
+ output_format_json_quote_64bit_integers: 0
211
+ }
212
+ });
213
+ const rows = await result.json();
214
+ const messages = transformRows(rows.data);
215
+ messages.forEach((message) => {
216
+ if (typeof message.content === "string") {
217
+ try {
218
+ message.content = JSON.parse(message.content);
219
+ } catch {
220
+ }
221
+ }
222
+ });
223
+ const list = new MessageList().add(messages, "memory");
224
+ return { messages: list.get.all.db() };
225
+ } catch (error) {
226
+ throw new MastraError(
227
+ {
228
+ id: "CLICKHOUSE_STORAGE_LIST_MESSAGES_BY_ID_FAILED",
229
+ domain: ErrorDomain.STORAGE,
230
+ category: ErrorCategory.THIRD_PARTY,
231
+ details: { messageIds: JSON.stringify(messageIds) }
232
+ },
233
+ error
234
+ );
235
+ }
60
236
  }
61
- transformEvalRow(row) {
62
- row = transformRow(row);
63
- const resultValue = JSON.parse(row.result);
64
- const testInfoValue = row.test_info ? JSON.parse(row.test_info) : void 0;
65
- if (!resultValue || typeof resultValue !== "object" || !("score" in resultValue)) {
66
- throw new Error(`Invalid MetricResult format: ${JSON.stringify(resultValue)}`);
237
+ async listMessages(args) {
238
+ const { threadId, resourceId, include, filter, perPage: perPageInput, page = 0, orderBy } = args;
239
+ if (page < 0) {
240
+ throw new MastraError(
241
+ {
242
+ id: "STORAGE_CLICKHOUSE_LIST_MESSAGES_INVALID_PAGE",
243
+ domain: ErrorDomain.STORAGE,
244
+ category: ErrorCategory.USER,
245
+ details: { page }
246
+ },
247
+ new Error("page must be >= 0")
248
+ );
249
+ }
250
+ if (!threadId.trim()) {
251
+ throw new MastraError(
252
+ {
253
+ id: "STORAGE_CLICKHOUSE_LIST_MESSAGES_INVALID_THREAD_ID",
254
+ domain: ErrorDomain.STORAGE,
255
+ category: ErrorCategory.THIRD_PARTY,
256
+ details: { threadId }
257
+ },
258
+ new Error("threadId must be a non-empty string")
259
+ );
260
+ }
261
+ const perPageForQuery = normalizePerPage(perPageInput, 40);
262
+ const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPageForQuery);
263
+ try {
264
+ let dataQuery = `
265
+ SELECT
266
+ id,
267
+ content,
268
+ role,
269
+ type,
270
+ toDateTime64(createdAt, 3) as createdAt,
271
+ thread_id AS "threadId",
272
+ resourceId
273
+ FROM ${TABLE_MESSAGES}
274
+ WHERE thread_id = {threadId:String}
275
+ `;
276
+ const dataParams = { threadId };
277
+ if (resourceId) {
278
+ dataQuery += ` AND resourceId = {resourceId:String}`;
279
+ dataParams.resourceId = resourceId;
280
+ }
281
+ if (filter?.dateRange?.start) {
282
+ const startDate = filter.dateRange.start instanceof Date ? filter.dateRange.start.toISOString() : new Date(filter.dateRange.start).toISOString();
283
+ dataQuery += ` AND createdAt >= parseDateTime64BestEffort({fromDate:String}, 3)`;
284
+ dataParams.fromDate = startDate;
285
+ }
286
+ if (filter?.dateRange?.end) {
287
+ const endDate = filter.dateRange.end instanceof Date ? filter.dateRange.end.toISOString() : new Date(filter.dateRange.end).toISOString();
288
+ dataQuery += ` AND createdAt <= parseDateTime64BestEffort({toDate:String}, 3)`;
289
+ dataParams.toDate = endDate;
290
+ }
291
+ const { field, direction } = this.parseOrderBy(orderBy);
292
+ dataQuery += ` ORDER BY "${field}" ${direction}`;
293
+ if (perPageForResponse === false) ; else {
294
+ dataQuery += ` LIMIT {limit:Int64} OFFSET {offset:Int64}`;
295
+ dataParams.limit = perPageForQuery;
296
+ dataParams.offset = offset;
297
+ }
298
+ const result = await this.client.query({
299
+ query: dataQuery,
300
+ query_params: dataParams,
301
+ clickhouse_settings: {
302
+ date_time_input_format: "best_effort",
303
+ date_time_output_format: "iso",
304
+ use_client_time_zone: 1,
305
+ output_format_json_quote_64bit_integers: 0
306
+ }
307
+ });
308
+ const rows = await result.json();
309
+ const paginatedMessages = transformRows(rows.data);
310
+ const paginatedCount = paginatedMessages.length;
311
+ let countQuery = `SELECT count() as total FROM ${TABLE_MESSAGES} WHERE thread_id = {threadId:String}`;
312
+ const countParams = { threadId };
313
+ if (resourceId) {
314
+ countQuery += ` AND resourceId = {resourceId:String}`;
315
+ countParams.resourceId = resourceId;
316
+ }
317
+ if (filter?.dateRange?.start) {
318
+ const startDate = filter.dateRange.start instanceof Date ? filter.dateRange.start.toISOString() : new Date(filter.dateRange.start).toISOString();
319
+ countQuery += ` AND createdAt >= parseDateTime64BestEffort({fromDate:String}, 3)`;
320
+ countParams.fromDate = startDate;
321
+ }
322
+ if (filter?.dateRange?.end) {
323
+ const endDate = filter.dateRange.end instanceof Date ? filter.dateRange.end.toISOString() : new Date(filter.dateRange.end).toISOString();
324
+ countQuery += ` AND createdAt <= parseDateTime64BestEffort({toDate:String}, 3)`;
325
+ countParams.toDate = endDate;
326
+ }
327
+ const countResult = await this.client.query({
328
+ query: countQuery,
329
+ query_params: countParams,
330
+ clickhouse_settings: {
331
+ date_time_input_format: "best_effort",
332
+ date_time_output_format: "iso",
333
+ use_client_time_zone: 1,
334
+ output_format_json_quote_64bit_integers: 0
335
+ }
336
+ });
337
+ const countData = await countResult.json();
338
+ const total = countData.data[0].total;
339
+ if (total === 0 && paginatedCount === 0 && (!include || include.length === 0)) {
340
+ return {
341
+ messages: [],
342
+ total: 0,
343
+ page,
344
+ perPage: perPageForResponse,
345
+ hasMore: false
346
+ };
347
+ }
348
+ const messageIds = new Set(paginatedMessages.map((m) => m.id));
349
+ let includeMessages = [];
350
+ if (include && include.length > 0) {
351
+ const unionQueries = [];
352
+ const params = [];
353
+ let paramIdx = 1;
354
+ for (const inc of include) {
355
+ const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
356
+ const searchId = inc.threadId || threadId;
357
+ unionQueries.push(`
358
+ SELECT * FROM (
359
+ WITH numbered_messages AS (
360
+ SELECT
361
+ id, content, role, type, "createdAt", thread_id, "resourceId",
362
+ ROW_NUMBER() OVER (ORDER BY "createdAt" ASC) as row_num
363
+ FROM "${TABLE_MESSAGES}"
364
+ WHERE thread_id = {var_thread_id_${paramIdx}:String}
365
+ ),
366
+ target_positions AS (
367
+ SELECT row_num as target_pos
368
+ FROM numbered_messages
369
+ WHERE id = {var_include_id_${paramIdx}:String}
370
+ )
371
+ SELECT DISTINCT m.id, m.content, m.role, m.type, m."createdAt", m.thread_id AS "threadId", m."resourceId"
372
+ FROM numbered_messages m
373
+ CROSS JOIN target_positions t
374
+ WHERE m.row_num BETWEEN (t.target_pos - {var_withPreviousMessages_${paramIdx}:Int64}) AND (t.target_pos + {var_withNextMessages_${paramIdx}:Int64})
375
+ ) AS query_${paramIdx}
376
+ `);
377
+ params.push(
378
+ { [`var_thread_id_${paramIdx}`]: searchId },
379
+ { [`var_include_id_${paramIdx}`]: id },
380
+ { [`var_withPreviousMessages_${paramIdx}`]: withPreviousMessages },
381
+ { [`var_withNextMessages_${paramIdx}`]: withNextMessages }
382
+ );
383
+ paramIdx++;
384
+ }
385
+ const finalQuery = unionQueries.join(" UNION ALL ") + ' ORDER BY "createdAt" ASC';
386
+ const mergedParams = params.reduce((acc, paramObj) => ({ ...acc, ...paramObj }), {});
387
+ const includeResult = await this.client.query({
388
+ query: finalQuery,
389
+ query_params: mergedParams,
390
+ clickhouse_settings: {
391
+ date_time_input_format: "best_effort",
392
+ date_time_output_format: "iso",
393
+ use_client_time_zone: 1,
394
+ output_format_json_quote_64bit_integers: 0
395
+ }
396
+ });
397
+ const includeRows = await includeResult.json();
398
+ includeMessages = transformRows(includeRows.data);
399
+ for (const includeMsg of includeMessages) {
400
+ if (!messageIds.has(includeMsg.id)) {
401
+ paginatedMessages.push(includeMsg);
402
+ messageIds.add(includeMsg.id);
403
+ }
404
+ }
405
+ }
406
+ const list = new MessageList().add(paginatedMessages, "memory");
407
+ let finalMessages = list.get.all.db();
408
+ finalMessages = finalMessages.sort((a, b) => {
409
+ const isDateField = field === "createdAt" || field === "updatedAt";
410
+ const aValue = isDateField ? new Date(a[field]).getTime() : a[field];
411
+ const bValue = isDateField ? new Date(b[field]).getTime() : b[field];
412
+ if (aValue === bValue) {
413
+ return a.id.localeCompare(b.id);
414
+ }
415
+ if (typeof aValue === "number" && typeof bValue === "number") {
416
+ return direction === "ASC" ? aValue - bValue : bValue - aValue;
417
+ }
418
+ return direction === "ASC" ? String(aValue).localeCompare(String(bValue)) : String(bValue).localeCompare(String(aValue));
419
+ });
420
+ const returnedThreadMessageIds = new Set(finalMessages.filter((m) => m.threadId === threadId).map((m) => m.id));
421
+ const allThreadMessagesReturned = returnedThreadMessageIds.size >= total;
422
+ const hasMore = perPageForResponse === false ? false : allThreadMessagesReturned ? false : offset + paginatedCount < total;
423
+ return {
424
+ messages: finalMessages,
425
+ total,
426
+ page,
427
+ perPage: perPageForResponse,
428
+ hasMore
429
+ };
430
+ } catch (error) {
431
+ const mastraError = new MastraError(
432
+ {
433
+ id: "STORAGE_CLICKHOUSE_STORE_LIST_MESSAGES_FAILED",
434
+ domain: ErrorDomain.STORAGE,
435
+ category: ErrorCategory.THIRD_PARTY,
436
+ details: {
437
+ threadId,
438
+ resourceId: resourceId ?? ""
439
+ }
440
+ },
441
+ error
442
+ );
443
+ this.logger?.error?.(mastraError.toString());
444
+ this.logger?.trackException?.(mastraError);
445
+ return {
446
+ messages: [],
447
+ total: 0,
448
+ page,
449
+ perPage: perPageForResponse,
450
+ hasMore: false
451
+ };
452
+ }
453
+ }
454
+ async saveMessages(args) {
455
+ const { messages } = args;
456
+ if (messages.length === 0) return { messages };
457
+ for (const message of messages) {
458
+ const resourceId = message.resourceId;
459
+ if (!resourceId) {
460
+ throw new Error("Resource ID is required");
461
+ }
462
+ if (!message.threadId) {
463
+ throw new Error("Thread ID is required");
464
+ }
465
+ const thread = await this.getThreadById({ threadId: message.threadId });
466
+ if (!thread) {
467
+ throw new Error(`Thread ${message.threadId} not found`);
468
+ }
469
+ }
470
+ const threadIdSet = /* @__PURE__ */ new Map();
471
+ await Promise.all(
472
+ messages.map(async (m) => {
473
+ const resourceId = m.resourceId;
474
+ if (!resourceId) {
475
+ throw new Error("Resource ID is required");
476
+ }
477
+ if (!m.threadId) {
478
+ throw new Error("Thread ID is required");
479
+ }
480
+ const thread = await this.getThreadById({ threadId: m.threadId });
481
+ if (!thread) {
482
+ throw new Error(`Thread ${m.threadId} not found`);
483
+ }
484
+ threadIdSet.set(m.threadId, thread);
485
+ })
486
+ );
487
+ try {
488
+ const existingResult = await this.client.query({
489
+ query: `SELECT id, thread_id FROM ${TABLE_MESSAGES} WHERE id IN ({ids:Array(String)})`,
490
+ query_params: {
491
+ ids: messages.map((m) => m.id)
492
+ },
493
+ clickhouse_settings: {
494
+ // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
495
+ date_time_input_format: "best_effort",
496
+ date_time_output_format: "iso",
497
+ use_client_time_zone: 1,
498
+ output_format_json_quote_64bit_integers: 0
499
+ },
500
+ format: "JSONEachRow"
501
+ });
502
+ const existingRows = await existingResult.json();
503
+ const existingSet = new Set(existingRows.map((row) => `${row.id}::${row.thread_id}`));
504
+ const toInsert = messages.filter((m) => !existingSet.has(`${m.id}::${m.threadId}`));
505
+ const toUpdate = messages.filter((m) => existingSet.has(`${m.id}::${m.threadId}`));
506
+ const toMove = messages.filter((m) => {
507
+ const existingRow = existingRows.find((row) => row.id === m.id);
508
+ return existingRow && existingRow.thread_id !== m.threadId;
509
+ });
510
+ const deletePromises = toMove.map((message) => {
511
+ const existingRow = existingRows.find((row) => row.id === message.id);
512
+ if (!existingRow) return Promise.resolve();
513
+ return this.client.command({
514
+ query: `DELETE FROM ${TABLE_MESSAGES} WHERE id = {var_id:String} AND thread_id = {var_old_thread_id:String}`,
515
+ query_params: {
516
+ var_id: message.id,
517
+ var_old_thread_id: existingRow.thread_id
518
+ },
519
+ clickhouse_settings: {
520
+ date_time_input_format: "best_effort",
521
+ use_client_time_zone: 1,
522
+ output_format_json_quote_64bit_integers: 0
523
+ }
524
+ });
525
+ });
526
+ const updatePromises = toUpdate.map(
527
+ (message) => this.client.command({
528
+ query: `
529
+ ALTER TABLE ${TABLE_MESSAGES}
530
+ UPDATE content = {var_content:String}, role = {var_role:String}, type = {var_type:String}, resourceId = {var_resourceId:String}
531
+ WHERE id = {var_id:String} AND thread_id = {var_thread_id:String}
532
+ `,
533
+ query_params: {
534
+ var_content: typeof message.content === "string" ? message.content : JSON.stringify(message.content),
535
+ var_role: message.role,
536
+ var_type: message.type || "v2",
537
+ var_resourceId: message.resourceId,
538
+ var_id: message.id,
539
+ var_thread_id: message.threadId
540
+ },
541
+ clickhouse_settings: {
542
+ // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
543
+ date_time_input_format: "best_effort",
544
+ use_client_time_zone: 1,
545
+ output_format_json_quote_64bit_integers: 0
546
+ }
547
+ })
548
+ );
549
+ await Promise.all([
550
+ // Insert new messages (including moved messages)
551
+ this.client.insert({
552
+ table: TABLE_MESSAGES,
553
+ format: "JSONEachRow",
554
+ values: toInsert.map((message) => ({
555
+ id: message.id,
556
+ thread_id: message.threadId,
557
+ resourceId: message.resourceId,
558
+ content: typeof message.content === "string" ? message.content : JSON.stringify(message.content),
559
+ createdAt: message.createdAt.toISOString(),
560
+ role: message.role,
561
+ type: message.type || "v2"
562
+ })),
563
+ clickhouse_settings: {
564
+ // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
565
+ date_time_input_format: "best_effort",
566
+ use_client_time_zone: 1,
567
+ output_format_json_quote_64bit_integers: 0
568
+ }
569
+ }),
570
+ ...updatePromises,
571
+ ...deletePromises,
572
+ // Update thread's updatedAt timestamp
573
+ this.client.insert({
574
+ table: TABLE_THREADS,
575
+ format: "JSONEachRow",
576
+ values: Array.from(threadIdSet.values()).map((thread) => ({
577
+ id: thread.id,
578
+ resourceId: thread.resourceId,
579
+ title: thread.title,
580
+ metadata: thread.metadata,
581
+ createdAt: thread.createdAt,
582
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
583
+ })),
584
+ clickhouse_settings: {
585
+ date_time_input_format: "best_effort",
586
+ use_client_time_zone: 1,
587
+ output_format_json_quote_64bit_integers: 0
588
+ }
589
+ })
590
+ ]);
591
+ const list = new MessageList().add(messages, "memory");
592
+ return { messages: list.get.all.db() };
593
+ } catch (error) {
594
+ throw new MastraError(
595
+ {
596
+ id: "CLICKHOUSE_STORAGE_SAVE_MESSAGES_FAILED",
597
+ domain: ErrorDomain.STORAGE,
598
+ category: ErrorCategory.THIRD_PARTY
599
+ },
600
+ error
601
+ );
602
+ }
603
+ }
604
+ async getThreadById({ threadId }) {
605
+ try {
606
+ const result = await this.client.query({
607
+ query: `SELECT
608
+ id,
609
+ "resourceId",
610
+ title,
611
+ metadata,
612
+ toDateTime64(createdAt, 3) as createdAt,
613
+ toDateTime64(updatedAt, 3) as updatedAt
614
+ FROM "${TABLE_THREADS}"
615
+ FINAL
616
+ WHERE id = {var_id:String}`,
617
+ query_params: { var_id: threadId },
618
+ clickhouse_settings: {
619
+ // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
620
+ date_time_input_format: "best_effort",
621
+ date_time_output_format: "iso",
622
+ use_client_time_zone: 1,
623
+ output_format_json_quote_64bit_integers: 0
624
+ }
625
+ });
626
+ const rows = await result.json();
627
+ const thread = transformRow(rows.data[0]);
628
+ if (!thread) {
629
+ return null;
630
+ }
631
+ return {
632
+ ...thread,
633
+ metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
634
+ createdAt: thread.createdAt,
635
+ updatedAt: thread.updatedAt
636
+ };
637
+ } catch (error) {
638
+ throw new MastraError(
639
+ {
640
+ id: "CLICKHOUSE_STORAGE_GET_THREAD_BY_ID_FAILED",
641
+ domain: ErrorDomain.STORAGE,
642
+ category: ErrorCategory.THIRD_PARTY,
643
+ details: { threadId }
644
+ },
645
+ error
646
+ );
647
+ }
648
+ }
649
+ async saveThread({ thread }) {
650
+ try {
651
+ await this.client.insert({
652
+ table: TABLE_THREADS,
653
+ values: [
654
+ {
655
+ ...thread,
656
+ createdAt: thread.createdAt.toISOString(),
657
+ updatedAt: thread.updatedAt.toISOString()
658
+ }
659
+ ],
660
+ format: "JSONEachRow",
661
+ clickhouse_settings: {
662
+ // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
663
+ date_time_input_format: "best_effort",
664
+ use_client_time_zone: 1,
665
+ output_format_json_quote_64bit_integers: 0
666
+ }
667
+ });
668
+ return thread;
669
+ } catch (error) {
670
+ throw new MastraError(
671
+ {
672
+ id: "CLICKHOUSE_STORAGE_SAVE_THREAD_FAILED",
673
+ domain: ErrorDomain.STORAGE,
674
+ category: ErrorCategory.THIRD_PARTY,
675
+ details: { threadId: thread.id }
676
+ },
677
+ error
678
+ );
679
+ }
680
+ }
681
+ async updateThread({
682
+ id,
683
+ title,
684
+ metadata
685
+ }) {
686
+ try {
687
+ const existingThread = await this.getThreadById({ threadId: id });
688
+ if (!existingThread) {
689
+ throw new Error(`Thread ${id} not found`);
690
+ }
691
+ const mergedMetadata = {
692
+ ...existingThread.metadata,
693
+ ...metadata
694
+ };
695
+ const updatedThread = {
696
+ ...existingThread,
697
+ title,
698
+ metadata: mergedMetadata,
699
+ updatedAt: /* @__PURE__ */ new Date()
700
+ };
701
+ await this.client.insert({
702
+ table: TABLE_THREADS,
703
+ format: "JSONEachRow",
704
+ values: [
705
+ {
706
+ id: updatedThread.id,
707
+ resourceId: updatedThread.resourceId,
708
+ title: updatedThread.title,
709
+ metadata: updatedThread.metadata,
710
+ createdAt: updatedThread.createdAt,
711
+ updatedAt: updatedThread.updatedAt.toISOString()
712
+ }
713
+ ],
714
+ clickhouse_settings: {
715
+ date_time_input_format: "best_effort",
716
+ use_client_time_zone: 1,
717
+ output_format_json_quote_64bit_integers: 0
718
+ }
719
+ });
720
+ return updatedThread;
721
+ } catch (error) {
722
+ throw new MastraError(
723
+ {
724
+ id: "CLICKHOUSE_STORAGE_UPDATE_THREAD_FAILED",
725
+ domain: ErrorDomain.STORAGE,
726
+ category: ErrorCategory.THIRD_PARTY,
727
+ details: { threadId: id, title }
728
+ },
729
+ error
730
+ );
731
+ }
732
+ }
733
+ async deleteThread({ threadId }) {
734
+ try {
735
+ await this.client.command({
736
+ query: `DELETE FROM "${TABLE_MESSAGES}" WHERE thread_id = {var_thread_id:String};`,
737
+ query_params: { var_thread_id: threadId },
738
+ clickhouse_settings: {
739
+ output_format_json_quote_64bit_integers: 0
740
+ }
741
+ });
742
+ await this.client.command({
743
+ query: `DELETE FROM "${TABLE_THREADS}" WHERE id = {var_id:String};`,
744
+ query_params: { var_id: threadId },
745
+ clickhouse_settings: {
746
+ output_format_json_quote_64bit_integers: 0
747
+ }
748
+ });
749
+ } catch (error) {
750
+ throw new MastraError(
751
+ {
752
+ id: "CLICKHOUSE_STORAGE_DELETE_THREAD_FAILED",
753
+ domain: ErrorDomain.STORAGE,
754
+ category: ErrorCategory.THIRD_PARTY,
755
+ details: { threadId }
756
+ },
757
+ error
758
+ );
759
+ }
760
+ }
761
+ async listThreadsByResourceId(args) {
762
+ const { resourceId, page = 0, perPage: perPageInput, orderBy } = args;
763
+ const perPage = normalizePerPage(perPageInput, 100);
764
+ if (page < 0) {
765
+ throw new MastraError(
766
+ {
767
+ id: "STORAGE_CLICKHOUSE_LIST_THREADS_BY_RESOURCE_ID_INVALID_PAGE",
768
+ domain: ErrorDomain.STORAGE,
769
+ category: ErrorCategory.USER,
770
+ details: { page }
771
+ },
772
+ new Error("page must be >= 0")
773
+ );
774
+ }
775
+ const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
776
+ const { field, direction } = this.parseOrderBy(orderBy);
777
+ try {
778
+ const countResult = await this.client.query({
779
+ query: `SELECT count() as total FROM ${TABLE_THREADS} WHERE resourceId = {resourceId:String}`,
780
+ query_params: { resourceId },
781
+ clickhouse_settings: {
782
+ date_time_input_format: "best_effort",
783
+ date_time_output_format: "iso",
784
+ use_client_time_zone: 1,
785
+ output_format_json_quote_64bit_integers: 0
786
+ }
787
+ });
788
+ const countData = await countResult.json();
789
+ const total = countData.data[0].total;
790
+ if (total === 0) {
791
+ return {
792
+ threads: [],
793
+ total: 0,
794
+ page,
795
+ perPage: perPageForResponse,
796
+ hasMore: false
797
+ };
798
+ }
799
+ const dataResult = await this.client.query({
800
+ query: `
801
+ SELECT
802
+ id,
803
+ resourceId,
804
+ title,
805
+ metadata,
806
+ toDateTime64(createdAt, 3) as createdAt,
807
+ toDateTime64(updatedAt, 3) as updatedAt
808
+ FROM ${TABLE_THREADS}
809
+ WHERE resourceId = {resourceId:String}
810
+ ORDER BY "${field}" ${direction === "DESC" ? "DESC" : "ASC"}
811
+ LIMIT {perPage:Int64} OFFSET {offset:Int64}
812
+ `,
813
+ query_params: {
814
+ resourceId,
815
+ perPage,
816
+ offset
817
+ },
818
+ clickhouse_settings: {
819
+ date_time_input_format: "best_effort",
820
+ date_time_output_format: "iso",
821
+ use_client_time_zone: 1,
822
+ output_format_json_quote_64bit_integers: 0
823
+ }
824
+ });
825
+ const rows = await dataResult.json();
826
+ const threads = transformRows(rows.data);
827
+ return {
828
+ threads,
829
+ total,
830
+ page,
831
+ perPage: perPageForResponse,
832
+ hasMore: offset + perPage < total
833
+ };
834
+ } catch (error) {
835
+ throw new MastraError(
836
+ {
837
+ id: "CLICKHOUSE_STORAGE_LIST_THREADS_BY_RESOURCE_ID_FAILED",
838
+ domain: ErrorDomain.STORAGE,
839
+ category: ErrorCategory.THIRD_PARTY,
840
+ details: { resourceId, page }
841
+ },
842
+ error
843
+ );
67
844
  }
68
- return {
69
- input: row.input,
70
- output: row.output,
71
- result: resultValue,
72
- agentName: row.agent_name,
73
- metricName: row.metric_name,
74
- instructions: row.instructions,
75
- testInfo: testInfoValue,
76
- globalRunId: row.global_run_id,
77
- runId: row.run_id,
78
- createdAt: row.created_at
79
- };
80
845
  }
81
- async getEvalsByAgentName(agentName, type) {
846
+ async updateMessages(args) {
847
+ const { messages } = args;
848
+ if (messages.length === 0) {
849
+ return [];
850
+ }
82
851
  try {
83
- const baseQuery = `SELECT *, toDateTime64(createdAt, 3) as createdAt FROM ${TABLE_EVALS} WHERE agent_name = {var_agent_name:String}`;
84
- const typeCondition = type === "test" ? " AND test_info IS NOT NULL AND JSONExtractString(test_info, 'testPath') IS NOT NULL" : type === "live" ? " AND (test_info IS NULL OR JSONExtractString(test_info, 'testPath') IS NULL)" : "";
85
- const result = await this.db.query({
86
- query: `${baseQuery}${typeCondition} ORDER BY createdAt DESC`,
87
- query_params: { var_agent_name: agentName },
852
+ const messageIds = messages.map((m) => m.id);
853
+ const existingResult = await this.client.query({
854
+ query: `SELECT id, content, role, type, "createdAt", thread_id AS "threadId", "resourceId" FROM ${TABLE_MESSAGES} WHERE id IN (${messageIds.map((_, i) => `{id_${i}:String}`).join(",")})`,
855
+ query_params: messageIds.reduce((acc, m, i) => ({ ...acc, [`id_${i}`]: m }), {}),
88
856
  clickhouse_settings: {
89
857
  date_time_input_format: "best_effort",
90
858
  date_time_output_format: "iso",
@@ -92,131 +860,437 @@ var ClickhouseStore = class extends MastraStorage {
92
860
  output_format_json_quote_64bit_integers: 0
93
861
  }
94
862
  });
95
- if (!result) {
863
+ const existingRows = await existingResult.json();
864
+ const existingMessages = transformRows(existingRows.data);
865
+ if (existingMessages.length === 0) {
96
866
  return [];
97
867
  }
98
- const rows = await result.json();
99
- return rows.data.map((row) => this.transformEvalRow(row));
868
+ const parsedExistingMessages = existingMessages.map((msg) => {
869
+ if (typeof msg.content === "string") {
870
+ try {
871
+ msg.content = JSON.parse(msg.content);
872
+ } catch {
873
+ }
874
+ }
875
+ return msg;
876
+ });
877
+ const threadIdsToUpdate = /* @__PURE__ */ new Set();
878
+ const updatePromises = [];
879
+ for (const existingMessage of parsedExistingMessages) {
880
+ const updatePayload = messages.find((m) => m.id === existingMessage.id);
881
+ if (!updatePayload) continue;
882
+ const { id, ...fieldsToUpdate } = updatePayload;
883
+ if (Object.keys(fieldsToUpdate).length === 0) continue;
884
+ threadIdsToUpdate.add(existingMessage.threadId);
885
+ if (updatePayload.threadId && updatePayload.threadId !== existingMessage.threadId) {
886
+ threadIdsToUpdate.add(updatePayload.threadId);
887
+ }
888
+ const setClauses = [];
889
+ const values = {};
890
+ let paramIdx = 1;
891
+ let newContent = null;
892
+ const updatableFields = { ...fieldsToUpdate };
893
+ if (updatableFields.content) {
894
+ const existingContent = existingMessage.content || {};
895
+ const existingMetadata = existingContent.metadata || {};
896
+ const updateMetadata = updatableFields.content.metadata || {};
897
+ newContent = {
898
+ ...existingContent,
899
+ ...updatableFields.content,
900
+ // Deep merge metadata
901
+ metadata: {
902
+ ...existingMetadata,
903
+ ...updateMetadata
904
+ }
905
+ };
906
+ setClauses.push(`content = {var_content_${paramIdx}:String}`);
907
+ values[`var_content_${paramIdx}`] = JSON.stringify(newContent);
908
+ paramIdx++;
909
+ delete updatableFields.content;
910
+ }
911
+ for (const key in updatableFields) {
912
+ if (Object.prototype.hasOwnProperty.call(updatableFields, key)) {
913
+ const dbColumn = key === "threadId" ? "thread_id" : key;
914
+ setClauses.push(`"${dbColumn}" = {var_${key}_${paramIdx}:String}`);
915
+ values[`var_${key}_${paramIdx}`] = updatableFields[key];
916
+ paramIdx++;
917
+ }
918
+ }
919
+ if (setClauses.length > 0) {
920
+ values[`var_id_${paramIdx}`] = id;
921
+ const updateQuery = `
922
+ ALTER TABLE ${TABLE_MESSAGES}
923
+ UPDATE ${setClauses.join(", ")}
924
+ WHERE id = {var_id_${paramIdx}:String}
925
+ `;
926
+ console.info("Updating message:", id, "with query:", updateQuery, "values:", values);
927
+ updatePromises.push(
928
+ this.client.command({
929
+ query: updateQuery,
930
+ query_params: values,
931
+ clickhouse_settings: {
932
+ date_time_input_format: "best_effort",
933
+ use_client_time_zone: 1,
934
+ output_format_json_quote_64bit_integers: 0
935
+ }
936
+ })
937
+ );
938
+ }
939
+ }
940
+ if (updatePromises.length > 0) {
941
+ await Promise.all(updatePromises);
942
+ }
943
+ await this.client.command({
944
+ query: `OPTIMIZE TABLE ${TABLE_MESSAGES} FINAL`,
945
+ clickhouse_settings: {
946
+ date_time_input_format: "best_effort",
947
+ use_client_time_zone: 1,
948
+ output_format_json_quote_64bit_integers: 0
949
+ }
950
+ });
951
+ for (const existingMessage of parsedExistingMessages) {
952
+ const updatePayload = messages.find((m) => m.id === existingMessage.id);
953
+ if (!updatePayload) continue;
954
+ const { id, ...fieldsToUpdate } = updatePayload;
955
+ if (Object.keys(fieldsToUpdate).length === 0) continue;
956
+ const verifyResult = await this.client.query({
957
+ query: `SELECT id, content, role, type, "createdAt", thread_id AS "threadId", "resourceId" FROM ${TABLE_MESSAGES} WHERE id = {messageId:String}`,
958
+ query_params: { messageId: id },
959
+ clickhouse_settings: {
960
+ date_time_input_format: "best_effort",
961
+ date_time_output_format: "iso",
962
+ use_client_time_zone: 1,
963
+ output_format_json_quote_64bit_integers: 0
964
+ }
965
+ });
966
+ const verifyRows = await verifyResult.json();
967
+ if (verifyRows.data.length > 0) {
968
+ const updatedMessage = transformRows(verifyRows.data)[0];
969
+ if (updatedMessage) {
970
+ let needsRetry = false;
971
+ for (const [key, value] of Object.entries(fieldsToUpdate)) {
972
+ if (key === "content") {
973
+ const expectedContent = typeof value === "string" ? value : JSON.stringify(value);
974
+ const actualContent = typeof updatedMessage.content === "string" ? updatedMessage.content : JSON.stringify(updatedMessage.content);
975
+ if (actualContent !== expectedContent) {
976
+ needsRetry = true;
977
+ break;
978
+ }
979
+ } else if (updatedMessage[key] !== value) {
980
+ needsRetry = true;
981
+ break;
982
+ }
983
+ }
984
+ if (needsRetry) {
985
+ console.info("Update not applied correctly, retrying with DELETE + INSERT for message:", id);
986
+ await this.client.command({
987
+ query: `DELETE FROM ${TABLE_MESSAGES} WHERE id = {messageId:String}`,
988
+ query_params: { messageId: id },
989
+ clickhouse_settings: {
990
+ date_time_input_format: "best_effort",
991
+ use_client_time_zone: 1,
992
+ output_format_json_quote_64bit_integers: 0
993
+ }
994
+ });
995
+ let updatedContent = existingMessage.content || {};
996
+ if (fieldsToUpdate.content) {
997
+ const existingContent = existingMessage.content || {};
998
+ const existingMetadata = existingContent.metadata || {};
999
+ const updateMetadata = fieldsToUpdate.content.metadata || {};
1000
+ updatedContent = {
1001
+ ...existingContent,
1002
+ ...fieldsToUpdate.content,
1003
+ metadata: {
1004
+ ...existingMetadata,
1005
+ ...updateMetadata
1006
+ }
1007
+ };
1008
+ }
1009
+ const updatedMessageData = {
1010
+ ...existingMessage,
1011
+ ...fieldsToUpdate,
1012
+ content: updatedContent
1013
+ };
1014
+ await this.client.insert({
1015
+ table: TABLE_MESSAGES,
1016
+ format: "JSONEachRow",
1017
+ values: [
1018
+ {
1019
+ id: updatedMessageData.id,
1020
+ thread_id: updatedMessageData.threadId,
1021
+ resourceId: updatedMessageData.resourceId,
1022
+ content: typeof updatedMessageData.content === "string" ? updatedMessageData.content : JSON.stringify(updatedMessageData.content),
1023
+ createdAt: updatedMessageData.createdAt.toISOString(),
1024
+ role: updatedMessageData.role,
1025
+ type: updatedMessageData.type || "v2"
1026
+ }
1027
+ ],
1028
+ clickhouse_settings: {
1029
+ date_time_input_format: "best_effort",
1030
+ use_client_time_zone: 1,
1031
+ output_format_json_quote_64bit_integers: 0
1032
+ }
1033
+ });
1034
+ }
1035
+ }
1036
+ }
1037
+ }
1038
+ if (threadIdsToUpdate.size > 0) {
1039
+ await new Promise((resolve) => setTimeout(resolve, 10));
1040
+ const now = (/* @__PURE__ */ new Date()).toISOString().replace("Z", "");
1041
+ const threadUpdatePromises = Array.from(threadIdsToUpdate).map(async (threadId) => {
1042
+ const threadResult = await this.client.query({
1043
+ query: `SELECT id, resourceId, title, metadata, createdAt FROM ${TABLE_THREADS} WHERE id = {threadId:String}`,
1044
+ query_params: { threadId },
1045
+ clickhouse_settings: {
1046
+ date_time_input_format: "best_effort",
1047
+ date_time_output_format: "iso",
1048
+ use_client_time_zone: 1,
1049
+ output_format_json_quote_64bit_integers: 0
1050
+ }
1051
+ });
1052
+ const threadRows = await threadResult.json();
1053
+ if (threadRows.data.length > 0) {
1054
+ const existingThread = threadRows.data[0];
1055
+ await this.client.command({
1056
+ query: `DELETE FROM ${TABLE_THREADS} WHERE id = {threadId:String}`,
1057
+ query_params: { threadId },
1058
+ clickhouse_settings: {
1059
+ date_time_input_format: "best_effort",
1060
+ use_client_time_zone: 1,
1061
+ output_format_json_quote_64bit_integers: 0
1062
+ }
1063
+ });
1064
+ await this.client.insert({
1065
+ table: TABLE_THREADS,
1066
+ format: "JSONEachRow",
1067
+ values: [
1068
+ {
1069
+ id: existingThread.id,
1070
+ resourceId: existingThread.resourceId,
1071
+ title: existingThread.title,
1072
+ metadata: existingThread.metadata,
1073
+ createdAt: existingThread.createdAt,
1074
+ updatedAt: now
1075
+ }
1076
+ ],
1077
+ clickhouse_settings: {
1078
+ date_time_input_format: "best_effort",
1079
+ use_client_time_zone: 1,
1080
+ output_format_json_quote_64bit_integers: 0
1081
+ }
1082
+ });
1083
+ }
1084
+ });
1085
+ await Promise.all(threadUpdatePromises);
1086
+ }
1087
+ const updatedMessages = [];
1088
+ for (const messageId of messageIds) {
1089
+ const updatedResult = await this.client.query({
1090
+ query: `SELECT id, content, role, type, "createdAt", thread_id AS "threadId", "resourceId" FROM ${TABLE_MESSAGES} WHERE id = {messageId:String}`,
1091
+ query_params: { messageId },
1092
+ clickhouse_settings: {
1093
+ date_time_input_format: "best_effort",
1094
+ date_time_output_format: "iso",
1095
+ use_client_time_zone: 1,
1096
+ output_format_json_quote_64bit_integers: 0
1097
+ }
1098
+ });
1099
+ const updatedRows = await updatedResult.json();
1100
+ if (updatedRows.data.length > 0) {
1101
+ const message = transformRows(updatedRows.data)[0];
1102
+ if (message) {
1103
+ updatedMessages.push(message);
1104
+ }
1105
+ }
1106
+ }
1107
+ return updatedMessages.map((message) => {
1108
+ if (typeof message.content === "string") {
1109
+ try {
1110
+ message.content = JSON.parse(message.content);
1111
+ } catch {
1112
+ }
1113
+ }
1114
+ return message;
1115
+ });
100
1116
  } catch (error) {
101
- if (error instanceof Error && error.message.includes("no such table")) {
102
- return [];
1117
+ throw new MastraError(
1118
+ {
1119
+ id: "CLICKHOUSE_STORAGE_UPDATE_MESSAGES_FAILED",
1120
+ domain: ErrorDomain.STORAGE,
1121
+ category: ErrorCategory.THIRD_PARTY,
1122
+ details: { messageIds: messages.map((m) => m.id).join(",") }
1123
+ },
1124
+ error
1125
+ );
1126
+ }
1127
+ }
1128
+ async getResourceById({ resourceId }) {
1129
+ try {
1130
+ const result = await this.client.query({
1131
+ query: `SELECT id, workingMemory, metadata, createdAt, updatedAt FROM ${TABLE_RESOURCES} WHERE id = {resourceId:String}`,
1132
+ query_params: { resourceId },
1133
+ clickhouse_settings: {
1134
+ date_time_input_format: "best_effort",
1135
+ date_time_output_format: "iso",
1136
+ use_client_time_zone: 1,
1137
+ output_format_json_quote_64bit_integers: 0
1138
+ }
1139
+ });
1140
+ const rows = await result.json();
1141
+ if (rows.data.length === 0) {
1142
+ return null;
103
1143
  }
104
- this.logger.error("Failed to get evals for the specified agent: " + error?.message);
105
- throw error;
1144
+ const resource = rows.data[0];
1145
+ return {
1146
+ id: resource.id,
1147
+ workingMemory: resource.workingMemory && typeof resource.workingMemory === "object" ? JSON.stringify(resource.workingMemory) : resource.workingMemory,
1148
+ metadata: resource.metadata && typeof resource.metadata === "string" ? JSON.parse(resource.metadata) : resource.metadata,
1149
+ createdAt: new Date(resource.createdAt),
1150
+ updatedAt: new Date(resource.updatedAt)
1151
+ };
1152
+ } catch (error) {
1153
+ throw new MastraError(
1154
+ {
1155
+ id: "CLICKHOUSE_STORAGE_GET_RESOURCE_BY_ID_FAILED",
1156
+ domain: ErrorDomain.STORAGE,
1157
+ category: ErrorCategory.THIRD_PARTY,
1158
+ details: { resourceId }
1159
+ },
1160
+ error
1161
+ );
106
1162
  }
107
1163
  }
108
- async batchInsert({ tableName, records }) {
1164
+ async saveResource({ resource }) {
109
1165
  try {
110
- await this.db.insert({
111
- table: tableName,
112
- values: records.map((record) => ({
113
- ...Object.fromEntries(
114
- Object.entries(record).map(([key, value]) => [
115
- key,
116
- TABLE_SCHEMAS[tableName]?.[key]?.type === "timestamp" ? new Date(value).toISOString() : value
117
- ])
118
- )
119
- })),
1166
+ await this.client.insert({
1167
+ table: TABLE_RESOURCES,
120
1168
  format: "JSONEachRow",
1169
+ values: [
1170
+ {
1171
+ id: resource.id,
1172
+ workingMemory: resource.workingMemory,
1173
+ metadata: JSON.stringify(resource.metadata),
1174
+ createdAt: resource.createdAt.toISOString(),
1175
+ updatedAt: resource.updatedAt.toISOString()
1176
+ }
1177
+ ],
121
1178
  clickhouse_settings: {
122
- // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
123
1179
  date_time_input_format: "best_effort",
124
1180
  use_client_time_zone: 1,
125
1181
  output_format_json_quote_64bit_integers: 0
126
1182
  }
127
1183
  });
1184
+ return resource;
128
1185
  } catch (error) {
129
- console.error(`Error inserting into ${tableName}:`, error);
130
- throw error;
1186
+ throw new MastraError(
1187
+ {
1188
+ id: "CLICKHOUSE_STORAGE_SAVE_RESOURCE_FAILED",
1189
+ domain: ErrorDomain.STORAGE,
1190
+ category: ErrorCategory.THIRD_PARTY,
1191
+ details: { resourceId: resource.id }
1192
+ },
1193
+ error
1194
+ );
131
1195
  }
132
1196
  }
133
- async getTraces({
134
- name,
135
- scope,
136
- page,
137
- perPage,
138
- attributes,
139
- filters,
140
- fromDate,
141
- toDate
1197
+ async updateResource({
1198
+ resourceId,
1199
+ workingMemory,
1200
+ metadata
142
1201
  }) {
143
- const limit = perPage;
144
- const offset = page * perPage;
145
- const args = {};
146
- const conditions = [];
147
- if (name) {
148
- conditions.push(`name LIKE CONCAT({var_name:String}, '%')`);
149
- args.var_name = name;
150
- }
151
- if (scope) {
152
- conditions.push(`scope = {var_scope:String}`);
153
- args.var_scope = scope;
154
- }
155
- if (attributes) {
156
- Object.entries(attributes).forEach(([key, value]) => {
157
- conditions.push(`JSONExtractString(attributes, '${key}') = {var_attr_${key}:String}`);
158
- args[`var_attr_${key}`] = value;
159
- });
160
- }
161
- if (filters) {
162
- Object.entries(filters).forEach(([key, value]) => {
163
- conditions.push(
164
- `${key} = {var_col_${key}:${COLUMN_TYPES[TABLE_SCHEMAS.mastra_traces?.[key]?.type ?? "text"]}}`
165
- );
166
- args[`var_col_${key}`] = value;
167
- });
168
- }
169
- if (fromDate) {
170
- conditions.push(`createdAt >= {var_from_date:DateTime64(3)}`);
171
- args.var_from_date = fromDate.getTime() / 1e3;
172
- }
173
- if (toDate) {
174
- conditions.push(`createdAt <= {var_to_date:DateTime64(3)}`);
175
- args.var_to_date = toDate.getTime() / 1e3;
176
- }
177
- const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
178
- const result = await this.db.query({
179
- query: `SELECT *, toDateTime64(createdAt, 3) as createdAt FROM ${TABLE_TRACES} ${whereClause} ORDER BY "createdAt" DESC LIMIT ${limit} OFFSET ${offset}`,
180
- query_params: args,
181
- clickhouse_settings: {
182
- // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
183
- date_time_input_format: "best_effort",
184
- date_time_output_format: "iso",
185
- use_client_time_zone: 1,
186
- output_format_json_quote_64bit_integers: 0
1202
+ try {
1203
+ const existingResource = await this.getResourceById({ resourceId });
1204
+ if (!existingResource) {
1205
+ const newResource = {
1206
+ id: resourceId,
1207
+ workingMemory,
1208
+ metadata: metadata || {},
1209
+ createdAt: /* @__PURE__ */ new Date(),
1210
+ updatedAt: /* @__PURE__ */ new Date()
1211
+ };
1212
+ return this.saveResource({ resource: newResource });
187
1213
  }
188
- });
189
- if (!result) {
190
- return [];
1214
+ const updatedResource = {
1215
+ ...existingResource,
1216
+ workingMemory: workingMemory !== void 0 ? workingMemory : existingResource.workingMemory,
1217
+ metadata: {
1218
+ ...existingResource.metadata,
1219
+ ...metadata
1220
+ },
1221
+ updatedAt: /* @__PURE__ */ new Date()
1222
+ };
1223
+ const updateQuery = `
1224
+ ALTER TABLE ${TABLE_RESOURCES}
1225
+ UPDATE workingMemory = {workingMemory:String}, metadata = {metadata:String}, updatedAt = {updatedAt:String}
1226
+ WHERE id = {resourceId:String}
1227
+ `;
1228
+ await this.client.command({
1229
+ query: updateQuery,
1230
+ query_params: {
1231
+ workingMemory: updatedResource.workingMemory,
1232
+ metadata: JSON.stringify(updatedResource.metadata),
1233
+ updatedAt: updatedResource.updatedAt.toISOString().replace("Z", ""),
1234
+ resourceId
1235
+ },
1236
+ clickhouse_settings: {
1237
+ date_time_input_format: "best_effort",
1238
+ use_client_time_zone: 1,
1239
+ output_format_json_quote_64bit_integers: 0
1240
+ }
1241
+ });
1242
+ await this.client.command({
1243
+ query: `OPTIMIZE TABLE ${TABLE_RESOURCES} FINAL`,
1244
+ clickhouse_settings: {
1245
+ date_time_input_format: "best_effort",
1246
+ use_client_time_zone: 1,
1247
+ output_format_json_quote_64bit_integers: 0
1248
+ }
1249
+ });
1250
+ return updatedResource;
1251
+ } catch (error) {
1252
+ throw new MastraError(
1253
+ {
1254
+ id: "CLICKHOUSE_STORAGE_UPDATE_RESOURCE_FAILED",
1255
+ domain: ErrorDomain.STORAGE,
1256
+ category: ErrorCategory.THIRD_PARTY,
1257
+ details: { resourceId }
1258
+ },
1259
+ error
1260
+ );
191
1261
  }
192
- const resp = await result.json();
193
- const rows = resp.data;
194
- return rows.map((row) => ({
195
- id: row.id,
196
- parentSpanId: row.parentSpanId,
197
- traceId: row.traceId,
198
- name: row.name,
199
- scope: row.scope,
200
- kind: row.kind,
201
- status: safelyParseJSON(row.status),
202
- events: safelyParseJSON(row.events),
203
- links: safelyParseJSON(row.links),
204
- attributes: safelyParseJSON(row.attributes),
205
- startTime: row.startTime,
206
- endTime: row.endTime,
207
- other: safelyParseJSON(row.other),
208
- createdAt: row.createdAt
209
- }));
210
1262
  }
211
- async optimizeTable({ tableName }) {
212
- await this.db.command({
213
- query: `OPTIMIZE TABLE ${tableName} FINAL`
214
- });
1263
+ };
1264
+ var StoreOperationsClickhouse = class extends StoreOperations {
1265
+ ttl;
1266
+ client;
1267
+ constructor({ client, ttl }) {
1268
+ super();
1269
+ this.ttl = ttl;
1270
+ this.client = client;
215
1271
  }
216
- async materializeTtl({ tableName }) {
217
- await this.db.command({
218
- query: `ALTER TABLE ${tableName} MATERIALIZE TTL;`
1272
+ async hasColumn(table, column) {
1273
+ const result = await this.client.query({
1274
+ query: `DESCRIBE TABLE ${table}`,
1275
+ format: "JSONEachRow"
219
1276
  });
1277
+ const columns = await result.json();
1278
+ return columns.some((c) => c.name === column);
1279
+ }
1280
+ getSqlType(type) {
1281
+ switch (type) {
1282
+ case "text":
1283
+ return "String";
1284
+ case "timestamp":
1285
+ return "DateTime64(3)";
1286
+ case "integer":
1287
+ case "bigint":
1288
+ return "Int64";
1289
+ case "jsonb":
1290
+ return "String";
1291
+ default:
1292
+ return super.getSqlType(type);
1293
+ }
220
1294
  }
221
1295
  async createTable({
222
1296
  tableName,
@@ -231,27 +1305,25 @@ var ClickhouseStore = class extends MastraStorage {
231
1305
  }).join(",\n");
232
1306
  const rowTtl = this.ttl?.[tableName]?.row;
233
1307
  const sql = tableName === TABLE_WORKFLOW_SNAPSHOT ? `
234
- CREATE TABLE IF NOT EXISTS ${tableName} (
235
- ${["id String"].concat(columns)}
236
- )
237
- ENGINE = ${TABLE_ENGINES[tableName]}
238
- PARTITION BY "createdAt"
239
- PRIMARY KEY (createdAt, run_id, workflow_name)
240
- ORDER BY (createdAt, run_id, workflow_name)
241
- ${rowTtl ? `TTL toDateTime(${rowTtl.ttlKey ?? "createdAt"}) + INTERVAL ${rowTtl.interval} ${rowTtl.unit}` : ""}
242
- SETTINGS index_granularity = 8192
243
- ` : `
244
- CREATE TABLE IF NOT EXISTS ${tableName} (
245
- ${columns}
246
- )
247
- ENGINE = ${TABLE_ENGINES[tableName]}
248
- PARTITION BY "createdAt"
249
- PRIMARY KEY (createdAt, ${tableName === TABLE_EVALS ? "run_id" : "id"})
250
- ORDER BY (createdAt, ${tableName === TABLE_EVALS ? "run_id" : "id"})
251
- ${this.ttl?.[tableName]?.row ? `TTL toDateTime(createdAt) + INTERVAL ${this.ttl[tableName].row.interval} ${this.ttl[tableName].row.unit}` : ""}
252
- SETTINGS index_granularity = 8192
253
- `;
254
- await this.db.query({
1308
+ CREATE TABLE IF NOT EXISTS ${tableName} (
1309
+ ${["id String"].concat(columns)}
1310
+ )
1311
+ ENGINE = ${TABLE_ENGINES[tableName] ?? "MergeTree()"}
1312
+ PRIMARY KEY (createdAt, run_id, workflow_name)
1313
+ ORDER BY (createdAt, run_id, workflow_name)
1314
+ ${rowTtl ? `TTL toDateTime(${rowTtl.ttlKey ?? "createdAt"}) + INTERVAL ${rowTtl.interval} ${rowTtl.unit}` : ""}
1315
+ SETTINGS index_granularity = 8192
1316
+ ` : `
1317
+ CREATE TABLE IF NOT EXISTS ${tableName} (
1318
+ ${columns}
1319
+ )
1320
+ ENGINE = ${TABLE_ENGINES[tableName] ?? "MergeTree()"}
1321
+ PRIMARY KEY (createdAt, ${"id"})
1322
+ ORDER BY (createdAt, ${"id"})
1323
+ ${this.ttl?.[tableName]?.row ? `TTL toDateTime(createdAt) + INTERVAL ${this.ttl[tableName].row.interval} ${this.ttl[tableName].row.unit}` : ""}
1324
+ SETTINGS index_granularity = 8192
1325
+ `;
1326
+ await this.client.query({
255
1327
  query: sql,
256
1328
  clickhouse_settings: {
257
1329
  // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
@@ -262,13 +1334,59 @@ var ClickhouseStore = class extends MastraStorage {
262
1334
  }
263
1335
  });
264
1336
  } catch (error) {
265
- console.error(`Error creating table ${tableName}:`, error);
266
- throw error;
1337
+ throw new MastraError(
1338
+ {
1339
+ id: "CLICKHOUSE_STORAGE_CREATE_TABLE_FAILED",
1340
+ domain: ErrorDomain.STORAGE,
1341
+ category: ErrorCategory.THIRD_PARTY,
1342
+ details: { tableName }
1343
+ },
1344
+ error
1345
+ );
1346
+ }
1347
+ }
1348
+ async alterTable({
1349
+ tableName,
1350
+ schema,
1351
+ ifNotExists
1352
+ }) {
1353
+ try {
1354
+ const describeSql = `DESCRIBE TABLE ${tableName}`;
1355
+ const result = await this.client.query({
1356
+ query: describeSql
1357
+ });
1358
+ const rows = await result.json();
1359
+ const existingColumnNames = new Set(rows.data.map((row) => row.name.toLowerCase()));
1360
+ for (const columnName of ifNotExists) {
1361
+ if (!existingColumnNames.has(columnName.toLowerCase()) && schema[columnName]) {
1362
+ const columnDef = schema[columnName];
1363
+ let sqlType = this.getSqlType(columnDef.type);
1364
+ if (columnDef.nullable !== false) {
1365
+ sqlType = `Nullable(${sqlType})`;
1366
+ }
1367
+ const defaultValue = columnDef.nullable === false ? this.getDefaultValue(columnDef.type) : "";
1368
+ const alterSql = `ALTER TABLE ${tableName} ADD COLUMN IF NOT EXISTS "${columnName}" ${sqlType} ${defaultValue}`.trim();
1369
+ await this.client.query({
1370
+ query: alterSql
1371
+ });
1372
+ this.logger?.debug?.(`Added column ${columnName} to table ${tableName}`);
1373
+ }
1374
+ }
1375
+ } catch (error) {
1376
+ throw new MastraError(
1377
+ {
1378
+ id: "CLICKHOUSE_STORAGE_ALTER_TABLE_FAILED",
1379
+ domain: ErrorDomain.STORAGE,
1380
+ category: ErrorCategory.THIRD_PARTY,
1381
+ details: { tableName }
1382
+ },
1383
+ error
1384
+ );
267
1385
  }
268
1386
  }
269
1387
  async clearTable({ tableName }) {
270
1388
  try {
271
- await this.db.query({
1389
+ await this.client.query({
272
1390
  query: `TRUNCATE TABLE ${tableName}`,
273
1391
  clickhouse_settings: {
274
1392
  // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
@@ -279,19 +1397,33 @@ var ClickhouseStore = class extends MastraStorage {
279
1397
  }
280
1398
  });
281
1399
  } catch (error) {
282
- console.error(`Error clearing table ${tableName}:`, error);
283
- throw error;
1400
+ throw new MastraError(
1401
+ {
1402
+ id: "CLICKHOUSE_STORAGE_CLEAR_TABLE_FAILED",
1403
+ domain: ErrorDomain.STORAGE,
1404
+ category: ErrorCategory.THIRD_PARTY,
1405
+ details: { tableName }
1406
+ },
1407
+ error
1408
+ );
284
1409
  }
285
1410
  }
1411
+ async dropTable({ tableName }) {
1412
+ await this.client.query({
1413
+ query: `DROP TABLE IF EXISTS ${tableName}`
1414
+ });
1415
+ }
286
1416
  async insert({ tableName, record }) {
1417
+ const createdAt = (record.createdAt || record.created_at || /* @__PURE__ */ new Date()).toISOString();
1418
+ const updatedAt = (record.updatedAt || /* @__PURE__ */ new Date()).toISOString();
287
1419
  try {
288
- await this.db.insert({
1420
+ const result = await this.client.insert({
289
1421
  table: tableName,
290
1422
  values: [
291
1423
  {
292
1424
  ...record,
293
- createdAt: record.createdAt.toISOString(),
294
- updatedAt: record.updatedAt.toISOString()
1425
+ createdAt,
1426
+ updatedAt
295
1427
  }
296
1428
  ],
297
1429
  format: "JSONEachRow",
@@ -302,13 +1434,55 @@ var ClickhouseStore = class extends MastraStorage {
302
1434
  use_client_time_zone: 1
303
1435
  }
304
1436
  });
1437
+ console.info("INSERT RESULT", result);
1438
+ } catch (error) {
1439
+ throw new MastraError(
1440
+ {
1441
+ id: "CLICKHOUSE_STORAGE_INSERT_FAILED",
1442
+ domain: ErrorDomain.STORAGE,
1443
+ category: ErrorCategory.THIRD_PARTY,
1444
+ details: { tableName }
1445
+ },
1446
+ error
1447
+ );
1448
+ }
1449
+ }
1450
+ async batchInsert({ tableName, records }) {
1451
+ const recordsToBeInserted = records.map((record) => ({
1452
+ ...Object.fromEntries(
1453
+ Object.entries(record).map(([key, value]) => [
1454
+ key,
1455
+ TABLE_SCHEMAS[tableName]?.[key]?.type === "timestamp" ? new Date(value).toISOString() : value
1456
+ ])
1457
+ )
1458
+ }));
1459
+ try {
1460
+ await this.client.insert({
1461
+ table: tableName,
1462
+ values: recordsToBeInserted,
1463
+ format: "JSONEachRow",
1464
+ clickhouse_settings: {
1465
+ // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
1466
+ date_time_input_format: "best_effort",
1467
+ use_client_time_zone: 1,
1468
+ output_format_json_quote_64bit_integers: 0
1469
+ }
1470
+ });
305
1471
  } catch (error) {
306
- console.error(`Error inserting into ${tableName}:`, error);
307
- throw error;
1472
+ throw new MastraError(
1473
+ {
1474
+ id: "CLICKHOUSE_STORAGE_BATCH_INSERT_FAILED",
1475
+ domain: ErrorDomain.STORAGE,
1476
+ category: ErrorCategory.THIRD_PARTY,
1477
+ details: { tableName }
1478
+ },
1479
+ error
1480
+ );
308
1481
  }
309
1482
  }
310
1483
  async load({ tableName, keys }) {
311
1484
  try {
1485
+ const engine = TABLE_ENGINES[tableName] ?? "MergeTree()";
312
1486
  const keyEntries = Object.entries(keys);
313
1487
  const conditions = keyEntries.map(
314
1488
  ([key]) => `"${key}" = {var_${key}:${COLUMN_TYPES[TABLE_SCHEMAS[tableName]?.[key]?.type ?? "text"]}}`
@@ -316,8 +1490,10 @@ var ClickhouseStore = class extends MastraStorage {
316
1490
  const values = keyEntries.reduce((acc, [key, value]) => {
317
1491
  return { ...acc, [`var_${key}`]: value };
318
1492
  }, {});
319
- const result = await this.db.query({
320
- query: `SELECT *, toDateTime64(createdAt, 3) as createdAt, toDateTime64(updatedAt, 3) as updatedAt FROM ${tableName} ${TABLE_ENGINES[tableName].startsWith("ReplacingMergeTree") ? "FINAL" : ""} WHERE ${conditions}`,
1493
+ const hasUpdatedAt = TABLE_SCHEMAS[tableName]?.updatedAt;
1494
+ const selectClause = `SELECT *, toDateTime64(createdAt, 3) as createdAt${hasUpdatedAt ? ", toDateTime64(updatedAt, 3) as updatedAt" : ""}`;
1495
+ const result = await this.client.query({
1496
+ query: `${selectClause} FROM ${tableName} ${engine.startsWith("ReplacingMergeTree") ? "FINAL" : ""} WHERE ${conditions} ORDER BY createdAt DESC LIMIT 1`,
321
1497
  query_params: values,
322
1498
  clickhouse_settings: {
323
1499
  // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
@@ -344,24 +1520,57 @@ var ClickhouseStore = class extends MastraStorage {
344
1520
  const data = transformRow(rows.data[0]);
345
1521
  return data;
346
1522
  } catch (error) {
347
- console.error(`Error loading from ${tableName}:`, error);
348
- throw error;
1523
+ throw new MastraError(
1524
+ {
1525
+ id: "CLICKHOUSE_STORAGE_LOAD_FAILED",
1526
+ domain: ErrorDomain.STORAGE,
1527
+ category: ErrorCategory.THIRD_PARTY,
1528
+ details: { tableName }
1529
+ },
1530
+ error
1531
+ );
349
1532
  }
350
1533
  }
351
- async getThreadById({ threadId }) {
1534
+ };
1535
+ var ScoresStorageClickhouse = class extends ScoresStorage {
1536
+ client;
1537
+ operations;
1538
+ constructor({ client, operations }) {
1539
+ super();
1540
+ this.client = client;
1541
+ this.operations = operations;
1542
+ }
1543
+ transformScoreRow(row) {
1544
+ const scorer = safelyParseJSON(row.scorer);
1545
+ const preprocessStepResult = safelyParseJSON(row.preprocessStepResult);
1546
+ const analyzeStepResult = safelyParseJSON(row.analyzeStepResult);
1547
+ const metadata = safelyParseJSON(row.metadata);
1548
+ const input = safelyParseJSON(row.input);
1549
+ const output = safelyParseJSON(row.output);
1550
+ const additionalContext = safelyParseJSON(row.additionalContext);
1551
+ const requestContext = safelyParseJSON(row.requestContext);
1552
+ const entity = safelyParseJSON(row.entity);
1553
+ return {
1554
+ ...row,
1555
+ scorer,
1556
+ preprocessStepResult,
1557
+ analyzeStepResult,
1558
+ metadata,
1559
+ input,
1560
+ output,
1561
+ additionalContext,
1562
+ requestContext,
1563
+ entity,
1564
+ createdAt: new Date(row.createdAt),
1565
+ updatedAt: new Date(row.updatedAt)
1566
+ };
1567
+ }
1568
+ async getScoreById({ id }) {
352
1569
  try {
353
- const result = await this.db.query({
354
- query: `SELECT
355
- id,
356
- "resourceId",
357
- title,
358
- metadata,
359
- toDateTime64(createdAt, 3) as createdAt,
360
- toDateTime64(updatedAt, 3) as updatedAt
361
- FROM "${TABLE_THREADS}"
362
- FINAL
363
- WHERE id = {var_id:String}`,
364
- query_params: { var_id: threadId },
1570
+ const result = await this.client.query({
1571
+ query: `SELECT * FROM ${TABLE_SCORERS} WHERE id = {var_id:String}`,
1572
+ query_params: { var_id: id },
1573
+ format: "JSONEachRow",
365
1574
  clickhouse_settings: {
366
1575
  // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
367
1576
  date_time_input_format: "best_effort",
@@ -370,223 +1579,269 @@ var ClickhouseStore = class extends MastraStorage {
370
1579
  output_format_json_quote_64bit_integers: 0
371
1580
  }
372
1581
  });
373
- const rows = await result.json();
374
- const thread = transformRow(rows.data[0]);
375
- if (!thread) {
1582
+ const resultJson = await result.json();
1583
+ if (!Array.isArray(resultJson) || resultJson.length === 0) {
376
1584
  return null;
377
1585
  }
378
- return {
379
- ...thread,
380
- metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
381
- createdAt: thread.createdAt,
382
- updatedAt: thread.updatedAt
383
- };
1586
+ return this.transformScoreRow(resultJson[0]);
384
1587
  } catch (error) {
385
- console.error(`Error getting thread ${threadId}:`, error);
386
- throw error;
1588
+ throw new MastraError(
1589
+ {
1590
+ id: "CLICKHOUSE_STORAGE_GET_SCORE_BY_ID_FAILED",
1591
+ domain: ErrorDomain.STORAGE,
1592
+ category: ErrorCategory.THIRD_PARTY,
1593
+ details: { scoreId: id }
1594
+ },
1595
+ error
1596
+ );
387
1597
  }
388
1598
  }
389
- async getThreadsByResourceId({ resourceId }) {
1599
+ async saveScore(score) {
1600
+ let parsedScore;
390
1601
  try {
391
- const result = await this.db.query({
392
- query: `SELECT
393
- id,
394
- "resourceId",
395
- title,
396
- metadata,
397
- toDateTime64(createdAt, 3) as createdAt,
398
- toDateTime64(updatedAt, 3) as updatedAt
399
- FROM "${TABLE_THREADS}"
400
- WHERE "resourceId" = {var_resourceId:String}`,
401
- query_params: { var_resourceId: resourceId },
402
- clickhouse_settings: {
403
- // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
404
- date_time_input_format: "best_effort",
405
- date_time_output_format: "iso",
406
- use_client_time_zone: 1,
407
- output_format_json_quote_64bit_integers: 0
408
- }
409
- });
410
- const rows = await result.json();
411
- const threads = transformRows(rows.data);
412
- return threads.map((thread) => ({
413
- ...thread,
414
- metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
415
- createdAt: thread.createdAt,
416
- updatedAt: thread.updatedAt
417
- }));
1602
+ parsedScore = saveScorePayloadSchema.parse(score);
418
1603
  } catch (error) {
419
- console.error(`Error getting threads for resource ${resourceId}:`, error);
420
- throw error;
1604
+ throw new MastraError(
1605
+ {
1606
+ id: "CLICKHOUSE_STORAGE_SAVE_SCORE_FAILED_INVALID_SCORE_PAYLOAD",
1607
+ domain: ErrorDomain.STORAGE,
1608
+ category: ErrorCategory.USER,
1609
+ details: { scoreId: score.id }
1610
+ },
1611
+ error
1612
+ );
421
1613
  }
422
- }
423
- async saveThread({ thread }) {
424
1614
  try {
425
- await this.db.insert({
426
- table: TABLE_THREADS,
427
- values: [
428
- {
429
- ...thread,
430
- createdAt: thread.createdAt.toISOString(),
431
- updatedAt: thread.updatedAt.toISOString()
432
- }
433
- ],
1615
+ const record = {
1616
+ ...parsedScore
1617
+ };
1618
+ await this.client.insert({
1619
+ table: TABLE_SCORERS,
1620
+ values: [record],
434
1621
  format: "JSONEachRow",
435
1622
  clickhouse_settings: {
436
- // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
437
1623
  date_time_input_format: "best_effort",
438
1624
  use_client_time_zone: 1,
439
1625
  output_format_json_quote_64bit_integers: 0
440
1626
  }
441
1627
  });
442
- return thread;
1628
+ return { score };
443
1629
  } catch (error) {
444
- console.error("Error saving thread:", error);
445
- throw error;
1630
+ throw new MastraError(
1631
+ {
1632
+ id: "CLICKHOUSE_STORAGE_SAVE_SCORE_FAILED",
1633
+ domain: ErrorDomain.STORAGE,
1634
+ category: ErrorCategory.THIRD_PARTY,
1635
+ details: { scoreId: score.id }
1636
+ },
1637
+ error
1638
+ );
446
1639
  }
447
1640
  }
448
- async updateThread({
449
- id,
450
- title,
451
- metadata
1641
+ async listScoresByRunId({
1642
+ runId,
1643
+ pagination
452
1644
  }) {
453
1645
  try {
454
- const existingThread = await this.getThreadById({ threadId: id });
455
- if (!existingThread) {
456
- throw new Error(`Thread ${id} not found`);
457
- }
458
- const mergedMetadata = {
459
- ...existingThread.metadata,
460
- ...metadata
461
- };
462
- const updatedThread = {
463
- ...existingThread,
464
- title,
465
- metadata: mergedMetadata,
466
- updatedAt: /* @__PURE__ */ new Date()
467
- };
468
- await this.db.insert({
469
- table: TABLE_THREADS,
470
- values: [
471
- {
472
- ...updatedThread,
473
- updatedAt: updatedThread.updatedAt.toISOString()
474
- }
475
- ],
1646
+ const countResult = await this.client.query({
1647
+ query: `SELECT COUNT(*) as count FROM ${TABLE_SCORERS} WHERE runId = {var_runId:String}`,
1648
+ query_params: { var_runId: runId },
1649
+ format: "JSONEachRow"
1650
+ });
1651
+ const countRows = await countResult.json();
1652
+ let total = 0;
1653
+ if (Array.isArray(countRows) && countRows.length > 0 && countRows[0]) {
1654
+ const countObj = countRows[0];
1655
+ total = Number(countObj.count);
1656
+ }
1657
+ const { page, perPage: perPageInput } = pagination;
1658
+ if (!total) {
1659
+ return {
1660
+ pagination: {
1661
+ total: 0,
1662
+ page,
1663
+ perPage: perPageInput,
1664
+ hasMore: false
1665
+ },
1666
+ scores: []
1667
+ };
1668
+ }
1669
+ const perPage = normalizePerPage(perPageInput, 100);
1670
+ const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
1671
+ const limitValue = perPageInput === false ? total : perPage;
1672
+ const end = perPageInput === false ? total : start + perPage;
1673
+ const result = await this.client.query({
1674
+ query: `SELECT * FROM ${TABLE_SCORERS} WHERE runId = {var_runId:String} ORDER BY createdAt DESC LIMIT {var_limit:Int64} OFFSET {var_offset:Int64}`,
1675
+ query_params: {
1676
+ var_runId: runId,
1677
+ var_limit: limitValue,
1678
+ var_offset: start
1679
+ },
476
1680
  format: "JSONEachRow",
477
1681
  clickhouse_settings: {
478
- // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
479
1682
  date_time_input_format: "best_effort",
1683
+ date_time_output_format: "iso",
480
1684
  use_client_time_zone: 1,
481
1685
  output_format_json_quote_64bit_integers: 0
482
1686
  }
483
1687
  });
484
- return updatedThread;
1688
+ const rows = await result.json();
1689
+ const scores = Array.isArray(rows) ? rows.map((row) => this.transformScoreRow(row)) : [];
1690
+ return {
1691
+ pagination: {
1692
+ total,
1693
+ page,
1694
+ perPage: perPageForResponse,
1695
+ hasMore: end < total
1696
+ },
1697
+ scores
1698
+ };
485
1699
  } catch (error) {
486
- console.error("Error updating thread:", error);
487
- throw error;
1700
+ throw new MastraError(
1701
+ {
1702
+ id: "CLICKHOUSE_STORAGE_GET_SCORES_BY_RUN_ID_FAILED",
1703
+ domain: ErrorDomain.STORAGE,
1704
+ category: ErrorCategory.THIRD_PARTY,
1705
+ details: { runId }
1706
+ },
1707
+ error
1708
+ );
488
1709
  }
489
1710
  }
490
- async deleteThread({ threadId }) {
1711
+ async listScoresByScorerId({
1712
+ scorerId,
1713
+ entityId,
1714
+ entityType,
1715
+ source,
1716
+ pagination
1717
+ }) {
1718
+ let whereClause = `scorerId = {var_scorerId:String}`;
1719
+ if (entityId) {
1720
+ whereClause += ` AND entityId = {var_entityId:String}`;
1721
+ }
1722
+ if (entityType) {
1723
+ whereClause += ` AND entityType = {var_entityType:String}`;
1724
+ }
1725
+ if (source) {
1726
+ whereClause += ` AND source = {var_source:String}`;
1727
+ }
491
1728
  try {
492
- await this.db.command({
493
- query: `DELETE FROM "${TABLE_MESSAGES}" WHERE thread_id = '${threadId}';`,
494
- query_params: { var_thread_id: threadId },
495
- clickhouse_settings: {
496
- output_format_json_quote_64bit_integers: 0
497
- }
1729
+ const countResult = await this.client.query({
1730
+ query: `SELECT COUNT(*) as count FROM ${TABLE_SCORERS} WHERE ${whereClause}`,
1731
+ query_params: {
1732
+ var_scorerId: scorerId,
1733
+ var_entityId: entityId,
1734
+ var_entityType: entityType,
1735
+ var_source: source
1736
+ },
1737
+ format: "JSONEachRow"
498
1738
  });
499
- await this.db.command({
500
- query: `DELETE FROM "${TABLE_THREADS}" WHERE id = {var_id:String};`,
501
- query_params: { var_id: threadId },
1739
+ const countRows = await countResult.json();
1740
+ let total = 0;
1741
+ if (Array.isArray(countRows) && countRows.length > 0 && countRows[0]) {
1742
+ const countObj = countRows[0];
1743
+ total = Number(countObj.count);
1744
+ }
1745
+ const { page, perPage: perPageInput } = pagination;
1746
+ if (!total) {
1747
+ return {
1748
+ pagination: {
1749
+ total: 0,
1750
+ page,
1751
+ perPage: perPageInput,
1752
+ hasMore: false
1753
+ },
1754
+ scores: []
1755
+ };
1756
+ }
1757
+ const perPage = normalizePerPage(perPageInput, 100);
1758
+ const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
1759
+ const limitValue = perPageInput === false ? total : perPage;
1760
+ const end = perPageInput === false ? total : start + perPage;
1761
+ const result = await this.client.query({
1762
+ query: `SELECT * FROM ${TABLE_SCORERS} WHERE ${whereClause} ORDER BY createdAt DESC LIMIT {var_limit:Int64} OFFSET {var_offset:Int64}`,
1763
+ query_params: {
1764
+ var_scorerId: scorerId,
1765
+ var_limit: limitValue,
1766
+ var_offset: start,
1767
+ var_entityId: entityId,
1768
+ var_entityType: entityType,
1769
+ var_source: source
1770
+ },
1771
+ format: "JSONEachRow",
502
1772
  clickhouse_settings: {
1773
+ date_time_input_format: "best_effort",
1774
+ date_time_output_format: "iso",
1775
+ use_client_time_zone: 1,
503
1776
  output_format_json_quote_64bit_integers: 0
504
1777
  }
505
1778
  });
1779
+ const rows = await result.json();
1780
+ const scores = Array.isArray(rows) ? rows.map((row) => this.transformScoreRow(row)) : [];
1781
+ return {
1782
+ pagination: {
1783
+ total,
1784
+ page,
1785
+ perPage: perPageForResponse,
1786
+ hasMore: end < total
1787
+ },
1788
+ scores
1789
+ };
506
1790
  } catch (error) {
507
- console.error("Error deleting thread:", error);
508
- throw error;
1791
+ throw new MastraError(
1792
+ {
1793
+ id: "CLICKHOUSE_STORAGE_GET_SCORES_BY_SCORER_ID_FAILED",
1794
+ domain: ErrorDomain.STORAGE,
1795
+ category: ErrorCategory.THIRD_PARTY,
1796
+ details: { scorerId }
1797
+ },
1798
+ error
1799
+ );
509
1800
  }
510
1801
  }
511
- async getMessages({ threadId, selectBy }) {
1802
+ async listScoresByEntityId({
1803
+ entityId,
1804
+ entityType,
1805
+ pagination
1806
+ }) {
512
1807
  try {
513
- const messages = [];
514
- const limit = typeof selectBy?.last === `number` ? selectBy.last : 40;
515
- const include = selectBy?.include || [];
516
- if (include.length) {
517
- const includeResult = await this.db.query({
518
- query: `
519
- WITH ordered_messages AS (
520
- SELECT
521
- *,
522
- toDateTime64(createdAt, 3) as createdAt,
523
- toDateTime64(updatedAt, 3) as updatedAt,
524
- ROW_NUMBER() OVER (ORDER BY "createdAt" DESC) as row_num
525
- FROM "${TABLE_MESSAGES}"
526
- WHERE thread_id = {var_thread_id:String}
527
- )
528
- SELECT
529
- m.id AS id,
530
- m.content as content,
531
- m.role as role,
532
- m.type as type,
533
- m.createdAt as createdAt,
534
- m.updatedAt as updatedAt,
535
- m.thread_id AS "threadId"
536
- FROM ordered_messages m
537
- WHERE m.id = ANY({var_include:Array(String)})
538
- OR EXISTS (
539
- SELECT 1 FROM ordered_messages target
540
- WHERE target.id = ANY({var_include:Array(String)})
541
- AND (
542
- -- Get previous messages based on the max withPreviousMessages
543
- (m.row_num <= target.row_num + {var_withPreviousMessages:Int64} AND m.row_num > target.row_num)
544
- OR
545
- -- Get next messages based on the max withNextMessages
546
- (m.row_num >= target.row_num - {var_withNextMessages:Int64} AND m.row_num < target.row_num)
547
- )
548
- )
549
- ORDER BY m."createdAt" DESC
550
- `,
551
- query_params: {
552
- var_thread_id: threadId,
553
- var_include: include.map((i) => i.id),
554
- var_withPreviousMessages: Math.max(...include.map((i) => i.withPreviousMessages || 0)),
555
- var_withNextMessages: Math.max(...include.map((i) => i.withNextMessages || 0))
1808
+ const countResult = await this.client.query({
1809
+ query: `SELECT COUNT(*) as count FROM ${TABLE_SCORERS} WHERE entityId = {var_entityId:String} AND entityType = {var_entityType:String}`,
1810
+ query_params: { var_entityId: entityId, var_entityType: entityType },
1811
+ format: "JSONEachRow"
1812
+ });
1813
+ const countRows = await countResult.json();
1814
+ let total = 0;
1815
+ if (Array.isArray(countRows) && countRows.length > 0 && countRows[0]) {
1816
+ const countObj = countRows[0];
1817
+ total = Number(countObj.count);
1818
+ }
1819
+ const { page, perPage: perPageInput } = pagination;
1820
+ if (!total) {
1821
+ return {
1822
+ pagination: {
1823
+ total: 0,
1824
+ page,
1825
+ perPage: perPageInput,
1826
+ hasMore: false
556
1827
  },
557
- clickhouse_settings: {
558
- // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
559
- date_time_input_format: "best_effort",
560
- date_time_output_format: "iso",
561
- use_client_time_zone: 1,
562
- output_format_json_quote_64bit_integers: 0
563
- }
564
- });
565
- const rows2 = await includeResult.json();
566
- messages.push(...transformRows(rows2.data));
1828
+ scores: []
1829
+ };
567
1830
  }
568
- const result = await this.db.query({
569
- query: `
570
- SELECT
571
- id,
572
- content,
573
- role,
574
- type,
575
- toDateTime64(createdAt, 3) as createdAt,
576
- thread_id AS "threadId"
577
- FROM "${TABLE_MESSAGES}"
578
- WHERE thread_id = {threadId:String}
579
- AND id NOT IN ({exclude:Array(String)})
580
- ORDER BY "createdAt" DESC
581
- LIMIT {limit:Int64}
582
- `,
1831
+ const perPage = normalizePerPage(perPageInput, 100);
1832
+ const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
1833
+ const limitValue = perPageInput === false ? total : perPage;
1834
+ const end = perPageInput === false ? total : start + perPage;
1835
+ const result = await this.client.query({
1836
+ query: `SELECT * FROM ${TABLE_SCORERS} WHERE entityId = {var_entityId:String} AND entityType = {var_entityType:String} ORDER BY createdAt DESC LIMIT {var_limit:Int64} OFFSET {var_offset:Int64}`,
583
1837
  query_params: {
584
- threadId,
585
- exclude: messages.map((m) => m.id),
586
- limit
1838
+ var_entityId: entityId,
1839
+ var_entityType: entityType,
1840
+ var_limit: limitValue,
1841
+ var_offset: start
587
1842
  },
1843
+ format: "JSONEachRow",
588
1844
  clickhouse_settings: {
589
- // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
590
1845
  date_time_input_format: "best_effort",
591
1846
  date_time_output_format: "iso",
592
1847
  use_client_time_zone: 1,
@@ -594,80 +1849,154 @@ var ClickhouseStore = class extends MastraStorage {
594
1849
  }
595
1850
  });
596
1851
  const rows = await result.json();
597
- messages.push(...transformRows(rows.data));
598
- messages.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
599
- messages.forEach((message) => {
600
- if (typeof message.content === "string") {
601
- try {
602
- message.content = JSON.parse(message.content);
603
- } catch {
604
- }
605
- }
606
- });
607
- return messages;
1852
+ const scores = Array.isArray(rows) ? rows.map((row) => this.transformScoreRow(row)) : [];
1853
+ return {
1854
+ pagination: {
1855
+ total,
1856
+ page,
1857
+ perPage: perPageForResponse,
1858
+ hasMore: end < total
1859
+ },
1860
+ scores
1861
+ };
608
1862
  } catch (error) {
609
- console.error("Error getting messages:", error);
610
- throw error;
1863
+ throw new MastraError(
1864
+ {
1865
+ id: "CLICKHOUSE_STORAGE_GET_SCORES_BY_ENTITY_ID_FAILED",
1866
+ domain: ErrorDomain.STORAGE,
1867
+ category: ErrorCategory.THIRD_PARTY,
1868
+ details: { entityId, entityType }
1869
+ },
1870
+ error
1871
+ );
611
1872
  }
612
1873
  }
613
- async saveMessages({ messages }) {
614
- if (messages.length === 0) return messages;
1874
+ async listScoresBySpan({
1875
+ traceId,
1876
+ spanId,
1877
+ pagination
1878
+ }) {
615
1879
  try {
616
- const threadId = messages[0]?.threadId;
617
- if (!threadId) {
618
- throw new Error("Thread ID is required");
1880
+ const countResult = await this.client.query({
1881
+ query: `SELECT COUNT(*) as count FROM ${TABLE_SCORERS} WHERE traceId = {var_traceId:String} AND spanId = {var_spanId:String}`,
1882
+ query_params: {
1883
+ var_traceId: traceId,
1884
+ var_spanId: spanId
1885
+ },
1886
+ format: "JSONEachRow"
1887
+ });
1888
+ const countRows = await countResult.json();
1889
+ let total = 0;
1890
+ if (Array.isArray(countRows) && countRows.length > 0 && countRows[0]) {
1891
+ const countObj = countRows[0];
1892
+ total = Number(countObj.count);
619
1893
  }
620
- const thread = await this.getThreadById({ threadId });
621
- if (!thread) {
622
- throw new Error(`Thread ${threadId} not found`);
1894
+ const { page, perPage: perPageInput } = pagination;
1895
+ if (!total) {
1896
+ return {
1897
+ pagination: {
1898
+ total: 0,
1899
+ page,
1900
+ perPage: perPageInput,
1901
+ hasMore: false
1902
+ },
1903
+ scores: []
1904
+ };
623
1905
  }
624
- await this.db.insert({
625
- table: TABLE_MESSAGES,
1906
+ const perPage = normalizePerPage(perPageInput, 100);
1907
+ const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
1908
+ const limitValue = perPageInput === false ? total : perPage;
1909
+ const end = perPageInput === false ? total : start + perPage;
1910
+ const result = await this.client.query({
1911
+ query: `SELECT * FROM ${TABLE_SCORERS} WHERE traceId = {var_traceId:String} AND spanId = {var_spanId:String} ORDER BY createdAt DESC LIMIT {var_limit:Int64} OFFSET {var_offset:Int64}`,
1912
+ query_params: {
1913
+ var_traceId: traceId,
1914
+ var_spanId: spanId,
1915
+ var_limit: limitValue,
1916
+ var_offset: start
1917
+ },
626
1918
  format: "JSONEachRow",
627
- values: messages.map((message) => ({
628
- id: message.id,
629
- thread_id: threadId,
630
- content: typeof message.content === "string" ? message.content : JSON.stringify(message.content),
631
- createdAt: message.createdAt.toISOString(),
632
- role: message.role,
633
- type: message.type
634
- })),
635
1919
  clickhouse_settings: {
636
- // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
637
1920
  date_time_input_format: "best_effort",
1921
+ date_time_output_format: "iso",
638
1922
  use_client_time_zone: 1,
639
1923
  output_format_json_quote_64bit_integers: 0
640
1924
  }
641
1925
  });
642
- return messages;
1926
+ const rows = await result.json();
1927
+ const scores = Array.isArray(rows) ? rows.map((row) => this.transformScoreRow(row)) : [];
1928
+ return {
1929
+ pagination: {
1930
+ total,
1931
+ page,
1932
+ perPage: perPageForResponse,
1933
+ hasMore: end < total
1934
+ },
1935
+ scores
1936
+ };
643
1937
  } catch (error) {
644
- console.error("Error saving messages:", error);
645
- throw error;
1938
+ throw new MastraError(
1939
+ {
1940
+ id: "CLICKHOUSE_STORAGE_GET_SCORES_BY_SPAN_FAILED",
1941
+ domain: ErrorDomain.STORAGE,
1942
+ category: ErrorCategory.THIRD_PARTY,
1943
+ details: { traceId, spanId }
1944
+ },
1945
+ error
1946
+ );
646
1947
  }
647
1948
  }
1949
+ };
1950
+ var WorkflowsStorageClickhouse = class extends WorkflowsStorage {
1951
+ client;
1952
+ operations;
1953
+ constructor({ client, operations }) {
1954
+ super();
1955
+ this.operations = operations;
1956
+ this.client = client;
1957
+ }
1958
+ updateWorkflowResults({
1959
+ // workflowName,
1960
+ // runId,
1961
+ // stepId,
1962
+ // result,
1963
+ // requestContext,
1964
+ }) {
1965
+ throw new Error("Method not implemented.");
1966
+ }
1967
+ updateWorkflowState({
1968
+ // workflowName,
1969
+ // runId,
1970
+ // opts,
1971
+ }) {
1972
+ throw new Error("Method not implemented.");
1973
+ }
648
1974
  async persistWorkflowSnapshot({
649
1975
  workflowName,
650
1976
  runId,
1977
+ resourceId,
651
1978
  snapshot
652
1979
  }) {
653
1980
  try {
654
- const currentSnapshot = await this.load({
1981
+ const currentSnapshot = await this.operations.load({
655
1982
  tableName: TABLE_WORKFLOW_SNAPSHOT,
656
1983
  keys: { workflow_name: workflowName, run_id: runId }
657
1984
  });
658
1985
  const now = /* @__PURE__ */ new Date();
659
1986
  const persisting = currentSnapshot ? {
660
1987
  ...currentSnapshot,
1988
+ resourceId,
661
1989
  snapshot: JSON.stringify(snapshot),
662
1990
  updatedAt: now.toISOString()
663
1991
  } : {
664
1992
  workflow_name: workflowName,
665
1993
  run_id: runId,
1994
+ resourceId,
666
1995
  snapshot: JSON.stringify(snapshot),
667
1996
  createdAt: now.toISOString(),
668
1997
  updatedAt: now.toISOString()
669
1998
  };
670
- await this.db.insert({
1999
+ await this.client.insert({
671
2000
  table: TABLE_WORKFLOW_SNAPSHOT,
672
2001
  format: "JSONEachRow",
673
2002
  values: [persisting],
@@ -679,8 +2008,15 @@ var ClickhouseStore = class extends MastraStorage {
679
2008
  }
680
2009
  });
681
2010
  } catch (error) {
682
- console.error("Error persisting workflow snapshot:", error);
683
- throw error;
2011
+ throw new MastraError(
2012
+ {
2013
+ id: "CLICKHOUSE_STORAGE_PERSIST_WORKFLOW_SNAPSHOT_FAILED",
2014
+ domain: ErrorDomain.STORAGE,
2015
+ category: ErrorCategory.THIRD_PARTY,
2016
+ details: { workflowName, runId }
2017
+ },
2018
+ error
2019
+ );
684
2020
  }
685
2021
  }
686
2022
  async loadWorkflowSnapshot({
@@ -688,7 +2024,7 @@ var ClickhouseStore = class extends MastraStorage {
688
2024
  runId
689
2025
  }) {
690
2026
  try {
691
- const result = await this.load({
2027
+ const result = await this.operations.load({
692
2028
  tableName: TABLE_WORKFLOW_SNAPSHOT,
693
2029
  keys: {
694
2030
  workflow_name: workflowName,
@@ -700,8 +2036,15 @@ var ClickhouseStore = class extends MastraStorage {
700
2036
  }
701
2037
  return result.snapshot;
702
2038
  } catch (error) {
703
- console.error("Error loading workflow snapshot:", error);
704
- throw error;
2039
+ throw new MastraError(
2040
+ {
2041
+ id: "CLICKHOUSE_STORAGE_LOAD_WORKFLOW_SNAPSHOT_FAILED",
2042
+ domain: ErrorDomain.STORAGE,
2043
+ category: ErrorCategory.THIRD_PARTY,
2044
+ details: { workflowName, runId }
2045
+ },
2046
+ error
2047
+ );
705
2048
  }
706
2049
  }
707
2050
  parseWorkflowRun(row) {
@@ -722,12 +2065,12 @@ var ClickhouseStore = class extends MastraStorage {
722
2065
  resourceId: row.resourceId
723
2066
  };
724
2067
  }
725
- async getWorkflowRuns({
2068
+ async listWorkflowRuns({
726
2069
  workflowName,
727
2070
  fromDate,
728
2071
  toDate,
729
- limit,
730
- offset,
2072
+ page,
2073
+ perPage,
731
2074
  resourceId
732
2075
  } = {}) {
733
2076
  try {
@@ -738,7 +2081,7 @@ var ClickhouseStore = class extends MastraStorage {
738
2081
  values.var_workflow_name = workflowName;
739
2082
  }
740
2083
  if (resourceId) {
741
- const hasResourceId = await this.hasColumn(TABLE_WORKFLOW_SNAPSHOT, "resourceId");
2084
+ const hasResourceId = await this.operations.hasColumn(TABLE_WORKFLOW_SNAPSHOT, "resourceId");
742
2085
  if (hasResourceId) {
743
2086
  conditions.push(`resourceId = {var_resourceId:String}`);
744
2087
  values.var_resourceId = resourceId;
@@ -755,11 +2098,14 @@ var ClickhouseStore = class extends MastraStorage {
755
2098
  values.var_to_date = toDate.getTime() / 1e3;
756
2099
  }
757
2100
  const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
758
- const limitClause = limit !== void 0 ? `LIMIT ${limit}` : "";
759
- const offsetClause = offset !== void 0 ? `OFFSET ${offset}` : "";
2101
+ const usePagination = perPage !== void 0 && page !== void 0;
2102
+ const normalizedPerPage = usePagination ? normalizePerPage(perPage, Number.MAX_SAFE_INTEGER) : 0;
2103
+ const offset = usePagination ? page * normalizedPerPage : 0;
2104
+ const limitClause = usePagination ? `LIMIT ${normalizedPerPage}` : "";
2105
+ const offsetClause = usePagination ? `OFFSET ${offset}` : "";
760
2106
  let total = 0;
761
- if (limit !== void 0 && offset !== void 0) {
762
- const countResult = await this.db.query({
2107
+ if (usePagination) {
2108
+ const countResult = await this.client.query({
763
2109
  query: `SELECT COUNT(*) as count FROM ${TABLE_WORKFLOW_SNAPSHOT} ${TABLE_ENGINES[TABLE_WORKFLOW_SNAPSHOT].startsWith("ReplacingMergeTree") ? "FINAL" : ""} ${whereClause}`,
764
2110
  query_params: values,
765
2111
  format: "JSONEachRow"
@@ -767,21 +2113,21 @@ var ClickhouseStore = class extends MastraStorage {
767
2113
  const countRows = await countResult.json();
768
2114
  total = Number(countRows[0]?.count ?? 0);
769
2115
  }
770
- const result = await this.db.query({
2116
+ const result = await this.client.query({
771
2117
  query: `
772
- SELECT
773
- workflow_name,
774
- run_id,
775
- snapshot,
776
- toDateTime64(createdAt, 3) as createdAt,
777
- toDateTime64(updatedAt, 3) as updatedAt,
778
- resourceId
779
- FROM ${TABLE_WORKFLOW_SNAPSHOT} ${TABLE_ENGINES[TABLE_WORKFLOW_SNAPSHOT].startsWith("ReplacingMergeTree") ? "FINAL" : ""}
780
- ${whereClause}
781
- ORDER BY createdAt DESC
782
- ${limitClause}
783
- ${offsetClause}
784
- `,
2118
+ SELECT
2119
+ workflow_name,
2120
+ run_id,
2121
+ snapshot,
2122
+ toDateTime64(createdAt, 3) as createdAt,
2123
+ toDateTime64(updatedAt, 3) as updatedAt,
2124
+ resourceId
2125
+ FROM ${TABLE_WORKFLOW_SNAPSHOT} ${TABLE_ENGINES[TABLE_WORKFLOW_SNAPSHOT].startsWith("ReplacingMergeTree") ? "FINAL" : ""}
2126
+ ${whereClause}
2127
+ ORDER BY createdAt DESC
2128
+ ${limitClause}
2129
+ ${offsetClause}
2130
+ `,
785
2131
  query_params: values,
786
2132
  format: "JSONEachRow"
787
2133
  });
@@ -792,8 +2138,15 @@ var ClickhouseStore = class extends MastraStorage {
792
2138
  });
793
2139
  return { runs, total: total || runs.length };
794
2140
  } catch (error) {
795
- console.error("Error getting workflow runs:", error);
796
- throw error;
2141
+ throw new MastraError(
2142
+ {
2143
+ id: "CLICKHOUSE_STORAGE_LIST_WORKFLOW_RUNS_FAILED",
2144
+ domain: ErrorDomain.STORAGE,
2145
+ category: ErrorCategory.THIRD_PARTY,
2146
+ details: { workflowName: workflowName ?? "", resourceId: resourceId ?? "" }
2147
+ },
2148
+ error
2149
+ );
797
2150
  }
798
2151
  }
799
2152
  async getWorkflowRunById({
@@ -812,18 +2165,19 @@ var ClickhouseStore = class extends MastraStorage {
812
2165
  values.var_workflow_name = workflowName;
813
2166
  }
814
2167
  const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
815
- const result = await this.db.query({
2168
+ const result = await this.client.query({
816
2169
  query: `
817
- SELECT
818
- workflow_name,
819
- run_id,
820
- snapshot,
821
- toDateTime64(createdAt, 3) as createdAt,
822
- toDateTime64(updatedAt, 3) as updatedAt,
823
- resourceId
824
- FROM ${TABLE_WORKFLOW_SNAPSHOT} ${TABLE_ENGINES[TABLE_WORKFLOW_SNAPSHOT].startsWith("ReplacingMergeTree") ? "FINAL" : ""}
825
- ${whereClause}
826
- `,
2170
+ SELECT
2171
+ workflow_name,
2172
+ run_id,
2173
+ snapshot,
2174
+ toDateTime64(createdAt, 3) as createdAt,
2175
+ toDateTime64(updatedAt, 3) as updatedAt,
2176
+ resourceId
2177
+ FROM ${TABLE_WORKFLOW_SNAPSHOT} ${TABLE_ENGINES[TABLE_WORKFLOW_SNAPSHOT].startsWith("ReplacingMergeTree") ? "FINAL" : ""}
2178
+ ${whereClause}
2179
+ ORDER BY createdAt DESC LIMIT 1
2180
+ `,
827
2181
  query_params: values,
828
2182
  format: "JSONEachRow"
829
2183
  });
@@ -833,17 +2187,244 @@ var ClickhouseStore = class extends MastraStorage {
833
2187
  }
834
2188
  return this.parseWorkflowRun(resultJson[0]);
835
2189
  } catch (error) {
836
- console.error("Error getting workflow run by ID:", error);
837
- throw error;
2190
+ throw new MastraError(
2191
+ {
2192
+ id: "CLICKHOUSE_STORAGE_GET_WORKFLOW_RUN_BY_ID_FAILED",
2193
+ domain: ErrorDomain.STORAGE,
2194
+ category: ErrorCategory.THIRD_PARTY,
2195
+ details: { runId: runId ?? "", workflowName: workflowName ?? "" }
2196
+ },
2197
+ error
2198
+ );
838
2199
  }
839
2200
  }
840
- async hasColumn(table, column) {
841
- const result = await this.db.query({
842
- query: `DESCRIBE TABLE ${table}`,
843
- format: "JSONEachRow"
2201
+ };
2202
+
2203
+ // src/storage/index.ts
2204
+ var ClickhouseStore = class extends MastraStorage {
2205
+ db;
2206
+ ttl = {};
2207
+ stores;
2208
+ constructor(config) {
2209
+ super({ name: "ClickhouseStore" });
2210
+ this.db = createClient({
2211
+ url: config.url,
2212
+ username: config.username,
2213
+ password: config.password,
2214
+ clickhouse_settings: {
2215
+ date_time_input_format: "best_effort",
2216
+ date_time_output_format: "iso",
2217
+ // This is crucial
2218
+ use_client_time_zone: 1,
2219
+ output_format_json_quote_64bit_integers: 0
2220
+ }
844
2221
  });
845
- const columns = await result.json();
846
- return columns.some((c) => c.name === column);
2222
+ this.ttl = config.ttl;
2223
+ const operations = new StoreOperationsClickhouse({ client: this.db, ttl: this.ttl });
2224
+ const workflows = new WorkflowsStorageClickhouse({ client: this.db, operations });
2225
+ const scores = new ScoresStorageClickhouse({ client: this.db, operations });
2226
+ const memory = new MemoryStorageClickhouse({ client: this.db, operations });
2227
+ this.stores = {
2228
+ operations,
2229
+ workflows,
2230
+ scores,
2231
+ memory
2232
+ };
2233
+ }
2234
+ get supports() {
2235
+ return {
2236
+ selectByIncludeResourceScope: true,
2237
+ resourceWorkingMemory: true,
2238
+ hasColumn: true,
2239
+ createTable: true,
2240
+ deleteMessages: false,
2241
+ listScoresBySpan: true
2242
+ };
2243
+ }
2244
+ async batchInsert({ tableName, records }) {
2245
+ await this.stores.operations.batchInsert({ tableName, records });
2246
+ }
2247
+ async optimizeTable({ tableName }) {
2248
+ try {
2249
+ await this.db.command({
2250
+ query: `OPTIMIZE TABLE ${tableName} FINAL`
2251
+ });
2252
+ } catch (error) {
2253
+ throw new MastraError(
2254
+ {
2255
+ id: "CLICKHOUSE_STORAGE_OPTIMIZE_TABLE_FAILED",
2256
+ domain: ErrorDomain.STORAGE,
2257
+ category: ErrorCategory.THIRD_PARTY,
2258
+ details: { tableName }
2259
+ },
2260
+ error
2261
+ );
2262
+ }
2263
+ }
2264
+ async materializeTtl({ tableName }) {
2265
+ try {
2266
+ await this.db.command({
2267
+ query: `ALTER TABLE ${tableName} MATERIALIZE TTL;`
2268
+ });
2269
+ } catch (error) {
2270
+ throw new MastraError(
2271
+ {
2272
+ id: "CLICKHOUSE_STORAGE_MATERIALIZE_TTL_FAILED",
2273
+ domain: ErrorDomain.STORAGE,
2274
+ category: ErrorCategory.THIRD_PARTY,
2275
+ details: { tableName }
2276
+ },
2277
+ error
2278
+ );
2279
+ }
2280
+ }
2281
+ async createTable({
2282
+ tableName,
2283
+ schema
2284
+ }) {
2285
+ return this.stores.operations.createTable({ tableName, schema });
2286
+ }
2287
+ async dropTable({ tableName }) {
2288
+ return this.stores.operations.dropTable({ tableName });
2289
+ }
2290
+ async alterTable({
2291
+ tableName,
2292
+ schema,
2293
+ ifNotExists
2294
+ }) {
2295
+ return this.stores.operations.alterTable({ tableName, schema, ifNotExists });
2296
+ }
2297
+ async clearTable({ tableName }) {
2298
+ return this.stores.operations.clearTable({ tableName });
2299
+ }
2300
+ async insert({ tableName, record }) {
2301
+ return this.stores.operations.insert({ tableName, record });
2302
+ }
2303
+ async load({ tableName, keys }) {
2304
+ return this.stores.operations.load({ tableName, keys });
2305
+ }
2306
+ async updateWorkflowResults({
2307
+ workflowName,
2308
+ runId,
2309
+ stepId,
2310
+ result,
2311
+ requestContext
2312
+ }) {
2313
+ return this.stores.workflows.updateWorkflowResults({ workflowName, runId, stepId, result, requestContext });
2314
+ }
2315
+ async updateWorkflowState({
2316
+ workflowName,
2317
+ runId,
2318
+ opts
2319
+ }) {
2320
+ return this.stores.workflows.updateWorkflowState({ workflowName, runId, opts });
2321
+ }
2322
+ async persistWorkflowSnapshot({
2323
+ workflowName,
2324
+ runId,
2325
+ resourceId,
2326
+ snapshot
2327
+ }) {
2328
+ return this.stores.workflows.persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot });
2329
+ }
2330
+ async loadWorkflowSnapshot({
2331
+ workflowName,
2332
+ runId
2333
+ }) {
2334
+ return this.stores.workflows.loadWorkflowSnapshot({ workflowName, runId });
2335
+ }
2336
+ async listWorkflowRuns({
2337
+ workflowName,
2338
+ fromDate,
2339
+ toDate,
2340
+ perPage,
2341
+ page,
2342
+ resourceId
2343
+ } = {}) {
2344
+ return this.stores.workflows.listWorkflowRuns({ workflowName, fromDate, toDate, perPage, page, resourceId });
2345
+ }
2346
+ async getWorkflowRunById({
2347
+ runId,
2348
+ workflowName
2349
+ }) {
2350
+ return this.stores.workflows.getWorkflowRunById({ runId, workflowName });
2351
+ }
2352
+ async getThreadById({ threadId }) {
2353
+ return this.stores.memory.getThreadById({ threadId });
2354
+ }
2355
+ async saveThread({ thread }) {
2356
+ return this.stores.memory.saveThread({ thread });
2357
+ }
2358
+ async updateThread({
2359
+ id,
2360
+ title,
2361
+ metadata
2362
+ }) {
2363
+ return this.stores.memory.updateThread({ id, title, metadata });
2364
+ }
2365
+ async deleteThread({ threadId }) {
2366
+ return this.stores.memory.deleteThread({ threadId });
2367
+ }
2368
+ async getMessages({
2369
+ threadId,
2370
+ resourceId,
2371
+ selectBy
2372
+ }) {
2373
+ return this.stores.memory.getMessages({ threadId, resourceId, selectBy });
2374
+ }
2375
+ async saveMessages(args) {
2376
+ return this.stores.memory.saveMessages(args);
2377
+ }
2378
+ async updateMessages(args) {
2379
+ return this.stores.memory.updateMessages(args);
2380
+ }
2381
+ async getResourceById({ resourceId }) {
2382
+ return this.stores.memory.getResourceById({ resourceId });
2383
+ }
2384
+ async saveResource({ resource }) {
2385
+ return this.stores.memory.saveResource({ resource });
2386
+ }
2387
+ async updateResource({
2388
+ resourceId,
2389
+ workingMemory,
2390
+ metadata
2391
+ }) {
2392
+ return this.stores.memory.updateResource({ resourceId, workingMemory, metadata });
2393
+ }
2394
+ async getScoreById({ id }) {
2395
+ return this.stores.scores.getScoreById({ id });
2396
+ }
2397
+ async saveScore(_score) {
2398
+ return this.stores.scores.saveScore(_score);
2399
+ }
2400
+ async listScoresByRunId({
2401
+ runId,
2402
+ pagination
2403
+ }) {
2404
+ return this.stores.scores.listScoresByRunId({ runId, pagination });
2405
+ }
2406
+ async listScoresByEntityId({
2407
+ entityId,
2408
+ entityType,
2409
+ pagination
2410
+ }) {
2411
+ return this.stores.scores.listScoresByEntityId({ entityId, entityType, pagination });
2412
+ }
2413
+ async listScoresByScorerId({
2414
+ scorerId,
2415
+ pagination,
2416
+ entityId,
2417
+ entityType,
2418
+ source
2419
+ }) {
2420
+ return this.stores.scores.listScoresByScorerId({ scorerId, pagination, entityId, entityType, source });
2421
+ }
2422
+ async listScoresBySpan({
2423
+ traceId,
2424
+ spanId,
2425
+ pagination
2426
+ }) {
2427
+ return this.stores.scores.listScoresBySpan({ traceId, spanId, pagination });
847
2428
  }
848
2429
  async close() {
849
2430
  await this.db.close();
@@ -851,3 +2432,5 @@ var ClickhouseStore = class extends MastraStorage {
851
2432
  };
852
2433
 
853
2434
  export { COLUMN_TYPES, ClickhouseStore, TABLE_ENGINES };
2435
+ //# sourceMappingURL=index.js.map
2436
+ //# sourceMappingURL=index.js.map