@mastra/cloudflare-d1 0.0.0-remove-unused-model-providers-api-20251030210744 → 0.0.0-remove-ai-peer-dep-from-evals-20260105220639

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/dist/index.js CHANGED
@@ -1,11 +1,28 @@
1
1
  import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
2
- import { MastraStorage, StoreOperations, ScoresStorage, TABLE_SCORERS, WorkflowsStorage, TABLE_WORKFLOW_SNAPSHOT, ensureDate, MemoryStorage, TABLE_RESOURCES, TABLE_THREADS, TABLE_MESSAGES, resolveMessageLimit, serializeDate, safelyParseJSON } from '@mastra/core/storage';
2
+ import { MemoryStorage, TABLE_SCHEMAS, TABLE_THREADS, TABLE_MESSAGES, TABLE_RESOURCES, ensureDate, createStorageErrorId, normalizePerPage, calculatePagination, serializeDate, ScoresStorage, TABLE_SCORERS, WorkflowsStorage, TABLE_WORKFLOW_SNAPSHOT, MastraStorage, 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
- import { saveScorePayloadSchema } from '@mastra/core/scores';
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,1332 +253,1201 @@ function parseSelectIdentifier(column) {
236
253
  return column;
237
254
  }
238
255
 
239
- // src/storage/domains/utils.ts
240
- function isArrayOfRecords(value) {
241
- return value && Array.isArray(value) && value.length > 0;
242
- }
243
- function deserializeValue(value, type) {
244
- if (value === null || value === void 0) return null;
245
- if (typeof value === "string" && (value.startsWith("{") || value.startsWith("["))) {
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
- return value;
253
- }
254
-
255
- // src/storage/domains/memory/index.ts
256
- var MemoryStorageD1 = class extends MemoryStorage {
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
- async getResourceById({ resourceId }) {
263
- const resource = await this.operations.load({
264
- tableName: TABLE_RESOURCES,
265
- keys: { id: resourceId }
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
- if (!resource) return null;
268
- try {
269
- return {
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: "CLOUDFLARE_D1_STORAGE_GET_RESOURCE_BY_ID_ERROR",
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 saveResource({ resource }) {
292
- const fullTableName = this.operations.getTableName(TABLE_RESOURCES);
293
- const resourceToSave = {
294
- id: resource.id,
295
- workingMemory: resource.workingMemory,
296
- metadata: resource.metadata ? JSON.stringify(resource.metadata) : null,
297
- createdAt: resource.createdAt,
298
- updatedAt: resource.updatedAt
299
- };
300
- const processedRecord = await this.operations.processRecord(resourceToSave);
301
- const columns = Object.keys(processedRecord);
302
- const values = Object.values(processedRecord);
303
- const updateMap = {
304
- workingMemory: "excluded.workingMemory",
305
- metadata: "excluded.metadata",
306
- createdAt: "excluded.createdAt",
307
- updatedAt: "excluded.updatedAt"
308
- };
309
- const query = createSqlBuilder().insert(fullTableName, columns, values, ["id"], updateMap);
310
- const { sql, params } = query.build();
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
- await this.operations.executeQuery({ sql, params });
313
- return resource;
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: "CLOUDFLARE_D1_STORAGE_SAVE_RESOURCE_ERROR",
346
+ id: createStorageErrorId("CLOUDFLARE_D1", "WORKERS_BINDING_QUERY", "FAILED"),
318
347
  domain: ErrorDomain.STORAGE,
319
348
  category: ErrorCategory.THIRD_PARTY,
320
- text: `Failed to save resource to ${fullTableName}: ${error instanceof Error ? error.message : String(error)}`,
321
- details: { resourceId: resource.id }
349
+ details: { sql }
322
350
  },
323
351
  error
324
352
  );
325
353
  }
326
354
  }
327
- async updateResource({
328
- resourceId,
329
- workingMemory,
330
- metadata
355
+ async executeRestQuery({
356
+ sql,
357
+ params = [],
358
+ first = false
331
359
  }) {
332
- const existingResource = await this.getResourceById({ resourceId });
333
- if (!existingResource) {
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
- await this.operations.executeQuery({ sql, params });
360
- return updatedResource;
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: "CLOUDFLARE_D1_STORAGE_UPDATE_RESOURCE_ERROR",
378
+ id: createStorageErrorId("CLOUDFLARE_D1", "REST_QUERY", "FAILED"),
365
379
  domain: ErrorDomain.STORAGE,
366
380
  category: ErrorCategory.THIRD_PARTY,
367
- text: `Failed to update resource ${resourceId}: ${error instanceof Error ? error.message : String(error)}`,
368
- details: { resourceId }
381
+ details: { sql }
369
382
  },
370
383
  error
371
384
  );
372
385
  }
373
386
  }
374
- async getThreadById({ threadId }) {
375
- const thread = await this.operations.load({
376
- tableName: TABLE_THREADS,
377
- keys: { id: threadId }
378
- });
379
- if (!thread) return null;
380
- try {
381
- return {
382
- ...thread,
383
- createdAt: ensureDate(thread.createdAt),
384
- updatedAt: ensureDate(thread.updatedAt),
385
- metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata || "{}") : thread.metadata || {}
386
- };
387
- } catch (error) {
388
- const mastraError = new MastraError(
389
- {
390
- id: "CLOUDFLARE_D1_STORAGE_GET_THREAD_BY_ID_ERROR",
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;
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");
401
394
  }
402
395
  }
403
- /**
404
- * @deprecated use getThreadsByResourceIdPaginated instead
405
- */
406
- async getThreadsByResourceId({ resourceId }) {
407
- const fullTableName = this.operations.getTableName(TABLE_THREADS);
396
+ async getTableColumns(tableName) {
408
397
  try {
409
- const query = createSqlBuilder().select("*").from(fullTableName).where("resourceId = ?", resourceId);
410
- const { sql, params } = query.build();
411
- const results = await this.operations.executeQuery({ sql, params });
412
- return (isArrayOfRecords(results) ? results : []).map((thread) => ({
413
- ...thread,
414
- createdAt: ensureDate(thread.createdAt),
415
- updatedAt: ensureDate(thread.updatedAt),
416
- metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata || "{}") : thread.metadata || {}
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
417
406
  }));
418
407
  } catch (error) {
419
- const mastraError = new MastraError(
420
- {
421
- id: "CLOUDFLARE_D1_STORAGE_GET_THREADS_BY_RESOURCE_ID_ERROR",
422
- domain: ErrorDomain.STORAGE,
423
- category: ErrorCategory.THIRD_PARTY,
424
- text: `Error getting threads by resourceId ${resourceId}: ${error instanceof Error ? error.message : String(error)}`,
425
- details: { resourceId }
426
- },
427
- error
428
- );
429
- this.logger?.error(mastraError.toString());
430
- this.logger?.trackException(mastraError);
408
+ this.logger.warn(`Failed to get table columns for ${tableName}:`, error);
431
409
  return [];
432
410
  }
433
411
  }
434
- async getThreadsByResourceIdPaginated(args) {
435
- const { resourceId, page, perPage } = args;
436
- const fullTableName = this.operations.getTableName(TABLE_THREADS);
437
- const mapRowToStorageThreadType = (row) => ({
438
- ...row,
439
- createdAt: ensureDate(row.createdAt),
440
- updatedAt: ensureDate(row.updatedAt),
441
- metadata: typeof row.metadata === "string" ? JSON.parse(row.metadata || "{}") : row.metadata || {}
442
- });
443
- try {
444
- const countQuery = createSqlBuilder().count().from(fullTableName).where("resourceId = ?", resourceId);
445
- const countResult = await this.operations.executeQuery(countQuery.build());
446
- const total = Number(countResult?.[0]?.count ?? 0);
447
- const selectQuery = createSqlBuilder().select("*").from(fullTableName).where("resourceId = ?", resourceId).orderBy("createdAt", "DESC").limit(perPage).offset(page * perPage);
448
- const results = await this.operations.executeQuery(selectQuery.build());
449
- const threads = results.map(mapRowToStorageThreadType);
450
- return {
451
- threads,
452
- total,
453
- page,
454
- perPage,
455
- hasMore: page * perPage + threads.length < total
456
- };
412
+ serializeValue(value) {
413
+ if (value === null || value === void 0) {
414
+ return null;
415
+ }
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
+ }) {
446
+ try {
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}`);
457
462
  } catch (error) {
458
- const mastraError = new MastraError(
463
+ throw new MastraError(
459
464
  {
460
- id: "CLOUDFLARE_D1_STORAGE_GET_THREADS_BY_RESOURCE_ID_PAGINATED_ERROR",
465
+ id: createStorageErrorId("CLOUDFLARE_D1", "CREATE_TABLE", "FAILED"),
461
466
  domain: ErrorDomain.STORAGE,
462
467
  category: ErrorCategory.THIRD_PARTY,
463
- text: `Error getting threads by resourceId ${resourceId}: ${error instanceof Error ? error.message : String(error)}`,
464
- details: { resourceId }
468
+ details: { tableName }
465
469
  },
466
470
  error
467
471
  );
468
- this.logger?.error(mastraError.toString());
469
- this.logger?.trackException(mastraError);
470
- return {
471
- threads: [],
472
- total: 0,
473
- page,
474
- perPage,
475
- hasMore: false
476
- };
477
472
  }
478
473
  }
479
- async saveThread({ thread }) {
480
- const fullTableName = this.operations.getTableName(TABLE_THREADS);
481
- const threadToSave = {
482
- id: thread.id,
483
- resourceId: thread.resourceId,
484
- title: thread.title,
485
- metadata: thread.metadata ? JSON.stringify(thread.metadata) : null,
486
- createdAt: thread.createdAt.toISOString(),
487
- updatedAt: thread.updatedAt.toISOString()
488
- };
489
- const processedRecord = await this.operations.processRecord(threadToSave);
490
- const columns = Object.keys(processedRecord);
491
- const values = Object.values(processedRecord);
492
- const updateMap = {
493
- resourceId: "excluded.resourceId",
494
- title: "excluded.title",
495
- metadata: "excluded.metadata",
496
- createdAt: "excluded.createdAt",
497
- updatedAt: "excluded.updatedAt"
498
- };
499
- const query = createSqlBuilder().insert(fullTableName, columns, values, ["id"], updateMap);
500
- const { sql, params } = query.build();
474
+ async clearTable({ tableName }) {
501
475
  try {
502
- await this.operations.executeQuery({ sql, params });
503
- return thread;
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}`);
504
481
  } catch (error) {
505
482
  throw new MastraError(
506
483
  {
507
- id: "CLOUDFLARE_D1_STORAGE_SAVE_THREAD_ERROR",
484
+ id: createStorageErrorId("CLOUDFLARE_D1", "CLEAR_TABLE", "FAILED"),
508
485
  domain: ErrorDomain.STORAGE,
509
486
  category: ErrorCategory.THIRD_PARTY,
510
- text: `Failed to save thread to ${fullTableName}: ${error instanceof Error ? error.message : String(error)}`,
511
- details: { threadId: thread.id }
487
+ details: { tableName }
512
488
  },
513
489
  error
514
490
  );
515
491
  }
516
492
  }
517
- async updateThread({
518
- id,
519
- title,
520
- metadata
521
- }) {
522
- const thread = await this.getThreadById({ threadId: id });
493
+ async dropTable({ tableName }) {
523
494
  try {
524
- if (!thread) {
525
- throw new Error(`Thread ${id} not found`);
526
- }
527
- const fullTableName = this.operations.getTableName(TABLE_THREADS);
528
- const mergedMetadata = {
529
- ...typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
530
- ...metadata
531
- };
532
- const updatedAt = /* @__PURE__ */ new Date();
533
- const columns = ["title", "metadata", "updatedAt"];
534
- const values = [title, JSON.stringify(mergedMetadata), updatedAt.toISOString()];
535
- const query = createSqlBuilder().update(fullTableName, columns, values).where("id = ?", id);
536
- const { sql, params } = query.build();
537
- await this.operations.executeQuery({ sql, params });
538
- return {
539
- ...thread,
540
- title,
541
- metadata: {
542
- ...typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
543
- ...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 }
544
506
  },
545
- updatedAt
546
- };
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
+ }
547
525
  } catch (error) {
548
526
  throw new MastraError(
549
527
  {
550
- id: "CLOUDFLARE_D1_STORAGE_UPDATE_THREAD_ERROR",
528
+ id: createStorageErrorId("CLOUDFLARE_D1", "ALTER_TABLE", "FAILED"),
551
529
  domain: ErrorDomain.STORAGE,
552
530
  category: ErrorCategory.THIRD_PARTY,
553
- text: `Failed to update thread ${id}: ${error instanceof Error ? error.message : String(error)}`,
554
- details: { threadId: id }
531
+ details: { tableName: args.tableName }
555
532
  },
556
533
  error
557
534
  );
558
535
  }
559
536
  }
560
- async deleteThread({ threadId }) {
561
- const fullTableName = this.operations.getTableName(TABLE_THREADS);
537
+ async insert({ tableName, record }) {
562
538
  try {
563
- const deleteThreadQuery = createSqlBuilder().delete(fullTableName).where("id = ?", threadId);
564
- const { sql: threadSql, params: threadParams } = deleteThreadQuery.build();
565
- await this.operations.executeQuery({ sql: threadSql, params: threadParams });
566
- const messagesTableName = this.operations.getTableName(TABLE_MESSAGES);
567
- const deleteMessagesQuery = createSqlBuilder().delete(messagesTableName).where("thread_id = ?", threadId);
568
- const { sql: messagesSql, params: messagesParams } = deleteMessagesQuery.build();
569
- await this.operations.executeQuery({ sql: messagesSql, params: messagesParams });
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 });
570
546
  } catch (error) {
571
547
  throw new MastraError(
572
548
  {
573
- id: "CLOUDFLARE_D1_STORAGE_DELETE_THREAD_ERROR",
549
+ id: createStorageErrorId("CLOUDFLARE_D1", "INSERT", "FAILED"),
574
550
  domain: ErrorDomain.STORAGE,
575
551
  category: ErrorCategory.THIRD_PARTY,
576
- text: `Failed to delete thread ${threadId}: ${error instanceof Error ? error.message : String(error)}`,
577
- details: { threadId }
552
+ details: { tableName }
578
553
  },
579
554
  error
580
555
  );
581
556
  }
582
557
  }
583
- async saveMessages(args) {
584
- const { messages, format = "v1" } = args;
585
- if (messages.length === 0) return [];
558
+ async batchInsert({ tableName, records }) {
586
559
  try {
587
- const now = /* @__PURE__ */ new Date();
588
- const threadId = messages[0]?.threadId;
589
- for (const [i, message] of messages.entries()) {
590
- if (!message.id) throw new Error(`Message at index ${i} missing id`);
591
- if (!message.threadId) {
592
- throw new Error(`Message at index ${i} missing threadId`);
593
- }
594
- if (!message.content) {
595
- throw new Error(`Message at index ${i} missing content`);
596
- }
597
- if (!message.role) {
598
- throw new Error(`Message at index ${i} missing role`);
599
- }
600
- if (!message.resourceId) {
601
- throw new Error(`Message at index ${i} missing resourceId`);
602
- }
603
- const thread = await this.getThreadById({ threadId: message.threadId });
604
- if (!thread) {
605
- throw new Error(`Thread ${message.threadId} not found`);
606
- }
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 });
607
569
  }
608
- const messagesToInsert = messages.map((message) => {
609
- const createdAt = message.createdAt ? new Date(message.createdAt) : now;
610
- return {
611
- id: message.id,
612
- thread_id: message.threadId,
613
- content: typeof message.content === "string" ? message.content : JSON.stringify(message.content),
614
- createdAt: createdAt.toISOString(),
615
- role: message.role,
616
- type: message.type || "v2",
617
- resourceId: message.resourceId
618
- };
619
- });
620
- await Promise.all([
621
- this.operations.batchUpsert({
622
- tableName: TABLE_MESSAGES,
623
- records: messagesToInsert
624
- }),
625
- // Update thread's updatedAt timestamp
626
- this.operations.executeQuery({
627
- sql: `UPDATE ${this.operations.getTableName(TABLE_THREADS)} SET updatedAt = ? WHERE id = ?`,
628
- params: [now.toISOString(), threadId]
629
- })
630
- ]);
631
- this.logger.debug(`Saved ${messages.length} messages`);
632
- const list = new MessageList().add(messages, "memory");
633
- if (format === `v2`) return list.get.all.v2();
634
- return list.get.all.v1();
635
570
  } catch (error) {
636
571
  throw new MastraError(
637
572
  {
638
- id: "CLOUDFLARE_D1_STORAGE_SAVE_MESSAGES_ERROR",
573
+ id: createStorageErrorId("CLOUDFLARE_D1", "BATCH_INSERT", "FAILED"),
639
574
  domain: ErrorDomain.STORAGE,
640
575
  category: ErrorCategory.THIRD_PARTY,
641
- text: `Failed to save messages: ${error instanceof Error ? error.message : String(error)}`
576
+ details: { tableName }
642
577
  },
643
578
  error
644
579
  );
645
580
  }
646
581
  }
647
- async _getIncludedMessages(threadId, selectBy) {
648
- if (!threadId.trim()) throw new Error("threadId must be a non-empty string");
649
- const include = selectBy?.include;
650
- if (!include) return null;
651
- const unionQueries = [];
652
- const params = [];
653
- let paramIdx = 1;
654
- for (const inc of include) {
655
- const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
656
- const searchId = inc.threadId || threadId;
657
- unionQueries.push(`
658
- SELECT * FROM (
659
- WITH ordered_messages AS (
660
- SELECT
661
- *,
662
- ROW_NUMBER() OVER (ORDER BY createdAt ASC) AS row_num
663
- FROM ${this.operations.getTableName(TABLE_MESSAGES)}
664
- WHERE thread_id = ?
665
- )
666
- SELECT
667
- m.id,
668
- m.content,
669
- m.role,
670
- m.type,
671
- m.createdAt,
672
- m.thread_id AS threadId,
673
- m.resourceId
674
- FROM ordered_messages m
675
- WHERE m.id = ?
676
- OR EXISTS (
677
- SELECT 1 FROM ordered_messages target
678
- WHERE target.id = ?
679
- AND (
680
- (m.row_num <= target.row_num + ? AND m.row_num > target.row_num)
681
- OR
682
- (m.row_num >= target.row_num - ? AND m.row_num < target.row_num)
683
- )
684
- )
685
- ) AS query_${paramIdx}
686
- `);
687
- params.push(searchId, id, id, withNextMessages, withPreviousMessages);
688
- 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
+ );
689
617
  }
690
- const finalQuery = unionQueries.join(" UNION ALL ") + " ORDER BY createdAt ASC";
691
- const messages = await this.operations.executeQuery({ sql: finalQuery, params });
692
- if (!Array.isArray(messages)) {
693
- return [];
618
+ }
619
+ async processRecord(record) {
620
+ const processed = {};
621
+ for (const [key, value] of Object.entries(record)) {
622
+ processed[key] = this.serializeValue(value);
694
623
  }
695
- const processedMessages = messages.map((message) => {
696
- const processedMsg = {};
697
- for (const [key, value] of Object.entries(message)) {
698
- if (key === `type` && value === `v2`) continue;
699
- processedMsg[key] = deserializeValue(value);
700
- }
701
- return processedMsg;
702
- });
703
- return processedMessages;
624
+ return processed;
704
625
  }
705
- async getMessages({
706
- threadId,
707
- resourceId,
708
- selectBy,
709
- format
710
- }) {
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);
711
634
  try {
712
- if (!threadId.trim()) throw new Error("threadId must be a non-empty string");
713
- const fullTableName = this.operations.getTableName(TABLE_MESSAGES);
714
- const limit = resolveMessageLimit({
715
- last: selectBy?.last,
716
- defaultLimit: 40
717
- });
718
- const include = selectBy?.include || [];
719
- const messages = [];
720
- if (include.length) {
721
- const includeResult = await this._getIncludedMessages(threadId, selectBy);
722
- if (Array.isArray(includeResult)) messages.push(...includeResult);
723
- }
724
- const excludeIds = messages.map((m) => m.id);
725
- const query = createSqlBuilder().select(["id", "content", "role", "type", "createdAt", "thread_id AS threadId"]).from(fullTableName).where("thread_id = ?", threadId);
726
- if (excludeIds.length > 0) {
727
- query.andWhere(`id NOT IN (${excludeIds.map(() => "?").join(",")})`, ...excludeIds);
728
- }
729
- query.orderBy("createdAt", "DESC").limit(limit);
730
- const { sql, params } = query.build();
731
- const result = await this.operations.executeQuery({ sql, params });
732
- if (Array.isArray(result)) messages.push(...result);
733
- messages.sort((a, b) => {
734
- const aRecord = a;
735
- const bRecord = b;
736
- const timeA = new Date(aRecord.createdAt).getTime();
737
- const timeB = new Date(bRecord.createdAt).getTime();
738
- return timeA - timeB;
739
- });
740
- const processedMessages = messages.map((message) => {
741
- const processedMsg = {};
742
- for (const [key, value] of Object.entries(message)) {
743
- if (key === `type` && value === `v2`) continue;
744
- processedMsg[key] = deserializeValue(value);
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
+ }
745
659
  }
746
- return processedMsg;
747
- });
748
- this.logger.debug(`Retrieved ${messages.length} messages for thread ${threadId}`);
749
- const list = new MessageList().add(processedMessages, "memory");
750
- if (format === `v2`) return list.get.all.v2();
751
- return list.get.all.v1();
660
+ this.logger.debug(
661
+ `Processed batch ${Math.floor(i / batchSize) + 1} of ${Math.ceil(records.length / batchSize)}`
662
+ );
663
+ }
664
+ this.logger.debug(`Successfully batch upserted ${records.length} records into ${tableName}`);
752
665
  } catch (error) {
753
- const mastraError = new MastraError(
666
+ throw new MastraError(
754
667
  {
755
- id: "CLOUDFLARE_D1_STORAGE_GET_MESSAGES_ERROR",
668
+ id: createStorageErrorId("CLOUDFLARE_D1", "BATCH_UPSERT", "FAILED"),
756
669
  domain: ErrorDomain.STORAGE,
757
670
  category: ErrorCategory.THIRD_PARTY,
758
- text: `Failed to retrieve messages for thread ${threadId}: ${error instanceof Error ? error.message : String(error)}`,
759
- details: { threadId, resourceId: resourceId ?? "" }
671
+ text: `Failed to batch upsert into ${tableName}: ${error instanceof Error ? error.message : String(error)}`,
672
+ details: { tableName }
760
673
  },
761
674
  error
762
675
  );
763
- this.logger?.error(mastraError.toString());
764
- this.logger?.trackException(mastraError);
765
- throw mastraError;
766
676
  }
767
677
  }
