@mastra/clickhouse 0.0.0-working-memory-per-user-20250620163010 → 0.0.0-zod-v4-compat-part-2-20250820135355

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +168 -4
  2. package/LICENSE.md +12 -4
  3. package/dist/index.cjs +2259 -566
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.ts +2 -4
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +2245 -550
  8. package/dist/index.js.map +1 -0
  9. package/dist/storage/domains/legacy-evals/index.d.ts +21 -0
  10. package/dist/storage/domains/legacy-evals/index.d.ts.map +1 -0
  11. package/dist/storage/domains/memory/index.d.ts +79 -0
  12. package/dist/storage/domains/memory/index.d.ts.map +1 -0
  13. package/dist/storage/domains/operations/index.d.ts +42 -0
  14. package/dist/storage/domains/operations/index.d.ts.map +1 -0
  15. package/dist/storage/domains/scores/index.d.ts +43 -0
  16. package/dist/storage/domains/scores/index.d.ts.map +1 -0
  17. package/dist/storage/domains/traces/index.d.ts +21 -0
  18. package/dist/storage/domains/traces/index.d.ts.map +1 -0
  19. package/dist/storage/domains/utils.d.ts +28 -0
  20. package/dist/storage/domains/utils.d.ts.map +1 -0
  21. package/dist/storage/domains/workflows/index.d.ts +36 -0
  22. package/dist/storage/domains/workflows/index.d.ts.map +1 -0
  23. package/dist/{_tsup-dts-rollup.d.cts → storage/index.d.ts} +106 -87
  24. package/dist/storage/index.d.ts.map +1 -0
  25. package/package.json +9 -9
  26. package/src/storage/domains/legacy-evals/index.ts +246 -0
  27. package/src/storage/domains/memory/index.ts +1393 -0
  28. package/src/storage/domains/operations/index.ts +319 -0
  29. package/src/storage/domains/scores/index.ts +326 -0
  30. package/src/storage/domains/traces/index.ts +275 -0
  31. package/src/storage/domains/utils.ts +86 -0
  32. package/src/storage/domains/workflows/index.ts +285 -0
  33. package/src/storage/index.test.ts +15 -1013
  34. package/src/storage/index.ts +214 -1013
  35. package/tsconfig.build.json +9 -0
  36. package/tsconfig.json +1 -1
  37. package/tsup.config.ts +22 -0
  38. package/dist/_tsup-dts-rollup.d.ts +0 -187
  39. package/dist/index.d.cts +0 -4
@@ -1,20 +1,12 @@
1
1
  import type { ClickHouseClient } from '@clickhouse/client';
2
2
  import { createClient } from '@clickhouse/client';
3
- import { MessageList } from '@mastra/core/agent';
4
3
  import type { MastraMessageContentV2 } from '@mastra/core/agent';
5
- import type { MetricResult, TestInfo } from '@mastra/core/eval';
4
+ import { MastraError, ErrorDomain, ErrorCategory } from '@mastra/core/error';
6
5
  import type { MastraMessageV1, MastraMessageV2, StorageThreadType } from '@mastra/core/memory';
7
- import {
8
- MastraStorage,
9
- TABLE_EVALS,
10
- TABLE_MESSAGES,
11
- TABLE_RESOURCES,
12
- TABLE_SCHEMAS,
13
- TABLE_THREADS,
14
- TABLE_TRACES,
15
- TABLE_WORKFLOW_SNAPSHOT,
16
- } from '@mastra/core/storage';
6
+ import type { ScoreRowData } from '@mastra/core/scores';
7
+ import { MastraStorage } from '@mastra/core/storage';
17
8
  import type {
9
+ TABLE_SCHEMAS,
18
10
  EvalRow,
19
11
  PaginationInfo,
20
12
  StorageColumn,
@@ -23,17 +15,20 @@ import type {
23
15
  WorkflowRun,
24
16
  WorkflowRuns,
25
17
  StorageGetTracesArg,
18
+ StorageGetTracesPaginatedArg,
19
+ StoragePagination,
20
+ StorageDomains,
21
+ PaginationArgs,
22
+ StorageResourceType,
26
23
  } from '@mastra/core/storage';
27
24
  import type { Trace } from '@mastra/core/telemetry';
28
25
  import type { WorkflowRunState } from '@mastra/core/workflows';
29
-
30
- function safelyParseJSON(jsonString: string): any {
31
- try {
32
- return JSON.parse(jsonString);
33
- } catch {
34
- return {};
35
- }
36
- }
26
+ import { LegacyEvalsStorageClickhouse } from './domains/legacy-evals';
27
+ import { MemoryStorageClickhouse } from './domains/memory';
28
+ import { StoreOperationsClickhouse } from './domains/operations';
29
+ import { ScoresStorageClickhouse } from './domains/scores';
30
+ import { TracesStorageClickhouse } from './domains/traces';
31
+ import { WorkflowsStorageClickhouse } from './domains/workflows';
37
32
 
38
33
  type IntervalUnit =
39
34
  | 'NANOSECOND'
@@ -66,48 +61,15 @@ export type ClickhouseConfig = {
66
61
  };
67
62
  };
68
63
 
