@mastra/cloudflare-d1 1.0.0-beta.5 → 1.0.0-beta.7
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/CHANGELOG.md +232 -0
- package/dist/index.cjs +1034 -1090
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1038 -1094
- package/dist/index.js.map +1 -1
- package/dist/storage/{domains/operations → db}/index.d.ts +42 -6
- package/dist/storage/db/index.d.ts.map +1 -0
- package/dist/storage/domains/memory/index.d.ts +6 -5
- package/dist/storage/domains/memory/index.d.ts.map +1 -1
- package/dist/storage/domains/scores/index.d.ts +11 -33
- package/dist/storage/domains/scores/index.d.ts.map +1 -1
- package/dist/storage/domains/workflows/index.d.ts +10 -14
- package/dist/storage/domains/workflows/index.d.ts.map +1 -1
- package/dist/storage/index.d.ts +20 -167
- package/dist/storage/index.d.ts.map +1 -1
- package/package.json +3 -4
- package/dist/storage/domains/operations/index.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,11 +1,28 @@
|
|
|
1
1
|
import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
|
|
2
|
-
import { MastraStorage, createStorageErrorId,
|
|
2
|
+
import { MastraStorage, createStorageErrorId, ScoresStorage, TABLE_SCHEMAS, TABLE_SCORERS, normalizePerPage, calculatePagination, WorkflowsStorage, TABLE_WORKFLOW_SNAPSHOT, ensureDate, MemoryStorage, TABLE_THREADS, TABLE_MESSAGES, TABLE_RESOURCES, serializeDate, getSqlType, getDefaultValue, transformScoreRow as transformScoreRow$1 } from '@mastra/core/storage';
|
|
3
3
|
import Cloudflare from 'cloudflare';
|
|
4
4
|
import { MessageList } from '@mastra/core/agent';
|
|
5
|
+
import { MastraBase } from '@mastra/core/base';
|
|
5
6
|
import { parseSqlIdentifier } from '@mastra/core/utils';
|
|
6
7
|
import { saveScorePayloadSchema } from '@mastra/core/evals';
|
|
7
8
|
|
|
8
9
|
// src/storage/index.ts
|
|
10
|
+
|
|
11
|
+
// src/storage/domains/utils.ts
|
|
12
|
+
function isArrayOfRecords(value) {
|
|
13
|
+
return value && Array.isArray(value) && value.length > 0;
|
|
14
|
+
}
|
|
15
|
+
function deserializeValue(value, type) {
|
|
16
|
+
if (value === null || value === void 0) return null;
|
|
17
|
+
if (typeof value === "string" && (value.startsWith("{") || value.startsWith("["))) {
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(value);
|
|
20
|
+
} catch {
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
9
26
|
var SqlBuilder = class {
|
|
10
27
|
sql = "";
|
|
11
28
|
params = [];
|
|
@@ -236,511 +253,600 @@ function parseSelectIdentifier(column) {
|
|
|
236
253
|
return column;
|
|
237
254
|
}
|
|
238
255
|
|
|
239
|
-
// src/storage/
|
|
240
|
-
function
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
try {
|
|
247
|
-
return JSON.parse(value);
|
|
248
|
-
} catch {
|
|
249
|
-
return value;
|
|
250
|
-
}
|
|
256
|
+
// src/storage/db/index.ts
|
|
257
|
+
function resolveD1Config(config) {
|
|
258
|
+
if ("client" in config) {
|
|
259
|
+
return {
|
|
260
|
+
client: config.client,
|
|
261
|
+
tablePrefix: config.tablePrefix
|
|
262
|
+
};
|
|
251
263
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
operations;
|
|
258
|
-
constructor({ operations }) {
|
|
259
|
-
super();
|
|
260
|
-
this.operations = operations;
|
|
264
|
+
if ("binding" in config) {
|
|
265
|
+
return {
|
|
266
|
+
binding: config.binding,
|
|
267
|
+
tablePrefix: config.tablePrefix
|
|
268
|
+
};
|
|
261
269
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
270
|
+
const cfClient = new Cloudflare({ apiToken: config.apiToken });
|
|
271
|
+
return {
|
|
272
|
+
client: {
|
|
273
|
+
query: ({ sql, params }) => {
|
|
274
|
+
return cfClient.d1.database.query(config.databaseId, {
|
|
275
|
+
account_id: config.accountId,
|
|
276
|
+
sql,
|
|
277
|
+
params
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
tablePrefix: config.tablePrefix
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
var D1DB = class extends MastraBase {
|
|
285
|
+
client;
|
|
286
|
+
binding;
|
|
287
|
+
tablePrefix;
|
|
288
|
+
constructor(config) {
|
|
289
|
+
super({
|
|
290
|
+
component: "STORAGE",
|
|
291
|
+
name: "D1_DB"
|
|
266
292
|
});
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
...resource,
|
|
271
|
-
createdAt: ensureDate(resource.createdAt),
|
|
272
|
-
updatedAt: ensureDate(resource.updatedAt),
|
|
273
|
-
metadata: typeof resource.metadata === "string" ? JSON.parse(resource.metadata || "{}") : resource.metadata
|
|
274
|
-
};
|
|
275
|
-
} catch (error) {
|
|
276
|
-
const mastraError = new MastraError(
|
|
277
|
-
{
|
|
278
|
-
id: createStorageErrorId("CLOUDFLARE_D1", "GET_RESOURCE_BY_ID", "FAILED"),
|
|
279
|
-
domain: ErrorDomain.STORAGE,
|
|
280
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
281
|
-
text: `Error processing resource ${resourceId}: ${error instanceof Error ? error.message : String(error)}`,
|
|
282
|
-
details: { resourceId }
|
|
283
|
-
},
|
|
284
|
-
error
|
|
285
|
-
);
|
|
286
|
-
this.logger?.error(mastraError.toString());
|
|
287
|
-
this.logger?.trackException(mastraError);
|
|
288
|
-
return null;
|
|
289
|
-
}
|
|
293
|
+
this.client = config.client;
|
|
294
|
+
this.binding = config.binding;
|
|
295
|
+
this.tablePrefix = config.tablePrefix || "";
|
|
290
296
|
}
|
|
291
|
-
async
|
|
292
|
-
const fullTableName = this.
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
297
|
+
async hasColumn(table, column) {
|
|
298
|
+
const fullTableName = table.startsWith(this.tablePrefix) ? table : `${this.tablePrefix}${table}`;
|
|
299
|
+
const sql = `PRAGMA table_info(${fullTableName});`;
|
|
300
|
+
const result = await this.executeQuery({ sql, params: [] });
|
|
301
|
+
if (!result || !Array.isArray(result)) return false;
|
|
302
|
+
return result.some((col) => col.name === column || col.name === column.toLowerCase());
|
|
303
|
+
}
|
|
304
|
+
getTableName(tableName) {
|
|
305
|
+
return `${this.tablePrefix}${tableName}`;
|
|
306
|
+
}
|
|
307
|
+
formatSqlParams(params) {
|
|
308
|
+
return params.map((p) => p === void 0 || p === null ? null : p);
|
|
309
|
+
}
|
|
310
|
+
async executeWorkersBindingQuery({
|
|
311
|
+
sql,
|
|
312
|
+
params = [],
|
|
313
|
+
first = false
|
|
314
|
+
}) {
|
|
315
|
+
if (!this.binding) {
|
|
316
|
+
throw new Error("Workers binding is not configured");
|
|
317
|
+
}
|
|
311
318
|
try {
|
|
312
|
-
|
|
313
|
-
|
|
319
|
+
const statement = this.binding.prepare(sql);
|
|
320
|
+
const formattedParams = this.formatSqlParams(params);
|
|
321
|
+
let result;
|
|
322
|
+
if (formattedParams.length > 0) {
|
|
323
|
+
if (first) {
|
|
324
|
+
result = await statement.bind(...formattedParams).first();
|
|
325
|
+
if (!result) return null;
|
|
326
|
+
return result;
|
|
327
|
+
} else {
|
|
328
|
+
result = await statement.bind(...formattedParams).all();
|
|
329
|
+
const results = result.results || [];
|
|
330
|
+
return results;
|
|
331
|
+
}
|
|
332
|
+
} else {
|
|
333
|
+
if (first) {
|
|
334
|
+
result = await statement.first();
|
|
335
|
+
if (!result) return null;
|
|
336
|
+
return result;
|
|
337
|
+
} else {
|
|
338
|
+
result = await statement.all();
|
|
339
|
+
const results = result.results || [];
|
|
340
|
+
return results;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
314
343
|
} catch (error) {
|
|
315
344
|
throw new MastraError(
|
|
316
345
|
{
|
|
317
|
-
id: createStorageErrorId("CLOUDFLARE_D1", "
|
|
346
|
+
id: createStorageErrorId("CLOUDFLARE_D1", "WORKERS_BINDING_QUERY", "FAILED"),
|
|
318
347
|
domain: ErrorDomain.STORAGE,
|
|
319
348
|
category: ErrorCategory.THIRD_PARTY,
|
|
320
|
-
|
|
321
|
-
details: { resourceId: resource.id }
|
|
349
|
+
details: { sql }
|
|
322
350
|
},
|
|
323
351
|
error
|
|
324
352
|
);
|
|
325
353
|
}
|
|
326
354
|
}
|
|
327
|
-
async
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
355
|
+
async executeRestQuery({
|
|
356
|
+
sql,
|
|
357
|
+
params = [],
|
|
358
|
+
first = false
|
|
331
359
|
}) {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
const newResource = {
|
|
335
|
-
id: resourceId,
|
|
336
|
-
workingMemory,
|
|
337
|
-
metadata: metadata || {},
|
|
338
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
339
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
340
|
-
};
|
|
341
|
-
return this.saveResource({ resource: newResource });
|
|
360
|
+
if (!this.client) {
|
|
361
|
+
throw new Error("D1 client is not configured");
|
|
342
362
|
}
|
|
343
|
-
const updatedAt = /* @__PURE__ */ new Date();
|
|
344
|
-
const updatedResource = {
|
|
345
|
-
...existingResource,
|
|
346
|
-
workingMemory: workingMemory !== void 0 ? workingMemory : existingResource.workingMemory,
|
|
347
|
-
metadata: {
|
|
348
|
-
...existingResource.metadata,
|
|
349
|
-
...metadata
|
|
350
|
-
},
|
|
351
|
-
updatedAt
|
|
352
|
-
};
|
|
353
|
-
const fullTableName = this.operations.getTableName(TABLE_RESOURCES);
|
|
354
|
-
const columns = ["workingMemory", "metadata", "updatedAt"];
|
|
355
|
-
const values = [updatedResource.workingMemory, JSON.stringify(updatedResource.metadata), updatedAt.toISOString()];
|
|
356
|
-
const query = createSqlBuilder().update(fullTableName, columns, values).where("id = ?", resourceId);
|
|
357
|
-
const { sql, params } = query.build();
|
|
358
363
|
try {
|
|
359
|
-
|
|
360
|
-
|
|
364
|
+
const formattedParams = this.formatSqlParams(params);
|
|
365
|
+
const response = await this.client.query({
|
|
366
|
+
sql,
|
|
367
|
+
params: formattedParams
|
|
368
|
+
});
|
|
369
|
+
const result = response.result || [];
|
|
370
|
+
const results = result.flatMap((r) => r.results || []);
|
|
371
|
+
if (first) {
|
|
372
|
+
return results[0] || null;
|
|
373
|
+
}
|
|
374
|
+
return results;
|
|
361
375
|
} catch (error) {
|
|
362
376
|
throw new MastraError(
|
|
363
377
|
{
|
|
364
|
-
id: createStorageErrorId("CLOUDFLARE_D1", "
|
|
378
|
+
id: createStorageErrorId("CLOUDFLARE_D1", "REST_QUERY", "FAILED"),
|
|
365
379
|
domain: ErrorDomain.STORAGE,
|
|
366
380
|
category: ErrorCategory.THIRD_PARTY,
|
|
367
|
-
|
|
368
|
-
details: { resourceId }
|
|
381
|
+
details: { sql }
|
|
369
382
|
},
|
|
370
383
|
error
|
|
371
384
|
);
|
|
372
385
|
}
|
|
373
386
|
}
|
|
374
|
-
async
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
387
|
+
async executeQuery(options) {
|
|
388
|
+
if (this.binding) {
|
|
389
|
+
return this.executeWorkersBindingQuery(options);
|
|
390
|
+
} else if (this.client) {
|
|
391
|
+
return this.executeRestQuery(options);
|
|
392
|
+
} else {
|
|
393
|
+
throw new Error("Neither binding nor client is configured");
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
async getTableColumns(tableName) {
|
|
380
397
|
try {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
398
|
+
const sql = `PRAGMA table_info(${tableName})`;
|
|
399
|
+
const result = await this.executeQuery({ sql });
|
|
400
|
+
if (!result || !Array.isArray(result)) {
|
|
401
|
+
return [];
|
|
402
|
+
}
|
|
403
|
+
return result.map((row) => ({
|
|
404
|
+
name: row.name,
|
|
405
|
+
type: row.type
|
|
406
|
+
}));
|
|
387
407
|
} catch (error) {
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
id: createStorageErrorId("CLOUDFLARE_D1", "GET_THREAD_BY_ID", "FAILED"),
|
|
391
|
-
domain: ErrorDomain.STORAGE,
|
|
392
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
393
|
-
text: `Error processing thread ${threadId}: ${error instanceof Error ? error.message : String(error)}`,
|
|
394
|
-
details: { threadId }
|
|
395
|
-
},
|
|
396
|
-
error
|
|
397
|
-
);
|
|
398
|
-
this.logger?.error(mastraError.toString());
|
|
399
|
-
this.logger?.trackException(mastraError);
|
|
400
|
-
return null;
|
|
408
|
+
this.logger.warn(`Failed to get table columns for ${tableName}:`, error);
|
|
409
|
+
return [];
|
|
401
410
|
}
|
|
402
411
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
if (page < 0) {
|
|
407
|
-
throw new MastraError(
|
|
408
|
-
{
|
|
409
|
-
id: createStorageErrorId("CLOUDFLARE_D1", "LIST_THREADS_BY_RESOURCE_ID", "INVALID_PAGE"),
|
|
410
|
-
domain: ErrorDomain.STORAGE,
|
|
411
|
-
category: ErrorCategory.USER,
|
|
412
|
-
details: { page }
|
|
413
|
-
},
|
|
414
|
-
new Error("page must be >= 0")
|
|
415
|
-
);
|
|
412
|
+
serializeValue(value) {
|
|
413
|
+
if (value === null || value === void 0) {
|
|
414
|
+
return null;
|
|
416
415
|
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
416
|
+
if (value instanceof Date) {
|
|
417
|
+
return value.toISOString();
|
|
418
|
+
}
|
|
419
|
+
if (typeof value === "object") {
|
|
420
|
+
return JSON.stringify(value);
|
|
421
|
+
}
|
|
422
|
+
return value;
|
|
423
|
+
}
|
|
424
|
+
getSqlType(type) {
|
|
425
|
+
switch (type) {
|
|
426
|
+
case "bigint":
|
|
427
|
+
return "INTEGER";
|
|
428
|
+
// SQLite uses INTEGER for all integer sizes
|
|
429
|
+
case "jsonb":
|
|
430
|
+
return "TEXT";
|
|
431
|
+
// Store JSON as TEXT in SQLite
|
|
432
|
+
case "boolean":
|
|
433
|
+
return "INTEGER";
|
|
434
|
+
// SQLite uses 0/1 for booleans
|
|
435
|
+
default:
|
|
436
|
+
return getSqlType(type);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
getDefaultValue(type) {
|
|
440
|
+
return getDefaultValue(type);
|
|
441
|
+
}
|
|
442
|
+
async createTable({
|
|
443
|
+
tableName,
|
|
444
|
+
schema
|
|
445
|
+
}) {
|
|
426
446
|
try {
|
|
427
|
-
const
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
};
|
|
447
|
+
const fullTableName = this.getTableName(tableName);
|
|
448
|
+
const columnDefinitions = Object.entries(schema).map(([colName, colDef]) => {
|
|
449
|
+
const type = this.getSqlType(colDef.type);
|
|
450
|
+
const nullable = colDef.nullable === false ? "NOT NULL" : "";
|
|
451
|
+
const primaryKey = colDef.primaryKey ? "PRIMARY KEY" : "";
|
|
452
|
+
return `${colName} ${type} ${nullable} ${primaryKey}`.trim();
|
|
453
|
+
});
|
|
454
|
+
const tableConstraints = [];
|
|
455
|
+
if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
|
|
456
|
+
tableConstraints.push("UNIQUE (workflow_name, run_id)");
|
|
457
|
+
}
|
|
458
|
+
const query = createSqlBuilder().createTable(fullTableName, columnDefinitions, tableConstraints);
|
|
459
|
+
const { sql, params } = query.build();
|
|
460
|
+
await this.executeQuery({ sql, params });
|
|
461
|
+
this.logger.debug(`Created table ${fullTableName}`);
|
|
441
462
|
} catch (error) {
|
|
442
|
-
|
|
463
|
+
throw new MastraError(
|
|
443
464
|
{
|
|
444
|
-
id: createStorageErrorId("CLOUDFLARE_D1", "
|
|
465
|
+
id: createStorageErrorId("CLOUDFLARE_D1", "CREATE_TABLE", "FAILED"),
|
|
445
466
|
domain: ErrorDomain.STORAGE,
|
|
446
467
|
category: ErrorCategory.THIRD_PARTY,
|
|
447
|
-
|
|
448
|
-
details: { resourceId }
|
|
468
|
+
details: { tableName }
|
|
449
469
|
},
|
|
450
470
|
error
|
|
451
471
|
);
|
|
452
|
-
this.logger?.error(mastraError.toString());
|
|
453
|
-
this.logger?.trackException(mastraError);
|
|
454
|
-
return {
|
|
455
|
-
threads: [],
|
|
456
|
-
total: 0,
|
|
457
|
-
page,
|
|
458
|
-
perPage: perPageForResponse,
|
|
459
|
-
hasMore: false
|
|
460
|
-
};
|
|
461
472
|
}
|
|
462
473
|
}
|
|
463
|
-
async
|
|
464
|
-
const fullTableName = this.operations.getTableName(TABLE_THREADS);
|
|
465
|
-
const threadToSave = {
|
|
466
|
-
id: thread.id,
|
|
467
|
-
resourceId: thread.resourceId,
|
|
468
|
-
title: thread.title,
|
|
469
|
-
metadata: thread.metadata ? JSON.stringify(thread.metadata) : null,
|
|
470
|
-
createdAt: thread.createdAt.toISOString(),
|
|
471
|
-
updatedAt: thread.updatedAt.toISOString()
|
|
472
|
-
};
|
|
473
|
-
const processedRecord = await this.operations.processRecord(threadToSave);
|
|
474
|
-
const columns = Object.keys(processedRecord);
|
|
475
|
-
const values = Object.values(processedRecord);
|
|
476
|
-
const updateMap = {
|
|
477
|
-
resourceId: "excluded.resourceId",
|
|
478
|
-
title: "excluded.title",
|
|
479
|
-
metadata: "excluded.metadata",
|
|
480
|
-
createdAt: "excluded.createdAt",
|
|
481
|
-
updatedAt: "excluded.updatedAt"
|
|
482
|
-
};
|
|
483
|
-
const query = createSqlBuilder().insert(fullTableName, columns, values, ["id"], updateMap);
|
|
484
|
-
const { sql, params } = query.build();
|
|
474
|
+
async clearTable({ tableName }) {
|
|
485
475
|
try {
|
|
486
|
-
|
|
487
|
-
|
|
476
|
+
const fullTableName = this.getTableName(tableName);
|
|
477
|
+
const query = createSqlBuilder().delete(fullTableName);
|
|
478
|
+
const { sql, params } = query.build();
|
|
479
|
+
await this.executeQuery({ sql, params });
|
|
480
|
+
this.logger.debug(`Cleared table ${fullTableName}`);
|
|
488
481
|
} catch (error) {
|
|
489
482
|
throw new MastraError(
|
|
490
483
|
{
|
|
491
|
-
id: createStorageErrorId("CLOUDFLARE_D1", "
|
|
484
|
+
id: createStorageErrorId("CLOUDFLARE_D1", "CLEAR_TABLE", "FAILED"),
|
|
492
485
|
domain: ErrorDomain.STORAGE,
|
|
493
486
|
category: ErrorCategory.THIRD_PARTY,
|
|
494
|
-
|
|
495
|
-
details: { threadId: thread.id }
|
|
487
|
+
details: { tableName }
|
|
496
488
|
},
|
|
497
489
|
error
|
|
498
490
|
);
|
|
499
491
|
}
|
|
500
492
|
}
|
|
501
|
-
async
|
|
502
|
-
id,
|
|
503
|
-
title,
|
|
504
|
-
metadata
|
|
505
|
-
}) {
|
|
506
|
-
const thread = await this.getThreadById({ threadId: id });
|
|
493
|
+
async dropTable({ tableName }) {
|
|
507
494
|
try {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
const query = createSqlBuilder().update(fullTableName, columns, values).where("id = ?", id);
|
|
520
|
-
const { sql, params } = query.build();
|
|
521
|
-
await this.operations.executeQuery({ sql, params });
|
|
522
|
-
return {
|
|
523
|
-
...thread,
|
|
524
|
-
title,
|
|
525
|
-
metadata: {
|
|
526
|
-
...typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
|
|
527
|
-
...metadata
|
|
495
|
+
const fullTableName = this.getTableName(tableName);
|
|
496
|
+
const sql = `DROP TABLE IF EXISTS ${fullTableName}`;
|
|
497
|
+
await this.executeQuery({ sql });
|
|
498
|
+
this.logger.debug(`Dropped table ${fullTableName}`);
|
|
499
|
+
} catch (error) {
|
|
500
|
+
throw new MastraError(
|
|
501
|
+
{
|
|
502
|
+
id: createStorageErrorId("CLOUDFLARE_D1", "DROP_TABLE", "FAILED"),
|
|
503
|
+
domain: ErrorDomain.STORAGE,
|
|
504
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
505
|
+
details: { tableName }
|
|
528
506
|
},
|
|
529
|
-
|
|
530
|
-
|
|
507
|
+
error
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
async alterTable(args) {
|
|
512
|
+
try {
|
|
513
|
+
const fullTableName = this.getTableName(args.tableName);
|
|
514
|
+
const existingColumns = await this.getTableColumns(fullTableName);
|
|
515
|
+
const existingColumnNames = new Set(existingColumns.map((col) => col.name));
|
|
516
|
+
for (const [columnName, column] of Object.entries(args.schema)) {
|
|
517
|
+
if (!existingColumnNames.has(columnName) && args.ifNotExists.includes(columnName)) {
|
|
518
|
+
const sqlType = this.getSqlType(column.type);
|
|
519
|
+
const defaultValue = this.getDefaultValue(column.type);
|
|
520
|
+
const sql = `ALTER TABLE ${fullTableName} ADD COLUMN ${columnName} ${sqlType} ${defaultValue}`;
|
|
521
|
+
await this.executeQuery({ sql });
|
|
522
|
+
this.logger.debug(`Added column ${columnName} to table ${fullTableName}`);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
531
525
|
} catch (error) {
|
|
532
526
|
throw new MastraError(
|
|
533
527
|
{
|
|
534
|
-
id: createStorageErrorId("CLOUDFLARE_D1", "
|
|
528
|
+
id: createStorageErrorId("CLOUDFLARE_D1", "ALTER_TABLE", "FAILED"),
|
|
535
529
|
domain: ErrorDomain.STORAGE,
|
|
536
530
|
category: ErrorCategory.THIRD_PARTY,
|
|
537
|
-
|
|
538
|
-
details: { threadId: id }
|
|
531
|
+
details: { tableName: args.tableName }
|
|
539
532
|
},
|
|
540
533
|
error
|
|
541
534
|
);
|
|
542
535
|
}
|
|
543
536
|
}
|
|
544
|
-
async
|
|
545
|
-
const fullTableName = this.operations.getTableName(TABLE_THREADS);
|
|
537
|
+
async insert({ tableName, record }) {
|
|
546
538
|
try {
|
|
547
|
-
const
|
|
548
|
-
const
|
|
549
|
-
|
|
550
|
-
const
|
|
551
|
-
const
|
|
552
|
-
const { sql
|
|
553
|
-
await this.
|
|
539
|
+
const fullTableName = this.getTableName(tableName);
|
|
540
|
+
const processedRecord = await this.processRecord(record);
|
|
541
|
+
const columns = Object.keys(processedRecord);
|
|
542
|
+
const values = Object.values(processedRecord);
|
|
543
|
+
const query = createSqlBuilder().insert(fullTableName, columns, values);
|
|
544
|
+
const { sql, params } = query.build();
|
|
545
|
+
await this.executeQuery({ sql, params });
|
|
554
546
|
} catch (error) {
|
|
555
547
|
throw new MastraError(
|
|
556
548
|
{
|
|
557
|
-
id: createStorageErrorId("CLOUDFLARE_D1", "
|
|
549
|
+
id: createStorageErrorId("CLOUDFLARE_D1", "INSERT", "FAILED"),
|
|
558
550
|
domain: ErrorDomain.STORAGE,
|
|
559
551
|
category: ErrorCategory.THIRD_PARTY,
|
|
560
|
-
|
|
561
|
-
details: { threadId }
|
|
552
|
+
details: { tableName }
|
|
562
553
|
},
|
|
563
554
|
error
|
|
564
555
|
);
|
|
565
556
|
}
|
|
566
557
|
}
|
|
567
|
-
async
|
|
568
|
-
const { messages } = args;
|
|
569
|
-
if (messages.length === 0) return { messages: [] };
|
|
558
|
+
async batchInsert({ tableName, records }) {
|
|
570
559
|
try {
|
|
571
|
-
|
|
572
|
-
const
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
}
|
|
581
|
-
if (!message.role) {
|
|
582
|
-
throw new Error(`Message at index ${i} missing role`);
|
|
583
|
-
}
|
|
584
|
-
if (!message.resourceId) {
|
|
585
|
-
throw new Error(`Message at index ${i} missing resourceId`);
|
|
586
|
-
}
|
|
587
|
-
const thread = await this.getThreadById({ threadId: message.threadId });
|
|
588
|
-
if (!thread) {
|
|
589
|
-
throw new Error(`Thread ${message.threadId} not found`);
|
|
590
|
-
}
|
|
560
|
+
if (records.length === 0) return;
|
|
561
|
+
const fullTableName = this.getTableName(tableName);
|
|
562
|
+
const processedRecords = await Promise.all(records.map((record) => this.processRecord(record)));
|
|
563
|
+
const columns = Object.keys(processedRecords[0] || {});
|
|
564
|
+
for (const record of processedRecords) {
|
|
565
|
+
const values = Object.values(record);
|
|
566
|
+
const query = createSqlBuilder().insert(fullTableName, columns, values);
|
|
567
|
+
const { sql, params } = query.build();
|
|
568
|
+
await this.executeQuery({ sql, params });
|
|
591
569
|
}
|
|
592
|
-
const messagesToInsert = messages.map((message) => {
|
|
593
|
-
const createdAt = message.createdAt ? new Date(message.createdAt) : now;
|
|
594
|
-
return {
|
|
595
|
-
id: message.id,
|
|
596
|
-
thread_id: message.threadId,
|
|
597
|
-
content: typeof message.content === "string" ? message.content : JSON.stringify(message.content),
|
|
598
|
-
createdAt: createdAt.toISOString(),
|
|
599
|
-
role: message.role,
|
|
600
|
-
type: message.type || "v2",
|
|
601
|
-
resourceId: message.resourceId
|
|
602
|
-
};
|
|
603
|
-
});
|
|
604
|
-
await Promise.all([
|
|
605
|
-
this.operations.batchUpsert({
|
|
606
|
-
tableName: TABLE_MESSAGES,
|
|
607
|
-
records: messagesToInsert
|
|
608
|
-
}),
|
|
609
|
-
// Update thread's updatedAt timestamp
|
|
610
|
-
this.operations.executeQuery({
|
|
611
|
-
sql: `UPDATE ${this.operations.getTableName(TABLE_THREADS)} SET updatedAt = ? WHERE id = ?`,
|
|
612
|
-
params: [now.toISOString(), threadId]
|
|
613
|
-
})
|
|
614
|
-
]);
|
|
615
|
-
this.logger.debug(`Saved ${messages.length} messages`);
|
|
616
|
-
const list = new MessageList().add(messages, "memory");
|
|
617
|
-
return { messages: list.get.all.db() };
|
|
618
570
|
} catch (error) {
|
|
619
571
|
throw new MastraError(
|
|
620
572
|
{
|
|
621
|
-
id: createStorageErrorId("CLOUDFLARE_D1", "
|
|
573
|
+
id: createStorageErrorId("CLOUDFLARE_D1", "BATCH_INSERT", "FAILED"),
|
|
622
574
|
domain: ErrorDomain.STORAGE,
|
|
623
575
|
category: ErrorCategory.THIRD_PARTY,
|
|
624
|
-
|
|
576
|
+
details: { tableName }
|
|
625
577
|
},
|
|
626
578
|
error
|
|
627
579
|
);
|
|
628
580
|
}
|
|
629
581
|
}
|
|
630
|
-
async
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
OR
|
|
666
|
-
(m.row_num >= target.row_num - ? AND m.row_num < target.row_num)
|
|
667
|
-
)
|
|
668
|
-
)
|
|
669
|
-
) AS query_${paramIdx}
|
|
670
|
-
`);
|
|
671
|
-
params.push(id, id, id, withNextMessages, withPreviousMessages);
|
|
672
|
-
paramIdx++;
|
|
582
|
+
async load({ tableName, keys }) {
|
|
583
|
+
try {
|
|
584
|
+
const fullTableName = this.getTableName(tableName);
|
|
585
|
+
const query = createSqlBuilder().select("*").from(fullTableName);
|
|
586
|
+
let firstKey = true;
|
|
587
|
+
for (const [key, value] of Object.entries(keys)) {
|
|
588
|
+
if (firstKey) {
|
|
589
|
+
query.where(`${key} = ?`, value);
|
|
590
|
+
firstKey = false;
|
|
591
|
+
} else {
|
|
592
|
+
query.andWhere(`${key} = ?`, value);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
query.orderBy("createdAt", "DESC");
|
|
596
|
+
query.limit(1);
|
|
597
|
+
const { sql, params } = query.build();
|
|
598
|
+
const result = await this.executeQuery({ sql, params, first: true });
|
|
599
|
+
if (!result) {
|
|
600
|
+
return null;
|
|
601
|
+
}
|
|
602
|
+
const deserializedResult = {};
|
|
603
|
+
for (const [key, value] of Object.entries(result)) {
|
|
604
|
+
deserializedResult[key] = deserializeValue(value);
|
|
605
|
+
}
|
|
606
|
+
return deserializedResult;
|
|
607
|
+
} catch (error) {
|
|
608
|
+
throw new MastraError(
|
|
609
|
+
{
|
|
610
|
+
id: createStorageErrorId("CLOUDFLARE_D1", "LOAD", "FAILED"),
|
|
611
|
+
domain: ErrorDomain.STORAGE,
|
|
612
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
613
|
+
details: { tableName }
|
|
614
|
+
},
|
|
615
|
+
error
|
|
616
|
+
);
|
|
673
617
|
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
618
|
+
}
|
|
619
|
+
async processRecord(record) {
|
|
620
|
+
const processed = {};
|
|
621
|
+
for (const [key, value] of Object.entries(record)) {
|
|
622
|
+
processed[key] = this.serializeValue(value);
|
|
678
623
|
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
624
|
+
return processed;
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Upsert multiple records in a batch operation
|
|
628
|
+
* @param tableName The table to insert into
|
|
629
|
+
* @param records The records to insert
|
|
630
|
+
*/
|
|
631
|
+
async batchUpsert({ tableName, records }) {
|
|
632
|
+
if (records.length === 0) return;
|
|
633
|
+
const fullTableName = this.getTableName(tableName);
|
|
634
|
+
try {
|
|
635
|
+
const batchSize = 50;
|
|
636
|
+
for (let i = 0; i < records.length; i += batchSize) {
|
|
637
|
+
const batch = records.slice(i, i + batchSize);
|
|
638
|
+
const recordsToInsert = batch;
|
|
639
|
+
if (recordsToInsert.length > 0) {
|
|
640
|
+
const firstRecord = recordsToInsert[0];
|
|
641
|
+
const columns = Object.keys(firstRecord || {});
|
|
642
|
+
for (const record of recordsToInsert) {
|
|
643
|
+
const values = columns.map((col) => {
|
|
644
|
+
if (!record) return null;
|
|
645
|
+
const value = typeof col === "string" ? record[col] : null;
|
|
646
|
+
return this.serializeValue(value);
|
|
647
|
+
});
|
|
648
|
+
const recordToUpsert = columns.reduce(
|
|
649
|
+
(acc, col) => {
|
|
650
|
+
if (col !== "createdAt") acc[col] = `excluded.${col}`;
|
|
651
|
+
return acc;
|
|
652
|
+
},
|
|
653
|
+
{}
|
|
654
|
+
);
|
|
655
|
+
const query = createSqlBuilder().insert(fullTableName, columns, values, ["id"], recordToUpsert);
|
|
656
|
+
const { sql, params } = query.build();
|
|
657
|
+
await this.executeQuery({ sql, params });
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
this.logger.debug(
|
|
661
|
+
`Processed batch ${Math.floor(i / batchSize) + 1} of ${Math.ceil(records.length / batchSize)}`
|
|
662
|
+
);
|
|
684
663
|
}
|
|
685
|
-
|
|
664
|
+
this.logger.debug(`Successfully batch upserted ${records.length} records into ${tableName}`);
|
|
665
|
+
} catch (error) {
|
|
666
|
+
throw new MastraError(
|
|
667
|
+
{
|
|
668
|
+
id: createStorageErrorId("CLOUDFLARE_D1", "BATCH_UPSERT", "FAILED"),
|
|
669
|
+
domain: ErrorDomain.STORAGE,
|
|
670
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
671
|
+
text: `Failed to batch upsert into ${tableName}: ${error instanceof Error ? error.message : String(error)}`,
|
|
672
|
+
details: { tableName }
|
|
673
|
+
},
|
|
674
|
+
error
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
// src/storage/domains/memory/index.ts
|
|
681
|
+
var MemoryStorageD1 = class extends MemoryStorage {
|
|
682
|
+
#db;
|
|
683
|
+
constructor(config) {
|
|
684
|
+
super();
|
|
685
|
+
this.#db = new D1DB(resolveD1Config(config));
|
|
686
|
+
}
|
|
687
|
+
async init() {
|
|
688
|
+
await this.#db.createTable({ tableName: TABLE_THREADS, schema: TABLE_SCHEMAS[TABLE_THREADS] });
|
|
689
|
+
await this.#db.createTable({ tableName: TABLE_MESSAGES, schema: TABLE_SCHEMAS[TABLE_MESSAGES] });
|
|
690
|
+
await this.#db.createTable({ tableName: TABLE_RESOURCES, schema: TABLE_SCHEMAS[TABLE_RESOURCES] });
|
|
691
|
+
await this.#db.alterTable({
|
|
692
|
+
tableName: TABLE_MESSAGES,
|
|
693
|
+
schema: TABLE_SCHEMAS[TABLE_MESSAGES],
|
|
694
|
+
ifNotExists: ["resourceId"]
|
|
686
695
|
});
|
|
687
|
-
return processedMessages;
|
|
688
696
|
}
|
|
689
|
-
async
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
697
|
+
async dangerouslyClearAll() {
|
|
698
|
+
await this.#db.clearTable({ tableName: TABLE_MESSAGES });
|
|
699
|
+
await this.#db.clearTable({ tableName: TABLE_THREADS });
|
|
700
|
+
await this.#db.clearTable({ tableName: TABLE_RESOURCES });
|
|
701
|
+
}
|
|
702
|
+
async getResourceById({ resourceId }) {
|
|
703
|
+
const resource = await this.#db.load({
|
|
704
|
+
tableName: TABLE_RESOURCES,
|
|
705
|
+
keys: { id: resourceId }
|
|
706
|
+
});
|
|
707
|
+
if (!resource) return null;
|
|
693
708
|
try {
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
const processedMsg = {};
|
|
701
|
-
for (const [key, value] of Object.entries(message)) {
|
|
702
|
-
if (key === `type` && value === `v2`) continue;
|
|
703
|
-
processedMsg[key] = deserializeValue(value);
|
|
704
|
-
}
|
|
705
|
-
return processedMsg;
|
|
706
|
-
});
|
|
707
|
-
this.logger.debug(`Retrieved ${messages.length} messages`);
|
|
708
|
-
const list = new MessageList().add(processedMessages, "memory");
|
|
709
|
-
return { messages: list.get.all.db() };
|
|
709
|
+
return {
|
|
710
|
+
...resource,
|
|
711
|
+
createdAt: ensureDate(resource.createdAt),
|
|
712
|
+
updatedAt: ensureDate(resource.updatedAt),
|
|
713
|
+
metadata: typeof resource.metadata === "string" ? JSON.parse(resource.metadata || "{}") : resource.metadata
|
|
714
|
+
};
|
|
710
715
|
} catch (error) {
|
|
711
716
|
const mastraError = new MastraError(
|
|
712
717
|
{
|
|
713
|
-
id: createStorageErrorId("CLOUDFLARE_D1", "
|
|
718
|
+
id: createStorageErrorId("CLOUDFLARE_D1", "GET_RESOURCE_BY_ID", "FAILED"),
|
|
714
719
|
domain: ErrorDomain.STORAGE,
|
|
715
720
|
category: ErrorCategory.THIRD_PARTY,
|
|
716
|
-
text: `
|
|
717
|
-
details: {
|
|
721
|
+
text: `Error processing resource ${resourceId}: ${error instanceof Error ? error.message : String(error)}`,
|
|
722
|
+
details: { resourceId }
|
|
718
723
|
},
|
|
719
724
|
error
|
|
720
725
|
);
|
|
721
726
|
this.logger?.error(mastraError.toString());
|
|
722
727
|
this.logger?.trackException(mastraError);
|
|
723
|
-
|
|
728
|
+
return null;
|
|
724
729
|
}
|
|
725
730
|
}
|
|
726
|
-
async
|
|
727
|
-
const
|
|
728
|
-
const
|
|
729
|
-
|
|
731
|
+
async saveResource({ resource }) {
|
|
732
|
+
const fullTableName = this.#db.getTableName(TABLE_RESOURCES);
|
|
733
|
+
const resourceToSave = {
|
|
734
|
+
id: resource.id,
|
|
735
|
+
workingMemory: resource.workingMemory,
|
|
736
|
+
metadata: resource.metadata ? JSON.stringify(resource.metadata) : null,
|
|
737
|
+
createdAt: resource.createdAt,
|
|
738
|
+
updatedAt: resource.updatedAt
|
|
739
|
+
};
|
|
740
|
+
const processedRecord = await this.#db.processRecord(resourceToSave);
|
|
741
|
+
const columns = Object.keys(processedRecord);
|
|
742
|
+
const values = Object.values(processedRecord);
|
|
743
|
+
const updateMap = {
|
|
744
|
+
workingMemory: "excluded.workingMemory",
|
|
745
|
+
metadata: "excluded.metadata",
|
|
746
|
+
createdAt: "excluded.createdAt",
|
|
747
|
+
updatedAt: "excluded.updatedAt"
|
|
748
|
+
};
|
|
749
|
+
const query = createSqlBuilder().insert(fullTableName, columns, values, ["id"], updateMap);
|
|
750
|
+
const { sql, params } = query.build();
|
|
751
|
+
try {
|
|
752
|
+
await this.#db.executeQuery({ sql, params });
|
|
753
|
+
return resource;
|
|
754
|
+
} catch (error) {
|
|
730
755
|
throw new MastraError(
|
|
731
756
|
{
|
|
732
|
-
id: createStorageErrorId("CLOUDFLARE_D1", "
|
|
757
|
+
id: createStorageErrorId("CLOUDFLARE_D1", "SAVE_RESOURCE", "FAILED"),
|
|
733
758
|
domain: ErrorDomain.STORAGE,
|
|
734
759
|
category: ErrorCategory.THIRD_PARTY,
|
|
735
|
-
|
|
760
|
+
text: `Failed to save resource to ${fullTableName}: ${error instanceof Error ? error.message : String(error)}`,
|
|
761
|
+
details: { resourceId: resource.id }
|
|
736
762
|
},
|
|
737
|
-
|
|
763
|
+
error
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
async updateResource({
|
|
768
|
+
resourceId,
|
|
769
|
+
workingMemory,
|
|
770
|
+
metadata
|
|
771
|
+
}) {
|
|
772
|
+
const existingResource = await this.getResourceById({ resourceId });
|
|
773
|
+
if (!existingResource) {
|
|
774
|
+
const newResource = {
|
|
775
|
+
id: resourceId,
|
|
776
|
+
workingMemory,
|
|
777
|
+
metadata: metadata || {},
|
|
778
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
779
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
780
|
+
};
|
|
781
|
+
return this.saveResource({ resource: newResource });
|
|
782
|
+
}
|
|
783
|
+
const updatedAt = /* @__PURE__ */ new Date();
|
|
784
|
+
const updatedResource = {
|
|
785
|
+
...existingResource,
|
|
786
|
+
workingMemory: workingMemory !== void 0 ? workingMemory : existingResource.workingMemory,
|
|
787
|
+
metadata: {
|
|
788
|
+
...existingResource.metadata,
|
|
789
|
+
...metadata
|
|
790
|
+
},
|
|
791
|
+
updatedAt
|
|
792
|
+
};
|
|
793
|
+
const fullTableName = this.#db.getTableName(TABLE_RESOURCES);
|
|
794
|
+
const columns = ["workingMemory", "metadata", "updatedAt"];
|
|
795
|
+
const values = [updatedResource.workingMemory, JSON.stringify(updatedResource.metadata), updatedAt.toISOString()];
|
|
796
|
+
const query = createSqlBuilder().update(fullTableName, columns, values).where("id = ?", resourceId);
|
|
797
|
+
const { sql, params } = query.build();
|
|
798
|
+
try {
|
|
799
|
+
await this.#db.executeQuery({ sql, params });
|
|
800
|
+
return updatedResource;
|
|
801
|
+
} catch (error) {
|
|
802
|
+
throw new MastraError(
|
|
803
|
+
{
|
|
804
|
+
id: createStorageErrorId("CLOUDFLARE_D1", "UPDATE_RESOURCE", "FAILED"),
|
|
805
|
+
domain: ErrorDomain.STORAGE,
|
|
806
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
807
|
+
text: `Failed to update resource ${resourceId}: ${error instanceof Error ? error.message : String(error)}`,
|
|
808
|
+
details: { resourceId }
|
|
809
|
+
},
|
|
810
|
+
error
|
|
738
811
|
);
|
|
739
812
|
}
|
|
813
|
+
}
|
|
814
|
+
async getThreadById({ threadId }) {
|
|
815
|
+
const thread = await this.#db.load({
|
|
816
|
+
tableName: TABLE_THREADS,
|
|
817
|
+
keys: { id: threadId }
|
|
818
|
+
});
|
|
819
|
+
if (!thread) return null;
|
|
820
|
+
try {
|
|
821
|
+
return {
|
|
822
|
+
...thread,
|
|
823
|
+
createdAt: ensureDate(thread.createdAt),
|
|
824
|
+
updatedAt: ensureDate(thread.updatedAt),
|
|
825
|
+
metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata || "{}") : thread.metadata || {}
|
|
826
|
+
};
|
|
827
|
+
} catch (error) {
|
|
828
|
+
const mastraError = new MastraError(
|
|
829
|
+
{
|
|
830
|
+
id: createStorageErrorId("CLOUDFLARE_D1", "GET_THREAD_BY_ID", "FAILED"),
|
|
831
|
+
domain: ErrorDomain.STORAGE,
|
|
832
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
833
|
+
text: `Error processing thread ${threadId}: ${error instanceof Error ? error.message : String(error)}`,
|
|
834
|
+
details: { threadId }
|
|
835
|
+
},
|
|
836
|
+
error
|
|
837
|
+
);
|
|
838
|
+
this.logger?.error(mastraError.toString());
|
|
839
|
+
this.logger?.trackException(mastraError);
|
|
840
|
+
return null;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
async listThreadsByResourceId(args) {
|
|
844
|
+
const { resourceId, page = 0, perPage: perPageInput, orderBy } = args;
|
|
845
|
+
const perPage = normalizePerPage(perPageInput, 100);
|
|
740
846
|
if (page < 0) {
|
|
741
847
|
throw new MastraError(
|
|
742
848
|
{
|
|
743
|
-
id: createStorageErrorId("CLOUDFLARE_D1", "
|
|
849
|
+
id: createStorageErrorId("CLOUDFLARE_D1", "LIST_THREADS_BY_RESOURCE_ID", "INVALID_PAGE"),
|
|
744
850
|
domain: ErrorDomain.STORAGE,
|
|
745
851
|
category: ErrorCategory.USER,
|
|
746
852
|
details: { page }
|
|
@@ -748,130 +854,45 @@ var MemoryStorageD1 = class extends MemoryStorage {
|
|
|
748
854
|
new Error("page must be >= 0")
|
|
749
855
|
);
|
|
750
856
|
}
|
|
751
|
-
const perPage = normalizePerPage(perPageInput, 40);
|
|
752
857
|
const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
|
|
858
|
+
const { field, direction } = this.parseOrderBy(orderBy);
|
|
859
|
+
const fullTableName = this.#db.getTableName(TABLE_THREADS);
|
|
860
|
+
const mapRowToStorageThreadType = (row) => ({
|
|
861
|
+
...row,
|
|
862
|
+
createdAt: ensureDate(row.createdAt),
|
|
863
|
+
updatedAt: ensureDate(row.updatedAt),
|
|
864
|
+
metadata: typeof row.metadata === "string" ? JSON.parse(row.metadata || "{}") : row.metadata || {}
|
|
865
|
+
});
|
|
753
866
|
try {
|
|
754
|
-
const
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
const
|
|
761
|
-
if (resourceId) {
|
|
762
|
-
query += ` AND resourceId = ?`;
|
|
763
|
-
queryParams.push(resourceId);
|
|
764
|
-
}
|
|
765
|
-
const dateRange = filter?.dateRange;
|
|
766
|
-
if (dateRange?.start) {
|
|
767
|
-
const startDate = dateRange.start instanceof Date ? serializeDate(dateRange.start) : serializeDate(new Date(dateRange.start));
|
|
768
|
-
query += ` AND createdAt >= ?`;
|
|
769
|
-
queryParams.push(startDate);
|
|
770
|
-
}
|
|
771
|
-
if (dateRange?.end) {
|
|
772
|
-
const endDate = dateRange.end instanceof Date ? serializeDate(dateRange.end) : serializeDate(new Date(dateRange.end));
|
|
773
|
-
query += ` AND createdAt <= ?`;
|
|
774
|
-
queryParams.push(endDate);
|
|
775
|
-
}
|
|
776
|
-
const { field, direction } = this.parseOrderBy(orderBy, "ASC");
|
|
777
|
-
query += ` ORDER BY "${field}" ${direction}`;
|
|
778
|
-
if (perPage !== Number.MAX_SAFE_INTEGER) {
|
|
779
|
-
query += ` LIMIT ? OFFSET ?`;
|
|
780
|
-
queryParams.push(perPage, offset);
|
|
781
|
-
}
|
|
782
|
-
const results = await this.operations.executeQuery({ sql: query, params: queryParams });
|
|
783
|
-
const paginatedMessages = (isArrayOfRecords(results) ? results : []).map((message) => {
|
|
784
|
-
const processedMsg = {};
|
|
785
|
-
for (const [key, value] of Object.entries(message)) {
|
|
786
|
-
if (key === `type` && value === `v2`) continue;
|
|
787
|
-
processedMsg[key] = deserializeValue(value);
|
|
788
|
-
}
|
|
789
|
-
return processedMsg;
|
|
790
|
-
});
|
|
791
|
-
const paginatedCount = paginatedMessages.length;
|
|
792
|
-
let countQuery = `SELECT count() as count FROM ${fullTableName} WHERE thread_id = ?`;
|
|
793
|
-
const countParams = [threadId];
|
|
794
|
-
if (resourceId) {
|
|
795
|
-
countQuery += ` AND resourceId = ?`;
|
|
796
|
-
countParams.push(resourceId);
|
|
797
|
-
}
|
|
798
|
-
if (dateRange?.start) {
|
|
799
|
-
const startDate = dateRange.start instanceof Date ? serializeDate(dateRange.start) : serializeDate(new Date(dateRange.start));
|
|
800
|
-
countQuery += ` AND createdAt >= ?`;
|
|
801
|
-
countParams.push(startDate);
|
|
802
|
-
}
|
|
803
|
-
if (dateRange?.end) {
|
|
804
|
-
const endDate = dateRange.end instanceof Date ? serializeDate(dateRange.end) : serializeDate(new Date(dateRange.end));
|
|
805
|
-
countQuery += ` AND createdAt <= ?`;
|
|
806
|
-
countParams.push(endDate);
|
|
807
|
-
}
|
|
808
|
-
const countResult = await this.operations.executeQuery({ sql: countQuery, params: countParams });
|
|
809
|
-
const total = Number(countResult[0]?.count ?? 0);
|
|
810
|
-
if (total === 0 && paginatedCount === 0 && (!include || include.length === 0)) {
|
|
811
|
-
return {
|
|
812
|
-
messages: [],
|
|
813
|
-
total: 0,
|
|
814
|
-
page,
|
|
815
|
-
perPage: perPageForResponse,
|
|
816
|
-
hasMore: false
|
|
817
|
-
};
|
|
818
|
-
}
|
|
819
|
-
const messageIds = new Set(paginatedMessages.map((m) => m.id));
|
|
820
|
-
let includeMessages = [];
|
|
821
|
-
if (include && include.length > 0) {
|
|
822
|
-
const includeResult = await this._getIncludedMessages(include);
|
|
823
|
-
if (Array.isArray(includeResult)) {
|
|
824
|
-
includeMessages = includeResult;
|
|
825
|
-
for (const includeMsg of includeMessages) {
|
|
826
|
-
if (!messageIds.has(includeMsg.id)) {
|
|
827
|
-
paginatedMessages.push(includeMsg);
|
|
828
|
-
messageIds.add(includeMsg.id);
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
const list = new MessageList().add(paginatedMessages, "memory");
|
|
834
|
-
let finalMessages = list.get.all.db();
|
|
835
|
-
finalMessages = finalMessages.sort((a, b) => {
|
|
836
|
-
const isDateField = field === "createdAt" || field === "updatedAt";
|
|
837
|
-
const aValue = isDateField ? new Date(a[field]).getTime() : a[field];
|
|
838
|
-
const bValue = isDateField ? new Date(b[field]).getTime() : b[field];
|
|
839
|
-
if (aValue === bValue) {
|
|
840
|
-
return a.id.localeCompare(b.id);
|
|
841
|
-
}
|
|
842
|
-
if (typeof aValue === "number" && typeof bValue === "number") {
|
|
843
|
-
return direction === "ASC" ? aValue - bValue : bValue - aValue;
|
|
844
|
-
}
|
|
845
|
-
return direction === "ASC" ? String(aValue).localeCompare(String(bValue)) : String(bValue).localeCompare(String(aValue));
|
|
846
|
-
});
|
|
847
|
-
const returnedThreadMessageIds = new Set(finalMessages.filter((m) => m.threadId === threadId).map((m) => m.id));
|
|
848
|
-
const allThreadMessagesReturned = returnedThreadMessageIds.size >= total;
|
|
849
|
-
const hasMore = perPageInput === false ? false : allThreadMessagesReturned ? false : offset + paginatedCount < total;
|
|
867
|
+
const countQuery = createSqlBuilder().count().from(fullTableName).where("resourceId = ?", resourceId);
|
|
868
|
+
const countResult = await this.#db.executeQuery(countQuery.build());
|
|
869
|
+
const total = Number(countResult?.[0]?.count ?? 0);
|
|
870
|
+
const limitValue = perPageInput === false ? total : perPage;
|
|
871
|
+
const selectQuery = createSqlBuilder().select("*").from(fullTableName).where("resourceId = ?", resourceId).orderBy(field, direction).limit(limitValue).offset(offset);
|
|
872
|
+
const results = await this.#db.executeQuery(selectQuery.build());
|
|
873
|
+
const threads = results.map(mapRowToStorageThreadType);
|
|
850
874
|
return {
|
|
851
|
-
|
|
875
|
+
threads,
|
|
852
876
|
total,
|
|
853
877
|
page,
|
|
854
878
|
perPage: perPageForResponse,
|
|
855
|
-
hasMore
|
|
879
|
+
hasMore: perPageInput === false ? false : offset + perPage < total
|
|
856
880
|
};
|
|
857
881
|
} catch (error) {
|
|
858
882
|
const mastraError = new MastraError(
|
|
859
883
|
{
|
|
860
|
-
id: createStorageErrorId("CLOUDFLARE_D1", "
|
|
884
|
+
id: createStorageErrorId("CLOUDFLARE_D1", "LIST_THREADS_BY_RESOURCE_ID", "FAILED"),
|
|
861
885
|
domain: ErrorDomain.STORAGE,
|
|
862
886
|
category: ErrorCategory.THIRD_PARTY,
|
|
863
|
-
text: `
|
|
864
|
-
details: {
|
|
865
|
-
threadId: Array.isArray(threadId) ? threadId.join(",") : threadId,
|
|
866
|
-
resourceId: resourceId ?? ""
|
|
867
|
-
}
|
|
887
|
+
text: `Error getting threads by resourceId ${resourceId}: ${error instanceof Error ? error.message : String(error)}`,
|
|
888
|
+
details: { resourceId }
|
|
868
889
|
},
|
|
869
890
|
error
|
|
870
891
|
);
|
|
871
|
-
this.logger?.error
|
|
872
|
-
this.logger?.trackException
|
|
892
|
+
this.logger?.error(mastraError.toString());
|
|
893
|
+
this.logger?.trackException(mastraError);
|
|
873
894
|
return {
|
|
874
|
-
|
|
895
|
+
threads: [],
|
|
875
896
|
total: 0,
|
|
876
897
|
page,
|
|
877
898
|
perPage: perPageForResponse,
|
|
@@ -879,487 +900,550 @@ var MemoryStorageD1 = class extends MemoryStorage {
|
|
|
879
900
|
};
|
|
880
901
|
}
|
|
881
902
|
}
|
|
882
|
-
async
|
|
883
|
-
const
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
}
|
|
905
|
-
return msg;
|
|
906
|
-
});
|
|
907
|
-
const threadIdsToUpdate = /* @__PURE__ */ new Set();
|
|
908
|
-
const updateQueries = [];
|
|
909
|
-
for (const existingMessage of parsedExistingMessages) {
|
|
910
|
-
const updatePayload = messages.find((m) => m.id === existingMessage.id);
|
|
911
|
-
if (!updatePayload) continue;
|
|
912
|
-
const { id, ...fieldsToUpdate } = updatePayload;
|
|
913
|
-
if (Object.keys(fieldsToUpdate).length === 0) continue;
|
|
914
|
-
threadIdsToUpdate.add(existingMessage.threadId);
|
|
915
|
-
if ("threadId" in updatePayload && updatePayload.threadId && updatePayload.threadId !== existingMessage.threadId) {
|
|
916
|
-
threadIdsToUpdate.add(updatePayload.threadId);
|
|
917
|
-
}
|
|
918
|
-
const setClauses = [];
|
|
919
|
-
const values = [];
|
|
920
|
-
const updatableFields = { ...fieldsToUpdate };
|
|
921
|
-
if (updatableFields.content) {
|
|
922
|
-
const existingContent = existingMessage.content || {};
|
|
923
|
-
const newContent = {
|
|
924
|
-
...existingContent,
|
|
925
|
-
...updatableFields.content,
|
|
926
|
-
// Deep merge metadata if it exists on both
|
|
927
|
-
...existingContent?.metadata && updatableFields.content.metadata ? {
|
|
928
|
-
metadata: {
|
|
929
|
-
...existingContent.metadata,
|
|
930
|
-
...updatableFields.content.metadata
|
|
931
|
-
}
|
|
932
|
-
} : {}
|
|
933
|
-
};
|
|
934
|
-
setClauses.push(`content = ?`);
|
|
935
|
-
values.push(JSON.stringify(newContent));
|
|
936
|
-
delete updatableFields.content;
|
|
937
|
-
}
|
|
938
|
-
for (const key in updatableFields) {
|
|
939
|
-
if (Object.prototype.hasOwnProperty.call(updatableFields, key)) {
|
|
940
|
-
const dbColumn = key === "threadId" ? "thread_id" : key;
|
|
941
|
-
setClauses.push(`${dbColumn} = ?`);
|
|
942
|
-
values.push(updatableFields[key]);
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
if (setClauses.length > 0) {
|
|
946
|
-
values.push(id);
|
|
947
|
-
const updateQuery = `UPDATE ${fullTableName} SET ${setClauses.join(", ")} WHERE id = ?`;
|
|
948
|
-
updateQueries.push({ sql: updateQuery, params: values });
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
for (const query of updateQueries) {
|
|
952
|
-
await this.operations.executeQuery(query);
|
|
953
|
-
}
|
|
954
|
-
if (threadIdsToUpdate.size > 0) {
|
|
955
|
-
const threadPlaceholders = Array.from(threadIdsToUpdate).map(() => "?").join(",");
|
|
956
|
-
const threadUpdateQuery = `UPDATE ${threadsTableName} SET updatedAt = ? WHERE id IN (${threadPlaceholders})`;
|
|
957
|
-
const threadUpdateParams = [(/* @__PURE__ */ new Date()).toISOString(), ...Array.from(threadIdsToUpdate)];
|
|
958
|
-
await this.operations.executeQuery({ sql: threadUpdateQuery, params: threadUpdateParams });
|
|
959
|
-
}
|
|
960
|
-
const updatedMessages = await this.operations.executeQuery({ sql: selectQuery, params: messageIds });
|
|
961
|
-
return updatedMessages.map((message) => {
|
|
962
|
-
if (typeof message.content === "string") {
|
|
963
|
-
try {
|
|
964
|
-
message.content = JSON.parse(message.content);
|
|
965
|
-
} catch {
|
|
966
|
-
}
|
|
967
|
-
}
|
|
968
|
-
return message;
|
|
969
|
-
});
|
|
970
|
-
} catch (error) {
|
|
971
|
-
throw new MastraError(
|
|
972
|
-
{
|
|
973
|
-
id: createStorageErrorId("CLOUDFLARE_D1", "UPDATE_MESSAGES", "FAILED"),
|
|
974
|
-
domain: ErrorDomain.STORAGE,
|
|
975
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
976
|
-
details: { count: messages.length }
|
|
977
|
-
},
|
|
978
|
-
error
|
|
979
|
-
);
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
};
|
|
983
|
-
var StoreOperationsD1 = class extends StoreOperations {
|
|
984
|
-
client;
|
|
985
|
-
binding;
|
|
986
|
-
tablePrefix;
|
|
987
|
-
constructor(config) {
|
|
988
|
-
super();
|
|
989
|
-
this.client = config.client;
|
|
990
|
-
this.binding = config.binding;
|
|
991
|
-
this.tablePrefix = config.tablePrefix || "";
|
|
992
|
-
}
|
|
993
|
-
async hasColumn(table, column) {
|
|
994
|
-
const fullTableName = table.startsWith(this.tablePrefix) ? table : `${this.tablePrefix}${table}`;
|
|
995
|
-
const sql = `PRAGMA table_info(${fullTableName});`;
|
|
996
|
-
const result = await this.executeQuery({ sql, params: [] });
|
|
997
|
-
if (!result || !Array.isArray(result)) return false;
|
|
998
|
-
return result.some((col) => col.name === column || col.name === column.toLowerCase());
|
|
999
|
-
}
|
|
1000
|
-
getTableName(tableName) {
|
|
1001
|
-
return `${this.tablePrefix}${tableName}`;
|
|
1002
|
-
}
|
|
1003
|
-
formatSqlParams(params) {
|
|
1004
|
-
return params.map((p) => p === void 0 || p === null ? null : p);
|
|
1005
|
-
}
|
|
1006
|
-
async executeWorkersBindingQuery({
|
|
1007
|
-
sql,
|
|
1008
|
-
params = [],
|
|
1009
|
-
first = false
|
|
1010
|
-
}) {
|
|
1011
|
-
if (!this.binding) {
|
|
1012
|
-
throw new Error("Workers binding is not configured");
|
|
1013
|
-
}
|
|
903
|
+
async saveThread({ thread }) {
|
|
904
|
+
const fullTableName = this.#db.getTableName(TABLE_THREADS);
|
|
905
|
+
const threadToSave = {
|
|
906
|
+
id: thread.id,
|
|
907
|
+
resourceId: thread.resourceId,
|
|
908
|
+
title: thread.title,
|
|
909
|
+
metadata: thread.metadata ? JSON.stringify(thread.metadata) : null,
|
|
910
|
+
createdAt: thread.createdAt.toISOString(),
|
|
911
|
+
updatedAt: thread.updatedAt.toISOString()
|
|
912
|
+
};
|
|
913
|
+
const processedRecord = await this.#db.processRecord(threadToSave);
|
|
914
|
+
const columns = Object.keys(processedRecord);
|
|
915
|
+
const values = Object.values(processedRecord);
|
|
916
|
+
const updateMap = {
|
|
917
|
+
resourceId: "excluded.resourceId",
|
|
918
|
+
title: "excluded.title",
|
|
919
|
+
metadata: "excluded.metadata",
|
|
920
|
+
createdAt: "excluded.createdAt",
|
|
921
|
+
updatedAt: "excluded.updatedAt"
|
|
922
|
+
};
|
|
923
|
+
const query = createSqlBuilder().insert(fullTableName, columns, values, ["id"], updateMap);
|
|
924
|
+
const { sql, params } = query.build();
|
|
1014
925
|
try {
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
let result;
|
|
1018
|
-
if (formattedParams.length > 0) {
|
|
1019
|
-
if (first) {
|
|
1020
|
-
result = await statement.bind(...formattedParams).first();
|
|
1021
|
-
if (!result) return null;
|
|
1022
|
-
return result;
|
|
1023
|
-
} else {
|
|
1024
|
-
result = await statement.bind(...formattedParams).all();
|
|
1025
|
-
const results = result.results || [];
|
|
1026
|
-
return results;
|
|
1027
|
-
}
|
|
1028
|
-
} else {
|
|
1029
|
-
if (first) {
|
|
1030
|
-
result = await statement.first();
|
|
1031
|
-
if (!result) return null;
|
|
1032
|
-
return result;
|
|
1033
|
-
} else {
|
|
1034
|
-
result = await statement.all();
|
|
1035
|
-
const results = result.results || [];
|
|
1036
|
-
return results;
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
926
|
+
await this.#db.executeQuery({ sql, params });
|
|
927
|
+
return thread;
|
|
1039
928
|
} catch (error) {
|
|
1040
929
|
throw new MastraError(
|
|
1041
930
|
{
|
|
1042
|
-
id: createStorageErrorId("CLOUDFLARE_D1", "
|
|
931
|
+
id: createStorageErrorId("CLOUDFLARE_D1", "SAVE_THREAD", "FAILED"),
|
|
1043
932
|
domain: ErrorDomain.STORAGE,
|
|
1044
933
|
category: ErrorCategory.THIRD_PARTY,
|
|
1045
|
-
|
|
934
|
+
text: `Failed to save thread to ${fullTableName}: ${error instanceof Error ? error.message : String(error)}`,
|
|
935
|
+
details: { threadId: thread.id }
|
|
1046
936
|
},
|
|
1047
937
|
error
|
|
1048
938
|
);
|
|
1049
939
|
}
|
|
1050
940
|
}
|
|
1051
|
-
async
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
941
|
+
async updateThread({
|
|
942
|
+
id,
|
|
943
|
+
title,
|
|
944
|
+
metadata
|
|
1055
945
|
}) {
|
|
1056
|
-
|
|
1057
|
-
throw new Error("D1 client is not configured");
|
|
1058
|
-
}
|
|
946
|
+
const thread = await this.getThreadById({ threadId: id });
|
|
1059
947
|
try {
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
sql,
|
|
1063
|
-
params: formattedParams
|
|
1064
|
-
});
|
|
1065
|
-
const result = response.result || [];
|
|
1066
|
-
const results = result.flatMap((r) => r.results || []);
|
|
1067
|
-
if (first) {
|
|
1068
|
-
return results[0] || null;
|
|
948
|
+
if (!thread) {
|
|
949
|
+
throw new Error(`Thread ${id} not found`);
|
|
1069
950
|
}
|
|
1070
|
-
|
|
951
|
+
const fullTableName = this.#db.getTableName(TABLE_THREADS);
|
|
952
|
+
const mergedMetadata = {
|
|
953
|
+
...typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
|
|
954
|
+
...metadata
|
|
955
|
+
};
|
|
956
|
+
const updatedAt = /* @__PURE__ */ new Date();
|
|
957
|
+
const columns = ["title", "metadata", "updatedAt"];
|
|
958
|
+
const values = [title, JSON.stringify(mergedMetadata), updatedAt.toISOString()];
|
|
959
|
+
const query = createSqlBuilder().update(fullTableName, columns, values).where("id = ?", id);
|
|
960
|
+
const { sql, params } = query.build();
|
|
961
|
+
await this.#db.executeQuery({ sql, params });
|
|
962
|
+
return {
|
|
963
|
+
...thread,
|
|
964
|
+
title,
|
|
965
|
+
metadata: {
|
|
966
|
+
...typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
|
|
967
|
+
...metadata
|
|
968
|
+
},
|
|
969
|
+
updatedAt
|
|
970
|
+
};
|
|
1071
971
|
} catch (error) {
|
|
1072
972
|
throw new MastraError(
|
|
1073
973
|
{
|
|
1074
|
-
id: createStorageErrorId("CLOUDFLARE_D1", "
|
|
974
|
+
id: createStorageErrorId("CLOUDFLARE_D1", "UPDATE_THREAD", "FAILED"),
|
|
1075
975
|
domain: ErrorDomain.STORAGE,
|
|
1076
976
|
category: ErrorCategory.THIRD_PARTY,
|
|
1077
|
-
|
|
977
|
+
text: `Failed to update thread ${id}: ${error instanceof Error ? error.message : String(error)}`,
|
|
978
|
+
details: { threadId: id }
|
|
1078
979
|
},
|
|
1079
980
|
error
|
|
1080
981
|
);
|
|
1081
982
|
}
|
|
1082
983
|
}
|
|
1083
|
-
async
|
|
1084
|
-
|
|
1085
|
-
return this.executeWorkersBindingQuery(options);
|
|
1086
|
-
} else if (this.client) {
|
|
1087
|
-
return this.executeRestQuery(options);
|
|
1088
|
-
} else {
|
|
1089
|
-
throw new Error("Neither binding nor client is configured");
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
async getTableColumns(tableName) {
|
|
984
|
+
async deleteThread({ threadId }) {
|
|
985
|
+
const fullTableName = this.#db.getTableName(TABLE_THREADS);
|
|
1093
986
|
try {
|
|
1094
|
-
const
|
|
1095
|
-
const
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
type: row.type
|
|
1102
|
-
}));
|
|
987
|
+
const deleteThreadQuery = createSqlBuilder().delete(fullTableName).where("id = ?", threadId);
|
|
988
|
+
const { sql: threadSql, params: threadParams } = deleteThreadQuery.build();
|
|
989
|
+
await this.#db.executeQuery({ sql: threadSql, params: threadParams });
|
|
990
|
+
const messagesTableName = this.#db.getTableName(TABLE_MESSAGES);
|
|
991
|
+
const deleteMessagesQuery = createSqlBuilder().delete(messagesTableName).where("thread_id = ?", threadId);
|
|
992
|
+
const { sql: messagesSql, params: messagesParams } = deleteMessagesQuery.build();
|
|
993
|
+
await this.#db.executeQuery({ sql: messagesSql, params: messagesParams });
|
|
1103
994
|
} catch (error) {
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
}
|
|
1115
|
-
if (typeof value === "object") {
|
|
1116
|
-
return JSON.stringify(value);
|
|
1117
|
-
}
|
|
1118
|
-
return value;
|
|
1119
|
-
}
|
|
1120
|
-
getSqlType(type) {
|
|
1121
|
-
switch (type) {
|
|
1122
|
-
case "bigint":
|
|
1123
|
-
return "INTEGER";
|
|
1124
|
-
// SQLite uses INTEGER for all integer sizes
|
|
1125
|
-
case "jsonb":
|
|
1126
|
-
return "TEXT";
|
|
1127
|
-
// Store JSON as TEXT in SQLite
|
|
1128
|
-
default:
|
|
1129
|
-
return super.getSqlType(type);
|
|
995
|
+
throw new MastraError(
|
|
996
|
+
{
|
|
997
|
+
id: createStorageErrorId("CLOUDFLARE_D1", "DELETE_THREAD", "FAILED"),
|
|
998
|
+
domain: ErrorDomain.STORAGE,
|
|
999
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
1000
|
+
text: `Failed to delete thread ${threadId}: ${error instanceof Error ? error.message : String(error)}`,
|
|
1001
|
+
details: { threadId }
|
|
1002
|
+
},
|
|
1003
|
+
error
|
|
1004
|
+
);
|
|
1130
1005
|
}
|
|
1131
1006
|
}
|
|
1132
|
-
async
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
}) {
|
|
1007
|
+
async saveMessages(args) {
|
|
1008
|
+
const { messages } = args;
|
|
1009
|
+
if (messages.length === 0) return { messages: [] };
|
|
1136
1010
|
try {
|
|
1137
|
-
const
|
|
1138
|
-
const
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1011
|
+
const now = /* @__PURE__ */ new Date();
|
|
1012
|
+
const threadId = messages[0]?.threadId;
|
|
1013
|
+
for (const [i, message] of messages.entries()) {
|
|
1014
|
+
if (!message.id) throw new Error(`Message at index ${i} missing id`);
|
|
1015
|
+
if (!message.threadId) {
|
|
1016
|
+
throw new Error(`Message at index ${i} missing threadId`);
|
|
1017
|
+
}
|
|
1018
|
+
if (!message.content) {
|
|
1019
|
+
throw new Error(`Message at index ${i} missing content`);
|
|
1020
|
+
}
|
|
1021
|
+
if (!message.role) {
|
|
1022
|
+
throw new Error(`Message at index ${i} missing role`);
|
|
1023
|
+
}
|
|
1024
|
+
if (!message.resourceId) {
|
|
1025
|
+
throw new Error(`Message at index ${i} missing resourceId`);
|
|
1026
|
+
}
|
|
1027
|
+
const thread = await this.getThreadById({ threadId: message.threadId });
|
|
1028
|
+
if (!thread) {
|
|
1029
|
+
throw new Error(`Thread ${message.threadId} not found`);
|
|
1030
|
+
}
|
|
1147
1031
|
}
|
|
1148
|
-
const
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1032
|
+
const messagesToInsert = messages.map((message) => {
|
|
1033
|
+
const createdAt = message.createdAt ? new Date(message.createdAt) : now;
|
|
1034
|
+
return {
|
|
1035
|
+
id: message.id,
|
|
1036
|
+
thread_id: message.threadId,
|
|
1037
|
+
content: typeof message.content === "string" ? message.content : JSON.stringify(message.content),
|
|
1038
|
+
createdAt: createdAt.toISOString(),
|
|
1039
|
+
role: message.role,
|
|
1040
|
+
type: message.type || "v2",
|
|
1041
|
+
resourceId: message.resourceId
|
|
1042
|
+
};
|
|
1043
|
+
});
|
|
1044
|
+
await Promise.all([
|
|
1045
|
+
this.#db.batchUpsert({
|
|
1046
|
+
tableName: TABLE_MESSAGES,
|
|
1047
|
+
records: messagesToInsert
|
|
1048
|
+
}),
|
|
1049
|
+
// Update thread's updatedAt timestamp
|
|
1050
|
+
this.#db.executeQuery({
|
|
1051
|
+
sql: `UPDATE ${this.#db.getTableName(TABLE_THREADS)} SET updatedAt = ? WHERE id = ?`,
|
|
1052
|
+
params: [now.toISOString(), threadId]
|
|
1053
|
+
})
|
|
1054
|
+
]);
|
|
1055
|
+
this.logger.debug(`Saved ${messages.length} messages`);
|
|
1056
|
+
const list = new MessageList().add(messages, "memory");
|
|
1057
|
+
return { messages: list.get.all.db() };
|
|
1152
1058
|
} catch (error) {
|
|
1153
1059
|
throw new MastraError(
|
|
1154
1060
|
{
|
|
1155
|
-
id: createStorageErrorId("CLOUDFLARE_D1", "
|
|
1061
|
+
id: createStorageErrorId("CLOUDFLARE_D1", "SAVE_MESSAGES", "FAILED"),
|
|
1156
1062
|
domain: ErrorDomain.STORAGE,
|
|
1157
1063
|
category: ErrorCategory.THIRD_PARTY,
|
|
1158
|
-
|
|
1064
|
+
text: `Failed to save messages: ${error instanceof Error ? error.message : String(error)}`
|
|
1159
1065
|
},
|
|
1160
1066
|
error
|
|
1161
1067
|
);
|
|
1162
1068
|
}
|
|
1163
1069
|
}
|
|
1164
|
-
async
|
|
1070
|
+
async _getIncludedMessages(include) {
|
|
1071
|
+
if (!include || include.length === 0) return null;
|
|
1072
|
+
const unionQueries = [];
|
|
1073
|
+
const params = [];
|
|
1074
|
+
let paramIdx = 1;
|
|
1075
|
+
const tableName = this.#db.getTableName(TABLE_MESSAGES);
|
|
1076
|
+
for (const inc of include) {
|
|
1077
|
+
const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
|
|
1078
|
+
unionQueries.push(`
|
|
1079
|
+
SELECT * FROM (
|
|
1080
|
+
WITH target_thread AS (
|
|
1081
|
+
SELECT thread_id FROM ${tableName} WHERE id = ?
|
|
1082
|
+
),
|
|
1083
|
+
ordered_messages AS (
|
|
1084
|
+
SELECT
|
|
1085
|
+
*,
|
|
1086
|
+
ROW_NUMBER() OVER (ORDER BY createdAt ASC) AS row_num
|
|
1087
|
+
FROM ${tableName}
|
|
1088
|
+
WHERE thread_id = (SELECT thread_id FROM target_thread)
|
|
1089
|
+
)
|
|
1090
|
+
SELECT
|
|
1091
|
+
m.id,
|
|
1092
|
+
m.content,
|
|
1093
|
+
m.role,
|
|
1094
|
+
m.type,
|
|
1095
|
+
m.createdAt,
|
|
1096
|
+
m.thread_id AS threadId,
|
|
1097
|
+
m.resourceId
|
|
1098
|
+
FROM ordered_messages m
|
|
1099
|
+
WHERE m.id = ?
|
|
1100
|
+
OR EXISTS (
|
|
1101
|
+
SELECT 1 FROM ordered_messages target
|
|
1102
|
+
WHERE target.id = ?
|
|
1103
|
+
AND (
|
|
1104
|
+
(m.row_num <= target.row_num + ? AND m.row_num > target.row_num)
|
|
1105
|
+
OR
|
|
1106
|
+
(m.row_num >= target.row_num - ? AND m.row_num < target.row_num)
|
|
1107
|
+
)
|
|
1108
|
+
)
|
|
1109
|
+
) AS query_${paramIdx}
|
|
1110
|
+
`);
|
|
1111
|
+
params.push(id, id, id, withNextMessages, withPreviousMessages);
|
|
1112
|
+
paramIdx++;
|
|
1113
|
+
}
|
|
1114
|
+
const finalQuery = unionQueries.join(" UNION ALL ") + " ORDER BY createdAt ASC";
|
|
1115
|
+
const messages = await this.#db.executeQuery({ sql: finalQuery, params });
|
|
1116
|
+
if (!Array.isArray(messages)) {
|
|
1117
|
+
return [];
|
|
1118
|
+
}
|
|
1119
|
+
const processedMessages = messages.map((message) => {
|
|
1120
|
+
const processedMsg = {};
|
|
1121
|
+
for (const [key, value] of Object.entries(message)) {
|
|
1122
|
+
if (key === `type` && value === `v2`) continue;
|
|
1123
|
+
processedMsg[key] = deserializeValue(value);
|
|
1124
|
+
}
|
|
1125
|
+
return processedMsg;
|
|
1126
|
+
});
|
|
1127
|
+
return processedMessages;
|
|
1128
|
+
}
|
|
1129
|
+
async listMessagesById({ messageIds }) {
|
|
1130
|
+
if (messageIds.length === 0) return { messages: [] };
|
|
1131
|
+
const fullTableName = this.#db.getTableName(TABLE_MESSAGES);
|
|
1132
|
+
const messages = [];
|
|
1165
1133
|
try {
|
|
1166
|
-
const
|
|
1167
|
-
|
|
1134
|
+
const query = createSqlBuilder().select(["id", "content", "role", "type", "createdAt", "thread_id AS threadId", "resourceId"]).from(fullTableName).where(`id in (${messageIds.map(() => "?").join(",")})`, ...messageIds);
|
|
1135
|
+
query.orderBy("createdAt", "DESC");
|
|
1168
1136
|
const { sql, params } = query.build();
|
|
1169
|
-
await this.executeQuery({ sql, params });
|
|
1170
|
-
|
|
1137
|
+
const result = await this.#db.executeQuery({ sql, params });
|
|
1138
|
+
if (Array.isArray(result)) messages.push(...result);
|
|
1139
|
+
const processedMessages = messages.map((message) => {
|
|
1140
|
+
const processedMsg = {};
|
|
1141
|
+
for (const [key, value] of Object.entries(message)) {
|
|
1142
|
+
if (key === `type` && value === `v2`) continue;
|
|
1143
|
+
processedMsg[key] = deserializeValue(value);
|
|
1144
|
+
}
|
|
1145
|
+
return processedMsg;
|
|
1146
|
+
});
|
|
1147
|
+
this.logger.debug(`Retrieved ${messages.length} messages`);
|
|
1148
|
+
const list = new MessageList().add(processedMessages, "memory");
|
|
1149
|
+
return { messages: list.get.all.db() };
|
|
1171
1150
|
} catch (error) {
|
|
1172
|
-
|
|
1151
|
+
const mastraError = new MastraError(
|
|
1173
1152
|
{
|
|
1174
|
-
id: createStorageErrorId("CLOUDFLARE_D1", "
|
|
1153
|
+
id: createStorageErrorId("CLOUDFLARE_D1", "LIST_MESSAGES_BY_ID", "FAILED"),
|
|
1175
1154
|
domain: ErrorDomain.STORAGE,
|
|
1176
1155
|
category: ErrorCategory.THIRD_PARTY,
|
|
1177
|
-
|
|
1156
|
+
text: `Failed to retrieve messages by ID: ${error instanceof Error ? error.message : String(error)}`,
|
|
1157
|
+
details: { messageIds: JSON.stringify(messageIds) }
|
|
1178
1158
|
},
|
|
1179
1159
|
error
|
|
1180
1160
|
);
|
|
1161
|
+
this.logger?.error(mastraError.toString());
|
|
1162
|
+
this.logger?.trackException(mastraError);
|
|
1163
|
+
throw mastraError;
|
|
1181
1164
|
}
|
|
1182
1165
|
}
|
|
1183
|
-
async
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
await this.executeQuery({ sql });
|
|
1188
|
-
this.logger.debug(`Dropped table ${fullTableName}`);
|
|
1189
|
-
} catch (error) {
|
|
1166
|
+
async listMessages(args) {
|
|
1167
|
+
const { threadId, resourceId, include, filter, perPage: perPageInput, page = 0, orderBy } = args;
|
|
1168
|
+
const threadIds = Array.isArray(threadId) ? threadId : [threadId];
|
|
1169
|
+
if (threadIds.length === 0 || threadIds.some((id) => !id.trim())) {
|
|
1190
1170
|
throw new MastraError(
|
|
1191
1171
|
{
|
|
1192
|
-
id: createStorageErrorId("CLOUDFLARE_D1", "
|
|
1172
|
+
id: createStorageErrorId("CLOUDFLARE_D1", "LIST_MESSAGES", "INVALID_THREAD_ID"),
|
|
1193
1173
|
domain: ErrorDomain.STORAGE,
|
|
1194
1174
|
category: ErrorCategory.THIRD_PARTY,
|
|
1195
|
-
details: {
|
|
1175
|
+
details: { threadId: Array.isArray(threadId) ? threadId.join(",") : threadId }
|
|
1196
1176
|
},
|
|
1197
|
-
|
|
1177
|
+
new Error("threadId must be a non-empty string or array of non-empty strings")
|
|
1198
1178
|
);
|
|
1199
1179
|
}
|
|
1200
|
-
|
|
1201
|
-
async alterTable(args) {
|
|
1202
|
-
try {
|
|
1203
|
-
const fullTableName = this.getTableName(args.tableName);
|
|
1204
|
-
const existingColumns = await this.getTableColumns(fullTableName);
|
|
1205
|
-
const existingColumnNames = new Set(existingColumns.map((col) => col.name));
|
|
1206
|
-
for (const [columnName, column] of Object.entries(args.schema)) {
|
|
1207
|
-
if (!existingColumnNames.has(columnName) && args.ifNotExists.includes(columnName)) {
|
|
1208
|
-
const sqlType = this.getSqlType(column.type);
|
|
1209
|
-
const defaultValue = this.getDefaultValue(column.type);
|
|
1210
|
-
const sql = `ALTER TABLE ${fullTableName} ADD COLUMN ${columnName} ${sqlType} ${defaultValue}`;
|
|
1211
|
-
await this.executeQuery({ sql });
|
|
1212
|
-
this.logger.debug(`Added column ${columnName} to table ${fullTableName}`);
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1215
|
-
} catch (error) {
|
|
1180
|
+
if (page < 0) {
|
|
1216
1181
|
throw new MastraError(
|
|
1217
1182
|
{
|
|
1218
|
-
id: createStorageErrorId("CLOUDFLARE_D1", "
|
|
1183
|
+
id: createStorageErrorId("CLOUDFLARE_D1", "LIST_MESSAGES", "INVALID_PAGE"),
|
|
1219
1184
|
domain: ErrorDomain.STORAGE,
|
|
1220
|
-
category: ErrorCategory.
|
|
1221
|
-
details: {
|
|
1185
|
+
category: ErrorCategory.USER,
|
|
1186
|
+
details: { page }
|
|
1222
1187
|
},
|
|
1223
|
-
|
|
1188
|
+
new Error("page must be >= 0")
|
|
1224
1189
|
);
|
|
1225
1190
|
}
|
|
1226
|
-
|
|
1227
|
-
|
|
1191
|
+
const perPage = normalizePerPage(perPageInput, 40);
|
|
1192
|
+
const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
|
|
1228
1193
|
try {
|
|
1229
|
-
const fullTableName = this.getTableName(
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1194
|
+
const fullTableName = this.#db.getTableName(TABLE_MESSAGES);
|
|
1195
|
+
let query = `
|
|
1196
|
+
SELECT id, content, role, type, createdAt, thread_id AS threadId, resourceId
|
|
1197
|
+
FROM ${fullTableName}
|
|
1198
|
+
WHERE thread_id = ?
|
|
1199
|
+
`;
|
|
1200
|
+
const queryParams = [threadId];
|
|
1201
|
+
if (resourceId) {
|
|
1202
|
+
query += ` AND resourceId = ?`;
|
|
1203
|
+
queryParams.push(resourceId);
|
|
1204
|
+
}
|
|
1205
|
+
const dateRange = filter?.dateRange;
|
|
1206
|
+
if (dateRange?.start) {
|
|
1207
|
+
const startDate = dateRange.start instanceof Date ? serializeDate(dateRange.start) : serializeDate(new Date(dateRange.start));
|
|
1208
|
+
query += ` AND createdAt >= ?`;
|
|
1209
|
+
queryParams.push(startDate);
|
|
1210
|
+
}
|
|
1211
|
+
if (dateRange?.end) {
|
|
1212
|
+
const endDate = dateRange.end instanceof Date ? serializeDate(dateRange.end) : serializeDate(new Date(dateRange.end));
|
|
1213
|
+
query += ` AND createdAt <= ?`;
|
|
1214
|
+
queryParams.push(endDate);
|
|
1215
|
+
}
|
|
1216
|
+
const { field, direction } = this.parseOrderBy(orderBy, "ASC");
|
|
1217
|
+
query += ` ORDER BY "${field}" ${direction}`;
|
|
1218
|
+
if (perPage !== Number.MAX_SAFE_INTEGER) {
|
|
1219
|
+
query += ` LIMIT ? OFFSET ?`;
|
|
1220
|
+
queryParams.push(perPage, offset);
|
|
1221
|
+
}
|
|
1222
|
+
const results = await this.#db.executeQuery({ sql: query, params: queryParams });
|
|
1223
|
+
const paginatedMessages = (isArrayOfRecords(results) ? results : []).map((message) => {
|
|
1224
|
+
const processedMsg = {};
|
|
1225
|
+
for (const [key, value] of Object.entries(message)) {
|
|
1226
|
+
if (key === `type` && value === `v2`) continue;
|
|
1227
|
+
processedMsg[key] = deserializeValue(value);
|
|
1228
|
+
}
|
|
1229
|
+
return processedMsg;
|
|
1230
|
+
});
|
|
1231
|
+
const paginatedCount = paginatedMessages.length;
|
|
1232
|
+
let countQuery = `SELECT count() as count FROM ${fullTableName} WHERE thread_id = ?`;
|
|
1233
|
+
const countParams = [threadId];
|
|
1234
|
+
if (resourceId) {
|
|
1235
|
+
countQuery += ` AND resourceId = ?`;
|
|
1236
|
+
countParams.push(resourceId);
|
|
1237
|
+
}
|
|
1238
|
+
if (dateRange?.start) {
|
|
1239
|
+
const startDate = dateRange.start instanceof Date ? serializeDate(dateRange.start) : serializeDate(new Date(dateRange.start));
|
|
1240
|
+
countQuery += ` AND createdAt >= ?`;
|
|
1241
|
+
countParams.push(startDate);
|
|
1242
|
+
}
|
|
1243
|
+
if (dateRange?.end) {
|
|
1244
|
+
const endDate = dateRange.end instanceof Date ? serializeDate(dateRange.end) : serializeDate(new Date(dateRange.end));
|
|
1245
|
+
countQuery += ` AND createdAt <= ?`;
|
|
1246
|
+
countParams.push(endDate);
|
|
1247
|
+
}
|
|
1248
|
+
const countResult = await this.#db.executeQuery({ sql: countQuery, params: countParams });
|
|
1249
|
+
const total = Number(countResult[0]?.count ?? 0);
|
|
1250
|
+
if (total === 0 && paginatedCount === 0 && (!include || include.length === 0)) {
|
|
1251
|
+
return {
|
|
1252
|
+
messages: [],
|
|
1253
|
+
total: 0,
|
|
1254
|
+
page,
|
|
1255
|
+
perPage: perPageForResponse,
|
|
1256
|
+
hasMore: false
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
const messageIds = new Set(paginatedMessages.map((m) => m.id));
|
|
1260
|
+
let includeMessages = [];
|
|
1261
|
+
if (include && include.length > 0) {
|
|
1262
|
+
const includeResult = await this._getIncludedMessages(include);
|
|
1263
|
+
if (Array.isArray(includeResult)) {
|
|
1264
|
+
includeMessages = includeResult;
|
|
1265
|
+
for (const includeMsg of includeMessages) {
|
|
1266
|
+
if (!messageIds.has(includeMsg.id)) {
|
|
1267
|
+
paginatedMessages.push(includeMsg);
|
|
1268
|
+
messageIds.add(includeMsg.id);
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
const list = new MessageList().add(paginatedMessages, "memory");
|
|
1274
|
+
let finalMessages = list.get.all.db();
|
|
1275
|
+
finalMessages = finalMessages.sort((a, b) => {
|
|
1276
|
+
const isDateField = field === "createdAt" || field === "updatedAt";
|
|
1277
|
+
const aValue = isDateField ? new Date(a[field]).getTime() : a[field];
|
|
1278
|
+
const bValue = isDateField ? new Date(b[field]).getTime() : b[field];
|
|
1279
|
+
if (aValue === bValue) {
|
|
1280
|
+
return a.id.localeCompare(b.id);
|
|
1281
|
+
}
|
|
1282
|
+
if (typeof aValue === "number" && typeof bValue === "number") {
|
|
1283
|
+
return direction === "ASC" ? aValue - bValue : bValue - aValue;
|
|
1284
|
+
}
|
|
1285
|
+
return direction === "ASC" ? String(aValue).localeCompare(String(bValue)) : String(bValue).localeCompare(String(aValue));
|
|
1286
|
+
});
|
|
1287
|
+
const returnedThreadMessageIds = new Set(finalMessages.filter((m) => m.threadId === threadId).map((m) => m.id));
|
|
1288
|
+
const allThreadMessagesReturned = returnedThreadMessageIds.size >= total;
|
|
1289
|
+
const hasMore = perPageInput === false ? false : allThreadMessagesReturned ? false : offset + paginatedCount < total;
|
|
1290
|
+
return {
|
|
1291
|
+
messages: finalMessages,
|
|
1292
|
+
total,
|
|
1293
|
+
page,
|
|
1294
|
+
perPage: perPageForResponse,
|
|
1295
|
+
hasMore
|
|
1296
|
+
};
|
|
1236
1297
|
} catch (error) {
|
|
1237
|
-
|
|
1298
|
+
const mastraError = new MastraError(
|
|
1238
1299
|
{
|
|
1239
|
-
id: createStorageErrorId("CLOUDFLARE_D1", "
|
|
1300
|
+
id: createStorageErrorId("CLOUDFLARE_D1", "LIST_MESSAGES", "FAILED"),
|
|
1240
1301
|
domain: ErrorDomain.STORAGE,
|
|
1241
1302
|
category: ErrorCategory.THIRD_PARTY,
|
|
1242
|
-
|
|
1303
|
+
text: `Failed to list messages for thread ${Array.isArray(threadId) ? threadId.join(",") : threadId}: ${error instanceof Error ? error.message : String(error)}`,
|
|
1304
|
+
details: {
|
|
1305
|
+
threadId: Array.isArray(threadId) ? threadId.join(",") : threadId,
|
|
1306
|
+
resourceId: resourceId ?? ""
|
|
1307
|
+
}
|
|
1243
1308
|
},
|
|
1244
1309
|
error
|
|
1245
1310
|
);
|
|
1311
|
+
this.logger?.error?.(mastraError.toString());
|
|
1312
|
+
this.logger?.trackException?.(mastraError);
|
|
1313
|
+
return {
|
|
1314
|
+
messages: [],
|
|
1315
|
+
total: 0,
|
|
1316
|
+
page,
|
|
1317
|
+
perPage: perPageForResponse,
|
|
1318
|
+
hasMore: false
|
|
1319
|
+
};
|
|
1246
1320
|
}
|
|
1247
1321
|
}
|
|
1248
|
-
async
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
const columns = Object.keys(processedRecords[0] || {});
|
|
1254
|
-
for (const record of processedRecords) {
|
|
1255
|
-
const values = Object.values(record);
|
|
1256
|
-
const query = createSqlBuilder().insert(fullTableName, columns, values);
|
|
1257
|
-
const { sql, params } = query.build();
|
|
1258
|
-
await this.executeQuery({ sql, params });
|
|
1259
|
-
}
|
|
1260
|
-
} catch (error) {
|
|
1261
|
-
throw new MastraError(
|
|
1262
|
-
{
|
|
1263
|
-
id: createStorageErrorId("CLOUDFLARE_D1", "BATCH_INSERT", "FAILED"),
|
|
1264
|
-
domain: ErrorDomain.STORAGE,
|
|
1265
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
1266
|
-
details: { tableName }
|
|
1267
|
-
},
|
|
1268
|
-
error
|
|
1269
|
-
);
|
|
1322
|
+
async updateMessages(args) {
|
|
1323
|
+
const { messages } = args;
|
|
1324
|
+
this.logger.debug("Updating messages", { count: messages.length });
|
|
1325
|
+
if (!messages.length) {
|
|
1326
|
+
return [];
|
|
1270
1327
|
}
|
|
1271
|
-
|
|
1272
|
-
|
|
1328
|
+
const messageIds = messages.map((m) => m.id);
|
|
1329
|
+
const fullTableName = this.#db.getTableName(TABLE_MESSAGES);
|
|
1330
|
+
const threadsTableName = this.#db.getTableName(TABLE_THREADS);
|
|
1273
1331
|
try {
|
|
1274
|
-
const
|
|
1275
|
-
const
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1332
|
+
const placeholders = messageIds.map(() => "?").join(",");
|
|
1333
|
+
const selectQuery = `SELECT id, content, role, type, createdAt, thread_id AS threadId, resourceId FROM ${fullTableName} WHERE id IN (${placeholders})`;
|
|
1334
|
+
const existingMessages = await this.#db.executeQuery({ sql: selectQuery, params: messageIds });
|
|
1335
|
+
if (existingMessages.length === 0) {
|
|
1336
|
+
return [];
|
|
1337
|
+
}
|
|
1338
|
+
const parsedExistingMessages = existingMessages.map((msg) => {
|
|
1339
|
+
if (typeof msg.content === "string") {
|
|
1340
|
+
try {
|
|
1341
|
+
msg.content = JSON.parse(msg.content);
|
|
1342
|
+
} catch {
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
return msg;
|
|
1346
|
+
});
|
|
1347
|
+
const threadIdsToUpdate = /* @__PURE__ */ new Set();
|
|
1348
|
+
const updateQueries = [];
|
|
1349
|
+
for (const existingMessage of parsedExistingMessages) {
|
|
1350
|
+
const updatePayload = messages.find((m) => m.id === existingMessage.id);
|
|
1351
|
+
if (!updatePayload) continue;
|
|
1352
|
+
const { id, ...fieldsToUpdate } = updatePayload;
|
|
1353
|
+
if (Object.keys(fieldsToUpdate).length === 0) continue;
|
|
1354
|
+
threadIdsToUpdate.add(existingMessage.threadId);
|
|
1355
|
+
if ("threadId" in updatePayload && updatePayload.threadId && updatePayload.threadId !== existingMessage.threadId) {
|
|
1356
|
+
threadIdsToUpdate.add(updatePayload.threadId);
|
|
1357
|
+
}
|
|
1358
|
+
const setClauses = [];
|
|
1359
|
+
const values = [];
|
|
1360
|
+
const updatableFields = { ...fieldsToUpdate };
|
|
1361
|
+
if (updatableFields.content) {
|
|
1362
|
+
const existingContent = existingMessage.content || {};
|
|
1363
|
+
const newContent = {
|
|
1364
|
+
...existingContent,
|
|
1365
|
+
...updatableFields.content,
|
|
1366
|
+
// Deep merge metadata if it exists on both
|
|
1367
|
+
...existingContent?.metadata && updatableFields.content.metadata ? {
|
|
1368
|
+
metadata: {
|
|
1369
|
+
...existingContent.metadata,
|
|
1370
|
+
...updatableFields.content.metadata
|
|
1371
|
+
}
|
|
1372
|
+
} : {}
|
|
1373
|
+
};
|
|
1374
|
+
setClauses.push(`content = ?`);
|
|
1375
|
+
values.push(JSON.stringify(newContent));
|
|
1376
|
+
delete updatableFields.content;
|
|
1377
|
+
}
|
|
1378
|
+
for (const key in updatableFields) {
|
|
1379
|
+
if (Object.prototype.hasOwnProperty.call(updatableFields, key)) {
|
|
1380
|
+
const dbColumn = key === "threadId" ? "thread_id" : key;
|
|
1381
|
+
setClauses.push(`${dbColumn} = ?`);
|
|
1382
|
+
values.push(updatableFields[key]);
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
if (setClauses.length > 0) {
|
|
1386
|
+
values.push(id);
|
|
1387
|
+
const updateQuery = `UPDATE ${fullTableName} SET ${setClauses.join(", ")} WHERE id = ?`;
|
|
1388
|
+
updateQueries.push({ sql: updateQuery, params: values });
|
|
1283
1389
|
}
|
|
1284
1390
|
}
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
const { sql, params } = query.build();
|
|
1288
|
-
const result = await this.executeQuery({ sql, params, first: true });
|
|
1289
|
-
if (!result) {
|
|
1290
|
-
return null;
|
|
1391
|
+
for (const query of updateQueries) {
|
|
1392
|
+
await this.#db.executeQuery(query);
|
|
1291
1393
|
}
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1394
|
+
if (threadIdsToUpdate.size > 0) {
|
|
1395
|
+
const threadPlaceholders = Array.from(threadIdsToUpdate).map(() => "?").join(",");
|
|
1396
|
+
const threadUpdateQuery = `UPDATE ${threadsTableName} SET updatedAt = ? WHERE id IN (${threadPlaceholders})`;
|
|
1397
|
+
const threadUpdateParams = [(/* @__PURE__ */ new Date()).toISOString(), ...Array.from(threadIdsToUpdate)];
|
|
1398
|
+
await this.#db.executeQuery({ sql: threadUpdateQuery, params: threadUpdateParams });
|
|
1295
1399
|
}
|
|
1296
|
-
|
|
1400
|
+
const updatedMessages = await this.#db.executeQuery({ sql: selectQuery, params: messageIds });
|
|
1401
|
+
return updatedMessages.map((message) => {
|
|
1402
|
+
if (typeof message.content === "string") {
|
|
1403
|
+
try {
|
|
1404
|
+
message.content = JSON.parse(message.content);
|
|
1405
|
+
} catch {
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
return message;
|
|
1409
|
+
});
|
|
1297
1410
|
} catch (error) {
|
|
1298
1411
|
throw new MastraError(
|
|
1299
1412
|
{
|
|
1300
|
-
id: createStorageErrorId("CLOUDFLARE_D1", "
|
|
1413
|
+
id: createStorageErrorId("CLOUDFLARE_D1", "UPDATE_MESSAGES", "FAILED"),
|
|
1301
1414
|
domain: ErrorDomain.STORAGE,
|
|
1302
1415
|
category: ErrorCategory.THIRD_PARTY,
|
|
1303
|
-
details: {
|
|
1416
|
+
details: { count: messages.length }
|
|
1304
1417
|
},
|
|
1305
1418
|
error
|
|
1306
1419
|
);
|
|
1307
1420
|
}
|
|
1308
1421
|
}
|
|
1309
|
-
async
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
}
|
|
1314
|
-
return processed;
|
|
1315
|
-
}
|
|
1316
|
-
/**
|
|
1317
|
-
* Upsert multiple records in a batch operation
|
|
1318
|
-
* @param tableName The table to insert into
|
|
1319
|
-
* @param records The records to insert
|
|
1320
|
-
*/
|
|
1321
|
-
async batchUpsert({ tableName, records }) {
|
|
1322
|
-
if (records.length === 0) return;
|
|
1323
|
-
const fullTableName = this.getTableName(tableName);
|
|
1422
|
+
async deleteMessages(messageIds) {
|
|
1423
|
+
if (messageIds.length === 0) return;
|
|
1424
|
+
const fullTableName = this.#db.getTableName(TABLE_MESSAGES);
|
|
1425
|
+
const threadsTableName = this.#db.getTableName(TABLE_THREADS);
|
|
1324
1426
|
try {
|
|
1325
|
-
const
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
});
|
|
1338
|
-
const recordToUpsert = columns.reduce(
|
|
1339
|
-
(acc, col) => {
|
|
1340
|
-
if (col !== "createdAt") acc[col] = `excluded.${col}`;
|
|
1341
|
-
return acc;
|
|
1342
|
-
},
|
|
1343
|
-
{}
|
|
1344
|
-
);
|
|
1345
|
-
const query = createSqlBuilder().insert(fullTableName, columns, values, ["id"], recordToUpsert);
|
|
1346
|
-
const { sql, params } = query.build();
|
|
1347
|
-
await this.executeQuery({ sql, params });
|
|
1348
|
-
}
|
|
1349
|
-
}
|
|
1350
|
-
this.logger.debug(
|
|
1351
|
-
`Processed batch ${Math.floor(i / batchSize) + 1} of ${Math.ceil(records.length / batchSize)}`
|
|
1352
|
-
);
|
|
1427
|
+
const placeholders = messageIds.map(() => "?").join(",");
|
|
1428
|
+
const selectQuery = `SELECT DISTINCT thread_id FROM ${fullTableName} WHERE id IN (${placeholders})`;
|
|
1429
|
+
const threadResults = await this.#db.executeQuery({ sql: selectQuery, params: messageIds });
|
|
1430
|
+
const threadIds = threadResults.map((r) => r.thread_id).filter(Boolean);
|
|
1431
|
+
const deleteQuery = createSqlBuilder().delete(fullTableName).where(`id IN (${placeholders})`, ...messageIds);
|
|
1432
|
+
const { sql, params } = deleteQuery.build();
|
|
1433
|
+
await this.#db.executeQuery({ sql, params });
|
|
1434
|
+
if (threadIds.length > 0) {
|
|
1435
|
+
const threadPlaceholders = threadIds.map(() => "?").join(",");
|
|
1436
|
+
const threadUpdateQuery = `UPDATE ${threadsTableName} SET updatedAt = ? WHERE id IN (${threadPlaceholders})`;
|
|
1437
|
+
const threadUpdateParams = [(/* @__PURE__ */ new Date()).toISOString(), ...threadIds];
|
|
1438
|
+
await this.#db.executeQuery({ sql: threadUpdateQuery, params: threadUpdateParams });
|
|
1353
1439
|
}
|
|
1354
|
-
this.logger.debug(`Successfully batch upserted ${records.length} records into ${tableName}`);
|
|
1355
1440
|
} catch (error) {
|
|
1356
1441
|
throw new MastraError(
|
|
1357
1442
|
{
|
|
1358
|
-
id: createStorageErrorId("CLOUDFLARE_D1", "
|
|
1443
|
+
id: createStorageErrorId("CLOUDFLARE_D1", "DELETE_MESSAGES", "FAILED"),
|
|
1359
1444
|
domain: ErrorDomain.STORAGE,
|
|
1360
1445
|
category: ErrorCategory.THIRD_PARTY,
|
|
1361
|
-
|
|
1362
|
-
details: { tableName }
|
|
1446
|
+
details: { messageIds: JSON.stringify(messageIds) }
|
|
1363
1447
|
},
|
|
1364
1448
|
error
|
|
1365
1449
|
);
|
|
@@ -1375,17 +1459,23 @@ function transformScoreRow(row) {
|
|
|
1375
1459
|
});
|
|
1376
1460
|
}
|
|
1377
1461
|
var ScoresStorageD1 = class extends ScoresStorage {
|
|
1378
|
-
|
|
1379
|
-
constructor(
|
|
1462
|
+
#db;
|
|
1463
|
+
constructor(config) {
|
|
1380
1464
|
super();
|
|
1381
|
-
this
|
|
1465
|
+
this.#db = new D1DB(resolveD1Config(config));
|
|
1466
|
+
}
|
|
1467
|
+
async init() {
|
|
1468
|
+
await this.#db.createTable({ tableName: TABLE_SCORERS, schema: TABLE_SCHEMAS[TABLE_SCORERS] });
|
|
1469
|
+
}
|
|
1470
|
+
async dangerouslyClearAll() {
|
|
1471
|
+
await this.#db.clearTable({ tableName: TABLE_SCORERS });
|
|
1382
1472
|
}
|
|
1383
1473
|
async getScoreById({ id }) {
|
|
1384
1474
|
try {
|
|
1385
|
-
const fullTableName = this.
|
|
1475
|
+
const fullTableName = this.#db.getTableName(TABLE_SCORERS);
|
|
1386
1476
|
const query = createSqlBuilder().select("*").from(fullTableName).where("id = ?", id);
|
|
1387
1477
|
const { sql, params } = query.build();
|
|
1388
|
-
const result = await this.
|
|
1478
|
+
const result = await this.#db.executeQuery({ sql, params, first: true });
|
|
1389
1479
|
if (!result) {
|
|
1390
1480
|
return null;
|
|
1391
1481
|
}
|
|
@@ -1412,7 +1502,7 @@ var ScoresStorageD1 = class extends ScoresStorage {
|
|
|
1412
1502
|
domain: ErrorDomain.STORAGE,
|
|
1413
1503
|
category: ErrorCategory.USER,
|
|
1414
1504
|
details: {
|
|
1415
|
-
scorer: score.scorer?.id ?? "unknown",
|
|
1505
|
+
scorer: typeof score.scorer?.id === "string" ? score.scorer.id : String(score.scorer?.id ?? "unknown"),
|
|
1416
1506
|
entityId: score.entityId ?? "unknown",
|
|
1417
1507
|
entityType: score.entityType ?? "unknown",
|
|
1418
1508
|
traceId: score.traceId ?? "",
|
|
@@ -1424,7 +1514,7 @@ var ScoresStorageD1 = class extends ScoresStorage {
|
|
|
1424
1514
|
}
|
|
1425
1515
|
const id = crypto.randomUUID();
|
|
1426
1516
|
try {
|
|
1427
|
-
const fullTableName = this.
|
|
1517
|
+
const fullTableName = this.#db.getTableName(TABLE_SCORERS);
|
|
1428
1518
|
const serializedRecord = {};
|
|
1429
1519
|
for (const [key, value] of Object.entries(parsedScore)) {
|
|
1430
1520
|
if (value !== null && value !== void 0) {
|
|
@@ -1445,7 +1535,7 @@ var ScoresStorageD1 = class extends ScoresStorage {
|
|
|
1445
1535
|
const values = Object.values(serializedRecord);
|
|
1446
1536
|
const query = createSqlBuilder().insert(fullTableName, columns, values);
|
|
1447
1537
|
const { sql, params } = query.build();
|
|
1448
|
-
await this.
|
|
1538
|
+
await this.#db.executeQuery({ sql, params });
|
|
1449
1539
|
return { score: { ...parsedScore, id, createdAt: now, updatedAt: now } };
|
|
1450
1540
|
} catch (error) {
|
|
1451
1541
|
throw new MastraError(
|
|
@@ -1470,7 +1560,7 @@ var ScoresStorageD1 = class extends ScoresStorage {
|
|
|
1470
1560
|
const { page, perPage: perPageInput } = pagination;
|
|
1471
1561
|
const perPage = normalizePerPage(perPageInput, 100);
|
|
1472
1562
|
const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
|
|
1473
|
-
const fullTableName = this.
|
|
1563
|
+
const fullTableName = this.#db.getTableName(TABLE_SCORERS);
|
|
1474
1564
|
const countQuery = createSqlBuilder().count().from(fullTableName).where("scorerId = ?", scorerId);
|
|
1475
1565
|
if (entityId) {
|
|
1476
1566
|
countQuery.andWhere("entityId = ?", entityId);
|
|
@@ -1481,7 +1571,7 @@ var ScoresStorageD1 = class extends ScoresStorage {
|
|
|
1481
1571
|
if (source) {
|
|
1482
1572
|
countQuery.andWhere("source = ?", source);
|
|
1483
1573
|
}
|
|
1484
|
-
const countResult = await this.
|
|
1574
|
+
const countResult = await this.#db.executeQuery(countQuery.build());
|
|
1485
1575
|
const total = Array.isArray(countResult) ? Number(countResult?.[0]?.count ?? 0) : Number(countResult?.count ?? 0);
|
|
1486
1576
|
if (total === 0) {
|
|
1487
1577
|
return {
|
|
@@ -1508,7 +1598,7 @@ var ScoresStorageD1 = class extends ScoresStorage {
|
|
|
1508
1598
|
}
|
|
1509
1599
|
selectQuery.limit(limitValue).offset(start);
|
|
1510
1600
|
const { sql, params } = selectQuery.build();
|
|
1511
|
-
const results = await this.
|
|
1601
|
+
const results = await this.#db.executeQuery({ sql, params });
|
|
1512
1602
|
const scores = Array.isArray(results) ? results.map(transformScoreRow) : [];
|
|
1513
1603
|
return {
|
|
1514
1604
|
pagination: {
|
|
@@ -1538,9 +1628,9 @@ var ScoresStorageD1 = class extends ScoresStorage {
|
|
|
1538
1628
|
const { page, perPage: perPageInput } = pagination;
|
|
1539
1629
|
const perPage = normalizePerPage(perPageInput, 100);
|
|
1540
1630
|
const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
|
|
1541
|
-
const fullTableName = this.
|
|
1631
|
+
const fullTableName = this.#db.getTableName(TABLE_SCORERS);
|
|
1542
1632
|
const countQuery = createSqlBuilder().count().from(fullTableName).where("runId = ?", runId);
|
|
1543
|
-
const countResult = await this.
|
|
1633
|
+
const countResult = await this.#db.executeQuery(countQuery.build());
|
|
1544
1634
|
const total = Array.isArray(countResult) ? Number(countResult?.[0]?.count ?? 0) : Number(countResult?.count ?? 0);
|
|
1545
1635
|
if (total === 0) {
|
|
1546
1636
|
return {
|
|
@@ -1557,7 +1647,7 @@ var ScoresStorageD1 = class extends ScoresStorage {
|
|
|
1557
1647
|
const limitValue = perPageInput === false ? total : perPage;
|
|
1558
1648
|
const selectQuery = createSqlBuilder().select("*").from(fullTableName).where("runId = ?", runId).limit(limitValue).offset(start);
|
|
1559
1649
|
const { sql, params } = selectQuery.build();
|
|
1560
|
-
const results = await this.
|
|
1650
|
+
const results = await this.#db.executeQuery({ sql, params });
|
|
1561
1651
|
const scores = Array.isArray(results) ? results.map(transformScoreRow) : [];
|
|
1562
1652
|
return {
|
|
1563
1653
|
pagination: {
|
|
@@ -1588,9 +1678,9 @@ var ScoresStorageD1 = class extends ScoresStorage {
|
|
|
1588
1678
|
const { page, perPage: perPageInput } = pagination;
|
|
1589
1679
|
const perPage = normalizePerPage(perPageInput, 100);
|
|
1590
1680
|
const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
|
|
1591
|
-
const fullTableName = this.
|
|
1681
|
+
const fullTableName = this.#db.getTableName(TABLE_SCORERS);
|
|
1592
1682
|
const countQuery = createSqlBuilder().count().from(fullTableName).where("entityId = ?", entityId).andWhere("entityType = ?", entityType);
|
|
1593
|
-
const countResult = await this.
|
|
1683
|
+
const countResult = await this.#db.executeQuery(countQuery.build());
|
|
1594
1684
|
const total = Array.isArray(countResult) ? Number(countResult?.[0]?.count ?? 0) : Number(countResult?.count ?? 0);
|
|
1595
1685
|
if (total === 0) {
|
|
1596
1686
|
return {
|
|
@@ -1607,7 +1697,7 @@ var ScoresStorageD1 = class extends ScoresStorage {
|
|
|
1607
1697
|
const limitValue = perPageInput === false ? total : perPage;
|
|
1608
1698
|
const selectQuery = createSqlBuilder().select("*").from(fullTableName).where("entityId = ?", entityId).andWhere("entityType = ?", entityType).limit(limitValue).offset(start);
|
|
1609
1699
|
const { sql, params } = selectQuery.build();
|
|
1610
|
-
const results = await this.
|
|
1700
|
+
const results = await this.#db.executeQuery({ sql, params });
|
|
1611
1701
|
const scores = Array.isArray(results) ? results.map(transformScoreRow) : [];
|
|
1612
1702
|
return {
|
|
1613
1703
|
pagination: {
|
|
@@ -1638,9 +1728,9 @@ var ScoresStorageD1 = class extends ScoresStorage {
|
|
|
1638
1728
|
const { page, perPage: perPageInput } = pagination;
|
|
1639
1729
|
const perPage = normalizePerPage(perPageInput, 100);
|
|
1640
1730
|
const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
|
|
1641
|
-
const fullTableName = this.
|
|
1731
|
+
const fullTableName = this.#db.getTableName(TABLE_SCORERS);
|
|
1642
1732
|
const countQuery = createSqlBuilder().count().from(fullTableName).where("traceId = ?", traceId).andWhere("spanId = ?", spanId);
|
|
1643
|
-
const countResult = await this.
|
|
1733
|
+
const countResult = await this.#db.executeQuery(countQuery.build());
|
|
1644
1734
|
const total = Array.isArray(countResult) ? Number(countResult?.[0]?.count ?? 0) : Number(countResult?.count ?? 0);
|
|
1645
1735
|
if (total === 0) {
|
|
1646
1736
|
return {
|
|
@@ -1657,7 +1747,7 @@ var ScoresStorageD1 = class extends ScoresStorage {
|
|
|
1657
1747
|
const limitValue = perPageInput === false ? total : perPage;
|
|
1658
1748
|
const selectQuery = createSqlBuilder().select("*").from(fullTableName).where("traceId = ?", traceId).andWhere("spanId = ?", spanId).orderBy("createdAt", "DESC").limit(limitValue).offset(start);
|
|
1659
1749
|
const { sql, params } = selectQuery.build();
|
|
1660
|
-
const results = await this.
|
|
1750
|
+
const results = await this.#db.executeQuery({ sql, params });
|
|
1661
1751
|
const scores = Array.isArray(results) ? results.map(transformScoreRow) : [];
|
|
1662
1752
|
return {
|
|
1663
1753
|
pagination: {
|
|
@@ -1681,10 +1771,16 @@ var ScoresStorageD1 = class extends ScoresStorage {
|
|
|
1681
1771
|
}
|
|
1682
1772
|
};
|
|
1683
1773
|
var WorkflowsStorageD1 = class extends WorkflowsStorage {
|
|
1684
|
-
|
|
1685
|
-
constructor(
|
|
1774
|
+
#db;
|
|
1775
|
+
constructor(config) {
|
|
1686
1776
|
super();
|
|
1687
|
-
this
|
|
1777
|
+
this.#db = new D1DB(resolveD1Config(config));
|
|
1778
|
+
}
|
|
1779
|
+
async init() {
|
|
1780
|
+
await this.#db.createTable({ tableName: TABLE_WORKFLOW_SNAPSHOT, schema: TABLE_SCHEMAS[TABLE_WORKFLOW_SNAPSHOT] });
|
|
1781
|
+
}
|
|
1782
|
+
async dangerouslyClearAll() {
|
|
1783
|
+
await this.#db.clearTable({ tableName: TABLE_WORKFLOW_SNAPSHOT });
|
|
1688
1784
|
}
|
|
1689
1785
|
updateWorkflowResults({
|
|
1690
1786
|
// workflowName,
|
|
@@ -1706,11 +1802,13 @@ var WorkflowsStorageD1 = class extends WorkflowsStorage {
|
|
|
1706
1802
|
workflowName,
|
|
1707
1803
|
runId,
|
|
1708
1804
|
resourceId,
|
|
1709
|
-
snapshot
|
|
1805
|
+
snapshot,
|
|
1806
|
+
createdAt,
|
|
1807
|
+
updatedAt
|
|
1710
1808
|
}) {
|
|
1711
|
-
const fullTableName = this.
|
|
1809
|
+
const fullTableName = this.#db.getTableName(TABLE_WORKFLOW_SNAPSHOT);
|
|
1712
1810
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1713
|
-
const currentSnapshot = await this.
|
|
1811
|
+
const currentSnapshot = await this.#db.load({
|
|
1714
1812
|
tableName: TABLE_WORKFLOW_SNAPSHOT,
|
|
1715
1813
|
keys: { workflow_name: workflowName, run_id: runId }
|
|
1716
1814
|
});
|
|
@@ -1718,16 +1816,16 @@ var WorkflowsStorageD1 = class extends WorkflowsStorage {
|
|
|
1718
1816
|
...currentSnapshot,
|
|
1719
1817
|
resourceId,
|
|
1720
1818
|
snapshot: JSON.stringify(snapshot),
|
|
1721
|
-
updatedAt: now
|
|
1819
|
+
updatedAt: updatedAt ? updatedAt.toISOString() : now
|
|
1722
1820
|
} : {
|
|
1723
1821
|
workflow_name: workflowName,
|
|
1724
1822
|
run_id: runId,
|
|
1725
1823
|
resourceId,
|
|
1726
1824
|
snapshot,
|
|
1727
|
-
createdAt: now,
|
|
1728
|
-
updatedAt: now
|
|
1825
|
+
createdAt: createdAt ? createdAt.toISOString() : now,
|
|
1826
|
+
updatedAt: updatedAt ? updatedAt.toISOString() : now
|
|
1729
1827
|
};
|
|
1730
|
-
const processedRecord = await this.
|
|
1828
|
+
const processedRecord = await this.#db.processRecord(persisting);
|
|
1731
1829
|
const columns = Object.keys(processedRecord);
|
|
1732
1830
|
const values = Object.values(processedRecord);
|
|
1733
1831
|
const updateMap = {
|
|
@@ -1738,7 +1836,7 @@ var WorkflowsStorageD1 = class extends WorkflowsStorage {
|
|
|
1738
1836
|
const query = createSqlBuilder().insert(fullTableName, columns, values, ["workflow_name", "run_id"], updateMap);
|
|
1739
1837
|
const { sql, params } = query.build();
|
|
1740
1838
|
try {
|
|
1741
|
-
await this.
|
|
1839
|
+
await this.#db.executeQuery({ sql, params });
|
|
1742
1840
|
} catch (error) {
|
|
1743
1841
|
throw new MastraError(
|
|
1744
1842
|
{
|
|
@@ -1756,7 +1854,7 @@ var WorkflowsStorageD1 = class extends WorkflowsStorage {
|
|
|
1756
1854
|
const { workflowName, runId } = params;
|
|
1757
1855
|
this.logger.debug("Loading workflow snapshot", { workflowName, runId });
|
|
1758
1856
|
try {
|
|
1759
|
-
const d = await this.
|
|
1857
|
+
const d = await this.#db.load({
|
|
1760
1858
|
tableName: TABLE_WORKFLOW_SNAPSHOT,
|
|
1761
1859
|
keys: {
|
|
1762
1860
|
workflow_name: workflowName,
|
|
@@ -1804,7 +1902,7 @@ var WorkflowsStorageD1 = class extends WorkflowsStorage {
|
|
|
1804
1902
|
resourceId,
|
|
1805
1903
|
status
|
|
1806
1904
|
} = {}) {
|
|
1807
|
-
const fullTableName = this.
|
|
1905
|
+
const fullTableName = this.#db.getTableName(TABLE_WORKFLOW_SNAPSHOT);
|
|
1808
1906
|
try {
|
|
1809
1907
|
const builder = createSqlBuilder().select().from(fullTableName);
|
|
1810
1908
|
const countBuilder = createSqlBuilder().count().from(fullTableName);
|
|
@@ -1814,7 +1912,7 @@ var WorkflowsStorageD1 = class extends WorkflowsStorage {
|
|
|
1814
1912
|
countBuilder.whereAnd("json_extract(snapshot, '$.status') = ?", status);
|
|
1815
1913
|
}
|
|
1816
1914
|
if (resourceId) {
|
|
1817
|
-
const hasResourceId = await this.
|
|
1915
|
+
const hasResourceId = await this.#db.hasColumn(fullTableName, "resourceId");
|
|
1818
1916
|
if (hasResourceId) {
|
|
1819
1917
|
builder.whereAnd("resourceId = ?", resourceId);
|
|
1820
1918
|
countBuilder.whereAnd("resourceId = ?", resourceId);
|
|
@@ -1840,14 +1938,14 @@ var WorkflowsStorageD1 = class extends WorkflowsStorage {
|
|
|
1840
1938
|
let total = 0;
|
|
1841
1939
|
if (perPage !== void 0 && page !== void 0) {
|
|
1842
1940
|
const { sql: countSql, params: countParams } = countBuilder.build();
|
|
1843
|
-
const countResult = await this.
|
|
1941
|
+
const countResult = await this.#db.executeQuery({
|
|
1844
1942
|
sql: countSql,
|
|
1845
1943
|
params: countParams,
|
|
1846
1944
|
first: true
|
|
1847
1945
|
});
|
|
1848
1946
|
total = Number(countResult?.count ?? 0);
|
|
1849
1947
|
}
|
|
1850
|
-
const results = await this.
|
|
1948
|
+
const results = await this.#db.executeQuery({ sql, params });
|
|
1851
1949
|
const runs = (isArrayOfRecords(results) ? results : []).map((row) => this.parseWorkflowRun(row));
|
|
1852
1950
|
return { runs, total: total || runs.length };
|
|
1853
1951
|
} catch (error) {
|
|
@@ -1870,7 +1968,7 @@ var WorkflowsStorageD1 = class extends WorkflowsStorage {
|
|
|
1870
1968
|
runId,
|
|
1871
1969
|
workflowName
|
|
1872
1970
|
}) {
|
|
1873
|
-
const fullTableName = this.
|
|
1971
|
+
const fullTableName = this.#db.getTableName(TABLE_WORKFLOW_SNAPSHOT);
|
|
1874
1972
|
try {
|
|
1875
1973
|
const conditions = [];
|
|
1876
1974
|
const params = [];
|
|
@@ -1884,7 +1982,7 @@ var WorkflowsStorageD1 = class extends WorkflowsStorage {
|
|
|
1884
1982
|
}
|
|
1885
1983
|
const whereClause = conditions.length > 0 ? "WHERE " + conditions.join(" AND ") : "";
|
|
1886
1984
|
const sql = `SELECT * FROM ${fullTableName} ${whereClause} ORDER BY createdAt DESC LIMIT 1`;
|
|
1887
|
-
const result = await this.
|
|
1985
|
+
const result = await this.#db.executeQuery({ sql, params, first: true });
|
|
1888
1986
|
if (!result) return null;
|
|
1889
1987
|
return this.parseWorkflowRun(result);
|
|
1890
1988
|
} catch (error) {
|
|
@@ -1901,11 +1999,11 @@ var WorkflowsStorageD1 = class extends WorkflowsStorage {
|
|
|
1901
1999
|
}
|
|
1902
2000
|
}
|
|
1903
2001
|
async deleteWorkflowRunById({ runId, workflowName }) {
|
|
1904
|
-
const fullTableName = this.
|
|
2002
|
+
const fullTableName = this.#db.getTableName(TABLE_WORKFLOW_SNAPSHOT);
|
|
1905
2003
|
try {
|
|
1906
2004
|
const sql = `DELETE FROM ${fullTableName} WHERE workflow_name = ? AND run_id = ?`;
|
|
1907
2005
|
const params = [workflowName, runId];
|
|
1908
|
-
await this.
|
|
2006
|
+
await this.#db.executeQuery({ sql, params });
|
|
1909
2007
|
} catch (error) {
|
|
1910
2008
|
throw new MastraError(
|
|
1911
2009
|
{
|
|
@@ -1925,7 +2023,6 @@ var WorkflowsStorageD1 = class extends WorkflowsStorage {
|
|
|
1925
2023
|
var D1Store = class extends MastraStorage {
|
|
1926
2024
|
client;
|
|
1927
2025
|
binding;
|
|
1928
|
-
// D1Database binding
|
|
1929
2026
|
tablePrefix;
|
|
1930
2027
|
stores;
|
|
1931
2028
|
/**
|
|
@@ -1980,22 +2077,21 @@ var D1Store = class extends MastraStorage {
|
|
|
1980
2077
|
error
|
|
1981
2078
|
);
|
|
1982
2079
|
}
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
}
|
|
2080
|
+
let scores;
|
|
2081
|
+
let workflows;
|
|
2082
|
+
let memory;
|
|
2083
|
+
if (this.binding) {
|
|
2084
|
+
const domainConfig = { binding: this.binding, tablePrefix: this.tablePrefix };
|
|
2085
|
+
scores = new ScoresStorageD1(domainConfig);
|
|
2086
|
+
workflows = new WorkflowsStorageD1(domainConfig);
|
|
2087
|
+
memory = new MemoryStorageD1(domainConfig);
|
|
2088
|
+
} else {
|
|
2089
|
+
const domainConfig = { client: this.client, tablePrefix: this.tablePrefix };
|
|
2090
|
+
scores = new ScoresStorageD1(domainConfig);
|
|
2091
|
+
workflows = new WorkflowsStorageD1(domainConfig);
|
|
2092
|
+
memory = new MemoryStorageD1(domainConfig);
|
|
2093
|
+
}
|
|
1997
2094
|
this.stores = {
|
|
1998
|
-
operations,
|
|
1999
2095
|
scores,
|
|
2000
2096
|
workflows,
|
|
2001
2097
|
memory
|
|
@@ -2007,165 +2103,13 @@ var D1Store = class extends MastraStorage {
|
|
|
2007
2103
|
resourceWorkingMemory: true,
|
|
2008
2104
|
hasColumn: true,
|
|
2009
2105
|
createTable: true,
|
|
2010
|
-
deleteMessages:
|
|
2011
|
-
|
|
2106
|
+
deleteMessages: true,
|
|
2107
|
+
observability: false,
|
|
2108
|
+
indexManagement: false,
|
|
2109
|
+
listScoresBySpan: true,
|
|
2110
|
+
agents: false
|
|
2012
2111
|
};
|
|
2013
2112
|
}
|
|
2014
|
-
async createTable({
|
|
2015
|
-
tableName,
|
|
2016
|
-
schema
|
|
2017
|
-
}) {
|
|
2018
|
-
return this.stores.operations.createTable({ tableName, schema });
|
|
2019
|
-
}
|
|
2020
|
-
/**
|
|
2021
|
-
* Alters table schema to add columns if they don't exist
|
|
2022
|
-
* @param tableName Name of the table
|
|
2023
|
-
* @param schema Schema of the table
|
|
2024
|
-
* @param ifNotExists Array of column names to add if they don't exist
|
|
2025
|
-
*/
|
|
2026
|
-
async alterTable({
|
|
2027
|
-
tableName,
|
|
2028
|
-
schema,
|
|
2029
|
-
ifNotExists
|
|
2030
|
-
}) {
|
|
2031
|
-
return this.stores.operations.alterTable({ tableName, schema, ifNotExists });
|
|
2032
|
-
}
|
|
2033
|
-
async clearTable({ tableName }) {
|
|
2034
|
-
return this.stores.operations.clearTable({ tableName });
|
|
2035
|
-
}
|
|
2036
|
-
async dropTable({ tableName }) {
|
|
2037
|
-
return this.stores.operations.dropTable({ tableName });
|
|
2038
|
-
}
|
|
2039
|
-
async hasColumn(table, column) {
|
|
2040
|
-
return this.stores.operations.hasColumn(table, column);
|
|
2041
|
-
}
|
|
2042
|
-
async insert({ tableName, record }) {
|
|
2043
|
-
return this.stores.operations.insert({ tableName, record });
|
|
2044
|
-
}
|
|
2045
|
-
async load({ tableName, keys }) {
|
|
2046
|
-
return this.stores.operations.load({ tableName, keys });
|
|
2047
|
-
}
|
|
2048
|
-
async getThreadById({ threadId }) {
|
|
2049
|
-
return this.stores.memory.getThreadById({ threadId });
|
|
2050
|
-
}
|
|
2051
|
-
async saveThread({ thread }) {
|
|
2052
|
-
return this.stores.memory.saveThread({ thread });
|
|
2053
|
-
}
|
|
2054
|
-
async updateThread({
|
|
2055
|
-
id,
|
|
2056
|
-
title,
|
|
2057
|
-
metadata
|
|
2058
|
-
}) {
|
|
2059
|
-
return this.stores.memory.updateThread({ id, title, metadata });
|
|
2060
|
-
}
|
|
2061
|
-
async deleteThread({ threadId }) {
|
|
2062
|
-
return this.stores.memory.deleteThread({ threadId });
|
|
2063
|
-
}
|
|
2064
|
-
async saveMessages(args) {
|
|
2065
|
-
return this.stores.memory.saveMessages(args);
|
|
2066
|
-
}
|
|
2067
|
-
async updateWorkflowResults({
|
|
2068
|
-
workflowName,
|
|
2069
|
-
runId,
|
|
2070
|
-
stepId,
|
|
2071
|
-
result,
|
|
2072
|
-
requestContext
|
|
2073
|
-
}) {
|
|
2074
|
-
return this.stores.workflows.updateWorkflowResults({ workflowName, runId, stepId, result, requestContext });
|
|
2075
|
-
}
|
|
2076
|
-
async updateWorkflowState({
|
|
2077
|
-
workflowName,
|
|
2078
|
-
runId,
|
|
2079
|
-
opts
|
|
2080
|
-
}) {
|
|
2081
|
-
return this.stores.workflows.updateWorkflowState({ workflowName, runId, opts });
|
|
2082
|
-
}
|
|
2083
|
-
async persistWorkflowSnapshot({
|
|
2084
|
-
workflowName,
|
|
2085
|
-
runId,
|
|
2086
|
-
resourceId,
|
|
2087
|
-
snapshot
|
|
2088
|
-
}) {
|
|
2089
|
-
return this.stores.workflows.persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot });
|
|
2090
|
-
}
|
|
2091
|
-
async loadWorkflowSnapshot(params) {
|
|
2092
|
-
return this.stores.workflows.loadWorkflowSnapshot(params);
|
|
2093
|
-
}
|
|
2094
|
-
async listWorkflowRuns(args = {}) {
|
|
2095
|
-
return this.stores.workflows.listWorkflowRuns(args);
|
|
2096
|
-
}
|
|
2097
|
-
async getWorkflowRunById({
|
|
2098
|
-
runId,
|
|
2099
|
-
workflowName
|
|
2100
|
-
}) {
|
|
2101
|
-
return this.stores.workflows.getWorkflowRunById({ runId, workflowName });
|
|
2102
|
-
}
|
|
2103
|
-
async deleteWorkflowRunById({ runId, workflowName }) {
|
|
2104
|
-
return this.stores.workflows.deleteWorkflowRunById({ runId, workflowName });
|
|
2105
|
-
}
|
|
2106
|
-
/**
|
|
2107
|
-
* Insert multiple records in a batch operation
|
|
2108
|
-
* @param tableName The table to insert into
|
|
2109
|
-
* @param records The records to insert
|
|
2110
|
-
*/
|
|
2111
|
-
async batchInsert({ tableName, records }) {
|
|
2112
|
-
return this.stores.operations.batchInsert({ tableName, records });
|
|
2113
|
-
}
|
|
2114
|
-
async updateMessages(_args) {
|
|
2115
|
-
return this.stores.memory.updateMessages(_args);
|
|
2116
|
-
}
|
|
2117
|
-
async getResourceById({ resourceId }) {
|
|
2118
|
-
return this.stores.memory.getResourceById({ resourceId });
|
|
2119
|
-
}
|
|
2120
|
-
async saveResource({ resource }) {
|
|
2121
|
-
return this.stores.memory.saveResource({ resource });
|
|
2122
|
-
}
|
|
2123
|
-
async updateResource({
|
|
2124
|
-
resourceId,
|
|
2125
|
-
workingMemory,
|
|
2126
|
-
metadata
|
|
2127
|
-
}) {
|
|
2128
|
-
return this.stores.memory.updateResource({ resourceId, workingMemory, metadata });
|
|
2129
|
-
}
|
|
2130
|
-
async getScoreById({ id: _id }) {
|
|
2131
|
-
return this.stores.scores.getScoreById({ id: _id });
|
|
2132
|
-
}
|
|
2133
|
-
async saveScore(score) {
|
|
2134
|
-
return this.stores.scores.saveScore(score);
|
|
2135
|
-
}
|
|
2136
|
-
async listScoresByRunId({
|
|
2137
|
-
runId: _runId,
|
|
2138
|
-
pagination: _pagination
|
|
2139
|
-
}) {
|
|
2140
|
-
return this.stores.scores.listScoresByRunId({ runId: _runId, pagination: _pagination });
|
|
2141
|
-
}
|
|
2142
|
-
async listScoresByEntityId({
|
|
2143
|
-
entityId: _entityId,
|
|
2144
|
-
entityType: _entityType,
|
|
2145
|
-
pagination: _pagination
|
|
2146
|
-
}) {
|
|
2147
|
-
return this.stores.scores.listScoresByEntityId({
|
|
2148
|
-
entityId: _entityId,
|
|
2149
|
-
entityType: _entityType,
|
|
2150
|
-
pagination: _pagination
|
|
2151
|
-
});
|
|
2152
|
-
}
|
|
2153
|
-
async listScoresByScorerId({
|
|
2154
|
-
scorerId,
|
|
2155
|
-
pagination,
|
|
2156
|
-
entityId,
|
|
2157
|
-
entityType,
|
|
2158
|
-
source
|
|
2159
|
-
}) {
|
|
2160
|
-
return this.stores.scores.listScoresByScorerId({ scorerId, pagination, entityId, entityType, source });
|
|
2161
|
-
}
|
|
2162
|
-
async listScoresBySpan({
|
|
2163
|
-
traceId,
|
|
2164
|
-
spanId,
|
|
2165
|
-
pagination
|
|
2166
|
-
}) {
|
|
2167
|
-
return this.stores.scores.listScoresBySpan({ traceId, spanId, pagination });
|
|
2168
|
-
}
|
|
2169
2113
|
/**
|
|
2170
2114
|
* Close the database connection
|
|
2171
2115
|
* No explicit cleanup needed for D1 in either REST or Workers Binding mode
|