@mastra/clickhouse 0.0.0-trigger-playground-ui-package-20250506151043 → 0.0.0-tsconfig-compile-20250703214351

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.
@@ -1,7 +1,10 @@
1
1
  import type { ClickHouseClient } from '@clickhouse/client';
2
2
  import { createClient } from '@clickhouse/client';
3
+ import { MessageList } from '@mastra/core/agent';
4
+ import type { MastraMessageContentV2 } from '@mastra/core/agent';
5
+ import { MastraError, ErrorDomain, ErrorCategory } from '@mastra/core/error';
3
6
  import type { MetricResult, TestInfo } from '@mastra/core/eval';
4
- import type { MessageType, StorageThreadType } from '@mastra/core/memory';
7
+ import type { MastraMessageV1, MastraMessageV2, StorageThreadType } from '@mastra/core/memory';
5
8
  import {
6
9
  MastraStorage,
7
10
  TABLE_EVALS,
@@ -13,14 +16,20 @@ import {
13
16
  } from '@mastra/core/storage';
14
17
  import type {
15
18
  EvalRow,
19
+ PaginationInfo,
16
20
  StorageColumn,
17
21
  StorageGetMessagesArg,
18
22
  TABLE_NAMES,
19
23
  WorkflowRun,
20
24
  WorkflowRuns,
25
+ StorageGetTracesArg,
26
+ TABLE_RESOURCES,
21
27
  } from '@mastra/core/storage';
28
+ import type { Trace } from '@mastra/core/telemetry';
22
29
  import type { WorkflowRunState } from '@mastra/core/workflows';
23
30
 
31
+ type SUPPORTED_TABLE_NAMES = Exclude<TABLE_NAMES, typeof TABLE_RESOURCES>;
32
+
24
33
  function safelyParseJSON(jsonString: string): any {
25
34
  try {
26
35
  return JSON.parse(jsonString);
@@ -60,7 +69,7 @@ export type ClickhouseConfig = {
60
69
  };
61
70
  };
62
71
 
63
- export const TABLE_ENGINES: Record<TABLE_NAMES, string> = {
72
+ export const TABLE_ENGINES: Record<SUPPORTED_TABLE_NAMES, string> = {
64
73
  [TABLE_MESSAGES]: `MergeTree()`,
65
74
  [TABLE_WORKFLOW_SNAPSHOT]: `ReplacingMergeTree()`,
66
75
  [TABLE_TRACES]: `MergeTree()`,
@@ -121,7 +130,12 @@ export class ClickhouseStore extends MastraStorage {
121
130
  const testInfoValue = row.test_info ? JSON.parse(row.test_info as string) : undefined;
122
131
 
123
132
  if (!resultValue || typeof resultValue !== 'object' || !('score' in resultValue)) {
124
- throw new Error(`Invalid MetricResult format: ${JSON.stringify(resultValue)}`);
133
+ throw new MastraError({
134
+ id: 'CLICKHOUSE_STORAGE_INVALID_METRIC_FORMAT',
135
+ text: `Invalid MetricResult format: ${JSON.stringify(resultValue)}`,
136
+ domain: ErrorDomain.STORAGE,
137
+ category: ErrorCategory.USER,
138
+ });
125
139
  }
126
140
 
127
141
  return {
@@ -138,6 +152,19 @@ export class ClickhouseStore extends MastraStorage {
138
152
  };
139
153
  }
140
154
 
155
+ private escape(value: any): string {
156
+ if (typeof value === 'string') {
157
+ return `'${value.replace(/'/g, "''")}'`;
158
+ }
159
+ if (value instanceof Date) {
160
+ return `'${value.toISOString()}'`;
161
+ }
162
+ if (value === null || value === undefined) {
163
+ return 'NULL';
164
+ }
165
+ return value.toString();
166
+ }
167
+
141
168
  async getEvalsByAgentName(agentName: string, type?: 'test' | 'live'): Promise<EvalRow[]> {
142
169
  try {
143
170
  const baseQuery = `SELECT *, toDateTime64(createdAt, 3) as createdAt FROM ${TABLE_EVALS} WHERE agent_name = {var_agent_name:String}`;
@@ -165,13 +192,19 @@ export class ClickhouseStore extends MastraStorage {
165
192
 
166
193
  const rows = await result.json();
167
194
  return rows.data.map((row: any) => this.transformEvalRow(row));
168
- } catch (error) {
169
- // Handle case where table doesn't exist yet
170
- if (error instanceof Error && error.message.includes('no such table')) {
195
+ } catch (error: any) {
196
+ if (error?.message?.includes('no such table') || error?.message?.includes('does not exist')) {
171
197
  return [];
172
198
  }
173
- this.logger.error('Failed to get evals for the specified agent: ' + (error as any)?.message);
174
- throw error;
199
+ throw new MastraError(
200
+ {
201
+ id: 'CLICKHOUSE_STORAGE_GET_EVALS_BY_AGENT_FAILED',
202
+ domain: ErrorDomain.STORAGE,
203
+ category: ErrorCategory.THIRD_PARTY,
204
+ details: { agentName, type: type ?? null },
205
+ },
206
+ error,
207
+ );
175
208
  }
176
209
  }
177
210
 
@@ -197,9 +230,16 @@ export class ClickhouseStore extends MastraStorage {
197
230
  output_format_json_quote_64bit_integers: 0,
198
231
  },
199
232
  });
200
- } catch (error) {
201
- console.error(`Error inserting into ${tableName}:`, error);
202
- throw error;
233
+ } catch (error: any) {
234
+ throw new MastraError(
235
+ {
236
+ id: 'CLICKHOUSE_STORAGE_BATCH_INSERT_FAILED',
237
+ domain: ErrorDomain.STORAGE,
238
+ category: ErrorCategory.THIRD_PARTY,
239
+ details: { tableName },
240
+ },
241
+ error,
242
+ );
203
243
  }
204
244
  }
205
245
 
@@ -264,59 +304,107 @@ export class ClickhouseStore extends MastraStorage {
264
304
 
265
305
  const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
266
306
 
267
- const result = await this.db.query({
268
- query: `SELECT *, toDateTime64(createdAt, 3) as createdAt FROM ${TABLE_TRACES} ${whereClause} ORDER BY "createdAt" DESC LIMIT ${limit} OFFSET ${offset}`,
269
- query_params: args,
270
- clickhouse_settings: {
271
- // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
272
- date_time_input_format: 'best_effort',
273
- date_time_output_format: 'iso',
274
- use_client_time_zone: 1,
275
- output_format_json_quote_64bit_integers: 0,
276
- },
277
- });
307
+ try {
308
+ const result = await this.db.query({
309
+ query: `SELECT *, toDateTime64(createdAt, 3) as createdAt FROM ${TABLE_TRACES} ${whereClause} ORDER BY "createdAt" DESC LIMIT ${limit} OFFSET ${offset}`,
310
+ query_params: args,
311
+ clickhouse_settings: {
312
+ // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
313
+ date_time_input_format: 'best_effort',
314
+ date_time_output_format: 'iso',
315
+ use_client_time_zone: 1,
316
+ output_format_json_quote_64bit_integers: 0,
317
+ },
318
+ });
278
319
 
279
- if (!result) {
280
- return [];
281
- }
320
+ if (!result) {
321
+ return [];
322
+ }
282
323
 
283
- const resp = await result.json();
284
- const rows: any[] = resp.data;
285
- return rows.map(row => ({
286
- id: row.id,
287
- parentSpanId: row.parentSpanId,
288
- traceId: row.traceId,
289
- name: row.name,
290
- scope: row.scope,
291
- kind: row.kind,
292
- status: safelyParseJSON(row.status as string),
293
- events: safelyParseJSON(row.events as string),
294
- links: safelyParseJSON(row.links as string),
295
- attributes: safelyParseJSON(row.attributes as string),
296
- startTime: row.startTime,
297
- endTime: row.endTime,
298
- other: safelyParseJSON(row.other as string),
299
- createdAt: row.createdAt,
300
- }));
324
+ const resp = await result.json();
325
+ const rows: any[] = resp.data;
326
+ return rows.map(row => ({
327
+ id: row.id,
328
+ parentSpanId: row.parentSpanId,
329
+ traceId: row.traceId,
330
+ name: row.name,
331
+ scope: row.scope,
332
+ kind: row.kind,
333
+ status: safelyParseJSON(row.status as string),
334
+ events: safelyParseJSON(row.events as string),
335
+ links: safelyParseJSON(row.links as string),
336
+ attributes: safelyParseJSON(row.attributes as string),
337
+ startTime: row.startTime,
338
+ endTime: row.endTime,
339
+ other: safelyParseJSON(row.other as string),
340
+ createdAt: row.createdAt,
341
+ }));
342
+ } catch (error: any) {
343
+ if (error?.message?.includes('no such table') || error?.message?.includes('does not exist')) {
344
+ return [];
345
+ }
346
+ throw new MastraError(
347
+ {
348
+ id: 'CLICKHOUSE_STORAGE_GET_TRACES_FAILED',
349
+ domain: ErrorDomain.STORAGE,
350
+ category: ErrorCategory.THIRD_PARTY,
351
+ details: {
352
+ name: name ?? null,
353
+ scope: scope ?? null,
354
+ page,
355
+ perPage,
356
+ attributes: attributes ? JSON.stringify(attributes) : null,
357
+ filters: filters ? JSON.stringify(filters) : null,
358
+ fromDate: fromDate?.toISOString() ?? null,
359
+ toDate: toDate?.toISOString() ?? null,
360
+ },
361
+ },
362
+ error,
363
+ );
364
+ }
301
365
  }
302
366
 
303
367
  async optimizeTable({ tableName }: { tableName: TABLE_NAMES }): Promise<void> {
304
- await this.db.command({
305
- query: `OPTIMIZE TABLE ${tableName} FINAL`,
306
- });
368
+ try {
369
+ await this.db.command({
370
+ query: `OPTIMIZE TABLE ${tableName} FINAL`,
371
+ });
372
+ } catch (error: any) {
373
+ throw new MastraError(
374
+ {
375
+ id: 'CLICKHOUSE_STORAGE_OPTIMIZE_TABLE_FAILED',
376
+ domain: ErrorDomain.STORAGE,
377
+ category: ErrorCategory.THIRD_PARTY,
378
+ details: { tableName },
379
+ },
380
+ error,
381
+ );
382
+ }
307
383
  }
308
384
 
309
385
  async materializeTtl({ tableName }: { tableName: TABLE_NAMES }): Promise<void> {
310
- await this.db.command({
311
- query: `ALTER TABLE ${tableName} MATERIALIZE TTL;`,
312
- });
386
+ try {
387
+ await this.db.command({
388
+ query: `ALTER TABLE ${tableName} MATERIALIZE TTL;`,
389
+ });
390
+ } catch (error: any) {
391
+ throw new MastraError(
392
+ {
393
+ id: 'CLICKHOUSE_STORAGE_MATERIALIZE_TTL_FAILED',
394
+ domain: ErrorDomain.STORAGE,
395
+ category: ErrorCategory.THIRD_PARTY,
396
+ details: { tableName },
397
+ },
398
+ error,
399
+ );
400
+ }
313
401
  }
314
402
 
315
403
  async createTable({
316
404
  tableName,
317
405
  schema,
318
406
  }: {
319
- tableName: TABLE_NAMES;
407
+ tableName: SUPPORTED_TABLE_NAMES;
320
408
  schema: Record<string, StorageColumn>;
321
409
  }): Promise<void> {
322
410
  try {
@@ -337,7 +425,6 @@ export class ClickhouseStore extends MastraStorage {
337
425
  ${['id String'].concat(columns)}
338
426
  )
339
427
  ENGINE = ${TABLE_ENGINES[tableName]}
340
- PARTITION BY "createdAt"
341
428
  PRIMARY KEY (createdAt, run_id, workflow_name)
342
429
  ORDER BY (createdAt, run_id, workflow_name)
343
430
  ${rowTtl ? `TTL toDateTime(${rowTtl.ttlKey ?? 'createdAt'}) + INTERVAL ${rowTtl.interval} ${rowTtl.unit}` : ''}
@@ -348,7 +435,6 @@ export class ClickhouseStore extends MastraStorage {
348
435
  ${columns}
349
436
  )
350
437
  ENGINE = ${TABLE_ENGINES[tableName]}
351
- PARTITION BY "createdAt"
352
438
  PRIMARY KEY (createdAt, ${tableName === TABLE_EVALS ? 'run_id' : 'id'})
353
439
  ORDER BY (createdAt, ${tableName === TABLE_EVALS ? 'run_id' : 'id'})
354
440
  ${this.ttl?.[tableName]?.row ? `TTL toDateTime(createdAt) + INTERVAL ${this.ttl[tableName].row.interval} ${this.ttl[tableName].row.unit}` : ''}
@@ -365,9 +451,88 @@ export class ClickhouseStore extends MastraStorage {
365
451
  output_format_json_quote_64bit_integers: 0,
366
452
  },
367
453
  });
368
- } catch (error) {
369
- console.error(`Error creating table ${tableName}:`, error);
370
- throw error;
454
+ } catch (error: any) {
455
+ throw new MastraError(
456
+ {
457
+ id: 'CLICKHOUSE_STORAGE_CREATE_TABLE_FAILED',
458
+ domain: ErrorDomain.STORAGE,
459
+ category: ErrorCategory.THIRD_PARTY,
460
+ details: { tableName },
461
+ },
462
+ error,
463
+ );
464
+ }
465
+ }
466
+
467
+ protected getSqlType(type: StorageColumn['type']): string {
468
+ switch (type) {
469
+ case 'text':
470
+ return 'String';
471
+ case 'timestamp':
472
+ return 'DateTime64(3)';
473
+ case 'integer':
474
+ case 'bigint':
475
+ return 'Int64';
476
+ case 'jsonb':
477
+ return 'String';
478
+ default:
479
+ return super.getSqlType(type); // fallback to base implementation
480
+ }
481
+ }
482
+
483
+ /**
484
+ * Alters table schema to add columns if they don't exist
485
+ * @param tableName Name of the table
486
+ * @param schema Schema of the table
487
+ * @param ifNotExists Array of column names to add if they don't exist
488
+ */
489
+ async alterTable({
490
+ tableName,
491
+ schema,
492
+ ifNotExists,
493
+ }: {
494
+ tableName: TABLE_NAMES;
495
+ schema: Record<string, StorageColumn>;
496
+ ifNotExists: string[];
497
+ }): Promise<void> {
498
+ try {
499
+ // 1. Get existing columns
500
+ const describeSql = `DESCRIBE TABLE ${tableName}`;
501
+ const result = await this.db.query({
502
+ query: describeSql,
503
+ });
504
+ const rows = await result.json();
505
+ const existingColumnNames = new Set(rows.data.map((row: any) => row.name.toLowerCase()));
506
+
507
+ // 2. Add missing columns
508
+ for (const columnName of ifNotExists) {
509
+ if (!existingColumnNames.has(columnName.toLowerCase()) && schema[columnName]) {
510
+ const columnDef = schema[columnName];
511
+ let sqlType = this.getSqlType(columnDef.type);
512
+ if (columnDef.nullable !== false) {
513
+ sqlType = `Nullable(${sqlType})`;
514
+ }
515
+ const defaultValue = columnDef.nullable === false ? this.getDefaultValue(columnDef.type) : '';
516
+ // Use backticks or double quotes as needed for identifiers
517
+ const alterSql =
518
+ `ALTER TABLE ${tableName} ADD COLUMN IF NOT EXISTS "${columnName}" ${sqlType} ${defaultValue}`.trim();
519
+
520
+ await this.db.query({
521
+ query: alterSql,
522
+ });
523
+ this.logger?.debug?.(`Added column ${columnName} to table ${tableName}`);
524
+ }
525
+ }
526
+ } catch (error: any) {
527
+ throw new MastraError(
528
+ {
529
+ id: 'CLICKHOUSE_STORAGE_ALTER_TABLE_FAILED',
530
+ domain: ErrorDomain.STORAGE,
531
+ category: ErrorCategory.THIRD_PARTY,
532
+ details: { tableName },
533
+ },
534
+ error,
535
+ );
371
536
  }
372
537
  }
373
538
 
@@ -383,9 +548,16 @@ export class ClickhouseStore extends MastraStorage {
383
548
  output_format_json_quote_64bit_integers: 0,
384
549
  },
385
550
  });
