@mastra/cloudflare-d1 0.1.5-alpha.2
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/LICENSE.md +46 -0
- package/README.md +142 -0
- package/dist/_tsup-dts-rollup.d.cts +313 -0
- package/dist/_tsup-dts-rollup.d.ts +313 -0
- package/dist/index.cjs +944 -0
- package/dist/index.d.cts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +938 -0
- package/package.json +46 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,944 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var storage = require('@mastra/core/storage');
|
|
4
|
+
var Cloudflare = require('cloudflare');
|
|
5
|
+
|
|
6
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
7
|
+
|
|
8
|
+
var Cloudflare__default = /*#__PURE__*/_interopDefault(Cloudflare);
|
|
9
|
+
|
|
10
|
+
// src/storage/index.ts
|
|
11
|
+
|
|
12
|
+
// src/storage/sql-builder.ts
|
|
13
|
+
var SqlBuilder = class {
|
|
14
|
+
sql = "";
|
|
15
|
+
params = [];
|
|
16
|
+
whereAdded = false;
|
|
17
|
+
// Basic query building
|
|
18
|
+
select(columns) {
|
|
19
|
+
if (!columns || Array.isArray(columns) && columns.length === 0) {
|
|
20
|
+
this.sql = "SELECT *";
|
|
21
|
+
} else {
|
|
22
|
+
this.sql = `SELECT ${Array.isArray(columns) ? columns.join(", ") : columns}`;
|
|
23
|
+
}
|
|
24
|
+
return this;
|
|
25
|
+
}
|
|
26
|
+
from(table) {
|
|
27
|
+
this.sql += ` FROM ${table}`;
|
|
28
|
+
return this;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Add a WHERE clause to the query
|
|
32
|
+
* @param condition The condition to add
|
|
33
|
+
* @param params Parameters to bind to the condition
|
|
34
|
+
*/
|
|
35
|
+
where(condition, ...params) {
|
|
36
|
+
this.sql += ` WHERE ${condition}`;
|
|
37
|
+
this.params.push(...params);
|
|
38
|
+
this.whereAdded = true;
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Add a WHERE clause if it hasn't been added yet, otherwise add an AND clause
|
|
43
|
+
* @param condition The condition to add
|
|
44
|
+
* @param params Parameters to bind to the condition
|
|
45
|
+
*/
|
|
46
|
+
whereAnd(condition, ...params) {
|
|
47
|
+
if (this.whereAdded) {
|
|
48
|
+
return this.andWhere(condition, ...params);
|
|
49
|
+
} else {
|
|
50
|
+
return this.where(condition, ...params);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
andWhere(condition, ...params) {
|
|
54
|
+
this.sql += ` AND ${condition}`;
|
|
55
|
+
this.params.push(...params);
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
orWhere(condition, ...params) {
|
|
59
|
+
this.sql += ` OR ${condition}`;
|
|
60
|
+
this.params.push(...params);
|
|
61
|
+
return this;
|
|
62
|
+
}
|
|
63
|
+
orderBy(column, direction = "ASC") {
|
|
64
|
+
this.sql += ` ORDER BY ${column} ${direction}`;
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
limit(count) {
|
|
68
|
+
this.sql += ` LIMIT ?`;
|
|
69
|
+
this.params.push(count);
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
offset(count) {
|
|
73
|
+
this.sql += ` OFFSET ?`;
|
|
74
|
+
this.params.push(count);
|
|
75
|
+
return this;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Insert a row, or update specific columns on conflict (upsert).
|
|
79
|
+
* @param table Table name
|
|
80
|
+
* @param columns Columns to insert
|
|
81
|
+
* @param values Values to insert
|
|
82
|
+
* @param conflictColumns Columns to check for conflict (usually PK or UNIQUE)
|
|
83
|
+
* @param updateMap Object mapping columns to update to their new value (e.g. { name: 'excluded.name' })
|
|
84
|
+
*/
|
|
85
|
+
insert(table, columns, values, conflictColumns, updateMap) {
|
|
86
|
+
const placeholders = columns.map(() => "?").join(", ");
|
|
87
|
+
if (conflictColumns && updateMap) {
|
|
88
|
+
const updateClause = Object.entries(updateMap).map(([col, expr]) => `${col} = ${expr}`).join(", ");
|
|
89
|
+
this.sql = `INSERT INTO ${table} (${columns.join(", ")}) VALUES (${placeholders}) ON CONFLICT(${conflictColumns.join(", ")}) DO UPDATE SET ${updateClause}`;
|
|
90
|
+
this.params.push(...values);
|
|
91
|
+
return this;
|
|
92
|
+
}
|
|
93
|
+
this.sql = `INSERT INTO ${table} (${columns.join(", ")}) VALUES (${placeholders})`;
|
|
94
|
+
this.params.push(...values);
|
|
95
|
+
return this;
|
|
96
|
+
}
|
|
97
|
+
// Update operations
|
|
98
|
+
update(table, columns, values) {
|
|
99
|
+
const setClause = columns.map((col) => `${col} = ?`).join(", ");
|
|
100
|
+
this.sql = `UPDATE ${table} SET ${setClause}`;
|
|
101
|
+
this.params.push(...values);
|
|
102
|
+
return this;
|
|
103
|
+
}
|
|
104
|
+
// Delete operations
|
|
105
|
+
delete(table) {
|
|
106
|
+
this.sql = `DELETE FROM ${table}`;
|
|
107
|
+
return this;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Create a table if it doesn't exist
|
|
111
|
+
* @param table The table name
|
|
112
|
+
* @param columnDefinitions The column definitions as an array of strings
|
|
113
|
+
* @param tableConstraints Optional constraints for the table
|
|
114
|
+
* @returns The builder instance
|
|
115
|
+
*/
|
|
116
|
+
createTable(table, columnDefinitions, tableConstraints) {
|
|
117
|
+
const columns = columnDefinitions.join(", ");
|
|
118
|
+
const constraints = tableConstraints && tableConstraints.length > 0 ? ", " + tableConstraints.join(", ") : "";
|
|
119
|
+
this.sql = `CREATE TABLE IF NOT EXISTS ${table} (${columns}${constraints})`;
|
|
120
|
+
return this;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Check if an index exists in the database
|
|
124
|
+
* @param indexName The name of the index to check
|
|
125
|
+
* @param tableName The table the index is on
|
|
126
|
+
* @returns The builder instance
|
|
127
|
+
*/
|
|
128
|
+
checkIndexExists(indexName, tableName) {
|
|
129
|
+
this.sql = `SELECT name FROM sqlite_master WHERE type='index' AND name=? AND tbl_name=?`;
|
|
130
|
+
this.params.push(indexName, tableName);
|
|
131
|
+
return this;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Create an index if it doesn't exist
|
|
135
|
+
* @param indexName The name of the index to create
|
|
136
|
+
* @param tableName The table to create the index on
|
|
137
|
+
* @param columnName The column to index
|
|
138
|
+
* @param indexType Optional index type (e.g., 'UNIQUE')
|
|
139
|
+
* @returns The builder instance
|
|
140
|
+
*/
|
|
141
|
+
createIndex(indexName, tableName, columnName, indexType = "") {
|
|
142
|
+
this.sql = `CREATE ${indexType ? indexType + " " : ""}INDEX IF NOT EXISTS ${indexName} ON ${tableName}(${columnName})`;
|
|
143
|
+
return this;
|
|
144
|
+
}
|
|
145
|
+
// Raw SQL with params
|
|
146
|
+
raw(sql, ...params) {
|
|
147
|
+
this.sql = sql;
|
|
148
|
+
this.params.push(...params);
|
|
149
|
+
return this;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Add a LIKE condition to the query
|
|
153
|
+
* @param column The column to check
|
|
154
|
+
* @param value The value to match (will be wrapped with % for LIKE)
|
|
155
|
+
* @param exact If true, will not add % wildcards
|
|
156
|
+
*/
|
|
157
|
+
like(column, value, exact = false) {
|
|
158
|
+
const likeValue = exact ? value : `%${value}%`;
|
|
159
|
+
if (this.whereAdded) {
|
|
160
|
+
this.sql += ` AND ${column} LIKE ?`;
|
|
161
|
+
} else {
|
|
162
|
+
this.sql += ` WHERE ${column} LIKE ?`;
|
|
163
|
+
this.whereAdded = true;
|
|
164
|
+
}
|
|
165
|
+
this.params.push(likeValue);
|
|
166
|
+
return this;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Add a JSON LIKE condition for searching in JSON fields
|
|
170
|
+
* @param column The JSON column to search in
|
|
171
|
+
* @param key The JSON key to match
|
|
172
|
+
* @param value The value to match
|
|
173
|
+
*/
|
|
174
|
+
jsonLike(column, key, value) {
|
|
175
|
+
const jsonPattern = `%"${key}":"${value}"%`;
|
|
176
|
+
if (this.whereAdded) {
|
|
177
|
+
this.sql += ` AND ${column} LIKE ?`;
|
|
178
|
+
} else {
|
|
179
|
+
this.sql += ` WHERE ${column} LIKE ?`;
|
|
180
|
+
this.whereAdded = true;
|
|
181
|
+
}
|
|
182
|
+
this.params.push(jsonPattern);
|
|
183
|
+
return this;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Get the built query
|
|
187
|
+
* @returns Object containing the SQL string and parameters array
|
|
188
|
+
*/
|
|
189
|
+
build() {
|
|
190
|
+
return {
|
|
191
|
+
sql: this.sql,
|
|
192
|
+
params: this.params
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Reset the builder for reuse
|
|
197
|
+
* @returns The reset builder instance
|
|
198
|
+
*/
|
|
199
|
+
reset() {
|
|
200
|
+
this.sql = "";
|
|
201
|
+
this.params = [];
|
|
202
|
+
this.whereAdded = false;
|
|
203
|
+
return this;
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
function createSqlBuilder() {
|
|
207
|
+
return new SqlBuilder();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// src/storage/index.ts
|
|
211
|
+
function isArrayOfRecords(value) {
|
|
212
|
+
return value && Array.isArray(value) && value.length > 0;
|
|
213
|
+
}
|
|
214
|
+
var D1Store = class extends storage.MastraStorage {
|
|
215
|
+
client;
|
|
216
|
+
accountId;
|
|
217
|
+
databaseId;
|
|
218
|
+
binding;
|
|
219
|
+
// D1Database binding
|
|
220
|
+
tablePrefix;
|
|
221
|
+
/**
|
|
222
|
+
* Creates a new D1Store instance
|
|
223
|
+
* @param config Configuration for D1 access (either REST API or Workers Binding API)
|
|
224
|
+
*/
|
|
225
|
+
constructor(config) {
|
|
226
|
+
super({ name: "D1" });
|
|
227
|
+
this.tablePrefix = config.tablePrefix || "";
|
|
228
|
+
if ("binding" in config) {
|
|
229
|
+
if (!config.binding) {
|
|
230
|
+
throw new Error("D1 binding is required when using Workers Binding API");
|
|
231
|
+
}
|
|
232
|
+
this.binding = config.binding;
|
|
233
|
+
this.logger.info("Using D1 Workers Binding API");
|
|
234
|
+
} else {
|
|
235
|
+
if (!config.accountId || !config.databaseId || !config.apiToken) {
|
|
236
|
+
throw new Error("accountId, databaseId, and apiToken are required when using REST API");
|
|
237
|
+
}
|
|
238
|
+
this.accountId = config.accountId;
|
|
239
|
+
this.databaseId = config.databaseId;
|
|
240
|
+
this.client = new Cloudflare__default.default({
|
|
241
|
+
apiToken: config.apiToken
|
|
242
|
+
});
|
|
243
|
+
this.logger.info("Using D1 REST API");
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
// Helper method to get the full table name with prefix
|
|
247
|
+
getTableName(tableName) {
|
|
248
|
+
return `${this.tablePrefix}${tableName}`;
|
|
249
|
+
}
|
|
250
|
+
formatSqlParams(params) {
|
|
251
|
+
return params.map((p) => p === void 0 || p === null ? null : p);
|
|
252
|
+
}
|
|
253
|
+
// Helper method to create SQL indexes for better query performance
|
|
254
|
+
async createIndexIfNotExists(tableName, columnName, indexType = "") {
|
|
255
|
+
const fullTableName = this.getTableName(tableName);
|
|
256
|
+
const indexName = `idx_${tableName}_${columnName}`;
|
|
257
|
+
try {
|
|
258
|
+
const checkQuery = createSqlBuilder().checkIndexExists(indexName, fullTableName);
|
|
259
|
+
const { sql: checkSql, params: checkParams } = checkQuery.build();
|
|
260
|
+
const indexExists = await this.executeQuery({
|
|
261
|
+
sql: checkSql,
|
|
262
|
+
params: checkParams,
|
|
263
|
+
first: true
|
|
264
|
+
});
|
|
265
|
+
if (!indexExists) {
|
|
266
|
+
const createQuery = createSqlBuilder().createIndex(indexName, fullTableName, columnName, indexType);
|
|
267
|
+
const { sql: createSql, params: createParams } = createQuery.build();
|
|
268
|
+
await this.executeQuery({ sql: createSql, params: createParams });
|
|
269
|
+
this.logger.debug(`Created index ${indexName} on ${fullTableName}(${columnName})`);
|
|
270
|
+
}
|
|
271
|
+
} catch (error) {
|
|
272
|
+
this.logger.error(`Error creating index on ${fullTableName}(${columnName}):`, {
|
|
273
|
+
message: error instanceof Error ? error.message : String(error)
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
async executeWorkersBindingQuery({
|
|
278
|
+
sql,
|
|
279
|
+
params = [],
|
|
280
|
+
first = false
|
|
281
|
+
}) {
|
|
282
|
+
if (!this.binding) {
|
|
283
|
+
throw new Error("Workers binding is not configured");
|
|
284
|
+
}
|
|
285
|
+
try {
|
|
286
|
+
const statement = this.binding.prepare(sql);
|
|
287
|
+
const formattedParams = this.formatSqlParams(params);
|
|
288
|
+
let result;
|
|
289
|
+
if (formattedParams.length > 0) {
|
|
290
|
+
if (first) {
|
|
291
|
+
result = await statement.bind(...formattedParams).first();
|
|
292
|
+
if (!result) return null;
|
|
293
|
+
return result;
|
|
294
|
+
} else {
|
|
295
|
+
result = await statement.bind(...formattedParams).all();
|
|
296
|
+
const results = result.results || [];
|
|
297
|
+
if (result.meta) {
|
|
298
|
+
this.logger.debug("Query metadata", { meta: result.meta });
|
|
299
|
+
}
|
|
300
|
+
return results;
|
|
301
|
+
}
|
|
302
|
+
} else {
|
|
303
|
+
if (first) {
|
|
304
|
+
result = await statement.first();
|
|
305
|
+
if (!result) return null;
|
|
306
|
+
return result;
|
|
307
|
+
} else {
|
|
308
|
+
result = await statement.all();
|
|
309
|
+
const results = result.results || [];
|
|
310
|
+
if (result.meta) {
|
|
311
|
+
this.logger.debug("Query metadata", { meta: result.meta });
|
|
312
|
+
}
|
|
313
|
+
return results;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
} catch (workerError) {
|
|
317
|
+
this.logger.error("Workers Binding API error", {
|
|
318
|
+
message: workerError instanceof Error ? workerError.message : String(workerError),
|
|
319
|
+
sql
|
|
320
|
+
});
|
|
321
|
+
throw new Error(`D1 Workers API error: ${workerError.message}`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
async executeRestQuery({
|
|
325
|
+
sql,
|
|
326
|
+
params = [],
|
|
327
|
+
first = false
|
|
328
|
+
}) {
|
|
329
|
+
if (!this.client || !this.accountId || !this.databaseId) {
|
|
330
|
+
throw new Error("Missing required REST API configuration");
|
|
331
|
+
}
|
|
332
|
+
try {
|
|
333
|
+
const response = await this.client.d1.database.query(this.databaseId, {
|
|
334
|
+
account_id: this.accountId,
|
|
335
|
+
sql,
|
|
336
|
+
params: this.formatSqlParams(params)
|
|
337
|
+
});
|
|
338
|
+
const result = response.result || [];
|
|
339
|
+
const results = result.flatMap((r) => r.results || []);
|
|
340
|
+
if (first) {
|
|
341
|
+
const firstResult = isArrayOfRecords(results) && results.length > 0 ? results[0] : null;
|
|
342
|
+
if (!firstResult) return null;
|
|
343
|
+
return firstResult;
|
|
344
|
+
}
|
|
345
|
+
return results;
|
|
346
|
+
} catch (restError) {
|
|
347
|
+
this.logger.error("REST API error", {
|
|
348
|
+
message: restError instanceof Error ? restError.message : String(restError),
|
|
349
|
+
sql
|
|
350
|
+
});
|
|
351
|
+
throw new Error(`D1 REST API error: ${restError.message}`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Execute a SQL query against the D1 database
|
|
356
|
+
* @param options Query options including SQL, parameters, and whether to return only the first result
|
|
357
|
+
* @returns Query results as an array or a single object if first=true
|
|
358
|
+
*/
|
|
359
|
+
async executeQuery(options) {
|
|
360
|
+
const { sql, params = [], first = false } = options;
|
|
361
|
+
try {
|
|
362
|
+
this.logger.debug("Executing SQL query", { sql, params, first });
|
|
363
|
+
if (this.binding) {
|
|
364
|
+
return this.executeWorkersBindingQuery({ sql, params, first });
|
|
365
|
+
} else if (this.client && this.accountId && this.databaseId) {
|
|
366
|
+
return this.executeRestQuery({ sql, params, first });
|
|
367
|
+
} else {
|
|
368
|
+
throw new Error("No valid D1 configuration provided");
|
|
369
|
+
}
|
|
370
|
+
} catch (error) {
|
|
371
|
+
this.logger.error("Error executing SQL query", {
|
|
372
|
+
message: error instanceof Error ? error.message : String(error),
|
|
373
|
+
sql,
|
|
374
|
+
params,
|
|
375
|
+
first
|
|
376
|
+
});
|
|
377
|
+
throw new Error(`D1 query error: ${error.message}`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
// Helper to convert storage type to SQL type
|
|
381
|
+
getSqlType(type) {
|
|
382
|
+
switch (type) {
|
|
383
|
+
case "text":
|
|
384
|
+
return "TEXT";
|
|
385
|
+
case "timestamp":
|
|
386
|
+
return "TIMESTAMP";
|
|
387
|
+
case "integer":
|
|
388
|
+
return "INTEGER";
|
|
389
|
+
case "bigint":
|
|
390
|
+
return "INTEGER";
|
|
391
|
+
// SQLite doesn't have a separate BIGINT type
|
|
392
|
+
case "jsonb":
|
|
393
|
+
return "TEXT";
|
|
394
|
+
// Store JSON as TEXT in SQLite
|
|
395
|
+
default:
|
|
396
|
+
return "TEXT";
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
ensureDate(date) {
|
|
400
|
+
if (!date) return void 0;
|
|
401
|
+
return date instanceof Date ? date : new Date(date);
|
|
402
|
+
}
|
|
403
|
+
serializeDate(date) {
|
|
404
|
+
if (!date) return void 0;
|
|
405
|
+
const dateObj = this.ensureDate(date);
|
|
406
|
+
return dateObj?.toISOString();
|
|
407
|
+
}
|
|
408
|
+
// Helper to serialize objects to JSON strings
|
|
409
|
+
serializeValue(value) {
|
|
410
|
+
if (value === null || value === void 0) return null;
|
|
411
|
+
if (value instanceof Date) {
|
|
412
|
+
return this.serializeDate(value);
|
|
413
|
+
}
|
|
414
|
+
if (typeof value === "object") {
|
|
415
|
+
return JSON.stringify(value);
|
|
416
|
+
}
|
|
417
|
+
return value;
|
|
418
|
+
}
|
|
419
|
+
// Helper to deserialize JSON strings to objects
|
|
420
|
+
deserializeValue(value, type) {
|
|
421
|
+
if (value === null || value === void 0) return null;
|
|
422
|
+
if (type === "date" && typeof value === "string") {
|
|
423
|
+
return new Date(value);
|
|
424
|
+
}
|
|
425
|
+
if (type === "jsonb" && typeof value === "string") {
|
|
426
|
+
try {
|
|
427
|
+
return JSON.parse(value);
|
|
428
|
+
} catch {
|
|
429
|
+
return value;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
if (typeof value === "string" && (value.startsWith("{") || value.startsWith("["))) {
|
|
433
|
+
try {
|
|
434
|
+
return JSON.parse(value);
|
|
435
|
+
} catch {
|
|
436
|
+
return value;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return value;
|
|
440
|
+
}
|
|
441
|
+
async createTable({
|
|
442
|
+
tableName,
|
|
443
|
+
schema
|
|
444
|
+
}) {
|
|
445
|
+
const fullTableName = this.getTableName(tableName);
|
|
446
|
+
const columnDefinitions = Object.entries(schema).map(([colName, colDef]) => {
|
|
447
|
+
const type = this.getSqlType(colDef.type);
|
|
448
|
+
const nullable = colDef.nullable === false ? "NOT NULL" : "";
|
|
449
|
+
const primaryKey = colDef.primaryKey ? "PRIMARY KEY" : "";
|
|
450
|
+
return `${colName} ${type} ${nullable} ${primaryKey}`.trim();
|
|
451
|
+
});
|
|
452
|
+
const tableConstraints = [];
|
|
453
|
+
if (tableName === storage.TABLE_WORKFLOW_SNAPSHOT) {
|
|
454
|
+
tableConstraints.push("UNIQUE (workflow_name, run_id)");
|
|
455
|
+
}
|
|
456
|
+
const query = createSqlBuilder().createTable(fullTableName, columnDefinitions, tableConstraints);
|
|
457
|
+
const { sql, params } = query.build();
|
|
458
|
+
try {
|
|
459
|
+
await this.executeQuery({ sql, params });
|
|
460
|
+
this.logger.debug(`Created table ${fullTableName}`);
|
|
461
|
+
} catch (error) {
|
|
462
|
+
this.logger.error(`Error creating table ${fullTableName}:`, {
|
|
463
|
+
message: error instanceof Error ? error.message : String(error)
|
|
464
|
+
});
|
|
465
|
+
throw new Error(`Failed to create table ${fullTableName}: ${error}`);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
async clearTable({ tableName }) {
|
|
469
|
+
const fullTableName = this.getTableName(tableName);
|
|
470
|
+
try {
|
|
471
|
+
const query = createSqlBuilder().delete(fullTableName);
|
|
472
|
+
const { sql, params } = query.build();
|
|
473
|
+
await this.executeQuery({ sql, params });
|
|
474
|
+
this.logger.debug(`Cleared table ${fullTableName}`);
|
|
475
|
+
} catch (error) {
|
|
476
|
+
this.logger.error(`Error clearing table ${fullTableName}:`, {
|
|
477
|
+
message: error instanceof Error ? error.message : String(error)
|
|
478
|
+
});
|
|
479
|
+
throw new Error(`Failed to clear table ${fullTableName}: ${error}`);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
async processRecord(record) {
|
|
483
|
+
const processedRecord = {};
|
|
484
|
+
for (const [key, value] of Object.entries(record)) {
|
|
485
|
+
processedRecord[key] = this.serializeValue(value);
|
|
486
|
+
}
|
|
487
|
+
return processedRecord;
|
|
488
|
+
}
|
|
489
|
+
async insert({ tableName, record }) {
|
|
490
|
+
const fullTableName = this.getTableName(tableName);
|
|
491
|
+
const processedRecord = await this.processRecord(record);
|
|
492
|
+
const columns = Object.keys(processedRecord);
|
|
493
|
+
const values = Object.values(processedRecord);
|
|
494
|
+
const query = createSqlBuilder().insert(fullTableName, columns, values);
|
|
495
|
+
const { sql, params } = query.build();
|
|
496
|
+
try {
|
|
497
|
+
await this.executeQuery({ sql, params });
|
|
498
|
+
} catch (error) {
|
|
499
|
+
this.logger.error(`Error inserting into ${fullTableName}:`, { error });
|
|
500
|
+
throw new Error(`Failed to insert into ${fullTableName}: ${error}`);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
async load({ tableName, keys }) {
|
|
504
|
+
const fullTableName = this.getTableName(tableName);
|
|
505
|
+
const query = createSqlBuilder().select("*").from(fullTableName);
|
|
506
|
+
let firstKey = true;
|
|
507
|
+
for (const [key, value] of Object.entries(keys)) {
|
|
508
|
+
if (firstKey) {
|
|
509
|
+
query.where(`${key} = ?`, value);
|
|
510
|
+
firstKey = false;
|
|
511
|
+
} else {
|
|
512
|
+
query.andWhere(`${key} = ?`, value);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
query.limit(1);
|
|
516
|
+
const { sql, params } = query.build();
|
|
517
|
+
try {
|
|
518
|
+
const result = await this.executeQuery({ sql, params, first: true });
|
|
519
|
+
if (!result) return null;
|
|
520
|
+
const processedResult = {};
|
|
521
|
+
for (const [key, value] of Object.entries(result)) {
|
|
522
|
+
processedResult[key] = this.deserializeValue(value);
|
|
523
|
+
}
|
|
524
|
+
return processedResult;
|
|
525
|
+
} catch (error) {
|
|
526
|
+
this.logger.error(`Error loading from ${fullTableName}:`, {
|
|
527
|
+
message: error instanceof Error ? error.message : String(error)
|
|
528
|
+
});
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
async getThreadById({ threadId }) {
|
|
533
|
+
const thread = await this.load({
|
|
534
|
+
tableName: storage.TABLE_THREADS,
|
|
535
|
+
keys: { id: threadId }
|
|
536
|
+
});
|
|
537
|
+
if (!thread) return null;
|
|
538
|
+
try {
|
|
539
|
+
return {
|
|
540
|
+
...thread,
|
|
541
|
+
createdAt: this.ensureDate(thread.createdAt),
|
|
542
|
+
updatedAt: this.ensureDate(thread.updatedAt),
|
|
543
|
+
metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata || "{}") : thread.metadata || {}
|
|
544
|
+
};
|
|
545
|
+
} catch (error) {
|
|
546
|
+
this.logger.error(`Error processing thread ${threadId}:`, {
|
|
547
|
+
message: error instanceof Error ? error.message : String(error)
|
|
548
|
+
});
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
async getThreadsByResourceId({ resourceId }) {
|
|
553
|
+
const fullTableName = this.getTableName(storage.TABLE_THREADS);
|
|
554
|
+
try {
|
|
555
|
+
const query = createSqlBuilder().select("*").from(fullTableName).where("resourceId = ?", resourceId);
|
|
556
|
+
const { sql, params } = query.build();
|
|
557
|
+
const results = await this.executeQuery({ sql, params });
|
|
558
|
+
return (isArrayOfRecords(results) ? results : []).map((thread) => ({
|
|
559
|
+
...thread,
|
|
560
|
+
createdAt: this.ensureDate(thread.createdAt),
|
|
561
|
+
updatedAt: this.ensureDate(thread.updatedAt),
|
|
562
|
+
metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata || "{}") : thread.metadata || {}
|
|
563
|
+
}));
|
|
564
|
+
} catch (error) {
|
|
565
|
+
this.logger.error(`Error getting threads by resourceId ${resourceId}:`, {
|
|
566
|
+
message: error instanceof Error ? error.message : String(error)
|
|
567
|
+
});
|
|
568
|
+
return [];
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
async saveThread({ thread }) {
|
|
572
|
+
const fullTableName = this.getTableName(storage.TABLE_THREADS);
|
|
573
|
+
const threadToSave = {
|
|
574
|
+
id: thread.id,
|
|
575
|
+
resourceId: thread.resourceId,
|
|
576
|
+
title: thread.title,
|
|
577
|
+
metadata: thread.metadata ? JSON.stringify(thread.metadata) : null,
|
|
578
|
+
createdAt: thread.createdAt,
|
|
579
|
+
updatedAt: thread.updatedAt
|
|
580
|
+
};
|
|
581
|
+
const processedRecord = await this.processRecord(threadToSave);
|
|
582
|
+
const columns = Object.keys(processedRecord);
|
|
583
|
+
const values = Object.values(processedRecord);
|
|
584
|
+
const updateMap = {
|
|
585
|
+
resourceId: "excluded.resourceId",
|
|
586
|
+
title: "excluded.title",
|
|
587
|
+
metadata: "excluded.metadata",
|
|
588
|
+
createdAt: "excluded.createdAt",
|
|
589
|
+
updatedAt: "excluded.updatedAt"
|
|
590
|
+
};
|
|
591
|
+
const query = createSqlBuilder().insert(fullTableName, columns, values, ["id"], updateMap);
|
|
592
|
+
const { sql, params } = query.build();
|
|
593
|
+
try {
|
|
594
|
+
await this.executeQuery({ sql, params });
|
|
595
|
+
return thread;
|
|
596
|
+
} catch (error) {
|
|
597
|
+
this.logger.error(`Error saving thread to ${fullTableName}:`, { error });
|
|
598
|
+
throw error;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
async updateThread({
|
|
602
|
+
id,
|
|
603
|
+
title,
|
|
604
|
+
metadata
|
|
605
|
+
}) {
|
|
606
|
+
const thread = await this.getThreadById({ threadId: id });
|
|
607
|
+
if (!thread) {
|
|
608
|
+
throw new Error(`Thread ${id} not found`);
|
|
609
|
+
}
|
|
610
|
+
const fullTableName = this.getTableName(storage.TABLE_THREADS);
|
|
611
|
+
const mergedMetadata = {
|
|
612
|
+
...typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
|
|
613
|
+
...metadata
|
|
614
|
+
};
|
|
615
|
+
const columns = ["title", "metadata", "updatedAt"];
|
|
616
|
+
const values = [title, JSON.stringify(mergedMetadata), (/* @__PURE__ */ new Date()).toISOString()];
|
|
617
|
+
const query = createSqlBuilder().update(fullTableName, columns, values).where("id = ?", id);
|
|
618
|
+
const { sql, params } = query.build();
|
|
619
|
+
try {
|
|
620
|
+
await this.executeQuery({ sql, params });
|
|
621
|
+
return {
|
|
622
|
+
...thread,
|
|
623
|
+
title,
|
|
624
|
+
metadata: {
|
|
625
|
+
...typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
|
|
626
|
+
...metadata
|
|
627
|
+
},
|
|
628
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
629
|
+
};
|
|
630
|
+
} catch (error) {
|
|
631
|
+
this.logger.error("Error updating thread:", { error });
|
|
632
|
+
throw error;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
async deleteThread({ threadId }) {
|
|
636
|
+
const fullTableName = this.getTableName(storage.TABLE_THREADS);
|
|
637
|
+
try {
|
|
638
|
+
const deleteThreadQuery = createSqlBuilder().delete(fullTableName).where("id = ?", threadId);
|
|
639
|
+
const { sql: threadSql, params: threadParams } = deleteThreadQuery.build();
|
|
640
|
+
await this.executeQuery({ sql: threadSql, params: threadParams });
|
|
641
|
+
const messagesTableName = this.getTableName(storage.TABLE_MESSAGES);
|
|
642
|
+
const deleteMessagesQuery = createSqlBuilder().delete(messagesTableName).where("thread_id = ?", threadId);
|
|
643
|
+
const { sql: messagesSql, params: messagesParams } = deleteMessagesQuery.build();
|
|
644
|
+
await this.executeQuery({ sql: messagesSql, params: messagesParams });
|
|
645
|
+
} catch (error) {
|
|
646
|
+
this.logger.error(`Error deleting thread ${threadId}:`, {
|
|
647
|
+
message: error instanceof Error ? error.message : String(error)
|
|
648
|
+
});
|
|
649
|
+
throw new Error(`Failed to delete thread ${threadId}: ${error}`);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
// Thread and message management methods
|
|
653
|
+
async saveMessages({ messages }) {
|
|
654
|
+
if (messages.length === 0) return [];
|
|
655
|
+
try {
|
|
656
|
+
const now = /* @__PURE__ */ new Date();
|
|
657
|
+
for (const [i, message] of messages.entries()) {
|
|
658
|
+
if (!message.id) throw new Error(`Message at index ${i} missing id`);
|
|
659
|
+
if (!message.threadId) throw new Error(`Message at index ${i} missing threadId`);
|
|
660
|
+
if (!message.content) throw new Error(`Message at index ${i} missing content`);
|
|
661
|
+
if (!message.role) throw new Error(`Message at index ${i} missing role`);
|
|
662
|
+
const thread = await this.getThreadById({ threadId: message.threadId });
|
|
663
|
+
if (!thread) {
|
|
664
|
+
throw new Error(`Thread ${message.threadId} not found`);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
const messagesToInsert = messages.map((message) => {
|
|
668
|
+
const createdAt = message.createdAt ? new Date(message.createdAt) : now;
|
|
669
|
+
return {
|
|
670
|
+
id: message.id,
|
|
671
|
+
thread_id: message.threadId,
|
|
672
|
+
content: typeof message.content === "string" ? message.content : JSON.stringify(message.content),
|
|
673
|
+
createdAt: createdAt.toISOString(),
|
|
674
|
+
role: message.role,
|
|
675
|
+
type: message.type
|
|
676
|
+
};
|
|
677
|
+
});
|
|
678
|
+
await this.batchInsert({
|
|
679
|
+
tableName: storage.TABLE_MESSAGES,
|
|
680
|
+
records: messagesToInsert
|
|
681
|
+
});
|
|
682
|
+
this.logger.debug(`Saved ${messages.length} messages`);
|
|
683
|
+
return messages;
|
|
684
|
+
} catch (error) {
|
|
685
|
+
this.logger.error("Error saving messages:", { message: error instanceof Error ? error.message : String(error) });
|
|
686
|
+
throw error;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
async getMessages({ threadId, selectBy }) {
|
|
690
|
+
const fullTableName = this.getTableName(storage.TABLE_MESSAGES);
|
|
691
|
+
const limit = typeof selectBy?.last === "number" ? selectBy.last : 40;
|
|
692
|
+
const include = selectBy?.include || [];
|
|
693
|
+
const messages = [];
|
|
694
|
+
try {
|
|
695
|
+
if (include.length) {
|
|
696
|
+
const prevMax = Math.max(...include.map((i) => i.withPreviousMessages || 0));
|
|
697
|
+
const nextMax = Math.max(...include.map((i) => i.withNextMessages || 0));
|
|
698
|
+
const includeIds = include.map((i) => i.id);
|
|
699
|
+
const sql2 = `
|
|
700
|
+
WITH ordered_messages AS (
|
|
701
|
+
SELECT
|
|
702
|
+
*,
|
|
703
|
+
ROW_NUMBER() OVER (ORDER BY createdAt DESC) AS row_num
|
|
704
|
+
FROM ${fullTableName}
|
|
705
|
+
WHERE thread_id = ?
|
|
706
|
+
)
|
|
707
|
+
SELECT
|
|
708
|
+
m.id,
|
|
709
|
+
m.content,
|
|
710
|
+
m.role,
|
|
711
|
+
m.type,
|
|
712
|
+
m.createdAt,
|
|
713
|
+
m.thread_id AS "threadId"
|
|
714
|
+
FROM ordered_messages m
|
|
715
|
+
WHERE m.id IN (${includeIds.map(() => "?").join(",")})
|
|
716
|
+
OR EXISTS (
|
|
717
|
+
SELECT 1 FROM ordered_messages target
|
|
718
|
+
WHERE target.id IN (${includeIds.map(() => "?").join(",")})
|
|
719
|
+
AND (
|
|
720
|
+
(m.row_num <= target.row_num + ? AND m.row_num > target.row_num)
|
|
721
|
+
OR
|
|
722
|
+
(m.row_num >= target.row_num - ? AND m.row_num < target.row_num)
|
|
723
|
+
)
|
|
724
|
+
)
|
|
725
|
+
ORDER BY m.createdAt DESC
|
|
726
|
+
`;
|
|
727
|
+
const params2 = [
|
|
728
|
+
threadId,
|
|
729
|
+
...includeIds,
|
|
730
|
+
// for m.id IN (...)
|
|
731
|
+
...includeIds,
|
|
732
|
+
// for target.id IN (...)
|
|
733
|
+
prevMax,
|
|
734
|
+
nextMax
|
|
735
|
+
];
|
|
736
|
+
const includeResult = await this.executeQuery({ sql: sql2, params: params2 });
|
|
737
|
+
if (Array.isArray(includeResult)) messages.push(...includeResult);
|
|
738
|
+
}
|
|
739
|
+
const excludeIds = messages.map((m) => m.id);
|
|
740
|
+
let query = createSqlBuilder().select(["id", "content", "role", "type", '"createdAt"', 'thread_id AS "threadId"']).from(fullTableName).where("thread_id = ?", threadId).andWhere(`id NOT IN (${excludeIds.map(() => "?").join(",")})`, ...excludeIds).orderBy("createdAt", "DESC").limit(limit);
|
|
741
|
+
const { sql, params } = query.build();
|
|
742
|
+
const result = await this.executeQuery({ sql, params });
|
|
743
|
+
if (Array.isArray(result)) messages.push(...result);
|
|
744
|
+
messages.sort((a, b) => {
|
|
745
|
+
const aRecord = a;
|
|
746
|
+
const bRecord = b;
|
|
747
|
+
const timeA = new Date(aRecord.createdAt).getTime();
|
|
748
|
+
const timeB = new Date(bRecord.createdAt).getTime();
|
|
749
|
+
return timeA - timeB;
|
|
750
|
+
});
|
|
751
|
+
const processedMessages = messages.map((message) => {
|
|
752
|
+
const processedMsg = {};
|
|
753
|
+
for (const [key, value] of Object.entries(message)) {
|
|
754
|
+
processedMsg[key] = this.deserializeValue(value);
|
|
755
|
+
}
|
|
756
|
+
return processedMsg;
|
|
757
|
+
});
|
|
758
|
+
this.logger.debug(`Retrieved ${messages.length} messages for thread ${threadId}`);
|
|
759
|
+
return processedMessages;
|
|
760
|
+
} catch (error) {
|
|
761
|
+
this.logger.error("Error retrieving messages for thread", {
|
|
762
|
+
threadId,
|
|
763
|
+
message: error instanceof Error ? error.message : String(error)
|
|
764
|
+
});
|
|
765
|
+
return [];
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
async persistWorkflowSnapshot({
|
|
769
|
+
workflowName,
|
|
770
|
+
runId,
|
|
771
|
+
snapshot
|
|
772
|
+
}) {
|
|
773
|
+
const fullTableName = this.getTableName(storage.TABLE_WORKFLOW_SNAPSHOT);
|
|
774
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
775
|
+
const currentSnapshot = await this.load({
|
|
776
|
+
tableName: storage.TABLE_WORKFLOW_SNAPSHOT,
|
|
777
|
+
keys: { workflow_name: workflowName, run_id: runId }
|
|
778
|
+
});
|
|
779
|
+
const persisting = currentSnapshot ? {
|
|
780
|
+
...currentSnapshot,
|
|
781
|
+
snapshot: JSON.stringify(snapshot),
|
|
782
|
+
updatedAt: now
|
|
783
|
+
} : {
|
|
784
|
+
workflow_name: workflowName,
|
|
785
|
+
run_id: runId,
|
|
786
|
+
snapshot,
|
|
787
|
+
createdAt: now,
|
|
788
|
+
updatedAt: now
|
|
789
|
+
};
|
|
790
|
+
const processedRecord = await this.processRecord(persisting);
|
|
791
|
+
const columns = Object.keys(processedRecord);
|
|
792
|
+
const values = Object.values(processedRecord);
|
|
793
|
+
const updateMap = {
|
|
794
|
+
snapshot: "excluded.snapshot",
|
|
795
|
+
updatedAt: "excluded.updatedAt"
|
|
796
|
+
};
|
|
797
|
+
this.logger.debug("Persisting workflow snapshot", { workflowName, runId });
|
|
798
|
+
const query = createSqlBuilder().insert(fullTableName, columns, values, ["workflow_name", "run_id"], updateMap);
|
|
799
|
+
const { sql, params } = query.build();
|
|
800
|
+
try {
|
|
801
|
+
await this.executeQuery({ sql, params });
|
|
802
|
+
} catch (error) {
|
|
803
|
+
this.logger.error("Error persisting workflow snapshot:", {
|
|
804
|
+
message: error instanceof Error ? error.message : String(error)
|
|
805
|
+
});
|
|
806
|
+
throw error;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
async loadWorkflowSnapshot(params) {
|
|
810
|
+
const { workflowName, runId } = params;
|
|
811
|
+
this.logger.debug("Loading workflow snapshot", { workflowName, runId });
|
|
812
|
+
const d = await this.load({
|
|
813
|
+
tableName: storage.TABLE_WORKFLOW_SNAPSHOT,
|
|
814
|
+
keys: {
|
|
815
|
+
workflow_name: workflowName,
|
|
816
|
+
run_id: runId
|
|
817
|
+
}
|
|
818
|
+
});
|
|
819
|
+
return d ? d.snapshot : null;
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* Insert multiple records in a batch operation
|
|
823
|
+
* @param tableName The table to insert into
|
|
824
|
+
* @param records The records to insert
|
|
825
|
+
*/
|
|
826
|
+
async batchInsert({ tableName, records }) {
|
|
827
|
+
if (records.length === 0) return;
|
|
828
|
+
const fullTableName = this.getTableName(tableName);
|
|
829
|
+
try {
|
|
830
|
+
const batchSize = 50;
|
|
831
|
+
for (let i = 0; i < records.length; i += batchSize) {
|
|
832
|
+
const batch = records.slice(i, i + batchSize);
|
|
833
|
+
const recordsToInsert = batch;
|
|
834
|
+
if (recordsToInsert.length > 0) {
|
|
835
|
+
const firstRecord = recordsToInsert[0];
|
|
836
|
+
const columns = Object.keys(firstRecord || {});
|
|
837
|
+
for (const record of recordsToInsert) {
|
|
838
|
+
const values = columns.map((col) => {
|
|
839
|
+
if (!record) return null;
|
|
840
|
+
const value = typeof col === "string" ? record[col] : null;
|
|
841
|
+
return this.serializeValue(value);
|
|
842
|
+
});
|
|
843
|
+
const query = createSqlBuilder().insert(fullTableName, columns, values);
|
|
844
|
+
const { sql, params } = query.build();
|
|
845
|
+
await this.executeQuery({ sql, params });
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
this.logger.debug(
|
|
849
|
+
`Processed batch ${Math.floor(i / batchSize) + 1} of ${Math.ceil(records.length / batchSize)}`
|
|
850
|
+
);
|
|
851
|
+
}
|
|
852
|
+
this.logger.debug(`Successfully batch inserted ${records.length} records into ${tableName}`);
|
|
853
|
+
} catch (error) {
|
|
854
|
+
this.logger.error(`Error batch inserting into ${tableName}:`, {
|
|
855
|
+
message: error instanceof Error ? error.message : String(error)
|
|
856
|
+
});
|
|
857
|
+
throw new Error(`Failed to batch insert into ${tableName}: ${error}`);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
async getTraces({
|
|
861
|
+
name,
|
|
862
|
+
scope,
|
|
863
|
+
page,
|
|
864
|
+
perPage,
|
|
865
|
+
attributes
|
|
866
|
+
}) {
|
|
867
|
+
const fullTableName = this.getTableName(storage.TABLE_TRACES);
|
|
868
|
+
try {
|
|
869
|
+
const query = createSqlBuilder().select("*").from(fullTableName).where("1=1");
|
|
870
|
+
if (name) {
|
|
871
|
+
query.andWhere("name LIKE ?", `%${name}%`);
|
|
872
|
+
}
|
|
873
|
+
if (scope) {
|
|
874
|
+
query.andWhere("scope = ?", scope);
|
|
875
|
+
}
|
|
876
|
+
if (attributes && Object.keys(attributes).length > 0) {
|
|
877
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
878
|
+
query.jsonLike("attributes", key, value);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
query.orderBy("startTime", "DESC").limit(perPage).offset((page - 1) * perPage);
|
|
882
|
+
const { sql, params } = query.build();
|
|
883
|
+
const results = await this.executeQuery({ sql, params });
|
|
884
|
+
return isArrayOfRecords(results) ? results.map((trace) => ({
|
|
885
|
+
...trace,
|
|
886
|
+
attributes: this.deserializeValue(trace.attributes, "jsonb"),
|
|
887
|
+
status: this.deserializeValue(trace.status, "jsonb"),
|
|
888
|
+
events: this.deserializeValue(trace.events, "jsonb"),
|
|
889
|
+
links: this.deserializeValue(trace.links, "jsonb"),
|
|
890
|
+
other: this.deserializeValue(trace.other, "jsonb")
|
|
891
|
+
})) : [];
|
|
892
|
+
} catch (error) {
|
|
893
|
+
this.logger.error("Error getting traces:", { message: error instanceof Error ? error.message : String(error) });
|
|
894
|
+
return [];
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
async getEvalsByAgentName(agentName, type) {
|
|
898
|
+
const fullTableName = this.getTableName(storage.TABLE_EVALS);
|
|
899
|
+
try {
|
|
900
|
+
let query = createSqlBuilder().select("*").from(fullTableName).where("agent_name = ?", agentName);
|
|
901
|
+
if (type === "test") {
|
|
902
|
+
query = query.andWhere("test_info IS NOT NULL AND json_extract(test_info, '$.testPath') IS NOT NULL");
|
|
903
|
+
} else if (type === "live") {
|
|
904
|
+
query = query.andWhere("(test_info IS NULL OR json_extract(test_info, '$.testPath') IS NULL)");
|
|
905
|
+
}
|
|
906
|
+
query.orderBy("created_at", "DESC");
|
|
907
|
+
const { sql, params } = query.build();
|
|
908
|
+
const results = await this.executeQuery({ sql, params });
|
|
909
|
+
return isArrayOfRecords(results) ? results.map((row) => {
|
|
910
|
+
const result = this.deserializeValue(row.result);
|
|
911
|
+
const testInfo = row.test_info ? this.deserializeValue(row.test_info) : void 0;
|
|
912
|
+
return {
|
|
913
|
+
input: row.input || "",
|
|
914
|
+
output: row.output || "",
|
|
915
|
+
result,
|
|
916
|
+
agentName: row.agent_name || "",
|
|
917
|
+
metricName: row.metric_name || "",
|
|
918
|
+
instructions: row.instructions || "",
|
|
919
|
+
runId: row.run_id || "",
|
|
920
|
+
globalRunId: row.global_run_id || "",
|
|
921
|
+
createdAt: row.created_at || "",
|
|
922
|
+
testInfo
|
|
923
|
+
};
|
|
924
|
+
}) : [];
|
|
925
|
+
} catch (error) {
|
|
926
|
+
this.logger.error(`Error getting evals for agent ${agentName}:`, {
|
|
927
|
+
message: error instanceof Error ? error.message : String(error)
|
|
928
|
+
});
|
|
929
|
+
return [];
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
getWorkflowRuns(_args) {
|
|
933
|
+
throw new Error("Method not implemented.");
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* Close the database connection
|
|
937
|
+
* No explicit cleanup needed for D1 in either REST or Workers Binding mode
|
|
938
|
+
*/
|
|
939
|
+
async close() {
|
|
940
|
+
this.logger.debug("Closing D1 connection");
|
|
941
|
+
}
|
|
942
|
+
};
|
|
943
|
+
|
|
944
|
+
exports.D1Store = D1Store;
|