768
- async listMessagesById({ messageIds }) {
769
- if (messageIds.length === 0) return [];
770
- const fullTableName = this.operations.getTableName(TABLE_MESSAGES);
771
- const messages = [];
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"]
695
+ });
696
+ }
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;
772
708
  try {
773
- const query = createSqlBuilder().select(["id", "content", "role", "type", "createdAt", "thread_id AS threadId", "resourceId"]).from(fullTableName).where(`id in (${messageIds.map(() => "?").join(",")})`, ...messageIds);
774
- query.orderBy("createdAt", "DESC");
775
- const { sql, params } = query.build();
776
- const result = await this.operations.executeQuery({ sql, params });
777
- if (Array.isArray(result)) messages.push(...result);
778
- const processedMessages = messages.map((message) => {
779
- const processedMsg = {};
780
- for (const [key, value] of Object.entries(message)) {
781
- if (key === `type` && value === `v2`) continue;
782
- processedMsg[key] = deserializeValue(value);
783
- }
784
- return processedMsg;
785
- });
786
- this.logger.debug(`Retrieved ${messages.length} messages`);
787
- const list = new MessageList().add(processedMessages, "memory");
788
- return list.get.all.v2();
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
+ };
789
715
  } catch (error) {
790
716
  const mastraError = new MastraError(
791
717
  {
792
- id: "CLOUDFLARE_D1_STORAGE_GET_MESSAGES_BY_ID_ERROR",
718
+ id: createStorageErrorId("CLOUDFLARE_D1", "GET_RESOURCE_BY_ID", "FAILED"),
793
719
  domain: ErrorDomain.STORAGE,
794
720
  category: ErrorCategory.THIRD_PARTY,
795
- text: `Failed to retrieve messages by ID: ${error instanceof Error ? error.message : String(error)}`,
796
- details: { messageIds: JSON.stringify(messageIds) }
721
+ text: `Error processing resource ${resourceId}: ${error instanceof Error ? error.message : String(error)}`,
722
+ details: { resourceId }
797
723
  },
798
724
  error
799
725
  );
800
726
  this.logger?.error(mastraError.toString());
801
727
  this.logger?.trackException(mastraError);
802
- throw mastraError;
728
+ return null;
803
729
  }
804
730
  }