386
- } catch (error) {
387
- console.error(`Error clearing table ${tableName}:`, error);
388
- throw error;
551
+ } catch (error: any) {
552
+ throw new MastraError(
553
+ {
554
+ id: 'CLICKHOUSE_STORAGE_CLEAR_TABLE_FAILED',
555
+ domain: ErrorDomain.STORAGE,
556
+ category: ErrorCategory.THIRD_PARTY,
557
+ details: { tableName },
558
+ },
559
+ error,
560
+ );
389
561
  }
390
562
  }
391
563
 
@@ -408,13 +580,26 @@ export class ClickhouseStore extends MastraStorage {
408
580
  use_client_time_zone: 1,
409
581
  },
410
582
  });
411
- } catch (error) {
412
- console.error(`Error inserting into ${tableName}:`, error);
413
- throw error;
583
+ } catch (error: any) {
584
+ throw new MastraError(
585
+ {
586
+ id: 'CLICKHOUSE_STORAGE_INSERT_FAILED',
587
+ domain: ErrorDomain.STORAGE,
588
+ category: ErrorCategory.THIRD_PARTY,
589
+ details: { tableName },
590
+ },
591
+ error,
592
+ );
414
593
  }
415
594
  }
416
595
 
417
- async load<R>({ tableName, keys }: { tableName: TABLE_NAMES; keys: Record<string, string> }): Promise<R | null> {
596
+ async load<R>({
597
+ tableName,
598
+ keys,
599
+ }: {
600
+ tableName: SUPPORTED_TABLE_NAMES;
601
+ keys: Record<string, string>;
602
+ }): Promise<R | null> {
418
603
  try {
419
604
  const keyEntries = Object.entries(keys);
420
605
  const conditions = keyEntries
@@ -428,7 +613,7 @@ export class ClickhouseStore extends MastraStorage {
428
613
  }, {});
429
614
 
430
615
  const result = await this.db.query({
431
- query: `SELECT *, toDateTime64(createdAt, 3) as createdAt, toDateTime64(updatedAt, 3) as updatedAt FROM ${tableName} ${TABLE_ENGINES[tableName as TABLE_NAMES].startsWith('ReplacingMergeTree') ? 'FINAL' : ''} WHERE ${conditions}`,
616
+ query: `SELECT *, toDateTime64(createdAt, 3) as createdAt, toDateTime64(updatedAt, 3) as updatedAt FROM ${tableName} ${TABLE_ENGINES[tableName as SUPPORTED_TABLE_NAMES].startsWith('ReplacingMergeTree') ? 'FINAL' : ''} WHERE ${conditions}`,
432
617
  query_params: values,
433
618
  clickhouse_settings: {
434
619
  // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
@@ -459,8 +644,15 @@ export class ClickhouseStore extends MastraStorage {
459
644
  const data: R = transformRow(rows.data[0]);
460
645
  return data;
461
646
  } catch (error) {
462
- console.error(`Error loading from ${tableName}:`, error);
463
- throw error;
647
+ throw new MastraError(
648
+ {
649
+ id: 'CLICKHOUSE_STORAGE_LOAD_FAILED',
650
+ domain: ErrorDomain.STORAGE,
651
+ category: ErrorCategory.THIRD_PARTY,
652
+ details: { tableName },
653
+ },
654
+ error,
655
+ );
464
656
  }
465
657
  }
466
658
 
@@ -500,9 +692,16 @@ export class ClickhouseStore extends MastraStorage {
500
692
  createdAt: thread.createdAt,
501
693
  updatedAt: thread.updatedAt,
502
694
  };
503
- } catch (error) {
504
- console.error(`Error getting thread ${threadId}:`, error);
505
- throw error;
695
+ } catch (error: any) {
696
+ throw new MastraError(
697
+ {
698
+ id: 'CLICKHOUSE_STORAGE_GET_THREAD_BY_ID_FAILED',
699
+ domain: ErrorDomain.STORAGE,
700
+ category: ErrorCategory.THIRD_PARTY,
701
+ details: { threadId },
702
+ },
703
+ error,
704
+ );
506
705
  }
507
706
  }
508
707
 
@@ -538,8 +737,15 @@ export class ClickhouseStore extends MastraStorage {
538
737
  updatedAt: thread.updatedAt,
539
738
  }));
