@mastra/cloudflare-d1 1.0.0-beta.5 → 1.0.0-beta.7

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