805
- async listMessages(args) {
806
- const { threadId, resourceId, include, filter, limit, offset = 0, orderBy } = args;
807
- if (!threadId.trim()) {
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) {
808
755
  throw new MastraError(
809
756
  {
810
- id: "STORAGE_CLOUDFLARE_D1_LIST_MESSAGES_INVALID_THREAD_ID",
757
+ id: createStorageErrorId("CLOUDFLARE_D1", "SAVE_RESOURCE", "FAILED"),
811
758
  domain: ErrorDomain.STORAGE,
812
759
  category: ErrorCategory.THIRD_PARTY,
813
- details: { threadId }
760
+ text: `Failed to save resource to ${fullTableName}: ${error instanceof Error ? error.message : String(error)}`,
761
+ details: { resourceId: resource.id }
814
762
  },
815
- new Error("threadId must be a non-empty string")
763
+ error
816
764
  );
817
765
  }
818
- try {
819
- let perPage = 40;
820
- if (limit !== void 0) {
821
- if (limit === false) {
822
- perPage = Number.MAX_SAFE_INTEGER;
823
- } else if (limit === 0) {
824
- perPage = 0;
825
- } else if (typeof limit === "number" && limit > 0) {
826
- perPage = limit;
827
- }
828
- }
829
- const page = perPage === 0 ? 0 : Math.floor(offset / perPage);
830
- const sortField = orderBy?.field || "createdAt";
831
- const sortDirection = orderBy?.direction || "DESC";
832
- const fullTableName = this.operations.getTableName(TABLE_MESSAGES);
833
- let query = `
834
- SELECT id, content, role, type, createdAt, thread_id AS threadId, resourceId
835
- FROM ${fullTableName}
836
- WHERE thread_id = ?
837
- `;
838
- const queryParams = [threadId];
839
- if (resourceId) {
840
- query += ` AND resourceId = ?`;
841
- queryParams.push(resourceId);
842
- }
843
- const dateRange = filter?.dateRange;
844
- if (dateRange?.start) {
845
- const startDate = dateRange.start instanceof Date ? serializeDate(dateRange.start) : serializeDate(new Date(dateRange.start));
846
- query += ` AND createdAt >= ?`;
847
- queryParams.push(startDate);
848
- }
849
- if (dateRange?.end) {
850
- const endDate = dateRange.end instanceof Date ? serializeDate(dateRange.end) : serializeDate(new Date(dateRange.end));
851
- query += ` AND createdAt <= ?`;
852
- queryParams.push(endDate);
853
- }
854
- const orderByField = sortField === "createdAt" ? "createdAt" : `"${sortField}"`;
855
- const orderByDirection = sortDirection === "ASC" ? "ASC" : "DESC";
856
- query += ` ORDER BY ${orderByField} ${orderByDirection}`;
857
- if (perPage !== Number.MAX_SAFE_INTEGER) {
858
- query += ` LIMIT ? OFFSET ?`;
859
- queryParams.push(perPage, offset);
860
- }
861
- const results = await this.operations.executeQuery({ sql: query, params: queryParams });
862
- const paginatedMessages = (isArrayOfRecords(results) ? results : []).map((message) => {
863
- const processedMsg = {};
864
- for (const [key, value] of Object.entries(message)) {
865
- if (key === `type` && value === `v2`) continue;
866
- processedMsg[key] = deserializeValue(value);
867
- }
868
- return processedMsg;
869
- });
870
- const paginatedCount = paginatedMessages.length;
871
- let countQuery = `SELECT count() as count FROM ${fullTableName} WHERE thread_id = ?`;
872
- const countParams = [threadId];
873
- if (resourceId) {
874
- countQuery += ` AND resourceId = ?`;
875
- countParams.push(resourceId);
876
- }
877
- if (dateRange?.start) {
878
- const startDate = dateRange.start instanceof Date ? serializeDate(dateRange.start) : serializeDate(new Date(dateRange.start));
879
- countQuery += ` AND createdAt >= ?`;
880
- countParams.push(startDate);
881
- }
882
- if (dateRange?.end) {
883
- const endDate = dateRange.end instanceof Date ? serializeDate(dateRange.end) : serializeDate(new Date(dateRange.end));
884
- countQuery += ` AND createdAt <= ?`;
885
- countParams.push(endDate);
886
- }
887
- const countResult = await this.operations.executeQuery({ sql: countQuery, params: countParams });
888
- const total = Number(countResult[0]?.count ?? 0);
889
- if (total === 0 && paginatedCount === 0) {
890
- return {
891
- messages: [],
892
- total: 0,
893
- page,
894
- perPage,
895
- hasMore: false
896
- };
897
- }
898
- const messageIds = new Set(paginatedMessages.map((m) => m.id));
899
- let includeMessages = [];
900
- if (include && include.length > 0) {
901
- const selectBy = { include };
902
- const includeResult = await this._getIncludedMessages(threadId, selectBy);
903
- if (Array.isArray(includeResult)) {
904
- includeMessages = includeResult;
905
- for (const includeMsg of includeMessages) {
906
- if (!messageIds.has(includeMsg.id)) {
907
- paginatedMessages.push(includeMsg);
908
- messageIds.add(includeMsg.id);
909
- }
910
- }
911
- }
912
- }
913
- const list = new MessageList().add(paginatedMessages, "memory");
914
- let finalMessages = list.get.all.v2();
915
- finalMessages = finalMessages.sort((a, b) => {
916
- const aValue = sortField === "createdAt" ? new Date(a.createdAt).getTime() : a[sortField];
917
- const bValue = sortField === "createdAt" ? new Date(b.createdAt).getTime() : b[sortField];
918
- if (aValue === bValue) {
919
- return a.id.localeCompare(b.id);
920
- }
921
- return sortDirection === "ASC" ? aValue - bValue : bValue - aValue;
922
- });
923
- const returnedThreadMessageIds = new Set(finalMessages.filter((m) => m.threadId === threadId).map((m) => m.id));
924
- const allThreadMessagesReturned = returnedThreadMessageIds.size >= total;
925
- const hasMore = limit === false ? false : allThreadMessagesReturned ? false : offset + paginatedCount < total;
926
- return {
927
- messages: finalMessages,
928
- total,
929
- page,
930
- perPage,
931
- hasMore
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()
932
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;
933
801
  } catch (error) {
934
- const mastraError = new MastraError(
802
+ throw new MastraError(
935
803
  {
936
- id: "CLOUDFLARE_D1_STORAGE_LIST_MESSAGES_ERROR",
804
+ id: createStorageErrorId("CLOUDFLARE_D1", "UPDATE_RESOURCE", "FAILED"),
937
805
  domain: ErrorDomain.STORAGE,
938
806
  category: ErrorCategory.THIRD_PARTY,
939
- text: `Failed to list messages for thread ${threadId}: ${error instanceof Error ? error.message : String(error)}`,
940
- details: {
941
- threadId,
942
- resourceId: resourceId ?? ""
943
- }
807
+ text: `Failed to update resource ${resourceId}: ${error instanceof Error ? error.message : String(error)}`,
808
+ details: { resourceId }
944
809
  },
945
810
  error
946
811
  );
947
- this.logger?.error?.(mastraError.toString());
948
- this.logger?.trackException?.(mastraError);
949
- return {
950
- messages: [],
951
- total: 0,
952
- page: Math.floor(offset / (limit === false ? Number.MAX_SAFE_INTEGER : limit || 40)),
953
- perPage: limit === false ? Number.MAX_SAFE_INTEGER : limit || 40,
954
- hasMore: false
955
- };
956
812
  }
957
813
  }
958
- /**
959
- * @todo When migrating from getThreadsByResourceIdPaginated to this method,
960
- * implement orderBy and sortDirection support for full sorting capabilities
961
- */
962
- async listThreadsByResourceId(args) {
963
- const { resourceId, limit, offset } = args;
964
- const page = Math.floor(offset / limit);
965
- const perPage = limit;
966
- return this.getThreadsByResourceIdPaginated({ resourceId, page, perPage });
967
- }
968
- async getMessagesPaginated({
969
- threadId,
970
- resourceId,
971
- selectBy,
972
- format
973
- }) {
974
- const { dateRange, page = 0, perPage: perPageInput } = selectBy?.pagination || {};
975
- const { start: fromDate, end: toDate } = dateRange || {};
976
- const perPage = perPageInput !== void 0 ? perPageInput : resolveMessageLimit({ last: selectBy?.last, defaultLimit: 40 });
977
- const fullTableName = this.operations.getTableName(TABLE_MESSAGES);
978
- const messages = [];
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;
979
820
  try {
980
- if (!threadId.trim()) throw new Error("threadId must be a non-empty string");
981
- if (selectBy?.include?.length) {
982
- const includeResult = await this._getIncludedMessages(threadId, selectBy);
983
- if (Array.isArray(includeResult)) messages.push(...includeResult);
984
- }
985
- const countQuery = createSqlBuilder().count().from(fullTableName).where("thread_id = ?", threadId);
986
- if (fromDate) {
987
- countQuery.andWhere("createdAt >= ?", serializeDate(fromDate));
988
- }
989
- if (toDate) {
990
- countQuery.andWhere("createdAt <= ?", serializeDate(toDate));
991
- }
992
- const countResult = await this.operations.executeQuery(countQuery.build());
993
- const total = Number(countResult[0]?.count ?? 0);
994
- if (total === 0 && messages.length === 0) {
995
- return {
996
- messages: [],
997
- total: 0,
998
- page,
999
- perPage,
1000
- hasMore: false
1001
- };
1002
- }
1003
- const excludeIds = messages.map((m) => m.id);
1004
- const excludeCondition = excludeIds.length > 0 ? `AND id NOT IN (${excludeIds.map(() => "?").join(",")})` : "";
1005
- let query;
1006
- let queryParams = [threadId];
1007
- if (fromDate) {
1008
- queryParams.push(serializeDate(fromDate));
1009
- }
1010
- if (toDate) {
1011
- queryParams.push(serializeDate(toDate));
1012
- }
1013
- if (excludeIds.length > 0) {
1014
- queryParams.push(...excludeIds);
1015
- }
1016
- if (selectBy?.last && selectBy.last > 0) {
1017
- query = `
1018
- SELECT id, content, role, type, createdAt, thread_id AS threadId, resourceId
1019
- FROM ${fullTableName}
1020
- WHERE thread_id = ?
1021
- ${fromDate ? "AND createdAt >= ?" : ""}
1022
- ${toDate ? "AND createdAt <= ?" : ""}
1023
- ${excludeCondition}
1024
- ORDER BY createdAt DESC
1025
- LIMIT ?
1026
- `;
1027
- queryParams.push(selectBy.last);
1028
- } else {
1029
- query = `
1030
- SELECT id, content, role, type, createdAt, thread_id AS threadId, resourceId
1031
- FROM ${fullTableName}
1032
- WHERE thread_id = ?
1033
- ${fromDate ? "AND createdAt >= ?" : ""}
1034
- ${toDate ? "AND createdAt <= ?" : ""}
1035
- ${excludeCondition}
1036
- ORDER BY createdAt DESC
1037
- LIMIT ? OFFSET ?
1038
- `;
1039
- queryParams.push(perPage, page * perPage);
1040
- }
1041
- const results = await this.operations.executeQuery({ sql: query, params: queryParams });
1042
- const processedMessages = results.map((message) => {
1043
- const processedMsg = {};
1044
- for (const [key, value] of Object.entries(message)) {
1045
- if (key === `type` && value === `v2`) continue;
1046
- processedMsg[key] = deserializeValue(value);
1047
- }
1048
- return processedMsg;
1049
- });
1050
- if (selectBy?.last) {
1051
- processedMessages.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
1052
- }
1053
- const list = new MessageList().add(processedMessages, "memory");
1054
- messages.push(...format === `v2` ? list.get.all.v2() : list.get.all.v1());
1055
821
  return {
1056
- messages,
1057
- total,
1058
- page,
1059
- perPage,
1060
- hasMore: selectBy?.last ? false : page * perPage + messages.length < total
822
+ ...thread,
823
+ createdAt: ensureDate(thread.createdAt),
824
+ updatedAt: ensureDate(thread.updatedAt),
825
+ metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata || "{}") : thread.metadata || {}
1061
826
  };
1062
827
  } catch (error) {
1063
828
  const mastraError = new MastraError(
1064
829
  {
1065
- id: "CLOUDFLARE_D1_STORAGE_GET_MESSAGES_PAGINATED_ERROR",
830
+ id: createStorageErrorId("CLOUDFLARE_D1", "GET_THREAD_BY_ID", "FAILED"),
1066
831
  domain: ErrorDomain.STORAGE,
1067
832
  category: ErrorCategory.THIRD_PARTY,
1068
- text: `Failed to retrieve messages for thread ${threadId}: ${error instanceof Error ? error.message : String(error)}`,
1069
- details: { threadId, resourceId: resourceId ?? "" }
833
+ text: `Error processing thread ${threadId}: ${error instanceof Error ? error.message : String(error)}`,
834
+ details: { threadId }
1070
835
  },
1071
836
  error
1072
837
  );
1073
838
  this.logger?.error(mastraError.toString());
1074
839
  this.logger?.trackException(mastraError);
1075
- return {
1076
- messages: [],
1077
- total: 0,
1078
- page,
1079
- perPage,
1080
- hasMore: false
1081
- };
840
+ return null;
1082
841
  }
1083
842
  }
1084
- async updateMessages(args) {
1085
- const { messages } = args;
1086
- this.logger.debug("Updating messages", { count: messages.length });
1087
- if (!messages.length) {
1088
- return [];
843
+ async listThreadsByResourceId(args) {
844
+ const { resourceId, page = 0, perPage: perPageInput, orderBy } = args;
845
+ const perPage = normalizePerPage(perPageInput, 100);
846
+ if (page < 0) {
847
+ throw new MastraError(
848
+ {
849
+ id: createStorageErrorId("CLOUDFLARE_D1", "LIST_THREADS_BY_RESOURCE_ID", "INVALID_PAGE"),
850
+ domain: ErrorDomain.STORAGE,
851
+ category: ErrorCategory.USER,
852
+ details: { page }
853
+ },
854
+ new Error("page must be >= 0")
855
+ );
1089
856
  }
1090
- const messageIds = messages.map((m) => m.id);
1091
- const fullTableName = this.operations.getTableName(TABLE_MESSAGES);
1092
- const threadsTableName = this.operations.getTableName(TABLE_THREADS);
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
+ });
1093
866
  try {
1094
- const placeholders = messageIds.map(() => "?").join(",");
1095
- const selectQuery = `SELECT id, content, role, type, createdAt, thread_id AS threadId, resourceId FROM ${fullTableName} WHERE id IN (${placeholders})`;
1096
- const existingMessages = await this.operations.executeQuery({ sql: selectQuery, params: messageIds });
1097
- if (existingMessages.length === 0) {
1098
- return [];
1099
- }
1100
- const parsedExistingMessages = existingMessages.map((msg) => {
1101
- if (typeof msg.content === "string") {
1102
- try {
1103
- msg.content = JSON.parse(msg.content);
1104
- } catch {
1105
- }
1106
- }
1107
- return msg;
1108
- });
1109
- const threadIdsToUpdate = /* @__PURE__ */ new Set();
1110
- const updateQueries = [];
1111
- for (const existingMessage of parsedExistingMessages) {
1112
- const updatePayload = messages.find((m) => m.id === existingMessage.id);
1113
- if (!updatePayload) continue;
1114
- const { id, ...fieldsToUpdate } = updatePayload;
1115
- if (Object.keys(fieldsToUpdate).length === 0) continue;
1116
- threadIdsToUpdate.add(existingMessage.threadId);
1117
- if ("threadId" in updatePayload && updatePayload.threadId && updatePayload.threadId !== existingMessage.threadId) {
1118
- threadIdsToUpdate.add(updatePayload.threadId);
1119
- }
1120
- const setClauses = [];
1121
- const values = [];
1122
- const updatableFields = { ...fieldsToUpdate };
1123
- if (updatableFields.content) {
1124
- const existingContent = existingMessage.content || {};
1125
- const newContent = {
1126
- ...existingContent,
1127
- ...updatableFields.content,
1128
- // Deep merge metadata if it exists on both
1129
- ...existingContent?.metadata && updatableFields.content.metadata ? {
1130
- metadata: {
1131
- ...existingContent.metadata,
1132
- ...updatableFields.content.metadata
1133
- }
1134
- } : {}
1135
- };
1136
- setClauses.push(`content = ?`);
1137
- values.push(JSON.stringify(newContent));
1138
- delete updatableFields.content;
1139
- }
1140
- for (const key in updatableFields) {
1141
- if (Object.prototype.hasOwnProperty.call(updatableFields, key)) {
1142
- const dbColumn = key === "threadId" ? "thread_id" : key;
1143
- setClauses.push(`${dbColumn} = ?`);
1144
- values.push(updatableFields[key]);
1145
- }
1146
- }
1147
- if (setClauses.length > 0) {
1148
- values.push(id);
1149
- const updateQuery = `UPDATE ${fullTableName} SET ${setClauses.join(", ")} WHERE id = ?`;
1150
- updateQueries.push({ sql: updateQuery, params: values });
1151
- }
1152
- }
1153
- for (const query of updateQueries) {
1154
- await this.operations.executeQuery(query);
1155
- }
1156
- if (threadIdsToUpdate.size > 0) {
1157
- const threadPlaceholders = Array.from(threadIdsToUpdate).map(() => "?").join(",");
1158
- const threadUpdateQuery = `UPDATE ${threadsTableName} SET updatedAt = ? WHERE id IN (${threadPlaceholders})`;
1159
- const threadUpdateParams = [(/* @__PURE__ */ new Date()).toISOString(), ...Array.from(threadIdsToUpdate)];
1160
- await this.operations.executeQuery({ sql: threadUpdateQuery, params: threadUpdateParams });
1161
- }
1162
- const updatedMessages = await this.operations.executeQuery({ sql: selectQuery, params: messageIds });
1163
- return updatedMessages.map((message) => {
1164
- if (typeof message.content === "string") {
1165
- try {
1166
- message.content = JSON.parse(message.content);
1167
- } catch {
1168
- }
1169
- }
1170
- return message;
1171
- });
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);
874
+ return {
875
+ threads,
876
+ total,
877
+ page,
878
+ perPage: perPageForResponse,
879
+ hasMore: perPageInput === false ? false : offset + perPage < total
880
+ };
1172
881
  } catch (error) {
1173
- throw new MastraError(
882
+ const mastraError = new MastraError(
1174
883
  {
1175
- id: "CLOUDFLARE_D1_STORAGE_UPDATE_MESSAGES_FAILED",
884
+ id: createStorageErrorId("CLOUDFLARE_D1", "LIST_THREADS_BY_RESOURCE_ID", "FAILED"),
1176
885
  domain: ErrorDomain.STORAGE,
1177
886
  category: ErrorCategory.THIRD_PARTY,
1178
- details: { count: messages.length }
887
+ text: `Error getting threads by resourceId ${resourceId}: ${error instanceof Error ? error.message : String(error)}`,
888
+ details: { resourceId }
1179
889
  },
1180
890
  error
1181
891
  );
892
+ this.logger?.error(mastraError.toString());
893
+ this.logger?.trackException(mastraError);
894
+ return {
895
+ threads: [],
896
+ total: 0,
897
+ page,
898
+ perPage: perPageForResponse,
899
+ hasMore: false
900
+ };
1182
901
  }
1183
902
  }
1184
- };
1185
- var StoreOperationsD1 = class extends StoreOperations {
1186
- client;
1187
- binding;
1188
- tablePrefix;
1189
- constructor(config) {
1190
- super();
1191
- this.client = config.client;
1192
- this.binding = config.binding;
1193
- this.tablePrefix = config.tablePrefix || "";
1194
- }
1195
- async hasColumn(table, column) {
1196
- const fullTableName = table.startsWith(this.tablePrefix) ? table : `${this.tablePrefix}${table}`;
1197
- const sql = `PRAGMA table_info(${fullTableName});`;
1198
- const result = await this.executeQuery({ sql, params: [] });
1199
- if (!result || !Array.isArray(result)) return false;
1200
- return result.some((col) => col.name === column || col.name === column.toLowerCase());
1201
- }
1202
- getTableName(tableName) {
1203
- return `${this.tablePrefix}${tableName}`;
1204
- }
1205
- formatSqlParams(params) {
1206
- return params.map((p) => p === void 0 || p === null ? null : p);
1207
- }
1208
- async executeWorkersBindingQuery({
1209
- sql,
1210
- params = [],
1211
- first = false
1212
- }) {
1213
- if (!this.binding) {
1214
- throw new Error("Workers binding is not configured");
1215
- }
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();
1216
925
  try {
1217
- const statement = this.binding.prepare(sql);
1218
- const formattedParams = this.formatSqlParams(params);
1219
- let result;
1220
- if (formattedParams.length > 0) {
1221
- if (first) {
1222
- result = await statement.bind(...formattedParams).first();
1223
- if (!result) return null;
1224
- return result;
1225
- } else {
1226
- result = await statement.bind(...formattedParams).all();
1227
- const results = result.results || [];
1228
- return results;
1229
- }
1230
- } else {
1231
- if (first) {
1232
- result = await statement.first();
1233
- if (!result) return null;
1234
- return result;
1235
- } else {
1236
- result = await statement.all();
1237
- const results = result.results || [];
1238
- return results;
1239
- }
1240
- }
926
+ await this.#db.executeQuery({ sql, params });
927
+ return thread;
1241
928
  } catch (error) {
1242
929
  throw new MastraError(
1243
930
  {
1244
- id: "CLOUDFLARE_D1_STORE_OPERATIONS_WORKERS_BINDING_QUERY_FAILED",
931
+ id: createStorageErrorId("CLOUDFLARE_D1", "SAVE_THREAD", "FAILED"),
1245
932
  domain: ErrorDomain.STORAGE,
1246
933
  category: ErrorCategory.THIRD_PARTY,
1247
- details: { sql }
934
+ text: `Failed to save thread to ${fullTableName}: ${error instanceof Error ? error.message : String(error)}`,
935
+ details: { threadId: thread.id }
1248
936
  },
1249
937
  error
1250
938
  );
1251
939
  }
1252
940
  }
1253
- async executeRestQuery({
1254
- sql,
1255
- params = [],
1256
- first = false
941
+ async updateThread({
942
+ id,
943
+ title,
944
+ metadata
1257
945
  }) {
1258
- if (!this.client) {
1259
- throw new Error("D1 client is not configured");
1260
- }
946
+ const thread = await this.getThreadById({ threadId: id });
1261
947
  try {
1262
- const formattedParams = this.formatSqlParams(params);
1263
- const response = await this.client.query({
1264
- sql,
1265
- params: formattedParams
1266
- });
1267
- const result = response.result || [];
1268
- const results = result.flatMap((r) => r.results || []);
1269
- if (first) {
1270
- return results[0] || null;
948
+ if (!thread) {
949
+ throw new Error(`Thread ${id} not found`);
1271
950
  }
1272
- return results;
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
+ };
1273
971
  } catch (error) {
1274
972
  throw new MastraError(
1275
973
  {
1276
- id: "CLOUDFLARE_D1_STORE_OPERATIONS_REST_QUERY_FAILED",
974
+ id: createStorageErrorId("CLOUDFLARE_D1", "UPDATE_THREAD", "FAILED"),
1277
975
  domain: ErrorDomain.STORAGE,
1278
976
  category: ErrorCategory.THIRD_PARTY,
1279
- details: { sql }
977
+ text: `Failed to update thread ${id}: ${error instanceof Error ? error.message : String(error)}`,
978
+ details: { threadId: id }
1280
979
  },
1281
980
  error
1282
981
  );
1283
982
  }
1284
983
  }
1285
- async executeQuery(options) {
1286
- if (this.binding) {
1287
- return this.executeWorkersBindingQuery(options);
1288
- } else if (this.client) {
1289
- return this.executeRestQuery(options);
1290
- } else {
1291
- throw new Error("Neither binding nor client is configured");
1292
- }
1293
- }
1294
- async getTableColumns(tableName) {
1295
- try {
1296
- const sql = `PRAGMA table_info(${tableName})`;
1297
- const result = await this.executeQuery({ sql });
1298
- if (!result || !Array.isArray(result)) {
1299
- return [];
1300
- }
1301
- return result.map((row) => ({
1302
- name: row.name,
1303
- type: row.type
1304
- }));
1305
- } catch (error) {
1306
- this.logger.warn(`Failed to get table columns for ${tableName}:`, error);
1307
- return [];
1308
- }
1309
- }
1310
- serializeValue(value) {
1311
- if (value === null || value === void 0) {
1312
- return null;
1313
- }
1314
- if (value instanceof Date) {
1315
- return value.toISOString();
1316
- }
1317
- if (typeof value === "object") {
1318
- return JSON.stringify(value);
1319
- }
1320
- return value;
1321
- }
1322
- getSqlType(type) {
1323
- switch (type) {
1324
- case "bigint":
1325
- return "INTEGER";
1326
- // SQLite uses INTEGER for all integer sizes
1327
- case "jsonb":
1328
- return "TEXT";
1329
- // Store JSON as TEXT in SQLite
1330
- default:
1331
- return super.getSqlType(type);
1332
- }
1333
- }
1334
- async createTable({
1335
- tableName,
1336
- schema
1337
- }) {
984
+ async deleteThread({ threadId }) {
985
+ const fullTableName = this.#db.getTableName(TABLE_THREADS);
1338
986
  try {
1339
- const fullTableName = this.getTableName(tableName);
1340
- const columnDefinitions = Object.entries(schema).map(([colName, colDef]) => {
1341
- const type = this.getSqlType(colDef.type);
1342
- const nullable = colDef.nullable === false ? "NOT NULL" : "";
1343
- const primaryKey = colDef.primaryKey ? "PRIMARY KEY" : "";
1344
- return `${colName} ${type} ${nullable} ${primaryKey}`.trim();
1345
- });
1346
- const tableConstraints = [];
1347
- if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
1348
- tableConstraints.push("UNIQUE (workflow_name, run_id)");
1349
- }
1350
- const query = createSqlBuilder().createTable(fullTableName, columnDefinitions, tableConstraints);
1351
- const { sql, params } = query.build();
1352
- await this.executeQuery({ sql, params });
1353
- this.logger.debug(`Created table ${fullTableName}`);
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 });
1354
994
  } catch (error) {
1355
995
  throw new MastraError(
1356
996
  {
1357
- id: "CLOUDFLARE_D1_STORE_OPERATIONS_CREATE_TABLE_FAILED",
997
+ id: createStorageErrorId("CLOUDFLARE_D1", "DELETE_THREAD", "FAILED"),
1358
998
  domain: ErrorDomain.STORAGE,
1359
999
  category: ErrorCategory.THIRD_PARTY,
1360
- details: { tableName }
1000
+ text: `Failed to delete thread ${threadId}: ${error instanceof Error ? error.message : String(error)}`,
1001
+ details: { threadId }
1361
1002
  },
1362
1003
  error
1363
1004
  );
1364
1005
  }
1365
1006
  }
1366
- async clearTable({ tableName }) {
1007
+ async saveMessages(args) {
1008
+ const { messages } = args;
1009
+ if (messages.length === 0) return { messages: [] };
1367
1010
  try {
1368
- const fullTableName = this.getTableName(tableName);
1369
- const query = createSqlBuilder().delete(fullTableName);
1370
- const { sql, params } = query.build();
1371
- await this.executeQuery({ sql, params });
1372
- this.logger.debug(`Cleared table ${fullTableName}`);
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
+ }
1031
+ }
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() };
1373
1058
  } catch (error) {
1374
1059
  throw new MastraError(
1375
1060
  {
1376
- id: "CLOUDFLARE_D1_STORE_OPERATIONS_CLEAR_TABLE_FAILED",
1061
+ id: createStorageErrorId("CLOUDFLARE_D1", "SAVE_MESSAGES", "FAILED"),
1377
1062
  domain: ErrorDomain.STORAGE,
1378
1063
  category: ErrorCategory.THIRD_PARTY,
1379
- details: { tableName }
1064
+ text: `Failed to save messages: ${error instanceof Error ? error.message : String(error)}`
1380
1065
  },
1381
1066
  error
1382
1067
  );
1383
1068
  }
