@mastra/clickhouse 0.0.0-taofeeqInngest-20250603090617 → 0.0.0-transpile-packages-20250724123433

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