540
739
  } catch (error) {
541
- console.error(`Error getting threads for resource ${resourceId}:`, error);
542
- throw error;
740
+ throw new MastraError(
741
+ {
742
+ id: 'CLICKHOUSE_STORAGE_GET_THREADS_BY_RESOURCE_ID_FAILED',
743
+ domain: ErrorDomain.STORAGE,
744
+ category: ErrorCategory.THIRD_PARTY,
745
+ details: { resourceId },
746
+ },
747
+ error,
748
+ );
543
749
  }
544
750
  }
545
751
 
@@ -565,8 +771,15 @@ export class ClickhouseStore extends MastraStorage {
565
771
 
566
772
  return thread;
567
773
  } catch (error) {
568
- console.error('Error saving thread:', error);
569
- throw error;
774
+ throw new MastraError(
775
+ {
776
+ id: 'CLICKHOUSE_STORAGE_SAVE_THREAD_FAILED',
777
+ domain: ErrorDomain.STORAGE,
778
+ category: ErrorCategory.THIRD_PARTY,
779
+ details: { threadId: thread.id },
780
+ },
781
+ error,
782
+ );
570
783
  }
571
784
  }
572
785
 
@@ -601,15 +814,18 @@ export class ClickhouseStore extends MastraStorage {
601
814
 
602
815
  await this.db.insert({
603
816
  table: TABLE_THREADS,
817
+ format: 'JSONEachRow',
604
818
  values: [
605
819
  {
606
- ...updatedThread,
820
+ id: updatedThread.id,
821
+ resourceId: updatedThread.resourceId,
822
+ title: updatedThread.title,
823
+ metadata: updatedThread.metadata,
824
+ createdAt: updatedThread.createdAt,
607
825
  updatedAt: updatedThread.updatedAt.toISOString(),
608
826
  },
609
827
  ],
610
- format: 'JSONEachRow',
611
828
  clickhouse_settings: {
612
- // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
613
829
  date_time_input_format: 'best_effort',
614
830
  use_client_time_zone: 1,
615
831
  output_format_json_quote_64bit_integers: 0,
@@ -618,8 +834,15 @@ export class ClickhouseStore extends MastraStorage {
618
834
 
619
835
  return updatedThread;
620
836
  } catch (error) {
621
- console.error('Error updating thread:', error);
622
- throw error;
837
+ throw new MastraError(
838
+ {
839
+ id: 'CLICKHOUSE_STORAGE_UPDATE_THREAD_FAILED',
840
+ domain: ErrorDomain.STORAGE,
841
+ category: ErrorCategory.THIRD_PARTY,
842
+ details: { threadId: id, title },
843
+ },
844
+ error,
845
+ );
623
846
  }
624
847
  }
625
848
 
@@ -627,7 +850,7 @@ export class ClickhouseStore extends MastraStorage {
627
850
  try {
628
851
  // First delete all messages associated with this thread
629
852
  await this.db.command({
630
- query: `DELETE FROM "${TABLE_MESSAGES}" WHERE thread_id = '${threadId}';`,
853
+ query: `DELETE FROM "${TABLE_MESSAGES}" WHERE thread_id = {var_thread_id:String};`,
631
854
  query_params: { var_thread_id: threadId },
632
855
  clickhouse_settings: {
633
856
  output_format_json_quote_64bit_integers: 0,
@@ -643,15 +866,29 @@ export class ClickhouseStore extends MastraStorage {
643
866
  },
644
867
  });
645
868
  } catch (error) {
646
- console.error('Error deleting thread:', error);
647
- throw error;
869
+ throw new MastraError(
870
+ {
871
+ id: 'CLICKHOUSE_STORAGE_DELETE_THREAD_FAILED',
872
+ domain: ErrorDomain.STORAGE,
873
+ category: ErrorCategory.THIRD_PARTY,
874
+ details: { threadId },
875
+ },
876
+ error,
877
+ );
648
878
  }
649
879
  }
650
880
 
651
- async getMessages<T = unknown>({ threadId, selectBy }: StorageGetMessagesArg): Promise<T[]> {
881
+ public async getMessages(args: StorageGetMessagesArg & { format?: 'v1' }): Promise<MastraMessageV1[]>;
882
+ public async getMessages(args: StorageGetMessagesArg & { format: 'v2' }): Promise<MastraMessageV2[]>;
883
+ public async getMessages({
884
+ threadId,
885
+ resourceId,
886
+ selectBy,
887
+ format,
888
+ }: StorageGetMessagesArg & { format?: 'v1' | 'v2' }): Promise<MastraMessageV1[] | MastraMessageV2[]> {
652
889
  try {
653
890
  const messages: any[] = [];
654
- const limit = typeof selectBy?.last === `number` ? selectBy.last : 40;
891
+ const limit = this.resolveMessageLimit({ last: selectBy?.last, defaultLimit: 40 });
655
892
  const include = selectBy?.include || [];
656
893
 
657
894
  if (include.length) {
@@ -755,18 +992,33 @@ export class ClickhouseStore extends MastraStorage {
755
992
  }
756
993
  });
757
994
 
758
- return messages as T[];
995
+ const list = new MessageList({ threadId, resourceId }).add(messages, 'memory');
996
+ if (format === `v2`) return list.get.all.v2();
997
+ return list.get.all.v1();
759
998
  } catch (error) {
760
- console.error('Error getting messages:', error);
761
- throw error;
999
+ throw new MastraError(
1000
+ {
1001
+ id: 'CLICKHOUSE_STORAGE_GET_MESSAGES_FAILED',
1002
+ domain: ErrorDomain.STORAGE,
1003
+ category: ErrorCategory.THIRD_PARTY,
1004
+ details: { threadId, resourceId: resourceId ?? '' },
1005
+ },
1006
+ error,
1007
+ );
762
1008
  }
763
1009
  }
764
1010
 
765
- async saveMessages({ messages }: { messages: MessageType[] }): Promise<MessageType[]> {
1011
+ async saveMessages(args: { messages: MastraMessageV1[]; format?: undefined | 'v1' }): Promise<MastraMessageV1[]>;
1012
+ async saveMessages(args: { messages: MastraMessageV2[]; format: 'v2' }): Promise<MastraMessageV2[]>;
1013
+ async saveMessages(
1014
+ args: { messages: MastraMessageV1[]; format?: undefined | 'v1' } | { messages: MastraMessageV2[]; format: 'v2' },
1015
+ ): Promise<MastraMessageV2[] | MastraMessageV1[]> {
1016
+ const { messages, format = 'v1' } = args;
766
1017
  if (messages.length === 0) return messages;
767
1018
 
768
1019
  try {
769
1020
  const threadId = messages[0]?.threadId;
1021
+ const resourceId = messages[0]?.resourceId;
770
1022
  if (!threadId) {
771
1023
  throw new Error('Thread ID is required');
772
1024
  }
@@ -777,29 +1029,111 @@ export class ClickhouseStore extends MastraStorage {
777
1029
  throw new Error(`Thread ${threadId} not found`);
778
1030
  }
779
1031
 
780
- await this.db.insert({
781
- table: TABLE_MESSAGES,
782
- format: 'JSONEachRow',
783
- values: messages.map(message => ({
784
- id: message.id,
785
- thread_id: threadId,
786
- content: typeof message.content === 'string' ? message.content : JSON.stringify(message.content),
787
- createdAt: message.createdAt.toISOString(),
788
- role: message.role,
789
- type: message.type,
790
- })),
1032
+ // Clickhouse's MergeTree engine does not support native upserts or unique constraints on (id, thread_id).
1033
+ // Note: We cannot switch to ReplacingMergeTree without a schema migration,
1034
+ // as it would require altering the table engine.
1035
+ // To ensure correct upsert behavior, we first fetch existing (id, thread_id) pairs for the incoming messages.
1036
+ const existingResult = await this.db.query({
1037
+ query: `SELECT id, thread_id FROM ${TABLE_MESSAGES} WHERE id IN ({ids:Array(String)})`,
1038
+ query_params: {
1039
+ ids: messages.map(m => m.id),
1040
+ },
791
1041
  clickhouse_settings: {
792
1042
  // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
793
1043
  date_time_input_format: 'best_effort',
1044
+ date_time_output_format: 'iso',
794
1045
  use_client_time_zone: 1,
795
1046
  output_format_json_quote_64bit_integers: 0,
796
1047
  },
1048
+ format: 'JSONEachRow',
797
1049
  });
798
-
799
- return messages;
800
- } catch (error) {
801
- console.error('Error saving messages:', error);
802
- throw error;
1050
+ const existingRows: Array<{ id: string; thread_id: string }> = await existingResult.json();
1051
+
1052
+ const existingSet = new Set(existingRows.map(row => `${row.id}::${row.thread_id}`));
1053
+ // Partition the batch into new inserts and updates:
1054
+ // New messages are inserted in bulk.
1055
+ const toInsert = messages.filter(m => !existingSet.has(`${m.id}::${threadId}`));
1056
+ // Existing messages are updated via ALTER TABLE ... UPDATE.
1057
+ const toUpdate = messages.filter(m => existingSet.has(`${m.id}::${threadId}`));
1058
+ const updatePromises = toUpdate.map(message =>
1059
+ this.db.command({
1060
+ query: `
1061
+ ALTER TABLE ${TABLE_MESSAGES}
1062
+ UPDATE content = {var_content:String}, role = {var_role:String}, type = {var_type:String}
1063
+ WHERE id = {var_id:String} AND thread_id = {var_thread_id:String}
1064
+ `,
1065
+ query_params: {
1066
+ var_content: typeof message.content === 'string' ? message.content : JSON.stringify(message.content),
1067
+ var_role: message.role,
1068
+ var_type: message.type || 'v2',
1069
+ var_id: message.id,
1070
+ var_thread_id: threadId,
1071
+ },
1072
+ clickhouse_settings: {
1073
+ // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
1074
+ date_time_input_format: 'best_effort',
1075
+ use_client_time_zone: 1,
1076
+ output_format_json_quote_64bit_integers: 0,
1077
+ },
1078
+ }),
1079
+ );
1080
+
1081
+ // Execute message inserts and thread update in parallel for better performance
1082
+ await Promise.all([
1083
+ // Insert messages
1084
+ this.db.insert({
1085
+ table: TABLE_MESSAGES,
1086
+ format: 'JSONEachRow',
1087
+ values: toInsert.map(message => ({
1088
+ id: message.id,
1089
+ thread_id: threadId,
1090
+ content: typeof message.content === 'string' ? message.content : JSON.stringify(message.content),
1091
+ createdAt: message.createdAt.toISOString(),
1092
+ role: message.role,
1093
+ type: message.type || 'v2',
1094
+ })),
1095
+ clickhouse_settings: {
1096
+ // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
1097
+ date_time_input_format: 'best_effort',
1098
+ use_client_time_zone: 1,
1099
+ output_format_json_quote_64bit_integers: 0,
1100
+ },
1101
+ }),
1102
+ ...updatePromises,
1103
+ // Update thread's updatedAt timestamp
1104
+ this.db.insert({
1105
+ table: TABLE_THREADS,
1106
+ format: 'JSONEachRow',
1107
+ values: [
1108
+ {
1109
+ id: thread.id,
1110
+ resourceId: thread.resourceId,
1111
+ title: thread.title,
1112
+ metadata: thread.metadata,
1113
+ createdAt: thread.createdAt,
1114
+ updatedAt: new Date().toISOString(),
1115
+ },
1116
+ ],
1117
+ clickhouse_settings: {
1118
+ date_time_input_format: 'best_effort',
1119
+ use_client_time_zone: 1,
1120
+ output_format_json_quote_64bit_integers: 0,
1121
+ },
1122
+ }),
1123
+ ]);
1124
+
1125
+ const list = new MessageList({ threadId, resourceId }).add(messages, 'memory');
1126
+ if (format === `v2`) return list.get.all.v2();
1127
+ return list.get.all.v1();
1128
+ } catch (error: any) {
1129
+ throw new MastraError(
1130
+ {
1131
+ id: 'CLICKHOUSE_STORAGE_SAVE_MESSAGES_FAILED',
1132
+ domain: ErrorDomain.STORAGE,
1133
+ category: ErrorCategory.THIRD_PARTY,
1134
+ },
1135
+ error,
1136
+ );
803
1137
  }
804
1138
  }
805
1139
 
@@ -844,9 +1178,16 @@ export class ClickhouseStore extends MastraStorage {
844
1178
  output_format_json_quote_64bit_integers: 0,
845
1179
  },
846
1180
  });
847
- } catch (error) {
848
- console.error('Error persisting workflow snapshot:', error);
849
- throw error;
1181
+ } catch (error: any) {
1182
+ throw new MastraError(
1183
+ {
1184
+ id: 'CLICKHOUSE_STORAGE_PERSIST_WORKFLOW_SNAPSHOT_FAILED',
1185
+ domain: ErrorDomain.STORAGE,
1186
+ category: ErrorCategory.THIRD_PARTY,
1187
+ details: { workflowName, runId },
1188
+ },
1189
+ error,
1190
+ );
850
1191
  }
851
1192
  }
852
1193
 
@@ -871,9 +1212,16 @@ export class ClickhouseStore extends MastraStorage {
871
1212
  }
872
1213
 
873
1214
  return (result as any).snapshot;
874
- } catch (error) {
875
- console.error('Error loading workflow snapshot:', error);
876
- throw error;
1215
+ } catch (error: any) {
1216
+ throw new MastraError(
1217
+ {
1218
+ id: 'CLICKHOUSE_STORAGE_LOAD_WORKFLOW_SNAPSHOT_FAILED',
1219
+ domain: ErrorDomain.STORAGE,
1220
+ category: ErrorCategory.THIRD_PARTY,
1221
+ details: { workflowName, runId },
1222
+ },
1223
+ error,
1224
+ );
877
1225
  }
878
1226
  }
879
1227
 
@@ -986,9 +1334,16 @@ export class ClickhouseStore extends MastraStorage {
986
1334
 
987
1335
  // Use runs.length as total when not paginating
988
1336
  return { runs, total: total || runs.length };
989
- } catch (error) {
990
- console.error('Error getting workflow runs:', error);
991
- throw error;
1337
+ } catch (error: any) {
1338
+ throw new MastraError(
1339
+ {
1340
+ id: 'CLICKHOUSE_STORAGE_GET_WORKFLOW_RUNS_FAILED',
1341
+ domain: ErrorDomain.STORAGE,
1342
+ category: ErrorCategory.THIRD_PARTY,
1343
+ details: { workflowName: workflowName ?? '', resourceId: resourceId ?? '' },
1344
+ },
1345
+ error,
1346
+ );
992
1347
  }
993
1348
  }