1384
1069
  }
1385
- async dropTable({ tableName }) {
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 = [];
1386
1133
  try {
1387
- const fullTableName = this.getTableName(tableName);
1388
- const sql = `DROP TABLE IF EXISTS ${fullTableName}`;
1389
- await this.executeQuery({ sql });
1390
- this.logger.debug(`Dropped table ${fullTableName}`);
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");
1136
+ const { sql, params } = query.build();
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() };
1391
1150
  } catch (error) {
1392
- throw new MastraError(
1151
+ const mastraError = new MastraError(
1393
1152
  {
1394
- id: "CLOUDFLARE_D1_STORE_OPERATIONS_DROP_TABLE_FAILED",
1153
+ id: createStorageErrorId("CLOUDFLARE_D1", "LIST_MESSAGES_BY_ID", "FAILED"),
1395
1154
  domain: ErrorDomain.STORAGE,
1396
1155
  category: ErrorCategory.THIRD_PARTY,
1397
- details: { tableName }
1156
+ text: `Failed to retrieve messages by ID: ${error instanceof Error ? error.message : String(error)}`,
1157
+ details: { messageIds: JSON.stringify(messageIds) }
1398
1158
  },
1399
1159
  error
1400
1160
  );
1161
+ this.logger?.error(mastraError.toString());
1162
+ this.logger?.trackException(mastraError);
1163
+ throw mastraError;
1401
1164
  }
1402
1165
  }
1403
- async alterTable(args) {
1404
- try {
1405
- const fullTableName = this.getTableName(args.tableName);
1406
- const existingColumns = await this.getTableColumns(fullTableName);
1407
- const existingColumnNames = new Set(existingColumns.map((col) => col.name));
1408
- for (const [columnName, column] of Object.entries(args.schema)) {
1409
- if (!existingColumnNames.has(columnName) && args.ifNotExists.includes(columnName)) {
1410
- const sqlType = this.getSqlType(column.type);
1411
- const defaultValue = this.getDefaultValue(column.type);
1412
- const sql = `ALTER TABLE ${fullTableName} ADD COLUMN ${columnName} ${sqlType} ${defaultValue}`;
1413
- await this.executeQuery({ sql });
1414
- this.logger.debug(`Added column ${columnName} to table ${fullTableName}`);
1415
- }
1416
- }
1417
- } 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())) {
1418
1170
  throw new MastraError(
1419
1171
  {
1420
- id: "CLOUDFLARE_D1_STORE_OPERATIONS_ALTER_TABLE_FAILED",
1172
+ id: createStorageErrorId("CLOUDFLARE_D1", "LIST_MESSAGES", "INVALID_THREAD_ID"),
1421
1173
  domain: ErrorDomain.STORAGE,
1422
1174
  category: ErrorCategory.THIRD_PARTY,
1423
- details: { tableName: args.tableName }
1175
+ details: { threadId: Array.isArray(threadId) ? threadId.join(",") : threadId }
1424
1176
  },
1425
- error
1177
+ new Error("threadId must be a non-empty string or array of non-empty strings")
1426
1178
  );
1427
1179
  }
