@mastra/cloudflare-d1 1.0.0-beta.1 → 1.0.0-beta.11

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