994
1349
 
@@ -1037,9 +1392,16 @@ export class ClickhouseStore extends MastraStorage {
1037
1392
  return null;
1038
1393
  }
1039
1394
  return this.parseWorkflowRun(resultJson[0]);
1040
- } catch (error) {
1041
- console.error('Error getting workflow run by ID:', error);
1042
- throw error;
1395
+ } catch (error: any) {
1396
+ throw new MastraError(
1397
+ {
1398
+ id: 'CLICKHOUSE_STORAGE_GET_WORKFLOW_RUN_BY_ID_FAILED',
1399
+ domain: ErrorDomain.STORAGE,
1400
+ category: ErrorCategory.THIRD_PARTY,
1401
+ details: { runId: runId ?? '', workflowName: workflowName ?? '' },
1402
+ },
1403
+ error,
1404
+ );
1043
1405
  }
1044
1406
  }
1045
1407
 
@@ -1052,7 +1414,51 @@ export class ClickhouseStore extends MastraStorage {
1052
1414
  return columns.some(c => c.name === column);
1053
1415
  }
1054
1416
 
1417
+ async getTracesPaginated(_args: StorageGetTracesArg): Promise<PaginationInfo & { traces: Trace[] }> {
1418
+ throw new MastraError({
1419
+ id: 'CLICKHOUSE_STORAGE_GET_TRACES_PAGINATED_FAILED',
1420
+ domain: ErrorDomain.STORAGE,
1421
+ category: ErrorCategory.USER,
1422
+ text: 'Method not implemented.',
1423
+ });
1424
+ }
1425
+
1426
+ async getThreadsByResourceIdPaginated(_args: {
1427
+ resourceId: string;
1428
+ page?: number;
1429
+ perPage?: number;
1430
+ }): Promise<PaginationInfo & { threads: StorageThreadType[] }> {
1431
+ throw new MastraError({
1432
+ id: 'CLICKHOUSE_STORAGE_GET_THREADS_BY_RESOURCE_ID_PAGINATED_FAILED',
1433
+ domain: ErrorDomain.STORAGE,
1434
+ category: ErrorCategory.USER,
1435
+ text: 'Method not implemented.',
1436
+ });
1437
+ }
1438
+
1439
+ async getMessagesPaginated(
1440
+ _args: StorageGetMessagesArg,
1441
+ ): Promise<PaginationInfo & { messages: MastraMessageV1[] | MastraMessageV2[] }> {
1442
+ throw new MastraError({
1443
+ id: 'CLICKHOUSE_STORAGE_GET_MESSAGES_PAGINATED_FAILED',
1444
+ domain: ErrorDomain.STORAGE,
1445
+ category: ErrorCategory.USER,
1446
+ text: 'Method not implemented.',
1447
+ });
1448
+ }
1449
+
1055
1450
  async close(): Promise<void> {
1056
1451
  await this.db.close();
1057
1452
  }
1453
+
1454
+ async updateMessages(_args: {
1455
+ messages: Partial<Omit<MastraMessageV2, 'createdAt'>> &
1456
+ {
1457
+ id: string;
1458
+ content?: { metadata?: MastraMessageContentV2['metadata']; content?: MastraMessageContentV2['content'] };
1459
+ }[];
1460
+ }): Promise<MastraMessageV2[]> {
1461
+ this.logger.error('updateMessages is not yet implemented in ClickhouseStore');
1462
+ throw new Error('Method not implemented');
1463
+ }
1058
1464
  }