1428
- }
1429
- async insert({ tableName, record }) {
1430
- try {
1431
- const fullTableName = this.getTableName(tableName);
1432
- const processedRecord = await this.processRecord(record);
1433
- const columns = Object.keys(processedRecord);
1434
- const values = Object.values(processedRecord);
1435
- const query = createSqlBuilder().insert(fullTableName, columns, values);
1436
- const { sql, params } = query.build();
1437
- await this.executeQuery({ sql, params });
1438
- } catch (error) {
1180
+ if (page < 0) {
1439
1181
  throw new MastraError(
1440
1182
  {
1441
- id: "CLOUDFLARE_D1_STORE_OPERATIONS_INSERT_FAILED",
1183
+ id: createStorageErrorId("CLOUDFLARE_D1", "LIST_MESSAGES", "INVALID_PAGE"),
1442
1184
  domain: ErrorDomain.STORAGE,
1443
- category: ErrorCategory.THIRD_PARTY,
1444
- details: { tableName }
1185
+ category: ErrorCategory.USER,
1186
+ details: { page }
1445
1187
  },
1446
- error
1188
+ new Error("page must be >= 0")
1447
1189
  );
1448
1190
  }
1449
- }
1450
- async batchInsert({ tableName, records }) {
1191
+ const perPage = normalizePerPage(perPageInput, 40);
1192
+ const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
1451
1193
  try {
1452
- if (records.length === 0) return;
1453
- const fullTableName = this.getTableName(tableName);
1454
- const processedRecords = await Promise.all(records.map((record) => this.processRecord(record)));
1455
- const columns = Object.keys(processedRecords[0] || {});
1456
- for (const record of processedRecords) {
1457
- const values = Object.values(record);
1458
- const query = createSqlBuilder().insert(fullTableName, columns, values);
1459
- const { sql, params } = query.build();
1460
- await this.executeQuery({ sql, params });
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
+ const startOp = dateRange.startExclusive ? ">" : ">=";
1209
+ query += ` AND createdAt ${startOp} ?`;
1210
+ queryParams.push(startDate);
1211
+ }
1212
+ if (dateRange?.end) {
1213
+ const endDate = dateRange.end instanceof Date ? serializeDate(dateRange.end) : serializeDate(new Date(dateRange.end));
1214
+ const endOp = dateRange.endExclusive ? "<" : "<=";
1215
+ query += ` AND createdAt ${endOp} ?`;
1216
+ queryParams.push(endDate);
1217
+ }
1218
+ const { field, direction } = this.parseOrderBy(orderBy, "ASC");
1219
+ query += ` ORDER BY "${field}" ${direction}`;
1220
+ if (perPage !== Number.MAX_SAFE_INTEGER) {
1221
+ query += ` LIMIT ? OFFSET ?`;
1222
+ queryParams.push(perPage, offset);
1223
+ }
1224
+ const results = await this.#db.executeQuery({ sql: query, params: queryParams });
1225
+ const paginatedMessages = (isArrayOfRecords(results) ? results : []).map((message) => {
1226
+ const processedMsg = {};
1227
+ for (const [key, value] of Object.entries(message)) {
1228
+ if (key === `type` && value === `v2`) continue;
1229
+ processedMsg[key] = deserializeValue(value);
1230
+ }
1231
+ return processedMsg;
1232
+ });
1233
+ const paginatedCount = paginatedMessages.length;
1234
+ let countQuery = `SELECT count() as count FROM ${fullTableName} WHERE thread_id = ?`;
1235
+ const countParams = [threadId];
1236
+ if (resourceId) {
1237
+ countQuery += ` AND resourceId = ?`;
1238
+ countParams.push(resourceId);
1239
+ }
1240
+ if (dateRange?.start) {
1241
+ const startDate = dateRange.start instanceof Date ? serializeDate(dateRange.start) : serializeDate(new Date(dateRange.start));
1242
+ const startOp = dateRange.startExclusive ? ">" : ">=";
1243
+ countQuery += ` AND createdAt ${startOp} ?`;
1244
+ countParams.push(startDate);
1245
+ }
1246
+ if (dateRange?.end) {
1247
+ const endDate = dateRange.end instanceof Date ? serializeDate(dateRange.end) : serializeDate(new Date(dateRange.end));
1248
+ const endOp = dateRange.endExclusive ? "<" : "<=";
1249
+ countQuery += ` AND createdAt ${endOp} ?`;
1250
+ countParams.push(endDate);
1251
+ }
1252
+ const countResult = await this.#db.executeQuery({ sql: countQuery, params: countParams });
1253
+ const total = Number(countResult[0]?.count ?? 0);
1254
+ if (total === 0 && paginatedCount === 0 && (!include || include.length === 0)) {
1255
+ return {
1256
+ messages: [],
1257
+ total: 0,
1258
+ page,
1259
+ perPage: perPageForResponse,
1260
+ hasMore: false
1261
+ };
1262
+ }
1263
+ const messageIds = new Set(paginatedMessages.map((m) => m.id));
1264
+ let includeMessages = [];
1265
+ if (include && include.length > 0) {
1266
+ const includeResult = await this._getIncludedMessages(include);
1267
+ if (Array.isArray(includeResult)) {
1268
+ includeMessages = includeResult;
1269
+ for (const includeMsg of includeMessages) {
1270
+ if (!messageIds.has(includeMsg.id)) {
1271
+ paginatedMessages.push(includeMsg);
1272
+ messageIds.add(includeMsg.id);
1273
+ }
1274
+ }
1275
+ }
1461
1276
  }
1277
+ const list = new MessageList().add(paginatedMessages, "memory");
1278
+ let finalMessages = list.get.all.db();
1279
+ finalMessages = finalMessages.sort((a, b) => {
1280
+ const isDateField = field === "createdAt" || field === "updatedAt";
1281
+ const aValue = isDateField ? new Date(a[field]).getTime() : a[field];
1282
+ const bValue = isDateField ? new Date(b[field]).getTime() : b[field];
1283
+ if (aValue === bValue) {
1284
+ return a.id.localeCompare(b.id);
1285
+ }
1286
+ if (typeof aValue === "number" && typeof bValue === "number") {
1287
+ return direction === "ASC" ? aValue - bValue : bValue - aValue;
1288
+ }
1289
+ return direction === "ASC" ? String(aValue).localeCompare(String(bValue)) : String(bValue).localeCompare(String(aValue));
1290
+ });
1291
+ const returnedThreadMessageIds = new Set(finalMessages.filter((m) => m.threadId === threadId).map((m) => m.id));
1292
+ const allThreadMessagesReturned = returnedThreadMessageIds.size >= total;
1293
+ const hasMore = perPageInput === false ? false : allThreadMessagesReturned ? false : offset + paginatedCount < total;
1294
+ return {
1295
+ messages: finalMessages,
1296
+ total,
1297
+ page,
1298
+ perPage: perPageForResponse,
1299
+ hasMore
1300
+ };
1462
1301
  } catch (error) {
1463
- throw new MastraError(
1302
+ const mastraError = new MastraError(
1464
1303
  {
1465
- id: "CLOUDFLARE_D1_STORE_OPERATIONS_BATCH_INSERT_FAILED",
1304
+ id: createStorageErrorId("CLOUDFLARE_D1", "LIST_MESSAGES", "FAILED"),
1466
1305
  domain: ErrorDomain.STORAGE,
1467
1306
  category: ErrorCategory.THIRD_PARTY,
1468
- details: { tableName }
1307
+ text: `Failed to list messages for thread ${Array.isArray(threadId) ? threadId.join(",") : threadId}: ${error instanceof Error ? error.message : String(error)}`,
1308
+ details: {
1309
+ threadId: Array.isArray(threadId) ? threadId.join(",") : threadId,
1310
+ resourceId: resourceId ?? ""
1311
+ }
1469
1312
  },
1470
1313
  error
1471
1314
  );
1315
+ this.logger?.error?.(mastraError.toString());
1316
+ this.logger?.trackException?.(mastraError);
1317
+ return {
1318
+ messages: [],
1319
+ total: 0,
1320
+ page,
1321
+ perPage: perPageForResponse,
1322
+ hasMore: false
1323
+ };
1472
1324
  }
1473
1325
  }
1474
- async load({ tableName, keys }) {
1326
+ async updateMessages(args) {
1327
+ const { messages } = args;
1328
+ this.logger.debug("Updating messages", { count: messages.length });
1329
+ if (!messages.length) {
1330
+ return [];
1331
+ }
1332
+ const messageIds = messages.map((m) => m.id);
1333
+ const fullTableName = this.#db.getTableName(TABLE_MESSAGES);
1334
+ const threadsTableName = this.#db.getTableName(TABLE_THREADS);
1475
1335
  try {
1476
- const fullTableName = this.getTableName(tableName);
1477
- const query = createSqlBuilder().select("*").from(fullTableName);
1478
- let firstKey = true;
1479
- for (const [key, value] of Object.entries(keys)) {
1480
- if (firstKey) {
1481
- query.where(`${key} = ?`, value);
1482
- firstKey = false;
1483
- } else {
1484
- query.andWhere(`${key} = ?`, value);
1336
+ const placeholders = messageIds.map(() => "?").join(",");
1337
+ const selectQuery = `SELECT id, content, role, type, createdAt, thread_id AS threadId, resourceId FROM ${fullTableName} WHERE id IN (${placeholders})`;
1338
+ const existingMessages = await this.#db.executeQuery({ sql: selectQuery, params: messageIds });
1339
+ if (existingMessages.length === 0) {
1340
+ return [];
1341
+ }
1342
+ const parsedExistingMessages = existingMessages.map((msg) => {
1343
+ if (typeof msg.content === "string") {
1344
+ try {
1345
+ msg.content = JSON.parse(msg.content);
1346
+ } catch {
1347
+ }
1348
+ }
1349
+ return msg;
1350
+ });
1351
+ const threadIdsToUpdate = /* @__PURE__ */ new Set();
1352
+ const updateQueries = [];
1353
+ for (const existingMessage of parsedExistingMessages) {
1354
+ const updatePayload = messages.find((m) => m.id === existingMessage.id);
1355
+ if (!updatePayload) continue;
1356
+ const { id, ...fieldsToUpdate } = updatePayload;
1357
+ if (Object.keys(fieldsToUpdate).length === 0) continue;
1358
+ threadIdsToUpdate.add(existingMessage.threadId);
1359
+ if ("threadId" in updatePayload && updatePayload.threadId && updatePayload.threadId !== existingMessage.threadId) {
1360
+ threadIdsToUpdate.add(updatePayload.threadId);
1361
+ }
1362
+ const setClauses = [];
1363
+ const values = [];
1364
+ const updatableFields = { ...fieldsToUpdate };
1365
+ if (updatableFields.content) {
1366
+ const existingContent = existingMessage.content || {};
1367
+ const newContent = {
1368
+ ...existingContent,
1369
+ ...updatableFields.content,
1370
+ // Deep merge metadata if it exists on both
1371
+ ...existingContent?.metadata && updatableFields.content.metadata ? {
1372
+ metadata: {
1373
+ ...existingContent.metadata,
1374
+ ...updatableFields.content.metadata
1375
+ }
1376
+ } : {}
1377
+ };
1378
+ setClauses.push(`content = ?`);
1379
+ values.push(JSON.stringify(newContent));
1380
+ delete updatableFields.content;
1381
+ }
1382
+ for (const key in updatableFields) {
1383
+ if (Object.prototype.hasOwnProperty.call(updatableFields, key)) {
1384
+ const dbColumn = key === "threadId" ? "thread_id" : key;
1385
+ setClauses.push(`${dbColumn} = ?`);
1386
+ values.push(updatableFields[key]);
1387
+ }
1388
+ }
1389
+ if (setClauses.length > 0) {
1390
+ values.push(id);
1391
+ const updateQuery = `UPDATE ${fullTableName} SET ${setClauses.join(", ")} WHERE id = ?`;
1392
+ updateQueries.push({ sql: updateQuery, params: values });
1485
1393
  }
1486
1394
  }
1487
- query.orderBy("createdAt", "DESC");
1488
- query.limit(1);
1489
- const { sql, params } = query.build();
1490
- const result = await this.executeQuery({ sql, params, first: true });
1491
- if (!result) {
1492
- return null;
1395
+ for (const query of updateQueries) {
1396
+ await this.#db.executeQuery(query);
1493
1397
  }
1494
- const deserializedResult = {};
1495
- for (const [key, value] of Object.entries(result)) {
1496
- deserializedResult[key] = deserializeValue(value);
1398
+ if (threadIdsToUpdate.size > 0) {
1399
+ const threadPlaceholders = Array.from(threadIdsToUpdate).map(() => "?").join(",");
1400
+ const threadUpdateQuery = `UPDATE ${threadsTableName} SET updatedAt = ? WHERE id IN (${threadPlaceholders})`;
1401
+ const threadUpdateParams = [(/* @__PURE__ */ new Date()).toISOString(), ...Array.from(threadIdsToUpdate)];
1402
+ await this.#db.executeQuery({ sql: threadUpdateQuery, params: threadUpdateParams });
1497
1403
  }
1498
- return deserializedResult;
1404
+ const updatedMessages = await this.#db.executeQuery({ sql: selectQuery, params: messageIds });
1405
+ return updatedMessages.map((message) => {
1406
+ if (typeof message.content === "string") {
1407
+ try {
1408
+ message.content = JSON.parse(message.content);
1409
+ } catch {
1410
+ }
1411
+ }
1412
+ return message;
1413
+ });
1499
1414
  } catch (error) {
1500
1415
  throw new MastraError(
1501
1416
  {
1502
- id: "CLOUDFLARE_D1_STORE_OPERATIONS_LOAD_FAILED",
1417
+ id: createStorageErrorId("CLOUDFLARE_D1", "UPDATE_MESSAGES", "FAILED"),
1503
1418
  domain: ErrorDomain.STORAGE,
1504
1419
  category: ErrorCategory.THIRD_PARTY,
1505
- details: { tableName }
1420
+ details: { count: messages.length }
1506
1421
  },
1507
1422
  error
1508
1423
  );
1509
1424
  }
1510
1425
  }
1511
- async processRecord(record) {
1512
- const processed = {};
1513
- for (const [key, value] of Object.entries(record)) {
1514
- processed[key] = this.serializeValue(value);
1515
- }
1516
- return processed;
1517
- }
1518
- /**
1519
- * Upsert multiple records in a batch operation
1520
- * @param tableName The table to insert into
1521
- * @param records The records to insert
1522
- */
1523
- async batchUpsert({ tableName, records }) {
1524
- if (records.length === 0) return;
1525
- const fullTableName = this.getTableName(tableName);
1426
+ async deleteMessages(messageIds) {
1427
+ if (messageIds.length === 0) return;
1428
+ const fullTableName = this.#db.getTableName(TABLE_MESSAGES);
1429
+ const threadsTableName = this.#db.getTableName(TABLE_THREADS);
1526
1430
  try {
1527
- const batchSize = 50;
1528
- for (let i = 0; i < records.length; i += batchSize) {
1529
- const batch = records.slice(i, i + batchSize);
1530
- const recordsToInsert = batch;
1531
- if (recordsToInsert.length > 0) {
1532
- const firstRecord = recordsToInsert[0];
1533
- const columns = Object.keys(firstRecord || {});
1534
- for (const record of recordsToInsert) {
1535
- const values = columns.map((col) => {
1536
- if (!record) return null;
1537
- const value = typeof col === "string" ? record[col] : null;
1538
- return this.serializeValue(value);
1539
- });
1540
- const recordToUpsert = columns.reduce(
1541
- (acc, col) => {
1542
- if (col !== "createdAt") acc[col] = `excluded.${col}`;
1543
- return acc;
1544
- },
1545
- {}
1546
- );
1547
- const query = createSqlBuilder().insert(fullTableName, columns, values, ["id"], recordToUpsert);
1548
- const { sql, params } = query.build();
1549
- await this.executeQuery({ sql, params });
1550
- }
1551
- }
1552
- this.logger.debug(
1553
- `Processed batch ${Math.floor(i / batchSize) + 1} of ${Math.ceil(records.length / batchSize)}`
1554
- );
1431
+ const placeholders = messageIds.map(() => "?").join(",");
1432
+ const selectQuery = `SELECT DISTINCT thread_id FROM ${fullTableName} WHERE id IN (${placeholders})`;
1433
+ const threadResults = await this.#db.executeQuery({ sql: selectQuery, params: messageIds });
1434
+ const threadIds = threadResults.map((r) => r.thread_id).filter(Boolean);
1435
+ const deleteQuery = createSqlBuilder().delete(fullTableName).where(`id IN (${placeholders})`, ...messageIds);
1436
+ const { sql, params } = deleteQuery.build();
1437
+ await this.#db.executeQuery({ sql, params });
1438
+ if (threadIds.length > 0) {
1439
+ const threadPlaceholders = threadIds.map(() => "?").join(",");
1440
+ const threadUpdateQuery = `UPDATE ${threadsTableName} SET updatedAt = ? WHERE id IN (${threadPlaceholders})`;
1441
+ const threadUpdateParams = [(/* @__PURE__ */ new Date()).toISOString(), ...threadIds];
1442
+ await this.#db.executeQuery({ sql: threadUpdateQuery, params: threadUpdateParams });
1555
1443
  }
1556
- this.logger.debug(`Successfully batch upserted ${records.length} records into ${tableName}`);
1557
1444
  } catch (error) {
1558
1445
  throw new MastraError(
1559
1446
  {
1560
- id: "CLOUDFLARE_D1_STORAGE_BATCH_UPSERT_ERROR",
1447
+ id: createStorageErrorId("CLOUDFLARE_D1", "DELETE_MESSAGES", "FAILED"),
1561
1448
  domain: ErrorDomain.STORAGE,
1562
1449
  category: ErrorCategory.THIRD_PARTY,
1563
- text: `Failed to batch upsert into ${tableName}: ${error instanceof Error ? error.message : String(error)}`,
1564
- details: { tableName }
1450
+ details: { messageIds: JSON.stringify(messageIds) }
1565
1451
  },
1566
1452
  error
1567
1453
  );
@@ -1569,32 +1455,31 @@ var StoreOperationsD1 = class extends StoreOperations {
1569
1455
  }
1570
1456
  };
1571
1457
  function transformScoreRow(row) {
1572
- const deserialized = { ...row };
1573
- deserialized.input = safelyParseJSON(row.input);
1574
- deserialized.output = safelyParseJSON(row.output);
1575
- deserialized.scorer = safelyParseJSON(row.scorer);
1576
- deserialized.preprocessStepResult = safelyParseJSON(row.preprocessStepResult);
1577
- deserialized.analyzeStepResult = safelyParseJSON(row.analyzeStepResult);
1578
- deserialized.metadata = safelyParseJSON(row.metadata);
1579
- deserialized.additionalContext = safelyParseJSON(row.additionalContext);
1580
- deserialized.requestContext = safelyParseJSON(row.requestContext);
1581
- deserialized.entity = safelyParseJSON(row.entity);
1582
- deserialized.createdAt = row.createdAtZ || row.createdAt;
1583
- deserialized.updatedAt = row.updatedAtZ || row.updatedAt;
1584
- return deserialized;
1458
+ return transformScoreRow$1(row, {
1459
+ preferredTimestampFields: {
1460
+ createdAt: "createdAtZ",
1461
+ updatedAt: "updatedAtZ"
1462
+ }
1463
+ });
1585
1464
  }
1586
1465
  var ScoresStorageD1 = class extends ScoresStorage {
1587
- operations;
1588
- constructor({ operations }) {
1466
+ #db;
1467
+ constructor(config) {
1589
1468
  super();
1590
- this.operations = operations;
1469
+ this.#db = new D1DB(resolveD1Config(config));
1470
+ }
1471
+ async init() {
1472
+ await this.#db.createTable({ tableName: TABLE_SCORERS, schema: TABLE_SCHEMAS[TABLE_SCORERS] });
1473
+ }
1474
+ async dangerouslyClearAll() {
1475
+ await this.#db.clearTable({ tableName: TABLE_SCORERS });
1591
1476
  }
1592
1477
  async getScoreById({ id }) {
1593
1478
  try {
1594
- const fullTableName = this.operations.getTableName(TABLE_SCORERS);
1479
+ const fullTableName = this.#db.getTableName(TABLE_SCORERS);
1595
1480
  const query = createSqlBuilder().select("*").from(fullTableName).where("id = ?", id);
1596
1481
  const { sql, params } = query.build();
1597
- const result = await this.operations.executeQuery({ sql, params, first: true });
1482
+ const result = await this.#db.executeQuery({ sql, params, first: true });
1598
1483
  if (!result) {
1599
1484
  return null;
1600
1485
  }
@@ -1602,7 +1487,7 @@ var ScoresStorageD1 = class extends ScoresStorage {
1602
1487
  } catch (error) {
1603
1488
  throw new MastraError(
1604
1489
  {
1605
- id: "CLOUDFLARE_D1_STORE_SCORES_GET_SCORE_BY_ID_FAILED",
1490
+ id: createStorageErrorId("CLOUDFLARE_D1", "GET_SCORE_BY_ID", "FAILED"),
1606
1491
  domain: ErrorDomain.STORAGE,
1607
1492
  category: ErrorCategory.THIRD_PARTY
1608
1493
  },
@@ -1617,17 +1502,23 @@ var ScoresStorageD1 = class extends ScoresStorage {
1617
1502
  } catch (error) {
1618
1503
  throw new MastraError(
1619
1504
  {
1620
- id: "CLOUDFLARE_D1_STORE_SCORES_SAVE_SCORE_FAILED_INVALID_SCORE_PAYLOAD",
1505
+ id: createStorageErrorId("CLOUDFLARE_D1", "SAVE_SCORE", "VALIDATION_FAILED"),
1621
1506
  domain: ErrorDomain.STORAGE,
1622
1507
  category: ErrorCategory.USER,
1623
- details: { scoreId: score.id }
1508
+ details: {
1509
+ scorer: typeof score.scorer?.id === "string" ? score.scorer.id : String(score.scorer?.id ?? "unknown"),
1510
+ entityId: score.entityId ?? "unknown",
1511
+ entityType: score.entityType ?? "unknown",
1512
+ traceId: score.traceId ?? "",
1513
+ spanId: score.spanId ?? ""
1514
+ }
1624
1515
  },
1625
1516
  error
1626
1517
  );
1627
1518
  }
1519
+ const id = crypto.randomUUID();
1628
1520
  try {
1629
- const id = crypto.randomUUID();
1630
- const fullTableName = this.operations.getTableName(TABLE_SCORERS);
1521
+ const fullTableName = this.#db.getTableName(TABLE_SCORERS);
1631
1522
  const serializedRecord = {};
1632
1523
  for (const [key, value] of Object.entries(parsedScore)) {
1633
1524
  if (value !== null && value !== void 0) {
@@ -1640,28 +1531,29 @@ var ScoresStorageD1 = class extends ScoresStorage {
1640
1531
  serializedRecord[key] = null;
1641
1532
  }
1642
1533
  }
1534
+ const now = /* @__PURE__ */ new Date();
1643
1535
  serializedRecord.id = id;
1644
- serializedRecord.createdAt = (/* @__PURE__ */ new Date()).toISOString();
1645
- serializedRecord.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1536
+ serializedRecord.createdAt = now.toISOString();
1537
+ serializedRecord.updatedAt = now.toISOString();
1646
1538
  const columns = Object.keys(serializedRecord);
1647
1539
  const values = Object.values(serializedRecord);
1648
1540
  const query = createSqlBuilder().insert(fullTableName, columns, values);
1649
1541
  const { sql, params } = query.build();
1650
- await this.operations.executeQuery({ sql, params });
1651
- const scoreFromDb = await this.getScoreById({ id });
1652
- return { score: scoreFromDb };
1542
+ await this.#db.executeQuery({ sql, params });
1543
+ return { score: { ...parsedScore, id, createdAt: now, updatedAt: now } };
1653
1544
  } catch (error) {
1654
1545
  throw new MastraError(
1655
1546
  {
1656
- id: "CLOUDFLARE_D1_STORE_SCORES_SAVE_SCORE_FAILED",
1547
+ id: createStorageErrorId("CLOUDFLARE_D1", "SAVE_SCORE", "FAILED"),
1657
1548
  domain: ErrorDomain.STORAGE,
1658
- category: ErrorCategory.THIRD_PARTY
1549
+ category: ErrorCategory.THIRD_PARTY,
1550
+ details: { id }
1659
1551
  },
1660
1552
  error
1661
1553
  );
1662
1554
  }
1663
1555
  }
1664
- async getScoresByScorerId({
1556
+ async listScoresByScorerId({
1665
1557
  scorerId,
1666
1558
  entityId,
1667
1559
  entityType,
@@ -1669,7 +1561,10 @@ var ScoresStorageD1 = class extends ScoresStorage {
1669
1561
  pagination
1670
1562
  }) {
1671
1563
  try {
1672
- const fullTableName = this.operations.getTableName(TABLE_SCORERS);
1564
+ const { page, perPage: perPageInput } = pagination;
1565
+ const perPage = normalizePerPage(perPageInput, 100);
1566
+ const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
1567
+ const fullTableName = this.#db.getTableName(TABLE_SCORERS);
1673
1568
  const countQuery = createSqlBuilder().count().from(fullTableName).where("scorerId = ?", scorerId);
1674
1569
  if (entityId) {
1675
1570
  countQuery.andWhere("entityId = ?", entityId);
@@ -1680,19 +1575,21 @@ var ScoresStorageD1 = class extends ScoresStorage {
1680
1575
  if (source) {
1681
1576
  countQuery.andWhere("source = ?", source);
1682
1577
  }
1683
- const countResult = await this.operations.executeQuery(countQuery.build());
1578
+ const countResult = await this.#db.executeQuery(countQuery.build());
1684
1579
  const total = Array.isArray(countResult) ? Number(countResult?.[0]?.count ?? 0) : Number(countResult?.count ?? 0);
1685
1580
  if (total === 0) {
1686
1581
  return {
1687
1582
  pagination: {
1688
1583
  total: 0,
1689
- page: pagination.page,
1690
- perPage: pagination.perPage,
1584
+ page,
1585
+ perPage: perPageForResponse,
1691
1586
  hasMore: false
1692
1587
  },
1693
1588
  scores: []
1694
1589
  };
1695
1590
  }
1591
+ const end = perPageInput === false ? total : start + perPage;
1592
+ const limitValue = perPageInput === false ? total : perPage;
1696
1593
  const selectQuery = createSqlBuilder().select("*").from(fullTableName).where("scorerId = ?", scorerId);
1697
1594
  if (entityId) {
1698
1595
  selectQuery.andWhere("entityId = ?", entityId);
@@ -1703,23 +1600,23 @@ var ScoresStorageD1 = class extends ScoresStorage {
1703
1600
  if (source) {
1704
1601
  selectQuery.andWhere("source = ?", source);
1705
1602
  }
1706
- selectQuery.limit(pagination.perPage).offset(pagination.page * pagination.perPage);
1603
+ selectQuery.limit(limitValue).offset(start);
1707
1604
  const { sql, params } = selectQuery.build();
1708
- const results = await this.operations.executeQuery({ sql, params });
1605
+ const results = await this.#db.executeQuery({ sql, params });
1709
1606
  const scores = Array.isArray(results) ? results.map(transformScoreRow) : [];
1710
1607
  return {
1711
1608
  pagination: {
1712
1609
  total,
1713
- page: pagination.page,
1714
- perPage: pagination.perPage,
1715
- hasMore: total > (pagination.page + 1) * pagination.perPage
1610
+ page,
1611
+ perPage: perPageForResponse,
1612
+ hasMore: end < total
1716
1613
  },
1717
1614
  scores
1718
1615
  };
1719
1616
  } catch (error) {
1720
1617
  throw new MastraError(
1721
1618
  {
1722
- id: "CLOUDFLARE_D1_STORE_SCORES_GET_SCORES_BY_SCORER_ID_FAILED",
1619
+ id: createStorageErrorId("CLOUDFLARE_D1", "GET_SCORES_BY_SCORER_ID", "FAILED"),
1723
1620
  domain: ErrorDomain.STORAGE,
1724
1621
  category: ErrorCategory.THIRD_PARTY
1725
1622
  },
@@ -1727,43 +1624,48 @@ var ScoresStorageD1 = class extends ScoresStorage {
1727
1624
  );
1728
1625
  }
1729
1626
  }
1730
- async getScoresByRunId({
1627
+ async listScoresByRunId({
1731
1628
  runId,
1732
1629
  pagination
1733
1630
  }) {
1734
1631
  try {
1735
- const fullTableName = this.operations.getTableName(TABLE_SCORERS);
1632
+ const { page, perPage: perPageInput } = pagination;
1633
+ const perPage = normalizePerPage(perPageInput, 100);
1634
+ const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
1635
+ const fullTableName = this.#db.getTableName(TABLE_SCORERS);
1736
1636
  const countQuery = createSqlBuilder().count().from(fullTableName).where("runId = ?", runId);
1737
- const countResult = await this.operations.executeQuery(countQuery.build());
1637
+ const countResult = await this.#db.executeQuery(countQuery.build());
1738
1638
  const total = Array.isArray(countResult) ? Number(countResult?.[0]?.count ?? 0) : Number(countResult?.count ?? 0);
1739
1639
  if (total === 0) {
1740
1640
  return {
1741
1641
  pagination: {
1742
1642
  total: 0,
1743
- page: pagination.page,
1744
- perPage: pagination.perPage,
1643
+ page,
1644
+ perPage: perPageForResponse,
1745
1645
  hasMore: false
1746
1646
  },
1747
1647
  scores: []
1748
1648
  };
1749
1649
  }
1750
- const selectQuery = createSqlBuilder().select("*").from(fullTableName).where("runId = ?", runId).limit(pagination.perPage).offset(pagination.page * pagination.perPage);
1650
+ const end = perPageInput === false ? total : start + perPage;
1651
+ const limitValue = perPageInput === false ? total : perPage;
1652
+ const selectQuery = createSqlBuilder().select("*").from(fullTableName).where("runId = ?", runId).limit(limitValue).offset(start);
1751
1653
  const { sql, params } = selectQuery.build();
1752
- const results = await this.operations.executeQuery({ sql, params });
1654
+ const results = await this.#db.executeQuery({ sql, params });
1753
1655
  const scores = Array.isArray(results) ? results.map(transformScoreRow) : [];
1754
1656
  return {
1755
1657
  pagination: {
1756
1658
  total,
1757
- page: pagination.page,
1758
- perPage: pagination.perPage,
1759
- hasMore: total > (pagination.page + 1) * pagination.perPage
1659
+ page,
1660
+ perPage: perPageForResponse,
1661
+ hasMore: end < total
1760
1662
  },
1761
1663
  scores
1762
1664
  };
1763
1665
  } catch (error) {
1764
1666
  throw new MastraError(
1765
1667
  {
1766
- id: "CLOUDFLARE_D1_STORE_SCORES_GET_SCORES_BY_RUN_ID_FAILED",
1668
+ id: createStorageErrorId("CLOUDFLARE_D1", "GET_SCORES_BY_RUN_ID", "FAILED"),
1767
1669
  domain: ErrorDomain.STORAGE,
1768
1670
  category: ErrorCategory.THIRD_PARTY
1769
1671
  },
@@ -1771,44 +1673,49 @@ var ScoresStorageD1 = class extends ScoresStorage {
1771
1673
  );
1772
1674
  }
1773
1675
  }
1774
- async getScoresByEntityId({
1676
+ async listScoresByEntityId({
1775
1677
  entityId,
1776
1678
  entityType,
1777
1679
  pagination
1778
1680
  }) {
1779
1681
  try {
1780
- const fullTableName = this.operations.getTableName(TABLE_SCORERS);
1682
+ const { page, perPage: perPageInput } = pagination;
1683
+ const perPage = normalizePerPage(perPageInput, 100);
1684
+ const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
1685
+ const fullTableName = this.#db.getTableName(TABLE_SCORERS);
1781
1686
  const countQuery = createSqlBuilder().count().from(fullTableName).where("entityId = ?", entityId).andWhere("entityType = ?", entityType);
1782
- const countResult = await this.operations.executeQuery(countQuery.build());
1687
+ const countResult = await this.#db.executeQuery(countQuery.build());
1783
1688
  const total = Array.isArray(countResult) ? Number(countResult?.[0]?.count ?? 0) : Number(countResult?.count ?? 0);
1784
1689
  if (total === 0) {
1785
1690
  return {
1786
1691
  pagination: {
1787
1692
  total: 0,
1788
- page: pagination.page,
1789
- perPage: pagination.perPage,
1693
+ page,
1694
+ perPage: perPageForResponse,
1790
1695
  hasMore: false
1791
1696
  },
1792
1697
  scores: []
1793
1698
  };
1794
1699
  }
1795
- const selectQuery = createSqlBuilder().select("*").from(fullTableName).where("entityId = ?", entityId).andWhere("entityType = ?", entityType).limit(pagination.perPage).offset(pagination.page * pagination.perPage);
1700
+ const end = perPageInput === false ? total : start + perPage;
1701
+ const limitValue = perPageInput === false ? total : perPage;
1702
+ const selectQuery = createSqlBuilder().select("*").from(fullTableName).where("entityId = ?", entityId).andWhere("entityType = ?", entityType).limit(limitValue).offset(start);
1796
1703
  const { sql, params } = selectQuery.build();
1797
- const results = await this.operations.executeQuery({ sql, params });
1704
+ const results = await this.#db.executeQuery({ sql, params });
1798
1705
  const scores = Array.isArray(results) ? results.map(transformScoreRow) : [];
1799
1706
  return {
1800
1707
  pagination: {
1801
1708
  total,
1802
- page: pagination.page,
1803
- perPage: pagination.perPage,
1804
- hasMore: total > (pagination.page + 1) * pagination.perPage
1709
+ page,
1710
+ perPage: perPageForResponse,
1711
+ hasMore: end < total
1805
1712
  },
1806
1713
  scores
1807
1714
  };
1808
1715
  } catch (error) {
1809
1716
  throw new MastraError(
1810
1717
  {
1811
- id: "CLOUDFLARE_D1_STORE_SCORES_GET_SCORES_BY_ENTITY_ID_FAILED",
1718
+ id: createStorageErrorId("CLOUDFLARE_D1", "GET_SCORES_BY_ENTITY_ID", "FAILED"),
1812
1719
  domain: ErrorDomain.STORAGE,
1813
1720
  category: ErrorCategory.THIRD_PARTY
1814
1721
  },
@@ -1816,46 +1723,49 @@ var ScoresStorageD1 = class extends ScoresStorage {
1816
1723
  );
1817
1724
  }
1818
1725
  }
1819
- async getScoresBySpan({
1726
+ async listScoresBySpan({
1820
1727
  traceId,
1821
1728
  spanId,
1822
1729
  pagination
1823
1730
  }) {
1824
1731
  try {
1825
- const fullTableName = this.operations.getTableName(TABLE_SCORERS);
1732
+ const { page, perPage: perPageInput } = pagination;
1733
+ const perPage = normalizePerPage(perPageInput, 100);
1734
+ const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
1735
+ const fullTableName = this.#db.getTableName(TABLE_SCORERS);
1826
1736
  const countQuery = createSqlBuilder().count().from(fullTableName).where("traceId = ?", traceId).andWhere("spanId = ?", spanId);
1827
- const countResult = await this.operations.executeQuery(countQuery.build());
1737
+ const countResult = await this.#db.executeQuery(countQuery.build());
1828
1738
  const total = Array.isArray(countResult) ? Number(countResult?.[0]?.count ?? 0) : Number(countResult?.count ?? 0);
1829
1739
  if (total === 0) {
1830
1740
  return {
1831
1741
  pagination: {
1832
1742
  total: 0,
1833
- page: pagination.page,
1834
- perPage: pagination.perPage,
1743
+ page,
1744
+ perPage: perPageForResponse,
1835
1745
  hasMore: false
1836
1746
  },
1837
1747
  scores: []
1838
1748
  };
1839
1749
  }
1840
- const limit = pagination.perPage + 1;
1841
- const selectQuery = createSqlBuilder().select("*").from(fullTableName).where("traceId = ?", traceId).andWhere("spanId = ?", spanId).orderBy("createdAt", "DESC").limit(limit).offset(pagination.page * pagination.perPage);
1750
+ const end = perPageInput === false ? total : start + perPage;
1751
+ const limitValue = perPageInput === false ? total : perPage;
1752
+ const selectQuery = createSqlBuilder().select("*").from(fullTableName).where("traceId = ?", traceId).andWhere("spanId = ?", spanId).orderBy("createdAt", "DESC").limit(limitValue).offset(start);
1842
1753
  const { sql, params } = selectQuery.build();
1843
- const results = await this.operations.executeQuery({ sql, params });
1844
- const rows = Array.isArray(results) ? results : [];
1845
- const scores = rows.slice(0, pagination.perPage).map(transformScoreRow);
1754
+ const results = await this.#db.executeQuery({ sql, params });
1755
+ const scores = Array.isArray(results) ? results.map(transformScoreRow) : [];
1846
1756
  return {
1847
1757
  pagination: {
1848
1758
  total,
1849
- page: pagination.page,
1850
- perPage: pagination.perPage,
1851
- hasMore: rows.length > pagination.perPage
1759
+ page,
1760
+ perPage: perPageForResponse,
1761
+ hasMore: end < total
1852
1762
  },
1853
1763
  scores
1854
1764
  };
1855
1765
  } catch (error) {
1856
1766
  throw new MastraError(
1857
1767
  {
1858
- id: "CLOUDFLARE_D1_STORE_SCORES_GET_SCORES_BY_SPAN_FAILED",
1768
+ id: createStorageErrorId("CLOUDFLARE_D1", "GET_SCORES_BY_SPAN", "FAILED"),
1859
1769
  domain: ErrorDomain.STORAGE,
1860
1770
  category: ErrorCategory.THIRD_PARTY
1861
1771
  },
@@ -1865,10 +1775,16 @@ var ScoresStorageD1 = class extends ScoresStorage {
1865
1775
  }
1866
1776
  };
1867
1777
  var WorkflowsStorageD1 = class extends WorkflowsStorage {
1868
- operations;
1869
- constructor({ operations }) {
1778
+ #db;
1779
+ constructor(config) {
1870
1780
  super();
1871
- this.operations = operations;
1781
+ this.#db = new D1DB(resolveD1Config(config));
1782
+ }
1783
+ async init() {
1784
+ await this.#db.createTable({ tableName: TABLE_WORKFLOW_SNAPSHOT, schema: TABLE_SCHEMAS[TABLE_WORKFLOW_SNAPSHOT] });
1785
+ }
1786
+ async dangerouslyClearAll() {
1787
+ await this.#db.clearTable({ tableName: TABLE_WORKFLOW_SNAPSHOT });
1872
1788
  }
1873
1789
  updateWorkflowResults({
1874
1790
  // workflowName,
@@ -1890,11 +1806,13 @@ var WorkflowsStorageD1 = class extends WorkflowsStorage {
1890
1806
  workflowName,
1891
1807
  runId,
1892
1808
  resourceId,
1893
- snapshot
1809
+ snapshot,
1810
+ createdAt,
1811
+ updatedAt
1894
1812
  }) {
1895
- const fullTableName = this.operations.getTableName(TABLE_WORKFLOW_SNAPSHOT);
1813
+ const fullTableName = this.#db.getTableName(TABLE_WORKFLOW_SNAPSHOT);
1896
1814
  const now = (/* @__PURE__ */ new Date()).toISOString();
1897
- const currentSnapshot = await this.operations.load({
1815
+ const currentSnapshot = await this.#db.load({
1898
1816
  tableName: TABLE_WORKFLOW_SNAPSHOT,
1899
1817
  keys: { workflow_name: workflowName, run_id: runId }
1900
1818
  });
@@ -1902,16 +1820,16 @@ var WorkflowsStorageD1 = class extends WorkflowsStorage {
1902
1820
  ...currentSnapshot,
1903
1821
  resourceId,
1904
1822
  snapshot: JSON.stringify(snapshot),
1905
- updatedAt: now
1823
+ updatedAt: updatedAt ? updatedAt.toISOString() : now
1906
1824
  } : {
1907
1825
  workflow_name: workflowName,
1908
1826
  run_id: runId,
1909
1827
  resourceId,
1910
1828
  snapshot,
1911
- createdAt: now,
1912
- updatedAt: now
1829
+ createdAt: createdAt ? createdAt.toISOString() : now,
1830
+ updatedAt: updatedAt ? updatedAt.toISOString() : now
1913
1831
  };
1914
- const processedRecord = await this.operations.processRecord(persisting);
1832
+ const processedRecord = await this.#db.processRecord(persisting);
1915
1833
  const columns = Object.keys(processedRecord);
1916
1834
  const values = Object.values(processedRecord);
1917
1835
  const updateMap = {
@@ -1922,11 +1840,11 @@ var WorkflowsStorageD1 = class extends WorkflowsStorage {
1922
1840
  const query = createSqlBuilder().insert(fullTableName, columns, values, ["workflow_name", "run_id"], updateMap);
1923
1841
  const { sql, params } = query.build();
1924
1842
  try {
1925
- await this.operations.executeQuery({ sql, params });
1843
+ await this.#db.executeQuery({ sql, params });
1926
1844
  } catch (error) {
1927
1845
  throw new MastraError(
1928
1846
  {
1929
- id: "CLOUDFLARE_D1_STORAGE_PERSIST_WORKFLOW_SNAPSHOT_ERROR",
1847
+ id: createStorageErrorId("CLOUDFLARE_D1", "PERSIST_WORKFLOW_SNAPSHOT", "FAILED"),
1930
1848
  domain: ErrorDomain.STORAGE,
1931
1849
  category: ErrorCategory.THIRD_PARTY,
1932
1850
  text: `Failed to persist workflow snapshot: ${error instanceof Error ? error.message : String(error)}`,
@@ -1940,7 +1858,7 @@ var WorkflowsStorageD1 = class extends WorkflowsStorage {
1940
1858
  const { workflowName, runId } = params;
1941
1859
  this.logger.debug("Loading workflow snapshot", { workflowName, runId });
1942
1860
  try {
1943
- const d = await this.operations.load({
1861
+ const d = await this.#db.load({
1944
1862
  tableName: TABLE_WORKFLOW_SNAPSHOT,
1945
1863
  keys: {
1946
1864
  workflow_name: workflowName,
@@ -1951,7 +1869,7 @@ var WorkflowsStorageD1 = class extends WorkflowsStorage {
1951
1869
  } catch (error) {
1952
1870
  throw new MastraError(
1953
1871
  {
1954
- id: "CLOUDFLARE_D1_STORAGE_LOAD_WORKFLOW_SNAPSHOT_ERROR",
1872
+ id: createStorageErrorId("CLOUDFLARE_D1", "LOAD_WORKFLOW_SNAPSHOT", "FAILED"),
1955
1873
  domain: ErrorDomain.STORAGE,
1956
1874
  category: ErrorCategory.THIRD_PARTY,
1957
1875
  text: `Failed to load workflow snapshot: ${error instanceof Error ? error.message : String(error)}`,
@@ -1983,17 +1901,22 @@ var WorkflowsStorageD1 = class extends WorkflowsStorage {
1983
1901
  workflowName,
1984
1902
  fromDate,
1985
1903
  toDate,
1986
- limit,
1987
- offset,
1988
- resourceId
1904
+ page,
1905
+ perPage,
1906
+ resourceId,
1907
+ status
1989
1908
  } = {}) {
1990
- const fullTableName = this.operations.getTableName(TABLE_WORKFLOW_SNAPSHOT);
1909
+ const fullTableName = this.#db.getTableName(TABLE_WORKFLOW_SNAPSHOT);
1991
1910
  try {
1992
1911
  const builder = createSqlBuilder().select().from(fullTableName);
1993
1912
  const countBuilder = createSqlBuilder().count().from(fullTableName);
1994
1913
  if (workflowName) builder.whereAnd("workflow_name = ?", workflowName);
1914
+ if (status) {
1915
+ builder.whereAnd("json_extract(snapshot, '$.status') = ?", status);
1916
+ countBuilder.whereAnd("json_extract(snapshot, '$.status') = ?", status);
1917
+ }
1995
1918
  if (resourceId) {
1996
- const hasResourceId = await this.operations.hasColumn(fullTableName, "resourceId");
1919
+ const hasResourceId = await this.#db.hasColumn(fullTableName, "resourceId");
1997
1920
  if (hasResourceId) {
1998
1921
  builder.whereAnd("resourceId = ?", resourceId);
1999
1922
  countBuilder.whereAnd("resourceId = ?", resourceId);
@@ -2010,26 +1933,29 @@ var WorkflowsStorageD1 = class extends WorkflowsStorage {
2010
1933
  countBuilder.whereAnd("createdAt <= ?", toDate instanceof Date ? toDate.toISOString() : toDate);
2011
1934
  }
2012
1935
  builder.orderBy("createdAt", "DESC");
2013
- if (typeof limit === "number") builder.limit(limit);
2014
- if (typeof offset === "number") builder.offset(offset);
1936
+ if (typeof perPage === "number" && typeof page === "number") {
1937
+ const offset = page * perPage;
1938
+ builder.limit(perPage);
1939
+ builder.offset(offset);
1940
+ }
2015
1941
  const { sql, params } = builder.build();
2016
1942
  let total = 0;
2017
- if (limit !== void 0 && offset !== void 0) {
1943
+ if (perPage !== void 0 && page !== void 0) {
2018
1944
  const { sql: countSql, params: countParams } = countBuilder.build();
2019
- const countResult = await this.operations.executeQuery({
1945
+ const countResult = await this.#db.executeQuery({
2020
1946
  sql: countSql,
2021
1947
  params: countParams,
2022
1948
  first: true
2023
1949
  });
2024
1950
  total = Number(countResult?.count ?? 0);
2025
1951
  }
2026
- const results = await this.operations.executeQuery({ sql, params });
1952
+ const results = await this.#db.executeQuery({ sql, params });
2027
1953
  const runs = (isArrayOfRecords(results) ? results : []).map((row) => this.parseWorkflowRun(row));
2028
1954
  return { runs, total: total || runs.length };
2029
1955
  } catch (error) {
2030
1956
  throw new MastraError(
2031
1957
  {
2032
- id: "CLOUDFLARE_D1_STORAGE_GET_WORKFLOW_RUNS_ERROR",
1958
+ id: createStorageErrorId("CLOUDFLARE_D1", "LIST_WORKFLOW_RUNS", "FAILED"),
2033
1959
  domain: ErrorDomain.STORAGE,
2034
1960
  category: ErrorCategory.THIRD_PARTY,
2035
1961
  text: `Failed to retrieve workflow runs: ${error instanceof Error ? error.message : String(error)}`,
@@ -2046,7 +1972,7 @@ var WorkflowsStorageD1 = class extends WorkflowsStorage {
2046
1972
  runId,
2047
1973
  workflowName
2048
1974
  }) {
2049
- const fullTableName = this.operations.getTableName(TABLE_WORKFLOW_SNAPSHOT);
1975
+ const fullTableName = this.#db.getTableName(TABLE_WORKFLOW_SNAPSHOT);
2050
1976
  try {
2051
1977
  const conditions = [];
2052
1978
  const params = [];
@@ -2060,13 +1986,13 @@ var WorkflowsStorageD1 = class extends WorkflowsStorage {
2060
1986
  }
2061
1987
  const whereClause = conditions.length > 0 ? "WHERE " + conditions.join(" AND ") : "";
2062
1988
  const sql = `SELECT * FROM ${fullTableName} ${whereClause} ORDER BY createdAt DESC LIMIT 1`;
2063
- const result = await this.operations.executeQuery({ sql, params, first: true });
1989
+ const result = await this.#db.executeQuery({ sql, params, first: true });
2064
1990
  if (!result) return null;
2065
1991
  return this.parseWorkflowRun(result);
2066
1992
  } catch (error) {
2067
1993
  throw new MastraError(
2068
1994
  {
2069
- id: "CLOUDFLARE_D1_STORAGE_GET_WORKFLOW_RUN_BY_ID_ERROR",
1995
+ id: createStorageErrorId("CLOUDFLARE_D1", "GET_WORKFLOW_RUN_BY_ID", "FAILED"),
2070
1996
  domain: ErrorDomain.STORAGE,
2071
1997
  category: ErrorCategory.THIRD_PARTY,
2072
1998
  text: `Failed to retrieve workflow run by ID: ${error instanceof Error ? error.message : String(error)}`,
@@ -2076,13 +2002,31 @@ var WorkflowsStorageD1 = class extends WorkflowsStorage {
2076
2002
  );
2077
2003
  }
2078
2004
  }
2005
+ async deleteWorkflowRunById({ runId, workflowName }) {
2006
+ const fullTableName = this.#db.getTableName(TABLE_WORKFLOW_SNAPSHOT);
2007
+ try {
2008
+ const sql = `DELETE FROM ${fullTableName} WHERE workflow_name = ? AND run_id = ?`;
2009
+ const params = [workflowName, runId];
2010
+ await this.#db.executeQuery({ sql, params });
2011
+ } catch (error) {
2012
+ throw new MastraError(
2013
+ {
2014
+ id: createStorageErrorId("CLOUDFLARE_D1", "DELETE_WORKFLOW_RUN_BY_ID", "FAILED"),
2015
+ domain: ErrorDomain.STORAGE,
2016
+ category: ErrorCategory.THIRD_PARTY,
2017
+ text: `Failed to delete workflow run by ID: ${error instanceof Error ? error.message : String(error)}`,
2018
+ details: { runId, workflowName }
2019
+ },
2020
+ error
2021
+ );
2022
+ }
2023
+ }
2079
2024
  };
2080
2025
 
2081
2026
  // src/storage/index.ts
2082
2027
  var D1Store = class extends MastraStorage {
2083
2028
  client;
2084
2029
  binding;
2085
- // D1Database binding
2086
2030
  tablePrefix;
2087
2031
  stores;
2088
2032
  /**
@@ -2091,7 +2035,7 @@ var D1Store = class extends MastraStorage {
2091
2035
  */
2092
2036
  constructor(config) {
2093
2037
  try {
2094
- super({ name: "D1" });
2038
+ super({ id: config.id, name: "D1", disableInit: config.disableInit });
2095
2039
  if (config.tablePrefix && !/^[a-zA-Z0-9_]*$/.test(config.tablePrefix)) {
2096
2040
  throw new Error("Invalid tablePrefix: only letters, numbers, and underscores are allowed.");
2097
2041
  }
@@ -2129,7 +2073,7 @@ var D1Store = class extends MastraStorage {
2129
2073
  } catch (error) {
2130
2074
  throw new MastraError(
2131
2075
  {
2132
- id: "CLOUDFLARE_D1_STORAGE_INITIALIZATION_ERROR",
2076
+ id: createStorageErrorId("CLOUDFLARE_D1", "INITIALIZATION", "FAILED"),
2133
2077
  domain: ErrorDomain.STORAGE,
2134
2078
  category: ErrorCategory.SYSTEM,
2135
2079
  text: "Error initializing D1Store"
@@ -2137,219 +2081,26 @@ var D1Store = class extends MastraStorage {
2137
2081
  error
2138
2082
  );
2139
2083
  }
2140
- const operations = new StoreOperationsD1({
2141
- client: this.client,
2142
- binding: this.binding,
2143
- tablePrefix: this.tablePrefix
2144
- });
2145
- const scores = new ScoresStorageD1({
2146
- operations
2147
- });
2148
- const workflows = new WorkflowsStorageD1({
2149
- operations
2150
- });
2151
- const memory = new MemoryStorageD1({
2152
- operations
2153
- });
2084
+ let scores;
2085
+ let workflows;
2086
+ let memory;
2087
+ if (this.binding) {
2088
+ const domainConfig = { binding: this.binding, tablePrefix: this.tablePrefix };
2089
+ scores = new ScoresStorageD1(domainConfig);
2090
+ workflows = new WorkflowsStorageD1(domainConfig);
2091
+ memory = new MemoryStorageD1(domainConfig);
2092
+ } else {
2093
+ const domainConfig = { client: this.client, tablePrefix: this.tablePrefix };
2094
+ scores = new ScoresStorageD1(domainConfig);
2095
+ workflows = new WorkflowsStorageD1(domainConfig);
2096
+ memory = new MemoryStorageD1(domainConfig);
2097
+ }
2154
2098
  this.stores = {
2155
- operations,
2156
2099
  scores,
2157
2100
  workflows,
2158
2101
  memory
2159
2102
  };
2160
2103
  }
2161
- get supports() {
2162
- return {
2163
- selectByIncludeResourceScope: true,
2164
- resourceWorkingMemory: true,
2165
- hasColumn: true,
2166
- createTable: true,
2167
- deleteMessages: false,
2168
- getScoresBySpan: true
2169
- };
2170
- }
2171
- async createTable({
2172
- tableName,
2173
- schema
2174
- }) {
2175
- return this.stores.operations.createTable({ tableName, schema });
2176
- }
2177
- /**
2178
- * Alters table schema to add columns if they don't exist
2179
- * @param tableName Name of the table
2180
- * @param schema Schema of the table
2181
- * @param ifNotExists Array of column names to add if they don't exist
2182
- */
2183
- async alterTable({
2184
- tableName,
2185
- schema,
2186
- ifNotExists
2187
- }) {
2188
- return this.stores.operations.alterTable({ tableName, schema, ifNotExists });
2189
- }
2190
- async clearTable({ tableName }) {
2191
- return this.stores.operations.clearTable({ tableName });
2192
- }
2193
- async dropTable({ tableName }) {
2194
- return this.stores.operations.dropTable({ tableName });
2195
- }
2196
- async hasColumn(table, column) {
2197
- return this.stores.operations.hasColumn(table, column);
2198
- }
2199
- async insert({ tableName, record }) {
2200
- return this.stores.operations.insert({ tableName, record });
2201
- }
2202
- async load({ tableName, keys }) {
2203
- return this.stores.operations.load({ tableName, keys });
2204
- }
2205
- async getThreadById({ threadId }) {
2206
- return this.stores.memory.getThreadById({ threadId });
2207
- }
2208
- /**
2209
- * @deprecated use getThreadsByResourceIdPaginated instead
2210
- */
2211
- async getThreadsByResourceId({ resourceId }) {
2212
- return this.stores.memory.getThreadsByResourceId({ resourceId });
2213
- }
2214
- async getThreadsByResourceIdPaginated(args) {
2215
- return this.stores.memory.getThreadsByResourceIdPaginated(args);
2216
- }
2217
- async saveThread({ thread }) {
2218
- return this.stores.memory.saveThread({ thread });
2219
- }
2220
- async updateThread({
2221
- id,
2222
- title,
2223
- metadata
2224
- }) {
2225
- return this.stores.memory.updateThread({ id, title, metadata });
2226
- }
2227
- async deleteThread({ threadId }) {
2228
- return this.stores.memory.deleteThread({ threadId });
2229
- }
2230
- async saveMessages(args) {
2231
- return this.stores.memory.saveMessages(args);
2232
- }
2233
- async getMessages({
2234
- threadId,
2235
- selectBy,
2236
- format
2237
- }) {
2238
- return this.stores.memory.getMessages({ threadId, selectBy, format });
2239
- }
2240
- async getMessagesPaginated({
2241
- threadId,
2242
- selectBy,
2243
- format
2244
- }) {
2245
- return this.stores.memory.getMessagesPaginated({ threadId, selectBy, format });
2246
- }
2247
- async updateWorkflowResults({
2248
- workflowName,
2249
- runId,
2250
- stepId,
2251
- result,
2252
- requestContext
2253
- }) {
2254
- return this.stores.workflows.updateWorkflowResults({ workflowName, runId, stepId, result, requestContext });
2255
- }
2256
- async updateWorkflowState({
2257
- workflowName,
2258
- runId,
2259
- opts
2260
- }) {
2261
- return this.stores.workflows.updateWorkflowState({ workflowName, runId, opts });
2262
- }
2263
- async persistWorkflowSnapshot({
2264
- workflowName,
2265
- runId,
2266
- resourceId,
2267
- snapshot
2268
- }) {
2269
- return this.stores.workflows.persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot });
2270
- }
2271
- async loadWorkflowSnapshot(params) {
2272
- return this.stores.workflows.loadWorkflowSnapshot(params);
2273
- }
2274
- async listWorkflowRuns({
2275
- workflowName,
2276
- fromDate,
2277
- toDate,
2278
- limit,
2279
- offset,
2280
- resourceId
2281
- } = {}) {
2282
- return this.stores.workflows.listWorkflowRuns({ workflowName, fromDate, toDate, limit, offset, resourceId });
2283
- }
2284
- async getWorkflowRunById({
2285
- runId,
2286
- workflowName
2287
- }) {
2288
- return this.stores.workflows.getWorkflowRunById({ runId, workflowName });
2289
- }
2290
- /**
2291
- * Insert multiple records in a batch operation
2292
- * @param tableName The table to insert into
2293
- * @param records The records to insert
2294
- */
2295
- async batchInsert({ tableName, records }) {
2296
- return this.stores.operations.batchInsert({ tableName, records });
2297
- }
2298
- async updateMessages(_args) {
2299
- return this.stores.memory.updateMessages(_args);
2300
- }
2301
- async getResourceById({ resourceId }) {
2302
- return this.stores.memory.getResourceById({ resourceId });
2303
- }
2304
- async saveResource({ resource }) {
2305
- return this.stores.memory.saveResource({ resource });
2306
- }
2307
- async updateResource({
2308
- resourceId,
2309
- workingMemory,
2310
- metadata
2311
- }) {
2312
- return this.stores.memory.updateResource({ resourceId, workingMemory, metadata });
2313
- }
2314
- async getScoreById({ id: _id }) {
2315
- return this.stores.scores.getScoreById({ id: _id });
2316
- }
2317
- async saveScore(_score) {
2318
- return this.stores.scores.saveScore(_score);
2319
- }
2320
- async getScoresByRunId({
2321
- runId: _runId,
2322
- pagination: _pagination
2323
- }) {
2324
- return this.stores.scores.getScoresByRunId({ runId: _runId, pagination: _pagination });
2325
- }
2326
- async getScoresByEntityId({
2327
- entityId: _entityId,
2328
- entityType: _entityType,
2329
- pagination: _pagination
2330
- }) {
2331
- return this.stores.scores.getScoresByEntityId({
2332
- entityId: _entityId,
2333
- entityType: _entityType,
2334
- pagination: _pagination
2335
- });
2336
- }
2337
- async getScoresByScorerId({
2338
- scorerId,
2339
- pagination,
2340
- entityId,
2341
- entityType,
2342
- source
2343
- }) {
2344
- return this.stores.scores.getScoresByScorerId({ scorerId, pagination, entityId, entityType, source });
2345
- }
2346
- async getScoresBySpan({
2347
- traceId,
2348
- spanId,
2349
- pagination
2350
- }) {
2351
- return this.stores.scores.getScoresBySpan({ traceId, spanId, pagination });
2352
- }
2353
2104
  /**
2354
2105
  * Close the database connection
2355
2106
  * No explicit cleanup needed for D1 in either REST or Workers Binding mode
@@ -2359,6 +2110,6 @@ var D1Store = class extends MastraStorage {
2359
2110
  }
2360
2111
  };
2361
2112
 
2362
- export { D1Store };
2113
+ export { D1Store, MemoryStorageD1, ScoresStorageD1, WorkflowsStorageD1 };
2363
2114
  //# sourceMappingURL=index.js.map
2364
2115
  //# sourceMappingURL=index.js.map