69
- export const TABLE_ENGINES: Record<TABLE_NAMES, string> = {
70
- [TABLE_MESSAGES]: `MergeTree()`,
71
- [TABLE_WORKFLOW_SNAPSHOT]: `ReplacingMergeTree()`,
72
- [TABLE_TRACES]: `MergeTree()`,
73
- [TABLE_THREADS]: `ReplacingMergeTree()`,
74
- [TABLE_EVALS]: `MergeTree()`,
75
- [TABLE_RESOURCES]: `ReplacingMergeTree()`,
76
- };
77
-
78
- export const COLUMN_TYPES: Record<StorageColumn['type'], string> = {
79
- text: 'String',
80
- timestamp: 'DateTime64(3)',
81
- uuid: 'String',
82
- jsonb: 'String',
83
- integer: 'Int64',
84
- bigint: 'Int64',
85
- };
86
-
87
- function transformRows<R>(rows: any[]): R[] {
88
- return rows.map((row: any) => transformRow<R>(row));
89
- }
90
-
91
- function transformRow<R>(row: any): R {
92
- if (!row) {
93
- return row;
94
- }
95
-
96
- if (row.createdAt) {
97
- row.createdAt = new Date(row.createdAt);
98
- }
99
- if (row.updatedAt) {
100
- row.updatedAt = new Date(row.updatedAt);
101
- }
102
- return row;
103
- }
104
-
105
64
  export class ClickhouseStore extends MastraStorage {
106
65
  protected db: ClickHouseClient;
107
66
  protected ttl: ClickhouseConfig['ttl'] = {};
108
67
 
68
+ stores: StorageDomains;
69
+
109
70
  constructor(config: ClickhouseConfig) {
110
71
  super({ name: 'ClickhouseStore' });
72
+
111
73
  this.db = createClient({
112
74
  url: config.url,
113
75
  username: config.username,
@@ -120,203 +82,89 @@ export class ClickhouseStore extends MastraStorage {
120
82
  },
121
83
  });
122
84
  this.ttl = config.ttl;
123
- }
124
-
125
- private transformEvalRow(row: Record<string, any>): EvalRow {
126
- row = transformRow(row);
127
- const resultValue = JSON.parse(row.result as string);
128
- const testInfoValue = row.test_info ? JSON.parse(row.test_info as string) : undefined;
129
85
 
130
- if (!resultValue || typeof resultValue !== 'object' || !('score' in resultValue)) {
131
- throw new Error(`Invalid MetricResult format: ${JSON.stringify(resultValue)}`);
132
- }
86
+ const operations = new StoreOperationsClickhouse({ client: this.db, ttl: this.ttl });
87
+ const workflows = new WorkflowsStorageClickhouse({ client: this.db, operations });
88
+ const scores = new ScoresStorageClickhouse({ client: this.db, operations });
89
+ const legacyEvals = new LegacyEvalsStorageClickhouse({ client: this.db, operations });
90
+ const traces = new TracesStorageClickhouse({ client: this.db, operations });
91
+ const memory = new MemoryStorageClickhouse({ client: this.db, operations });
92
+
93
+ this.stores = {
94
+ operations,
95
+ workflows,
96
+ scores,
97
+ legacyEvals,
98
+ traces,
99
+ memory,
100
+ };
101
+ }
133
102
 
103
+ get supports(): {
104
+ selectByIncludeResourceScope: boolean;
105
+ resourceWorkingMemory: boolean;
106
+ hasColumn: boolean;
107
+ createTable: boolean;
108
+ deleteMessages: boolean;
109
+ } {
134
110
  return {
135
- input: row.input as string,
136
- output: row.output as string,
137
- result: resultValue as MetricResult,
138
- agentName: row.agent_name as string,
139
- metricName: row.metric_name as string,
140
- instructions: row.instructions as string,
141
- testInfo: testInfoValue as TestInfo,
142
- globalRunId: row.global_run_id as string,
143
- runId: row.run_id as string,
144
- createdAt: row.created_at as string,
111
+ selectByIncludeResourceScope: true,
112
+ resourceWorkingMemory: true,
113
+ hasColumn: true,
114
+ createTable: true,
115
+ deleteMessages: false,
145
116
  };
146
117
  }
147
118
 
148
119
  async getEvalsByAgentName(agentName: string, type?: 'test' | 'live'): Promise<EvalRow[]> {
149
- try {
150
- const baseQuery = `SELECT *, toDateTime64(createdAt, 3) as createdAt FROM ${TABLE_EVALS} WHERE agent_name = {var_agent_name:String}`;
151
- const typeCondition =
152
- type === 'test'
153
- ? " AND test_info IS NOT NULL AND JSONExtractString(test_info, 'testPath') IS NOT NULL"
154
- : type === 'live'
155
- ? " AND (test_info IS NULL OR JSONExtractString(test_info, 'testPath') IS NULL)"
156
- : '';
157
-
158
- const result = await this.db.query({
159
- query: `${baseQuery}${typeCondition} ORDER BY createdAt DESC`,
160
- query_params: { var_agent_name: agentName },
161
- clickhouse_settings: {
162
- date_time_input_format: 'best_effort',
163
- date_time_output_format: 'iso',
164
- use_client_time_zone: 1,
165
- output_format_json_quote_64bit_integers: 0,
166
- },
167
- });
168
-
169
- if (!result) {
170
- return [];
171
- }
120
+ return this.stores.legacyEvals.getEvalsByAgentName(agentName, type);
121
+ }
172
122
 
173
- const rows = await result.json();
174
- return rows.data.map((row: any) => this.transformEvalRow(row));
175
- } catch (error) {
176
- // Handle case where table doesn't exist yet
177
- if (error instanceof Error && error.message.includes('no such table')) {
178
- return [];
179
- }
180
- this.logger.error('Failed to get evals for the specified agent: ' + (error as any)?.message);
181
- throw error;
182
- }
123
+ async getEvals(
124
+ options: { agentName?: string; type?: 'test' | 'live' } & PaginationArgs,
125
+ ): Promise<PaginationInfo & { evals: EvalRow[] }> {
126
+ return this.stores.legacyEvals.getEvals(options);
183
127
  }
184
128
 
185
129
  async batchInsert({ tableName, records }: { tableName: TABLE_NAMES; records: Record<string, any>[] }): Promise<void> {
186
- try {
187
- await this.db.insert({
188
- table: tableName,
189
- values: records.map(record => ({
190
- ...Object.fromEntries(
191
- Object.entries(record).map(([key, value]) => [
192
- key,
193
- TABLE_SCHEMAS[tableName as TABLE_NAMES]?.[key]?.type === 'timestamp'
194
- ? new Date(value).toISOString()
195
- : value,
196
- ]),
197
- ),
198
- })),
199
- format: 'JSONEachRow',
200
- clickhouse_settings: {
201
- // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
202
- date_time_input_format: 'best_effort',
203
- use_client_time_zone: 1,
204
- output_format_json_quote_64bit_integers: 0,
205
- },
206
- });
207
- } catch (error) {
208
- console.error(`Error inserting into ${tableName}:`, error);
209
- throw error;
210
- }
130
+ await this.stores.operations.batchInsert({ tableName, records });
131
+ // await this.optimizeTable({ tableName });
211
132
  }
212
133
 
213
- async getTraces({
214
- name,
215
- scope,
216
- page,
217
- perPage,
218
- attributes,
219
- filters,
220
- fromDate,
221
- toDate,
222
- }: {
223
- name?: string;
224
- scope?: string;
225
- page: number;
226
- perPage: number;
227
- attributes?: Record<string, string>;
228
- filters?: Record<string, any>;
229
- fromDate?: Date;
230
- toDate?: Date;
231
- }): Promise<any[]> {
232
- const limit = perPage;
233
- const offset = page * perPage;
234
-
235
- const args: Record<string, any> = {};
236
-
237
- const conditions: string[] = [];
238
- if (name) {
239
- conditions.push(`name LIKE CONCAT({var_name:String}, '%')`);
240
- args.var_name = name;
241
- }
242
- if (scope) {
243
- conditions.push(`scope = {var_scope:String}`);
244
- args.var_scope = scope;
245
- }
246
- if (attributes) {
247
- Object.entries(attributes).forEach(([key, value]) => {
248
- conditions.push(`JSONExtractString(attributes, '${key}') = {var_attr_${key}:String}`);
249
- args[`var_attr_${key}`] = value;
250
- });
251
- }
252
-
253
- if (filters) {
254
- Object.entries(filters).forEach(([key, value]) => {
255
- conditions.push(
256
- `${key} = {var_col_${key}:${COLUMN_TYPES[TABLE_SCHEMAS.mastra_traces?.[key]?.type ?? 'text']}}`,
257
- );
258
- args[`var_col_${key}`] = value;
134
+ async optimizeTable({ tableName }: { tableName: TABLE_NAMES }): Promise<void> {
135
+ try {
136
+ await this.db.command({
137
+ query: `OPTIMIZE TABLE ${tableName} FINAL`,
259
138
  });
139
+ } catch (error: any) {
140
+ throw new MastraError(
141
+ {
142
+ id: 'CLICKHOUSE_STORAGE_OPTIMIZE_TABLE_FAILED',
143
+ domain: ErrorDomain.STORAGE,
144
+ category: ErrorCategory.THIRD_PARTY,
145
+ details: { tableName },
146
+ },
147
+ error,
148
+ );
260
149
  }
261
-
262
- if (fromDate) {
263
- conditions.push(`createdAt >= {var_from_date:DateTime64(3)}`);
264
- args.var_from_date = fromDate.getTime() / 1000; // Convert to Unix timestamp
265
- }
266
-
267
- if (toDate) {
268
- conditions.push(`createdAt <= {var_to_date:DateTime64(3)}`);
269
- args.var_to_date = toDate.getTime() / 1000; // Convert to Unix timestamp
270
- }
271
-
272
- const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
273
-
274
- const result = await this.db.query({
275
- query: `SELECT *, toDateTime64(createdAt, 3) as createdAt FROM ${TABLE_TRACES} ${whereClause} ORDER BY "createdAt" DESC LIMIT ${limit} OFFSET ${offset}`,
276
- query_params: args,
277
- clickhouse_settings: {
278
- // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
279
- date_time_input_format: 'best_effort',
280
- date_time_output_format: 'iso',
281
- use_client_time_zone: 1,
282
- output_format_json_quote_64bit_integers: 0,
283
- },
284
- });
285
-
286
- if (!result) {
287
- return [];
288
- }
289
-
290
- const resp = await result.json();
291
- const rows: any[] = resp.data;
292
- return rows.map(row => ({
293
- id: row.id,
294
- parentSpanId: row.parentSpanId,
295
- traceId: row.traceId,
296
- name: row.name,
297
- scope: row.scope,
298
- kind: row.kind,
299
- status: safelyParseJSON(row.status as string),
300
- events: safelyParseJSON(row.events as string),
301
- links: safelyParseJSON(row.links as string),
302
- attributes: safelyParseJSON(row.attributes as string),
303
- startTime: row.startTime,
304
- endTime: row.endTime,
305
- other: safelyParseJSON(row.other as string),
306
- createdAt: row.createdAt,
307
- }));
308
- }
309
-
310
- async optimizeTable({ tableName }: { tableName: TABLE_NAMES }): Promise<void> {
311
- await this.db.command({
312
- query: `OPTIMIZE TABLE ${tableName} FINAL`,
313
- });
314
150
  }
315
151
 
316
152
  async materializeTtl({ tableName }: { tableName: TABLE_NAMES }): Promise<void> {
317
- await this.db.command({
318
- query: `ALTER TABLE ${tableName} MATERIALIZE TTL;`,
319
- });
153
+ try {
154
+ await this.db.command({
155
+ query: `ALTER TABLE ${tableName} MATERIALIZE TTL;`,
156
+ });
157
+ } catch (error: any) {
158
+ throw new MastraError(
159
+ {
160
+ id: 'CLICKHOUSE_STORAGE_MATERIALIZE_TTL_FAILED',
161
+ domain: ErrorDomain.STORAGE,
162
+ category: ErrorCategory.THIRD_PARTY,
163
+ details: { tableName },
164
+ },
165
+ error,
166
+ );
167
+ }
320
168
  }
321
169
 
322
170
  async createTable({
@@ -326,78 +174,13 @@ export class ClickhouseStore extends MastraStorage {
326
174
  tableName: TABLE_NAMES;
327
175
  schema: Record<string, StorageColumn>;
328
176
  }): Promise<void> {
329
- try {
330
- const columns = Object.entries(schema)
331
- .map(([name, def]) => {
332
- const constraints = [];
333
- if (!def.nullable) constraints.push('NOT NULL');
334
- const columnTtl = this.ttl?.[tableName]?.columns?.[name];
335
- return `"${name}" ${COLUMN_TYPES[def.type]} ${constraints.join(' ')} ${columnTtl ? `TTL toDateTime(${columnTtl.ttlKey ?? 'createdAt'}) + INTERVAL ${columnTtl.interval} ${columnTtl.unit}` : ''}`;
336
- })
337
- .join(',\n');
338
-
339
- const rowTtl = this.ttl?.[tableName]?.row;
340
- const sql =
341
- tableName === TABLE_WORKFLOW_SNAPSHOT
342
- ? `
343
- CREATE TABLE IF NOT EXISTS ${tableName} (
344
- ${['id String'].concat(columns)}
345
- )
346
- ENGINE = ${TABLE_ENGINES[tableName]}
347
- PRIMARY KEY (createdAt, run_id, workflow_name)
348
- ORDER BY (createdAt, run_id, workflow_name)
349
- ${rowTtl ? `TTL toDateTime(${rowTtl.ttlKey ?? 'createdAt'}) + INTERVAL ${rowTtl.interval} ${rowTtl.unit}` : ''}
350
- SETTINGS index_granularity = 8192
351
- `
352
- : `
353
- CREATE TABLE IF NOT EXISTS ${tableName} (
354
- ${columns}
355
- )
356
- ENGINE = ${TABLE_ENGINES[tableName]}
357
- PRIMARY KEY (createdAt, ${tableName === TABLE_EVALS ? 'run_id' : 'id'})
358
- ORDER BY (createdAt, ${tableName === TABLE_EVALS ? 'run_id' : 'id'})
359
- ${this.ttl?.[tableName]?.row ? `TTL toDateTime(createdAt) + INTERVAL ${this.ttl[tableName].row.interval} ${this.ttl[tableName].row.unit}` : ''}
360
- SETTINGS index_granularity = 8192
361
- `;
362
-
363
- await this.db.query({
364
- query: sql,
365
- clickhouse_settings: {
366
- // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
367
- date_time_input_format: 'best_effort',
368
- date_time_output_format: 'iso',
369
- use_client_time_zone: 1,
370
- output_format_json_quote_64bit_integers: 0,
371
- },
372
- });
373
- } catch (error) {
374
- console.error(`Error creating table ${tableName}:`, error);
375
- throw error;
376
- }
177
+ return this.stores.operations.createTable({ tableName, schema });
377
178
  }
378
179
 
379
- protected getSqlType(type: StorageColumn['type']): string {
380
- switch (type) {
381
- case 'text':
382
- return 'String';
383
- case 'timestamp':
384
- return 'DateTime64(3)';
385
- case 'integer':
386
- case 'bigint':
387
- return 'Int64';
388
- case 'jsonb':
389
- return 'String';
390
- default:
391
- return super.getSqlType(type); // fallback to base implementation
392
- }
180
+ async dropTable({ tableName }: { tableName: TABLE_NAMES }): Promise<void> {
181
+ return this.stores.operations.dropTable({ tableName });
393
182
  }
394
183
 
395
- /**
396
- * Alters table schema to add columns if they don't exist
397
- * @param tableName Name of the table
398
- * @param schema Schema of the table
399
- * @param ifNotExists Array of column names to add if they don't exist
400
- */
401
184
  async alterTable({
402
185
  tableName,
403
186
  schema,
@@ -407,239 +190,93 @@ export class ClickhouseStore extends MastraStorage {
407
190
  schema: Record<string, StorageColumn>;
408
191
  ifNotExists: string[];
409
192
  }): Promise<void> {
410
- try {
411
- // 1. Get existing columns
412
- const describeSql = `DESCRIBE TABLE ${tableName}`;
413
- const result = await this.db.query({
414
- query: describeSql,
415
- });
416
- const rows = await result.json();
417
- const existingColumnNames = new Set(rows.data.map((row: any) => row.name.toLowerCase()));
418
-
419
- // 2. Add missing columns
420
- for (const columnName of ifNotExists) {
421
- if (!existingColumnNames.has(columnName.toLowerCase()) && schema[columnName]) {
422
- const columnDef = schema[columnName];
423
- let sqlType = this.getSqlType(columnDef.type);
424
- if (columnDef.nullable !== false) {
425
- sqlType = `Nullable(${sqlType})`;
426
- }
427
- const defaultValue = columnDef.nullable === false ? this.getDefaultValue(columnDef.type) : '';
428
- // Use backticks or double quotes as needed for identifiers
429
- const alterSql =
430
- `ALTER TABLE ${tableName} ADD COLUMN IF NOT EXISTS "${columnName}" ${sqlType} ${defaultValue}`.trim();
431
-
432
- await this.db.query({
433
- query: alterSql,
434
- });
435
- this.logger?.debug?.(`Added column ${columnName} to table ${tableName}`);
436
- }
437
- }
438
- } catch (error) {
439
- this.logger?.error?.(
440
- `Error altering table ${tableName}: ${error instanceof Error ? error.message : String(error)}`,
441
- );
442
- throw new Error(`Failed to alter table ${tableName}: ${error}`);
443
- }
193
+ return this.stores.operations.alterTable({ tableName, schema, ifNotExists });
444
194
  }
445
195
 
446
196
  async clearTable({ tableName }: { tableName: TABLE_NAMES }): Promise<void> {
447
- try {
448
- await this.db.query({
449
- query: `TRUNCATE TABLE ${tableName}`,
450
- clickhouse_settings: {
451
- // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
452
- date_time_input_format: 'best_effort',
453
- date_time_output_format: 'iso',
454
- use_client_time_zone: 1,
455
- output_format_json_quote_64bit_integers: 0,
456
- },
457
- });
458
- } catch (error) {
459
- console.error(`Error clearing table ${tableName}:`, error);
460
- throw error;
461
- }
197
+ return this.stores.operations.clearTable({ tableName });
462
198
  }
463
199
 
464
200
  async insert({ tableName, record }: { tableName: TABLE_NAMES; record: Record<string, any> }): Promise<void> {
465
- try {
466
- await this.db.insert({
467
- table: tableName,
468
- values: [
469
- {
470
- ...record,
471
- createdAt: record.createdAt.toISOString(),
472
- updatedAt: record.updatedAt.toISOString(),
473
- },
474
- ],
475
- format: 'JSONEachRow',
476
- clickhouse_settings: {
477
- // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
478
- output_format_json_quote_64bit_integers: 0,
479
- date_time_input_format: 'best_effort',
480
- use_client_time_zone: 1,
481
- },
482
- });
483
- } catch (error) {
484
- console.error(`Error inserting into ${tableName}:`, error);
485
- throw error;
486
- }
201
+ return this.stores.operations.insert({ tableName, record });
487
202
  }
488
203
 
489
204
  async load<R>({ tableName, keys }: { tableName: TABLE_NAMES; keys: Record<string, string> }): Promise<R | null> {
490
- try {
491
- const keyEntries = Object.entries(keys);
492
- const conditions = keyEntries
493
- .map(
494
- ([key]) =>
495
- `"${key}" = {var_${key}:${COLUMN_TYPES[TABLE_SCHEMAS[tableName as TABLE_NAMES]?.[key]?.type ?? 'text']}}`,
496
- )
497
- .join(' AND ');
498
- const values = keyEntries.reduce((acc, [key, value]) => {
499
- return { ...acc, [`var_${key}`]: value };
500
- }, {});
205
+ return this.stores.operations.load({ tableName, keys });
206
+ }
501
207
 
502
- const result = await this.db.query({
503
- 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}`,
504
- query_params: values,
505
- clickhouse_settings: {
506
- // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
507
- date_time_input_format: 'best_effort',
508
- date_time_output_format: 'iso',
509
- use_client_time_zone: 1,
510
- output_format_json_quote_64bit_integers: 0,
511
- },
512
- });
208
+ async persistWorkflowSnapshot({
209
+ workflowName,
210
+ runId,
211
+ snapshot,
212
+ }: {
213
+ workflowName: string;
214
+ runId: string;
215
+ snapshot: WorkflowRunState;
216
+ }): Promise<void> {
217
+ return this.stores.workflows.persistWorkflowSnapshot({ workflowName, runId, snapshot });
218
+ }
513
219
 
514
- if (!result) {
515
- return null;
516
- }
220
+ async loadWorkflowSnapshot({
221
+ workflowName,
222
+ runId,
223
+ }: {
224
+ workflowName: string;
225
+ runId: string;
226
+ }): Promise<WorkflowRunState | null> {
227
+ return this.stores.workflows.loadWorkflowSnapshot({ workflowName, runId });
228
+ }
517
229
 
518
- const rows = await result.json();
519
- // If this is a workflow snapshot, parse the snapshot field
520
- if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
521
- const snapshot = rows.data[0] as any;
522
- if (!snapshot) {
523
- return null;
524
- }
525
- if (typeof snapshot.snapshot === 'string') {
526
- snapshot.snapshot = JSON.parse(snapshot.snapshot);
527
- }
528
- return transformRow(snapshot);
529
- }
230
+ async getWorkflowRuns({
231
+ workflowName,
232
+ fromDate,
233
+ toDate,
234
+ limit,
235
+ offset,
236
+ resourceId,
237
+ }: {
238
+ workflowName?: string;
239
+ fromDate?: Date;
240
+ toDate?: Date;
241
+ limit?: number;
242
+ offset?: number;
243
+ resourceId?: string;
244
+ } = {}): Promise<WorkflowRuns> {
245
+ return this.stores.workflows.getWorkflowRuns({ workflowName, fromDate, toDate, limit, offset, resourceId });
246
+ }
530
247
 
531
- const data: R = transformRow(rows.data[0]);
532
- return data;
533
- } catch (error) {
534
- console.error(`Error loading from ${tableName}:`, error);
535
- throw error;
536
- }
248
+ async getWorkflowRunById({
249
+ runId,
250
+ workflowName,
251
+ }: {
252
+ runId: string;
253
+ workflowName?: string;
254
+ }): Promise<WorkflowRun | null> {
255
+ return this.stores.workflows.getWorkflowRunById({ runId, workflowName });
537
256
  }
538
257
 
539
- async getThreadById({ threadId }: { threadId: string }): Promise<StorageThreadType | null> {
540
- try {
541
- const result = await this.db.query({
542
- query: `SELECT
543
- id,
544
- "resourceId",
545
- title,
546
- metadata,
547
- toDateTime64(createdAt, 3) as createdAt,
548
- toDateTime64(updatedAt, 3) as updatedAt
549
- FROM "${TABLE_THREADS}"
550
- FINAL
551
- WHERE id = {var_id:String}`,
552
- query_params: { var_id: threadId },
553
- clickhouse_settings: {
554
- // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
555
- date_time_input_format: 'best_effort',
556
- date_time_output_format: 'iso',
557
- use_client_time_zone: 1,
558
- output_format_json_quote_64bit_integers: 0,
559
- },
560
- });
258
+ async getTraces(args: StorageGetTracesArg): Promise<any[]> {
259
+ return this.stores.traces.getTraces(args);
260
+ }
561
261
 
562
- const rows = await result.json();
563
- const thread = transformRow(rows.data[0]) as StorageThreadType;
262
+ async getTracesPaginated(args: StorageGetTracesPaginatedArg): Promise<PaginationInfo & { traces: Trace[] }> {
263
+ return this.stores.traces.getTracesPaginated(args);
264
+ }
564
265
 
565
- if (!thread) {
566
- return null;
567
- }
266
+ async batchTraceInsert(args: { records: Trace[] }): Promise<void> {
267
+ return this.stores.traces.batchTraceInsert(args);
268
+ }
568
269
 
569
- return {
570
- ...thread,
571
- metadata: typeof thread.metadata === 'string' ? JSON.parse(thread.metadata) : thread.metadata,
572
- createdAt: thread.createdAt,
573
- updatedAt: thread.updatedAt,
574
- };
575
- } catch (error) {
576
- console.error(`Error getting thread ${threadId}:`, error);
577
- throw error;
578
- }
270
+ async getThreadById({ threadId }: { threadId: string }): Promise<StorageThreadType | null> {
271
+ return this.stores.memory.getThreadById({ threadId });
579
272
  }
580
273
 
581
274
  async getThreadsByResourceId({ resourceId }: { resourceId: string }): Promise<StorageThreadType[]> {
582
- try {
583
- const result = await this.db.query({
584
- query: `SELECT
585
- id,
586
- "resourceId",
587
- title,
588
- metadata,
589
- toDateTime64(createdAt, 3) as createdAt,
590
- toDateTime64(updatedAt, 3) as updatedAt
591
- FROM "${TABLE_THREADS}"
592
- WHERE "resourceId" = {var_resourceId:String}`,
593
- query_params: { var_resourceId: resourceId },
594
- clickhouse_settings: {
595
- // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
596
- date_time_input_format: 'best_effort',
597
- date_time_output_format: 'iso',
598
- use_client_time_zone: 1,
599
- output_format_json_quote_64bit_integers: 0,
600
- },
601
- });
602
-
603
- const rows = await result.json();
604
- const threads = transformRows(rows.data) as StorageThreadType[];
605
-
606
- return threads.map((thread: StorageThreadType) => ({
607
- ...thread,
608
- metadata: typeof thread.metadata === 'string' ? JSON.parse(thread.metadata) : thread.metadata,
609
- createdAt: thread.createdAt,
610
- updatedAt: thread.updatedAt,
611
- }));
612
- } catch (error) {
613
- console.error(`Error getting threads for resource ${resourceId}:`, error);
614
- throw error;
615
- }
275
+ return this.stores.memory.getThreadsByResourceId({ resourceId });
616
276
  }
617
277
 
618
278
  async saveThread({ thread }: { thread: StorageThreadType }): Promise<StorageThreadType> {
619
- try {
620
- await this.db.insert({
621
- table: TABLE_THREADS,
622
- values: [
623
- {
624
- ...thread,
625
- createdAt: thread.createdAt.toISOString(),
626
- updatedAt: thread.updatedAt.toISOString(),
627
- },
628
- ],
629
- format: 'JSONEachRow',
630
- clickhouse_settings: {
631
- // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
632
- date_time_input_format: 'best_effort',
633
- use_client_time_zone: 1,
634
- output_format_json_quote_64bit_integers: 0,
635
- },
636
- });
637
-
638
- return thread;
639
- } catch (error) {
640
- console.error('Error saving thread:', error);
641
- throw error;
642
- }
279
+ return this.stores.memory.saveThread({ thread });
643
280
  }
644
281
 
645
282
  async updateThread({
@@ -651,76 +288,19 @@ export class ClickhouseStore extends MastraStorage {
651
288
  title: string;
652
289
  metadata: Record<string, unknown>;
653
290
  }): Promise<StorageThreadType> {
654
- try {
655
- // First get the existing thread to merge metadata
656
- const existingThread = await this.getThreadById({ threadId: id });
657
- if (!existingThread) {
658
- throw new Error(`Thread ${id} not found`);
659
- }
660
-
661
- // Merge the existing metadata with the new metadata
662
- const mergedMetadata = {
663
- ...existingThread.metadata,
664
- ...metadata,
665
- };
666
-
667
- const updatedThread = {
668
- ...existingThread,
669
- title,
670
- metadata: mergedMetadata,
671
- updatedAt: new Date(),
672
- };
673
-
674
- await this.db.insert({
675
- table: TABLE_THREADS,
676
- format: 'JSONEachRow',
677
- values: [
678
- {
679
- id: updatedThread.id,
680
- resourceId: updatedThread.resourceId,
681
- title: updatedThread.title,
682
- metadata: updatedThread.metadata,
683
- createdAt: updatedThread.createdAt,
684
- updatedAt: updatedThread.updatedAt.toISOString(),
685
- },
686
- ],
687
- clickhouse_settings: {
688
- date_time_input_format: 'best_effort',
689
- use_client_time_zone: 1,
690
- output_format_json_quote_64bit_integers: 0,
691
- },
692
- });
693
-
694
- return updatedThread;
695
- } catch (error) {
696
- console.error('Error updating thread:', error);
697
- throw error;
698
- }
291
+ return this.stores.memory.updateThread({ id, title, metadata });
699
292
  }
700
293
 
701
294
  async deleteThread({ threadId }: { threadId: string }): Promise<void> {
702
- try {
703
- // First delete all messages associated with this thread
704
- await this.db.command({
705
- query: `DELETE FROM "${TABLE_MESSAGES}" WHERE thread_id = {var_thread_id:String};`,
706
- query_params: { var_thread_id: threadId },
707
- clickhouse_settings: {
708
- output_format_json_quote_64bit_integers: 0,
709
- },
710
- });
295
+ return this.stores.memory.deleteThread({ threadId });
296
+ }
711
297
 
712
- // Then delete the thread
713
- await this.db.command({
714
- query: `DELETE FROM "${TABLE_THREADS}" WHERE id = {var_id:String};`,
715
- query_params: { var_id: threadId },
716
- clickhouse_settings: {
717
- output_format_json_quote_64bit_integers: 0,
718
- },
719
- });
720
- } catch (error) {
721
- console.error('Error deleting thread:', error);
722
- throw error;
723
- }
298
+ async getThreadsByResourceIdPaginated(args: {
299
+ resourceId: string;
300
+ page: number;
301
+ perPage: number;
302
+ }): Promise<PaginationInfo & { threads: StorageThreadType[] }> {
303
+ return this.stores.memory.getThreadsByResourceIdPaginated(args);
724
304
  }
725
305
 
726
306
  public async getMessages(args: StorageGetMessagesArg & { format?: 'v1' }): Promise<MastraMessageV1[]>;
@@ -731,119 +311,7 @@ export class ClickhouseStore extends MastraStorage {
731
311
  selectBy,
732
312
  format,
733
313
  }: StorageGetMessagesArg & { format?: 'v1' | 'v2' }): Promise<MastraMessageV1[] | MastraMessageV2[]> {
734
- try {
735
- const messages: any[] = [];
736
- const limit = this.resolveMessageLimit({ last: selectBy?.last, defaultLimit: 40 });
737
- const include = selectBy?.include || [];
738
-
739
- if (include.length) {
740
- const includeResult = await this.db.query({
741
- query: `
742
- WITH ordered_messages AS (
743
- SELECT
744
- *,
745
- toDateTime64(createdAt, 3) as createdAt,
746
- toDateTime64(updatedAt, 3) as updatedAt,
747
- ROW_NUMBER() OVER (ORDER BY "createdAt" DESC) as row_num
748
- FROM "${TABLE_MESSAGES}"
749
- WHERE thread_id = {var_thread_id:String}
750
- )
751
- SELECT
752
- m.id AS id,
753
- m.content as content,
754
- m.role as role,
755
- m.type as type,
756
- m.createdAt as createdAt,
757
- m.updatedAt as updatedAt,
758
- m.thread_id AS "threadId"
759
- FROM ordered_messages m
760
- WHERE m.id = ANY({var_include:Array(String)})
761
- OR EXISTS (
762
- SELECT 1 FROM ordered_messages target
763
- WHERE target.id = ANY({var_include:Array(String)})
764
- AND (
765
- -- Get previous messages based on the max withPreviousMessages
766
- (m.row_num <= target.row_num + {var_withPreviousMessages:Int64} AND m.row_num > target.row_num)
767
- OR
768
- -- Get next messages based on the max withNextMessages
769
- (m.row_num >= target.row_num - {var_withNextMessages:Int64} AND m.row_num < target.row_num)
770
- )
771
- )
772
- ORDER BY m."createdAt" DESC
773
- `,
774
- query_params: {
775
- var_thread_id: threadId,
776
- var_include: include.map(i => i.id),
777
- var_withPreviousMessages: Math.max(...include.map(i => i.withPreviousMessages || 0)),
778
- var_withNextMessages: Math.max(...include.map(i => i.withNextMessages || 0)),
779
- },
780
- clickhouse_settings: {
781
- // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
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
-
789
- const rows = await includeResult.json();
790
- messages.push(...transformRows(rows.data));
791
- }
792
-
793
- // Then get the remaining messages, excluding the ids we just fetched
794
- const result = await this.db.query({
795
- query: `
796
- SELECT
797
- id,
798
- content,
799
- role,
800
- type,
801
- toDateTime64(createdAt, 3) as createdAt,
802
- thread_id AS "threadId"
803
- FROM "${TABLE_MESSAGES}"
804
- WHERE thread_id = {threadId:String}
805
- AND id NOT IN ({exclude:Array(String)})
806
- ORDER BY "createdAt" DESC
807
- LIMIT {limit:Int64}
808
- `,
809
- query_params: {
810
- threadId,
811
- exclude: messages.map(m => m.id),
812
- limit,
813
- },
814
- clickhouse_settings: {
815
- // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
816
- date_time_input_format: 'best_effort',
817
- date_time_output_format: 'iso',
818
- use_client_time_zone: 1,
819
- output_format_json_quote_64bit_integers: 0,
820
- },
821
- });
822
-
823
- const rows = await result.json();
824
- messages.push(...transformRows(rows.data));
825
-
826
- // Sort all messages by creation date
827
- messages.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
828
-
829
- // Parse message content
830
- messages.forEach(message => {
831
- if (typeof message.content === 'string') {
832
- try {
833
- message.content = JSON.parse(message.content);
834
- } catch {
835
- // If parsing fails, leave as string
836
- }
837
- }
838
- });
839
-
840
- const list = new MessageList({ threadId, resourceId }).add(messages, 'memory');
841
- if (format === `v2`) return list.get.all.v2();
842
- return list.get.all.v1();
843
- } catch (error) {
844
- console.error('Error getting messages:', error);
845
- throw error;
846
- }
314
+ return this.stores.memory.getMessages({ threadId, resourceId, selectBy, format });
847
315
  }
848
316
 
849
317
  async saveMessages(args: { messages: MastraMessageV1[]; format?: undefined | 'v1' }): Promise<MastraMessageV1[]>;
@@ -851,353 +319,86 @@ export class ClickhouseStore extends MastraStorage {
851
319
  async saveMessages(
852
320
  args: { messages: MastraMessageV1[]; format?: undefined | 'v1' } | { messages: MastraMessageV2[]; format: 'v2' },
853
321
  ): Promise<MastraMessageV2[] | MastraMessageV1[]> {
854
- const { messages, format = 'v1' } = args;
855
- if (messages.length === 0) return messages;
856
-
857
- try {
858
- const threadId = messages[0]?.threadId;
859
- const resourceId = messages[0]?.resourceId;
860
- if (!threadId) {
861
- throw new Error('Thread ID is required');
862
- }
863
-
864
- // Check if thread exists
865
- const thread = await this.getThreadById({ threadId });
866
- if (!thread) {
867
- throw new Error(`Thread ${threadId} not found`);
868
- }
869
-
870
- // Execute message inserts and thread update in parallel for better performance
871
- await Promise.all([
872
- // Insert messages
873
- this.db.insert({
874
- table: TABLE_MESSAGES,
875
- format: 'JSONEachRow',
876
- values: messages.map(message => ({
877
- id: message.id,
878
- thread_id: threadId,
879
- content: typeof message.content === 'string' ? message.content : JSON.stringify(message.content),
880
- createdAt: message.createdAt.toISOString(),
881
- role: message.role,
882
- type: message.type || 'v2',
883
- })),
884
- clickhouse_settings: {
885
- // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
886
- date_time_input_format: 'best_effort',
887
- use_client_time_zone: 1,
888
- output_format_json_quote_64bit_integers: 0,
889
- },
890
- }),
891
- // Update thread's updatedAt timestamp
892
- this.db.insert({
893
- table: TABLE_THREADS,
894
- format: 'JSONEachRow',
895
- values: [
896
- {
897
- id: thread.id,
898
- resourceId: thread.resourceId,
899
- title: thread.title,
900
- metadata: thread.metadata,
901
- createdAt: thread.createdAt,
902
- updatedAt: new Date().toISOString(),
903
- },
904
- ],
905
- clickhouse_settings: {
906
- date_time_input_format: 'best_effort',
907
- use_client_time_zone: 1,
908
- output_format_json_quote_64bit_integers: 0,
909
- },
910
- }),
911
- ]);
912
-
913
- const list = new MessageList({ threadId, resourceId }).add(messages, 'memory');
914
- if (format === `v2`) return list.get.all.v2();
915
- return list.get.all.v1();
916
- } catch (error) {
917
- console.error('Error saving messages:', error);
918
- throw error;
919
- }
322
+ return this.stores.memory.saveMessages(args);
920
323
  }
921
324
 
922
- async persistWorkflowSnapshot({
923
- workflowName,
924
- runId,
925
- snapshot,
926
- }: {
927
- workflowName: string;
928
- runId: string;
929
- snapshot: WorkflowRunState;
930
- }): Promise<void> {
931
- try {
932
- const currentSnapshot = await this.load({
933
- tableName: TABLE_WORKFLOW_SNAPSHOT,
934
- keys: { workflow_name: workflowName, run_id: runId },
935
- });
936
-
937
- const now = new Date();
938
- const persisting = currentSnapshot
939
- ? {
940
- ...currentSnapshot,
941
- snapshot: JSON.stringify(snapshot),
942
- updatedAt: now.toISOString(),
943
- }
944
- : {
945
- workflow_name: workflowName,
946
- run_id: runId,
947
- snapshot: JSON.stringify(snapshot),
948
- createdAt: now.toISOString(),
949
- updatedAt: now.toISOString(),
950
- };
951
-
952
- await this.db.insert({
953
- table: TABLE_WORKFLOW_SNAPSHOT,
954
- format: 'JSONEachRow',
955
- values: [persisting],
956
- clickhouse_settings: {
957
- // Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
958
- date_time_input_format: 'best_effort',
959
- use_client_time_zone: 1,
960
- output_format_json_quote_64bit_integers: 0,
961
- },
962
- });
963
- } catch (error) {
964
- console.error('Error persisting workflow snapshot:', error);
965
- throw error;
966
- }
325
+ async getMessagesPaginated(
326
+ args: StorageGetMessagesArg & { format?: 'v1' | 'v2' },
327
+ ): Promise<PaginationInfo & { messages: MastraMessageV1[] | MastraMessageV2[] }> {
328
+ return this.stores.memory.getMessagesPaginated(args);
967
329
  }
968
330
 
969
- async loadWorkflowSnapshot({
970
- workflowName,
971
- runId,
972
- }: {
973
- workflowName: string;
974
- runId: string;
975
- }): Promise<WorkflowRunState | null> {
976
- try {
977
- const result = await this.load({
978
- tableName: TABLE_WORKFLOW_SNAPSHOT,
979
- keys: {
980
- workflow_name: workflowName,
981
- run_id: runId,
982
- },
983
- });
984
-
985
- if (!result) {
986
- return null;
987
- }
988
-
989
- return (result as any).snapshot;
990
- } catch (error) {
991
- console.error('Error loading workflow snapshot:', error);
992
- throw error;
993
- }
331
+ async updateMessages(args: {
332
+ messages: (Partial<Omit<MastraMessageV2, 'createdAt'>> & {
333
+ id: string;
334
+ threadId?: string;
335
+ content?: { metadata?: MastraMessageContentV2['metadata']; content?: MastraMessageContentV2['content'] };
336
+ })[];
337
+ }): Promise<MastraMessageV2[]> {
338
+ return this.stores.memory.updateMessages(args);
994
339
  }
995
340
 
996
- private parseWorkflowRun(row: any): WorkflowRun {
997
- let parsedSnapshot: WorkflowRunState | string = row.snapshot as string;
998
- if (typeof parsedSnapshot === 'string') {
999
- try {
1000
- parsedSnapshot = JSON.parse(row.snapshot as string) as WorkflowRunState;
1001
- } catch (e) {
1002
- // If parsing fails, return the raw snapshot string
1003
- console.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
1004
- }
1005
- }
341
+ async getResourceById({ resourceId }: { resourceId: string }): Promise<StorageResourceType | null> {
342
+ return this.stores.memory.getResourceById({ resourceId });
343
+ }
1006
344
 
1007
- return {
1008
- workflowName: row.workflow_name,
1009
- runId: row.run_id,
1010
- snapshot: parsedSnapshot,
1011
- createdAt: new Date(row.createdAt),
1012
- updatedAt: new Date(row.updatedAt),
1013
- resourceId: row.resourceId,
1014
- };
345
+ async saveResource({ resource }: { resource: StorageResourceType }): Promise<StorageResourceType> {
346
+ return this.stores.memory.saveResource({ resource });
1015
347
  }
1016
348
 
1017
- async getWorkflowRuns({
1018
- workflowName,
1019
- fromDate,
1020
- toDate,
1021
- limit,
1022
- offset,
349
+ async updateResource({
1023
350
  resourceId,
351
+ workingMemory,
352
+ metadata,
1024
353
  }: {
1025
- workflowName?: string;
1026
- fromDate?: Date;
1027
- toDate?: Date;
1028
- limit?: number;
1029
- offset?: number;
1030
- resourceId?: string;
1031
- } = {}): Promise<WorkflowRuns> {
1032
- try {
1033
- const conditions: string[] = [];
1034
- const values: Record<string, any> = {};
1035
-
1036
- if (workflowName) {
1037
- conditions.push(`workflow_name = {var_workflow_name:String}`);
1038
- values.var_workflow_name = workflowName;
1039
- }
1040
-
1041
- if (resourceId) {
1042
- const hasResourceId = await this.hasColumn(TABLE_WORKFLOW_SNAPSHOT, 'resourceId');
1043
- if (hasResourceId) {
1044
- conditions.push(`resourceId = {var_resourceId:String}`);
1045
- values.var_resourceId = resourceId;
1046
- } else {
1047
- console.warn(`[${TABLE_WORKFLOW_SNAPSHOT}] resourceId column not found. Skipping resourceId filter.`);
1048
- }
1049
- }
1050
-
1051
- if (fromDate) {
1052
- conditions.push(`createdAt >= {var_from_date:DateTime64(3)}`);
1053
- values.var_from_date = fromDate.getTime() / 1000; // Convert to Unix timestamp
1054
- }
1055
-
1056
- if (toDate) {
1057
- conditions.push(`createdAt <= {var_to_date:DateTime64(3)}`);
1058
- values.var_to_date = toDate.getTime() / 1000; // Convert to Unix timestamp
1059
- }
1060
-
1061
- const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
1062
- const limitClause = limit !== undefined ? `LIMIT ${limit}` : '';
1063
- const offsetClause = offset !== undefined ? `OFFSET ${offset}` : '';
1064
-
1065
- let total = 0;
1066
- // Only get total count when using pagination
1067
- if (limit !== undefined && offset !== undefined) {
1068
- const countResult = await this.db.query({
1069
- query: `SELECT COUNT(*) as count FROM ${TABLE_WORKFLOW_SNAPSHOT} ${TABLE_ENGINES[TABLE_WORKFLOW_SNAPSHOT].startsWith('ReplacingMergeTree') ? 'FINAL' : ''} ${whereClause}`,
1070
- query_params: values,
1071
- format: 'JSONEachRow',
1072
- });
1073
- const countRows = await countResult.json();
1074
- total = Number((countRows as Array<{ count: string | number }>)[0]?.count ?? 0);
1075
- }
1076
-
1077
- // Get results
1078
- const result = await this.db.query({
1079
- query: `
1080
- SELECT
1081
- workflow_name,
1082
- run_id,
1083
- snapshot,
1084
- toDateTime64(createdAt, 3) as createdAt,
1085
- toDateTime64(updatedAt, 3) as updatedAt,
1086
- resourceId
1087
- FROM ${TABLE_WORKFLOW_SNAPSHOT} ${TABLE_ENGINES[TABLE_WORKFLOW_SNAPSHOT].startsWith('ReplacingMergeTree') ? 'FINAL' : ''}
1088
- ${whereClause}
1089
- ORDER BY createdAt DESC
1090
- ${limitClause}
1091
- ${offsetClause}
1092
- `,
1093
- query_params: values,
1094
- format: 'JSONEachRow',
1095
- });
354
+ resourceId: string;
355
+ workingMemory?: string;
356
+ metadata?: Record<string, unknown>;
357
+ }): Promise<StorageResourceType> {
358
+ return this.stores.memory.updateResource({ resourceId, workingMemory, metadata });
359
+ }
1096
360
 
1097
- const resultJson = await result.json();
1098
- const rows = resultJson as any[];
1099
- const runs = rows.map(row => {
1100
- return this.parseWorkflowRun(row);
1101
- });
361
+ async getScoreById({ id }: { id: string }): Promise<ScoreRowData | null> {
362
+ return this.stores.scores.getScoreById({ id });
363
+ }
1102
364
 
1103
- // Use runs.length as total when not paginating
1104
- return { runs, total: total || runs.length };
1105
- } catch (error) {
1106
- console.error('Error getting workflow runs:', error);
1107
- throw error;
1108
- }
365
+ async saveScore(_score: ScoreRowData): Promise<{ score: ScoreRowData }> {
366
+ return this.stores.scores.saveScore(_score);
1109
367
  }
1110
368
 
1111
- async getWorkflowRunById({
369
+ async getScoresByRunId({
1112
370
  runId,
1113
- workflowName,
371
+ pagination,
1114
372
  }: {
1115
373
  runId: string;
1116
- workflowName?: string;
1117
- }): Promise<WorkflowRun | null> {
1118
- try {
1119
- const conditions: string[] = [];
1120
- const values: Record<string, any> = {};
1121
-
1122
- if (runId) {
1123
- conditions.push(`run_id = {var_runId:String}`);
1124
- values.var_runId = runId;
1125
- }
1126
-
1127
- if (workflowName) {
1128
- conditions.push(`workflow_name = {var_workflow_name:String}`);
1129
- values.var_workflow_name = workflowName;
1130
- }
1131
-
1132
- const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
1133
-
1134
- // Get results
1135
- const result = await this.db.query({
1136
- query: `
1137
- SELECT
1138
- workflow_name,
1139
- run_id,
1140
- snapshot,
1141
- toDateTime64(createdAt, 3) as createdAt,
1142
- toDateTime64(updatedAt, 3) as updatedAt,
1143
- resourceId
1144
- FROM ${TABLE_WORKFLOW_SNAPSHOT} ${TABLE_ENGINES[TABLE_WORKFLOW_SNAPSHOT].startsWith('ReplacingMergeTree') ? 'FINAL' : ''}
1145
- ${whereClause}
1146
- `,
1147
- query_params: values,
1148
- format: 'JSONEachRow',
1149
- });
1150
-
1151
- const resultJson = await result.json();
1152
- if (!Array.isArray(resultJson) || resultJson.length === 0) {
1153
- return null;
1154
- }
1155
- return this.parseWorkflowRun(resultJson[0]);
1156
- } catch (error) {
1157
- console.error('Error getting workflow run by ID:', error);
1158
- throw error;
1159
- }
1160
- }
1161
-
1162
- private async hasColumn(table: string, column: string): Promise<boolean> {
1163
- const result = await this.db.query({
1164
- query: `DESCRIBE TABLE ${table}`,
1165
- format: 'JSONEachRow',
1166
- });
1167
- const columns = (await result.json()) as { name: string }[];
1168
- return columns.some(c => c.name === column);
1169
- }
1170
-
1171
- async getTracesPaginated(_args: StorageGetTracesArg): Promise<PaginationInfo & { traces: Trace[] }> {
1172
- throw new Error('Method not implemented.');
374
+ pagination: StoragePagination;
375
+ }): Promise<{ pagination: PaginationInfo; scores: ScoreRowData[] }> {
376
+ return this.stores.scores.getScoresByRunId({ runId, pagination });
1173
377
  }
1174
378
 
1175
- async getThreadsByResourceIdPaginated(_args: {
1176
- resourceId: string;
1177
- page?: number;
1178
- perPage?: number;
1179
- }): Promise<PaginationInfo & { threads: StorageThreadType[] }> {
1180
- throw new Error('Method not implemented.');
379
+ async getScoresByEntityId({
380
+ entityId,
381
+ entityType,
382
+ pagination,
383
+ }: {
384
+ pagination: StoragePagination;
385
+ entityId: string;
386
+ entityType: string;
387
+ }): Promise<{ pagination: PaginationInfo; scores: ScoreRowData[] }> {
388
+ return this.stores.scores.getScoresByEntityId({ entityId, entityType, pagination });
1181
389
  }
1182
390
 
1183
- async getMessagesPaginated(
1184
- _args: StorageGetMessagesArg,
1185
- ): Promise<PaginationInfo & { messages: MastraMessageV1[] | MastraMessageV2[] }> {
1186
- throw new Error('Method not implemented.');
391
+ async getScoresByScorerId({
392
+ scorerId,
393
+ pagination,
394
+ }: {
395
+ scorerId: string;
396
+ pagination: StoragePagination;
397
+ }): Promise<{ pagination: PaginationInfo; scores: ScoreRowData[] }> {
398
+ return this.stores.scores.getScoresByScorerId({ scorerId, pagination });
1187
399
  }
1188
400
 
1189
401
  async close(): Promise<void> {
1190
402
  await this.db.close();
1191
403
  }
1192
-
1193
- async updateMessages(_args: {
1194
- messages: Partial<Omit<MastraMessageV2, 'createdAt'>> &
1195
- {
1196
- id: string;
1197
- content?: { metadata?: MastraMessageContentV2['metadata']; content?: MastraMessageContentV2['content'] };
1198
- }[];
1199
- }): Promise<MastraMessageV2[]> {
1200
- this.logger.error('updateMessages is not yet implemented in ClickhouseStore');
1201
- throw new Error('Method not implemented');
1202
- }
1203
404
  }