@mastra/clickhouse 0.12.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +53 -0
- package/LICENSE.md +12 -4
- package/dist/_tsup-dts-rollup.d.cts +351 -64
- package/dist/_tsup-dts-rollup.d.ts +351 -64
- package/dist/index.cjs +2052 -609
- package/dist/index.d.cts +0 -2
- package/dist/index.d.ts +0 -2
- package/dist/index.js +2051 -606
- package/package.json +6 -6
- package/src/storage/domains/legacy-evals/index.ts +246 -0
- package/src/storage/domains/memory/index.ts +1393 -0
- package/src/storage/domains/operations/index.ts +319 -0
- package/src/storage/domains/scores/index.ts +326 -0
- package/src/storage/domains/traces/index.ts +275 -0
- package/src/storage/domains/utils.ts +86 -0
- package/src/storage/domains/workflows/index.ts +285 -0
- package/src/storage/index.test.ts +15 -1091
- package/src/storage/index.ts +184 -1246
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import type { ClickHouseClient } from '@clickhouse/client';
|
|
2
|
+
import { ErrorCategory, ErrorDomain, MastraError } from '@mastra/core/error';
|
|
3
|
+
import { StoreOperations, TABLE_WORKFLOW_SNAPSHOT, TABLE_EVALS, TABLE_SCHEMAS } from '@mastra/core/storage';
|
|
4
|
+
import type { StorageColumn, TABLE_NAMES } from '@mastra/core/storage';
|
|
5
|
+
import type { ClickhouseConfig } from '../utils';
|
|
6
|
+
import { COLUMN_TYPES, TABLE_ENGINES, transformRow } from '../utils';
|
|
7
|
+
|
|
8
|
+
export class StoreOperationsClickhouse extends StoreOperations {
|
|
9
|
+
protected ttl: ClickhouseConfig['ttl'];
|
|
10
|
+
protected client: ClickHouseClient;
|
|
11
|
+
constructor({ client, ttl }: { client: ClickHouseClient; ttl: ClickhouseConfig['ttl'] }) {
|
|
12
|
+
super();
|
|
13
|
+
this.ttl = ttl;
|
|
14
|
+
this.client = client;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async hasColumn(table: string, column: string): Promise<boolean> {
|
|
18
|
+
const result = await this.client.query({
|
|
19
|
+
query: `DESCRIBE TABLE ${table}`,
|
|
20
|
+
format: 'JSONEachRow',
|
|
21
|
+
});
|
|
22
|
+
const columns = (await result.json()) as { name: string }[];
|
|
23
|
+
return columns.some(c => c.name === column);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
protected getSqlType(type: StorageColumn['type']): string {
|
|
27
|
+
switch (type) {
|
|
28
|
+
case 'text':
|
|
29
|
+
return 'String';
|
|
30
|
+
case 'timestamp':
|
|
31
|
+
return 'DateTime64(3)';
|
|
32
|
+
case 'integer':
|
|
33
|
+
case 'bigint':
|
|
34
|
+
return 'Int64';
|
|
35
|
+
case 'jsonb':
|
|
36
|
+
return 'String';
|
|
37
|
+
default:
|
|
38
|
+
return super.getSqlType(type); // fallback to base implementation
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async createTable({
|
|
43
|
+
tableName,
|
|
44
|
+
schema,
|
|
45
|
+
}: {
|
|
46
|
+
tableName: TABLE_NAMES;
|
|
47
|
+
schema: Record<string, StorageColumn>;
|
|
48
|
+
}): Promise<void> {
|
|
49
|
+
try {
|
|
50
|
+
const columns = Object.entries(schema)
|
|
51
|
+
.map(([name, def]) => {
|
|
52
|
+
const constraints = [];
|
|
53
|
+
if (!def.nullable) constraints.push('NOT NULL');
|
|
54
|
+
const columnTtl = this.ttl?.[tableName]?.columns?.[name];
|
|
55
|
+
return `"${name}" ${COLUMN_TYPES[def.type]} ${constraints.join(' ')} ${columnTtl ? `TTL toDateTime(${columnTtl.ttlKey ?? 'createdAt'}) + INTERVAL ${columnTtl.interval} ${columnTtl.unit}` : ''}`;
|
|
56
|
+
})
|
|
57
|
+
.join(',\n');
|
|
58
|
+
|
|
59
|
+
const rowTtl = this.ttl?.[tableName]?.row;
|
|
60
|
+
const sql =
|
|
61
|
+
tableName === TABLE_WORKFLOW_SNAPSHOT
|
|
62
|
+
? `
|
|
63
|
+
CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
64
|
+
${['id String'].concat(columns)}
|
|
65
|
+
)
|
|
66
|
+
ENGINE = ${TABLE_ENGINES[tableName] ?? 'MergeTree()'}
|
|
67
|
+
PRIMARY KEY (createdAt, run_id, workflow_name)
|
|
68
|
+
ORDER BY (createdAt, run_id, workflow_name)
|
|
69
|
+
${rowTtl ? `TTL toDateTime(${rowTtl.ttlKey ?? 'createdAt'}) + INTERVAL ${rowTtl.interval} ${rowTtl.unit}` : ''}
|
|
70
|
+
SETTINGS index_granularity = 8192
|
|
71
|
+
`
|
|
72
|
+
: `
|
|
73
|
+
CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
74
|
+
${columns}
|
|
75
|
+
)
|
|
76
|
+
ENGINE = ${TABLE_ENGINES[tableName] ?? 'MergeTree()'}
|
|
77
|
+
PRIMARY KEY (createdAt, ${tableName === TABLE_EVALS ? 'run_id' : 'id'})
|
|
78
|
+
ORDER BY (createdAt, ${tableName === TABLE_EVALS ? 'run_id' : 'id'})
|
|
79
|
+
${this.ttl?.[tableName]?.row ? `TTL toDateTime(createdAt) + INTERVAL ${this.ttl[tableName].row.interval} ${this.ttl[tableName].row.unit}` : ''}
|
|
80
|
+
SETTINGS index_granularity = 8192
|
|
81
|
+
`;
|
|
82
|
+
|
|
83
|
+
await this.client.query({
|
|
84
|
+
query: sql,
|
|
85
|
+
clickhouse_settings: {
|
|
86
|
+
// Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
|
|
87
|
+
date_time_input_format: 'best_effort',
|
|
88
|
+
date_time_output_format: 'iso',
|
|
89
|
+
use_client_time_zone: 1,
|
|
90
|
+
output_format_json_quote_64bit_integers: 0,
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
} catch (error: any) {
|
|
94
|
+
throw new MastraError(
|
|
95
|
+
{
|
|
96
|
+
id: 'CLICKHOUSE_STORAGE_CREATE_TABLE_FAILED',
|
|
97
|
+
domain: ErrorDomain.STORAGE,
|
|
98
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
99
|
+
details: { tableName },
|
|
100
|
+
},
|
|
101
|
+
error,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async alterTable({
|
|
107
|
+
tableName,
|
|
108
|
+
schema,
|
|
109
|
+
ifNotExists,
|
|
110
|
+
}: {
|
|
111
|
+
tableName: TABLE_NAMES;
|
|
112
|
+
schema: Record<string, StorageColumn>;
|
|
113
|
+
ifNotExists: string[];
|
|
114
|
+
}): Promise<void> {
|
|
115
|
+
try {
|
|
116
|
+
// 1. Get existing columns
|
|
117
|
+
const describeSql = `DESCRIBE TABLE ${tableName}`;
|
|
118
|
+
const result = await this.client.query({
|
|
119
|
+
query: describeSql,
|
|
120
|
+
});
|
|
121
|
+
const rows = await result.json();
|
|
122
|
+
const existingColumnNames = new Set(rows.data.map((row: any) => row.name.toLowerCase()));
|
|
123
|
+
|
|
124
|
+
// 2. Add missing columns
|
|
125
|
+
for (const columnName of ifNotExists) {
|
|
126
|
+
if (!existingColumnNames.has(columnName.toLowerCase()) && schema[columnName]) {
|
|
127
|
+
const columnDef = schema[columnName];
|
|
128
|
+
let sqlType = this.getSqlType(columnDef.type);
|
|
129
|
+
if (columnDef.nullable !== false) {
|
|
130
|
+
sqlType = `Nullable(${sqlType})`;
|
|
131
|
+
}
|
|
132
|
+
const defaultValue = columnDef.nullable === false ? this.getDefaultValue(columnDef.type) : '';
|
|
133
|
+
// Use backticks or double quotes as needed for identifiers
|
|
134
|
+
const alterSql =
|
|
135
|
+
`ALTER TABLE ${tableName} ADD COLUMN IF NOT EXISTS "${columnName}" ${sqlType} ${defaultValue}`.trim();
|
|
136
|
+
|
|
137
|
+
await this.client.query({
|
|
138
|
+
query: alterSql,
|
|
139
|
+
});
|
|
140
|
+
this.logger?.debug?.(`Added column ${columnName} to table ${tableName}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
} catch (error: any) {
|
|
144
|
+
throw new MastraError(
|
|
145
|
+
{
|
|
146
|
+
id: 'CLICKHOUSE_STORAGE_ALTER_TABLE_FAILED',
|
|
147
|
+
domain: ErrorDomain.STORAGE,
|
|
148
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
149
|
+
details: { tableName },
|
|
150
|
+
},
|
|
151
|
+
error,
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async clearTable({ tableName }: { tableName: TABLE_NAMES }): Promise<void> {
|
|
157
|
+
try {
|
|
158
|
+
await this.client.query({
|
|
159
|
+
query: `TRUNCATE TABLE ${tableName}`,
|
|
160
|
+
clickhouse_settings: {
|
|
161
|
+
// Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
|
|
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
|
+
} catch (error: any) {
|
|
169
|
+
throw new MastraError(
|
|
170
|
+
{
|
|
171
|
+
id: 'CLICKHOUSE_STORAGE_CLEAR_TABLE_FAILED',
|
|
172
|
+
domain: ErrorDomain.STORAGE,
|
|
173
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
174
|
+
details: { tableName },
|
|
175
|
+
},
|
|
176
|
+
error,
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async dropTable({ tableName }: { tableName: TABLE_NAMES }): Promise<void> {
|
|
182
|
+
await this.client.query({
|
|
183
|
+
query: `DROP TABLE IF EXISTS ${tableName}`,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async insert({ tableName, record }: { tableName: TABLE_NAMES; record: Record<string, any> }): Promise<void> {
|
|
188
|
+
const createdAt = (record.createdAt || record.created_at || new Date()).toISOString();
|
|
189
|
+
const updatedAt = (record.updatedAt || new Date()).toISOString();
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
const result = await this.client.insert({
|
|
193
|
+
table: tableName,
|
|
194
|
+
values: [
|
|
195
|
+
{
|
|
196
|
+
...record,
|
|
197
|
+
createdAt,
|
|
198
|
+
updatedAt,
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
format: 'JSONEachRow',
|
|
202
|
+
clickhouse_settings: {
|
|
203
|
+
// Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
|
|
204
|
+
output_format_json_quote_64bit_integers: 0,
|
|
205
|
+
date_time_input_format: 'best_effort',
|
|
206
|
+
use_client_time_zone: 1,
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
console.log('INSERT RESULT', result);
|
|
210
|
+
} catch (error: any) {
|
|
211
|
+
throw new MastraError(
|
|
212
|
+
{
|
|
213
|
+
id: 'CLICKHOUSE_STORAGE_INSERT_FAILED',
|
|
214
|
+
domain: ErrorDomain.STORAGE,
|
|
215
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
216
|
+
details: { tableName },
|
|
217
|
+
},
|
|
218
|
+
error,
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async batchInsert({ tableName, records }: { tableName: TABLE_NAMES; records: Record<string, any>[] }): Promise<void> {
|
|
224
|
+
const recordsToBeInserted = records.map(record => ({
|
|
225
|
+
...Object.fromEntries(
|
|
226
|
+
Object.entries(record).map(([key, value]) => [
|
|
227
|
+
key,
|
|
228
|
+
TABLE_SCHEMAS[tableName as TABLE_NAMES]?.[key]?.type === 'timestamp' ? new Date(value).toISOString() : value,
|
|
229
|
+
]),
|
|
230
|
+
),
|
|
231
|
+
}));
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
await this.client.insert({
|
|
235
|
+
table: tableName,
|
|
236
|
+
values: recordsToBeInserted,
|
|
237
|
+
format: 'JSONEachRow',
|
|
238
|
+
clickhouse_settings: {
|
|
239
|
+
// Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
|
|
240
|
+
date_time_input_format: 'best_effort',
|
|
241
|
+
use_client_time_zone: 1,
|
|
242
|
+
output_format_json_quote_64bit_integers: 0,
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
} catch (error: any) {
|
|
246
|
+
throw new MastraError(
|
|
247
|
+
{
|
|
248
|
+
id: 'CLICKHOUSE_STORAGE_BATCH_INSERT_FAILED',
|
|
249
|
+
domain: ErrorDomain.STORAGE,
|
|
250
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
251
|
+
details: { tableName },
|
|
252
|
+
},
|
|
253
|
+
error,
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async load<R>({ tableName, keys }: { tableName: TABLE_NAMES; keys: Record<string, string> }): Promise<R | null> {
|
|
259
|
+
try {
|
|
260
|
+
const engine = TABLE_ENGINES[tableName] ?? 'MergeTree()';
|
|
261
|
+
const keyEntries = Object.entries(keys);
|
|
262
|
+
const conditions = keyEntries
|
|
263
|
+
.map(
|
|
264
|
+
([key]) =>
|
|
265
|
+
`"${key}" = {var_${key}:${COLUMN_TYPES[TABLE_SCHEMAS[tableName as TABLE_NAMES]?.[key]?.type ?? 'text']}}`,
|
|
266
|
+
)
|
|
267
|
+
.join(' AND ');
|
|
268
|
+
const values = keyEntries.reduce((acc, [key, value]) => {
|
|
269
|
+
return { ...acc, [`var_${key}`]: value };
|
|
270
|
+
}, {});
|
|
271
|
+
|
|
272
|
+
const hasUpdatedAt = TABLE_SCHEMAS[tableName as TABLE_NAMES]?.updatedAt;
|
|
273
|
+
|
|
274
|
+
const selectClause = `SELECT *, toDateTime64(createdAt, 3) as createdAt${hasUpdatedAt ? ', toDateTime64(updatedAt, 3) as updatedAt' : ''}`;
|
|
275
|
+
|
|
276
|
+
const result = await this.client.query({
|
|
277
|
+
query: `${selectClause} FROM ${tableName} ${engine.startsWith('ReplacingMergeTree') ? 'FINAL' : ''} WHERE ${conditions}`,
|
|
278
|
+
query_params: values,
|
|
279
|
+
clickhouse_settings: {
|
|
280
|
+
// Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
|
|
281
|
+
date_time_input_format: 'best_effort',
|
|
282
|
+
date_time_output_format: 'iso',
|
|
283
|
+
use_client_time_zone: 1,
|
|
284
|
+
output_format_json_quote_64bit_integers: 0,
|
|
285
|
+
},
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
if (!result) {
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const rows = await result.json();
|
|
293
|
+
// If this is a workflow snapshot, parse the snapshot field
|
|
294
|
+
if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
|
|
295
|
+
const snapshot = rows.data[0] as any;
|
|
296
|
+
if (!snapshot) {
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
if (typeof snapshot.snapshot === 'string') {
|
|
300
|
+
snapshot.snapshot = JSON.parse(snapshot.snapshot);
|
|
301
|
+
}
|
|
302
|
+
return transformRow(snapshot);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const data: R = transformRow(rows.data[0]);
|
|
306
|
+
return data;
|
|
307
|
+
} catch (error) {
|
|
308
|
+
throw new MastraError(
|
|
309
|
+
{
|
|
310
|
+
id: 'CLICKHOUSE_STORAGE_LOAD_FAILED',
|
|
311
|
+
domain: ErrorDomain.STORAGE,
|
|
312
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
313
|
+
details: { tableName },
|
|
314
|
+
},
|
|
315
|
+
error,
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import type { ClickHouseClient } from '@clickhouse/client';
|
|
2
|
+
import { ErrorCategory, ErrorDomain, MastraError } from '@mastra/core/error';
|
|
3
|
+
import type { ScoreRowData } from '@mastra/core/scores';
|
|
4
|
+
import { ScoresStorage, TABLE_SCORERS, safelyParseJSON } from '@mastra/core/storage';
|
|
5
|
+
import type { PaginationInfo, StoragePagination } from '@mastra/core/storage';
|
|
6
|
+
import type { StoreOperationsClickhouse } from '../operations';
|
|
7
|
+
|
|
8
|
+
export class ScoresStorageClickhouse extends ScoresStorage {
|
|
9
|
+
protected client: ClickHouseClient;
|
|
10
|
+
protected operations: StoreOperationsClickhouse;
|
|
11
|
+
constructor({ client, operations }: { client: ClickHouseClient; operations: StoreOperationsClickhouse }) {
|
|
12
|
+
super();
|
|
13
|
+
this.client = client;
|
|
14
|
+
this.operations = operations;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
private transformScoreRow(row: any): ScoreRowData {
|
|
18
|
+
const scorer = safelyParseJSON(row.scorer);
|
|
19
|
+
const extractStepResult = safelyParseJSON(row.extractStepResult);
|
|
20
|
+
const analyzeStepResult = safelyParseJSON(row.analyzeStepResult);
|
|
21
|
+
const metadata = safelyParseJSON(row.metadata);
|
|
22
|
+
const input = safelyParseJSON(row.input);
|
|
23
|
+
const output = safelyParseJSON(row.output);
|
|
24
|
+
const additionalContext = safelyParseJSON(row.additionalContext);
|
|
25
|
+
const runtimeContext = safelyParseJSON(row.runtimeContext);
|
|
26
|
+
const entity = safelyParseJSON(row.entity);
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
...row,
|
|
30
|
+
scorer,
|
|
31
|
+
extractStepResult,
|
|
32
|
+
analyzeStepResult,
|
|
33
|
+
metadata,
|
|
34
|
+
input,
|
|
35
|
+
output,
|
|
36
|
+
additionalContext,
|
|
37
|
+
runtimeContext,
|
|
38
|
+
entity,
|
|
39
|
+
createdAt: new Date(row.createdAt),
|
|
40
|
+
updatedAt: new Date(row.updatedAt),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async getScoreById({ id }: { id: string }): Promise<ScoreRowData | null> {
|
|
45
|
+
try {
|
|
46
|
+
const result = await this.client.query({
|
|
47
|
+
query: `SELECT * FROM ${TABLE_SCORERS} WHERE id = {var_id:String}`,
|
|
48
|
+
query_params: { var_id: id },
|
|
49
|
+
format: 'JSONEachRow',
|
|
50
|
+
clickhouse_settings: {
|
|
51
|
+
// Allows to insert serialized JS Dates (such as '2023-12-06T10:54:48.000Z')
|
|
52
|
+
date_time_input_format: 'best_effort',
|
|
53
|
+
date_time_output_format: 'iso',
|
|
54
|
+
use_client_time_zone: 1,
|
|
55
|
+
output_format_json_quote_64bit_integers: 0,
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const resultJson = await result.json();
|
|
60
|
+
if (!Array.isArray(resultJson) || resultJson.length === 0) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return this.transformScoreRow(resultJson[0]);
|
|
65
|
+
// return this.parseScoreRow(resultJson[0]);
|
|
66
|
+
} catch (error: any) {
|
|
67
|
+
throw new MastraError(
|
|
68
|
+
{
|
|
69
|
+
id: 'CLICKHOUSE_STORAGE_GET_SCORE_BY_ID_FAILED',
|
|
70
|
+
domain: ErrorDomain.STORAGE,
|
|
71
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
72
|
+
details: { scoreId: id },
|
|
73
|
+
},
|
|
74
|
+
error,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async saveScore(score: ScoreRowData): Promise<{ score: ScoreRowData }> {
|
|
80
|
+
try {
|
|
81
|
+
const record = {
|
|
82
|
+
...score,
|
|
83
|
+
};
|
|
84
|
+
await this.client.insert({
|
|
85
|
+
table: TABLE_SCORERS,
|
|
86
|
+
values: [record],
|
|
87
|
+
format: 'JSONEachRow',
|
|
88
|
+
clickhouse_settings: {
|
|
89
|
+
date_time_input_format: 'best_effort',
|
|
90
|
+
use_client_time_zone: 1,
|
|
91
|
+
output_format_json_quote_64bit_integers: 0,
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
return { score };
|
|
95
|
+
} catch (error) {
|
|
96
|
+
throw new MastraError(
|
|
97
|
+
{
|
|
98
|
+
id: 'CLICKHOUSE_STORAGE_SAVE_SCORE_FAILED',
|
|
99
|
+
domain: ErrorDomain.STORAGE,
|
|
100
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
101
|
+
details: { scoreId: score.id },
|
|
102
|
+
},
|
|
103
|
+
error,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async getScoresByRunId({
|
|
109
|
+
runId,
|
|
110
|
+
pagination,
|
|
111
|
+
}: {
|
|
112
|
+
runId: string;
|
|
113
|
+
pagination: StoragePagination;
|
|
114
|
+
}): Promise<{ pagination: PaginationInfo; scores: ScoreRowData[] }> {
|
|
115
|
+
try {
|
|
116
|
+
// Get total count
|
|
117
|
+
const countResult = await this.client.query({
|
|
118
|
+
query: `SELECT COUNT(*) as count FROM ${TABLE_SCORERS} WHERE runId = {var_runId:String}`,
|
|
119
|
+
query_params: { var_runId: runId },
|
|
120
|
+
format: 'JSONEachRow',
|
|
121
|
+
});
|
|
122
|
+
const countRows = await countResult.json();
|
|
123
|
+
let total = 0;
|
|
124
|
+
if (Array.isArray(countRows) && countRows.length > 0 && countRows[0]) {
|
|
125
|
+
const countObj = countRows[0] as { count: string | number };
|
|
126
|
+
total = Number(countObj.count);
|
|
127
|
+
}
|
|
128
|
+
if (!total) {
|
|
129
|
+
return {
|
|
130
|
+
pagination: {
|
|
131
|
+
total: 0,
|
|
132
|
+
page: pagination.page,
|
|
133
|
+
perPage: pagination.perPage,
|
|
134
|
+
hasMore: false,
|
|
135
|
+
},
|
|
136
|
+
scores: [],
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
// Get paginated results
|
|
140
|
+
const offset = pagination.page * pagination.perPage;
|
|
141
|
+
const result = await this.client.query({
|
|
142
|
+
query: `SELECT * FROM ${TABLE_SCORERS} WHERE runId = {var_runId:String} ORDER BY createdAt DESC LIMIT {var_limit:Int64} OFFSET {var_offset:Int64}`,
|
|
143
|
+
query_params: {
|
|
144
|
+
var_runId: runId,
|
|
145
|
+
var_limit: pagination.perPage,
|
|
146
|
+
var_offset: offset,
|
|
147
|
+
},
|
|
148
|
+
format: 'JSONEachRow',
|
|
149
|
+
clickhouse_settings: {
|
|
150
|
+
date_time_input_format: 'best_effort',
|
|
151
|
+
date_time_output_format: 'iso',
|
|
152
|
+
use_client_time_zone: 1,
|
|
153
|
+
output_format_json_quote_64bit_integers: 0,
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
const rows = await result.json();
|
|
157
|
+
const scores = Array.isArray(rows) ? rows.map(row => this.transformScoreRow(row)) : [];
|
|
158
|
+
return {
|
|
159
|
+
pagination: {
|
|
160
|
+
total,
|
|
161
|
+
page: pagination.page,
|
|
162
|
+
perPage: pagination.perPage,
|
|
163
|
+
hasMore: total > (pagination.page + 1) * pagination.perPage,
|
|
164
|
+
},
|
|
165
|
+
scores,
|
|
166
|
+
};
|
|
167
|
+
} catch (error: any) {
|
|
168
|
+
throw new MastraError(
|
|
169
|
+
{
|
|
170
|
+
id: 'CLICKHOUSE_STORAGE_GET_SCORES_BY_RUN_ID_FAILED',
|
|
171
|
+
domain: ErrorDomain.STORAGE,
|
|
172
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
173
|
+
details: { runId },
|
|
174
|
+
},
|
|
175
|
+
error,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async getScoresByScorerId({
|
|
181
|
+
scorerId,
|
|
182
|
+
pagination,
|
|
183
|
+
}: {
|
|
184
|
+
scorerId: string;
|
|
185
|
+
pagination: StoragePagination;
|
|
186
|
+
}): Promise<{ pagination: PaginationInfo; scores: ScoreRowData[] }> {
|
|
187
|
+
try {
|
|
188
|
+
// Get total count
|
|
189
|
+
const countResult = await this.client.query({
|
|
190
|
+
query: `SELECT COUNT(*) as count FROM ${TABLE_SCORERS} WHERE scorerId = {var_scorerId:String}`,
|
|
191
|
+
query_params: { var_scorerId: scorerId },
|
|
192
|
+
format: 'JSONEachRow',
|
|
193
|
+
});
|
|
194
|
+
const countRows = await countResult.json();
|
|
195
|
+
let total = 0;
|
|
196
|
+
if (Array.isArray(countRows) && countRows.length > 0 && countRows[0]) {
|
|
197
|
+
const countObj = countRows[0] as { count: string | number };
|
|
198
|
+
total = Number(countObj.count);
|
|
199
|
+
}
|
|
200
|
+
if (!total) {
|
|
201
|
+
return {
|
|
202
|
+
pagination: {
|
|
203
|
+
total: 0,
|
|
204
|
+
page: pagination.page,
|
|
205
|
+
perPage: pagination.perPage,
|
|
206
|
+
hasMore: false,
|
|
207
|
+
},
|
|
208
|
+
scores: [],
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
// Get paginated results
|
|
212
|
+
const offset = pagination.page * pagination.perPage;
|
|
213
|
+
const result = await this.client.query({
|
|
214
|
+
query: `SELECT * FROM ${TABLE_SCORERS} WHERE scorerId = {var_scorerId:String} ORDER BY createdAt DESC LIMIT {var_limit:Int64} OFFSET {var_offset:Int64}`,
|
|
215
|
+
query_params: {
|
|
216
|
+
var_scorerId: scorerId,
|
|
217
|
+
var_limit: pagination.perPage,
|
|
218
|
+
var_offset: offset,
|
|
219
|
+
},
|
|
220
|
+
format: 'JSONEachRow',
|
|
221
|
+
clickhouse_settings: {
|
|
222
|
+
date_time_input_format: 'best_effort',
|
|
223
|
+
date_time_output_format: 'iso',
|
|
224
|
+
use_client_time_zone: 1,
|
|
225
|
+
output_format_json_quote_64bit_integers: 0,
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
const rows = await result.json();
|
|
229
|
+
const scores = Array.isArray(rows) ? rows.map(row => this.transformScoreRow(row)) : [];
|
|
230
|
+
return {
|
|
231
|
+
pagination: {
|
|
232
|
+
total,
|
|
233
|
+
page: pagination.page,
|
|
234
|
+
perPage: pagination.perPage,
|
|
235
|
+
hasMore: total > (pagination.page + 1) * pagination.perPage,
|
|
236
|
+
},
|
|
237
|
+
scores,
|
|
238
|
+
};
|
|
239
|
+
} catch (error: any) {
|
|
240
|
+
throw new MastraError(
|
|
241
|
+
{
|
|
242
|
+
id: 'CLICKHOUSE_STORAGE_GET_SCORES_BY_SCORER_ID_FAILED',
|
|
243
|
+
domain: ErrorDomain.STORAGE,
|
|
244
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
245
|
+
details: { scorerId },
|
|
246
|
+
},
|
|
247
|
+
error,
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async getScoresByEntityId({
|
|
253
|
+
entityId,
|
|
254
|
+
entityType,
|
|
255
|
+
pagination,
|
|
256
|
+
}: {
|
|
257
|
+
pagination: StoragePagination;
|
|
258
|
+
entityId: string;
|
|
259
|
+
entityType: string;
|
|
260
|
+
}): Promise<{ pagination: PaginationInfo; scores: ScoreRowData[] }> {
|
|
261
|
+
try {
|
|
262
|
+
// Get total count
|
|
263
|
+
const countResult = await this.client.query({
|
|
264
|
+
query: `SELECT COUNT(*) as count FROM ${TABLE_SCORERS} WHERE entityId = {var_entityId:String} AND entityType = {var_entityType:String}`,
|
|
265
|
+
query_params: { var_entityId: entityId, var_entityType: entityType },
|
|
266
|
+
format: 'JSONEachRow',
|
|
267
|
+
});
|
|
268
|
+
const countRows = await countResult.json();
|
|
269
|
+
let total = 0;
|
|
270
|
+
if (Array.isArray(countRows) && countRows.length > 0 && countRows[0]) {
|
|
271
|
+
const countObj = countRows[0] as { count: string | number };
|
|
272
|
+
total = Number(countObj.count);
|
|
273
|
+
}
|
|
274
|
+
if (!total) {
|
|
275
|
+
return {
|
|
276
|
+
pagination: {
|
|
277
|
+
total: 0,
|
|
278
|
+
page: pagination.page,
|
|
279
|
+
perPage: pagination.perPage,
|
|
280
|
+
hasMore: false,
|
|
281
|
+
},
|
|
282
|
+
scores: [],
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
// Get paginated results
|
|
286
|
+
const offset = pagination.page * pagination.perPage;
|
|
287
|
+
const result = await this.client.query({
|
|
288
|
+
query: `SELECT * FROM ${TABLE_SCORERS} WHERE entityId = {var_entityId:String} AND entityType = {var_entityType:String} ORDER BY createdAt DESC LIMIT {var_limit:Int64} OFFSET {var_offset:Int64}`,
|
|
289
|
+
query_params: {
|
|
290
|
+
var_entityId: entityId,
|
|
291
|
+
var_entityType: entityType,
|
|
292
|
+
var_limit: pagination.perPage,
|
|
293
|
+
var_offset: offset,
|
|
294
|
+
},
|
|
295
|
+
format: 'JSONEachRow',
|
|
296
|
+
clickhouse_settings: {
|
|
297
|
+
date_time_input_format: 'best_effort',
|
|
298
|
+
date_time_output_format: 'iso',
|
|
299
|
+
use_client_time_zone: 1,
|
|
300
|
+
output_format_json_quote_64bit_integers: 0,
|
|
301
|
+
},
|
|
302
|
+
});
|
|
303
|
+
const rows = await result.json();
|
|
304
|
+
const scores = Array.isArray(rows) ? rows.map(row => this.transformScoreRow(row)) : [];
|
|
305
|
+
return {
|
|
306
|
+
pagination: {
|
|
307
|
+
total,
|
|
308
|
+
page: pagination.page,
|
|
309
|
+
perPage: pagination.perPage,
|
|
310
|
+
hasMore: total > (pagination.page + 1) * pagination.perPage,
|
|
311
|
+
},
|
|
312
|
+
scores,
|
|
313
|
+
};
|
|
314
|
+
} catch (error: any) {
|
|
315
|
+
throw new MastraError(
|
|
316
|
+
{
|
|
317
|
+
id: 'CLICKHOUSE_STORAGE_GET_SCORES_BY_ENTITY_ID_FAILED',
|
|
318
|
+
domain: ErrorDomain.STORAGE,
|
|
319
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
320
|
+
details: { entityId, entityType },
|
|
321
|
+
},
|
|
322
|
+
error,
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|