@mastra/cloudflare-d1 0.0.0-switch-to-core-20250424015131 → 0.0.0-tool-call-parts-20250630193309

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,9 +1,10 @@
1
+ import { MessageList } from '@mastra/core/agent';
2
+ import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
1
3
  import { MastraStorage, TABLE_THREADS, TABLE_MESSAGES, TABLE_WORKFLOW_SNAPSHOT, TABLE_TRACES, TABLE_EVALS } from '@mastra/core/storage';
2
4
  import Cloudflare from 'cloudflare';
5
+ import { parseSqlIdentifier } from '@mastra/core/utils';
3
6
 
4
7
  // src/storage/index.ts
5
-
6
- // src/storage/sql-builder.ts
7
8
  var SqlBuilder = class {
8
9
  sql = "";
9
10
  params = [];
@@ -13,12 +14,15 @@ var SqlBuilder = class {
13
14
  if (!columns || Array.isArray(columns) && columns.length === 0) {
14
15
  this.sql = "SELECT *";
15
16
  } else {
16
- this.sql = `SELECT ${Array.isArray(columns) ? columns.join(", ") : columns}`;
17
+ const cols = Array.isArray(columns) ? columns : [columns];
18
+ const parsedCols = cols.map((col) => parseSelectIdentifier(col));
19
+ this.sql = `SELECT ${parsedCols.join(", ")}`;
17
20
  }
18
21
  return this;
19
22
  }
20
23
  from(table) {
21
- this.sql += ` FROM ${table}`;
24
+ const parsedTableName = parseSqlIdentifier(table, "table name");
25
+ this.sql += ` FROM ${parsedTableName}`;
22
26
  return this;
23
27
  }
24
28
  /**
@@ -55,7 +59,11 @@ var SqlBuilder = class {
55
59
  return this;
56
60
  }
57
61
  orderBy(column, direction = "ASC") {
58
- this.sql += ` ORDER BY ${column} ${direction}`;
62
+ const parsedColumn = parseSqlIdentifier(column, "column name");
63
+ if (!["ASC", "DESC"].includes(direction)) {
64
+ throw new Error(`Invalid sort direction: ${direction}`);
65
+ }
66
+ this.sql += ` ORDER BY ${parsedColumn} ${direction}`;
59
67
  return this;
60
68
  }
61
69
  limit(count) {
@@ -68,6 +76,10 @@ var SqlBuilder = class {
68
76
  this.params.push(count);
69
77
  return this;
70
78
  }
79
+ count() {
80
+ this.sql += "SELECT COUNT(*) AS count";
81
+ return this;
82
+ }
71
83
  /**
72
84
  * Insert a row, or update specific columns on conflict (upsert).
73
85
  * @param table Table name
@@ -77,27 +89,33 @@ var SqlBuilder = class {
77
89
  * @param updateMap Object mapping columns to update to their new value (e.g. { name: 'excluded.name' })
78
90
  */
79
91
  insert(table, columns, values, conflictColumns, updateMap) {
80
- const placeholders = columns.map(() => "?").join(", ");
92
+ const parsedTableName = parseSqlIdentifier(table, "table name");
93
+ const parsedColumns = columns.map((col) => parseSqlIdentifier(col, "column name"));
94
+ const placeholders = parsedColumns.map(() => "?").join(", ");
81
95
  if (conflictColumns && updateMap) {
96
+ const parsedConflictColumns = conflictColumns.map((col) => parseSqlIdentifier(col, "column name"));
82
97
  const updateClause = Object.entries(updateMap).map(([col, expr]) => `${col} = ${expr}`).join(", ");
83
- this.sql = `INSERT INTO ${table} (${columns.join(", ")}) VALUES (${placeholders}) ON CONFLICT(${conflictColumns.join(", ")}) DO UPDATE SET ${updateClause}`;
98
+ this.sql = `INSERT INTO ${parsedTableName} (${parsedColumns.join(", ")}) VALUES (${placeholders}) ON CONFLICT(${parsedConflictColumns.join(", ")}) DO UPDATE SET ${updateClause}`;
84
99
  this.params.push(...values);
85
100
  return this;
86
101
  }
87
- this.sql = `INSERT INTO ${table} (${columns.join(", ")}) VALUES (${placeholders})`;
102
+ this.sql = `INSERT INTO ${parsedTableName} (${parsedColumns.join(", ")}) VALUES (${placeholders})`;
88
103
  this.params.push(...values);
89
104
  return this;
90
105
  }
91
106
  // Update operations
92
107
  update(table, columns, values) {
93
- const setClause = columns.map((col) => `${col} = ?`).join(", ");
94
- this.sql = `UPDATE ${table} SET ${setClause}`;
108
+ const parsedTableName = parseSqlIdentifier(table, "table name");
109
+ const parsedColumns = columns.map((col) => parseSqlIdentifier(col, "column name"));
110
+ const setClause = parsedColumns.map((col) => `${col} = ?`).join(", ");
111
+ this.sql = `UPDATE ${parsedTableName} SET ${setClause}`;
95
112
  this.params.push(...values);
96
113
  return this;
97
114
  }
98
115
  // Delete operations
99
116
  delete(table) {
100
- this.sql = `DELETE FROM ${table}`;
117
+ const parsedTableName = parseSqlIdentifier(table, "table name");
118
+ this.sql = `DELETE FROM ${parsedTableName}`;
101
119
  return this;
102
120
  }
103
121
  /**
@@ -108,9 +126,16 @@ var SqlBuilder = class {
108
126
  * @returns The builder instance
109
127
  */
110
128
  createTable(table, columnDefinitions, tableConstraints) {
111
- const columns = columnDefinitions.join(", ");
129
+ const parsedTableName = parseSqlIdentifier(table, "table name");
130
+ const parsedColumnDefinitions = columnDefinitions.map((def) => {
131
+ const colName = def.split(/\s+/)[0];
132
+ if (!colName) throw new Error("Empty column name in definition");
133
+ parseSqlIdentifier(colName, "column name");
134
+ return def;
135
+ });
136
+ const columns = parsedColumnDefinitions.join(", ");
112
137
  const constraints = tableConstraints && tableConstraints.length > 0 ? ", " + tableConstraints.join(", ") : "";
113
- this.sql = `CREATE TABLE IF NOT EXISTS ${table} (${columns}${constraints})`;
138
+ this.sql = `CREATE TABLE IF NOT EXISTS ${parsedTableName} (${columns}${constraints})`;
114
139
  return this;
115
140
  }
116
141
  /**
@@ -133,13 +158,10 @@ var SqlBuilder = class {
133
158
  * @returns The builder instance
134
159
  */
135
160
  createIndex(indexName, tableName, columnName, indexType = "") {
136
- this.sql = `CREATE ${indexType ? indexType + " " : ""}INDEX IF NOT EXISTS ${indexName} ON ${tableName}(${columnName})`;
137
- return this;
138
- }
139
- // Raw SQL with params
140
- raw(sql, ...params) {
141
- this.sql = sql;
142
- this.params.push(...params);
161
+ const parsedIndexName = parseSqlIdentifier(indexName, "index name");
162
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
163
+ const parsedColumnName = parseSqlIdentifier(columnName, "column name");
164
+ this.sql = `CREATE ${indexType ? indexType + " " : ""}INDEX IF NOT EXISTS ${parsedIndexName} ON ${parsedTableName}(${parsedColumnName})`;
143
165
  return this;
144
166
  }
145
167
  /**
@@ -149,11 +171,12 @@ var SqlBuilder = class {
149
171
  * @param exact If true, will not add % wildcards
150
172
  */
151
173
  like(column, value, exact = false) {
174
+ const parsedColumnName = parseSqlIdentifier(column, "column name");
152
175
  const likeValue = exact ? value : `%${value}%`;
153
176
  if (this.whereAdded) {
154
- this.sql += ` AND ${column} LIKE ?`;
177
+ this.sql += ` AND ${parsedColumnName} LIKE ?`;
155
178
  } else {
156
- this.sql += ` WHERE ${column} LIKE ?`;
179
+ this.sql += ` WHERE ${parsedColumnName} LIKE ?`;
157
180
  this.whereAdded = true;
158
181
  }
159
182
  this.params.push(likeValue);
@@ -166,11 +189,13 @@ var SqlBuilder = class {
166
189
  * @param value The value to match
167
190
  */
168
191
  jsonLike(column, key, value) {
169
- const jsonPattern = `%"${key}":"${value}"%`;
192
+ const parsedColumnName = parseSqlIdentifier(column, "column name");
193
+ const parsedKey = parseSqlIdentifier(key, "key name");
194
+ const jsonPattern = `%"${parsedKey}":"${value}"%`;
170
195
  if (this.whereAdded) {
171
- this.sql += ` AND ${column} LIKE ?`;
196
+ this.sql += ` AND ${parsedColumnName} LIKE ?`;
172
197
  } else {
173
- this.sql += ` WHERE ${column} LIKE ?`;
198
+ this.sql += ` WHERE ${parsedColumnName} LIKE ?`;
174
199
  this.whereAdded = true;
175
200
  }
176
201
  this.params.push(jsonPattern);
@@ -200,6 +225,15 @@ var SqlBuilder = class {
200
225
  function createSqlBuilder() {
201
226
  return new SqlBuilder();
202
227
  }
228
+ var SQL_IDENTIFIER_PATTERN = /^[a-zA-Z0-9_]+(\s+AS\s+[a-zA-Z0-9_]+)?$/;
229
+ function parseSelectIdentifier(column) {
230
+ if (column !== "*" && !SQL_IDENTIFIER_PATTERN.test(column)) {
231
+ throw new Error(
232
+ `Invalid column name: "${column}". Must be "*" or a valid identifier (letters, numbers, underscores), optionally with "AS alias".`
233
+ );
234
+ }
235
+ return column;
236
+ }
203
237
 
204
238
  // src/storage/index.ts
205
239
  function isArrayOfRecords(value) {
@@ -217,24 +251,39 @@ var D1Store = class extends MastraStorage {
217
251
  * @param config Configuration for D1 access (either REST API or Workers Binding API)
218
252
  */
219
253
  constructor(config) {
220
- super({ name: "D1" });
221
- this.tablePrefix = config.tablePrefix || "";
222
- if ("binding" in config) {
223
- if (!config.binding) {
224
- throw new Error("D1 binding is required when using Workers Binding API");
225
- }
226
- this.binding = config.binding;
227
- this.logger.info("Using D1 Workers Binding API");
228
- } else {
229
- if (!config.accountId || !config.databaseId || !config.apiToken) {
230
- throw new Error("accountId, databaseId, and apiToken are required when using REST API");
254
+ try {
255
+ super({ name: "D1" });
256
+ if (config.tablePrefix && !/^[a-zA-Z0-9_]*$/.test(config.tablePrefix)) {
257
+ throw new Error("Invalid tablePrefix: only letters, numbers, and underscores are allowed.");
231
258
  }
232
- this.accountId = config.accountId;
233
- this.databaseId = config.databaseId;
234
- this.client = new Cloudflare({
235
- apiToken: config.apiToken
236
- });
237
- this.logger.info("Using D1 REST API");
259
+ this.tablePrefix = config.tablePrefix || "";
260
+ if ("binding" in config) {
261
+ if (!config.binding) {
262
+ throw new Error("D1 binding is required when using Workers Binding API");
263
+ }
264
+ this.binding = config.binding;
265
+ this.logger.info("Using D1 Workers Binding API");
266
+ } else {
267
+ if (!config.accountId || !config.databaseId || !config.apiToken) {
268
+ throw new Error("accountId, databaseId, and apiToken are required when using REST API");
269
+ }
270
+ this.accountId = config.accountId;
271
+ this.databaseId = config.databaseId;
272
+ this.client = new Cloudflare({
273
+ apiToken: config.apiToken
274
+ });
275
+ this.logger.info("Using D1 REST API");
276
+ }
277
+ } catch (error) {
278
+ throw new MastraError(
279
+ {
280
+ id: "CLOUDFLARE_D1_STORAGE_INITIALIZATION_ERROR",
281
+ domain: ErrorDomain.STORAGE,
282
+ category: ErrorCategory.SYSTEM,
283
+ text: "Error initializing D1Store"
284
+ },
285
+ error
286
+ );
238
287
  }
239
288
  }
240
289
  // Helper method to get the full table name with prefix
@@ -244,30 +293,6 @@ var D1Store = class extends MastraStorage {
244
293
  formatSqlParams(params) {
245
294
  return params.map((p) => p === void 0 || p === null ? null : p);
246
295
  }
247
- // Helper method to create SQL indexes for better query performance
248
- async createIndexIfNotExists(tableName, columnName, indexType = "") {
249
- const fullTableName = this.getTableName(tableName);
250
- const indexName = `idx_${tableName}_${columnName}`;
251
- try {
252
- const checkQuery = createSqlBuilder().checkIndexExists(indexName, fullTableName);
253
- const { sql: checkSql, params: checkParams } = checkQuery.build();
254
- const indexExists = await this.executeQuery({
255
- sql: checkSql,
256
- params: checkParams,
257
- first: true
258
- });
259
- if (!indexExists) {
260
- const createQuery = createSqlBuilder().createIndex(indexName, fullTableName, columnName, indexType);
261
- const { sql: createSql, params: createParams } = createQuery.build();
262
- await this.executeQuery({ sql: createSql, params: createParams });
263
- this.logger.debug(`Created index ${indexName} on ${fullTableName}(${columnName})`);
264
- }
265
- } catch (error) {
266
- this.logger.error(`Error creating index on ${fullTableName}(${columnName}):`, {
267
- message: error instanceof Error ? error.message : String(error)
268
- });
269
- }
270
- }
271
296
  async executeWorkersBindingQuery({
272
297
  sql,
273
298
  params = [],
@@ -371,34 +396,25 @@ var D1Store = class extends MastraStorage {
371
396
  throw new Error(`D1 query error: ${error.message}`);
372
397
  }
373
398
  }
374
- // Helper to convert storage type to SQL type
375
- getSqlType(type) {
376
- switch (type) {
377
- case "text":
378
- return "TEXT";
379
- case "timestamp":
380
- return "TIMESTAMP";
381
- case "integer":
382
- return "INTEGER";
383
- case "bigint":
384
- return "INTEGER";
385
- // SQLite doesn't have a separate BIGINT type
386
- case "jsonb":
387
- return "TEXT";
388
- // Store JSON as TEXT in SQLite
389
- default:
390
- return "TEXT";
399
+ // Helper to get existing table columns
400
+ async getTableColumns(tableName) {
401
+ try {
402
+ const sql = `PRAGMA table_info(${tableName})`;
403
+ const result = await this.executeQuery({ sql, params: [] });
404
+ if (!result || !Array.isArray(result)) {
405
+ return [];
406
+ }
407
+ return result.map((row) => ({
408
+ name: row.name,
409
+ type: row.type
410
+ }));
411
+ } catch (error) {
412
+ this.logger.error(`Error getting table columns for ${tableName}:`, {
413
+ message: error instanceof Error ? error.message : String(error)
414
+ });
415
+ return [];
391
416
  }
392
417
  }
393
- ensureDate(date) {
394
- if (!date) return void 0;
395
- return date instanceof Date ? date : new Date(date);
396
- }
397
- serializeDate(date) {
398
- if (!date) return void 0;
399
- const dateObj = this.ensureDate(date);
400
- return dateObj?.toISOString();
401
- }
402
418
  // Helper to serialize objects to JSON strings
403
419
  serializeValue(value) {
404
420
  if (value === null || value === void 0) return null;
@@ -432,6 +448,18 @@ var D1Store = class extends MastraStorage {
432
448
  }
433
449
  return value;
434
450
  }
451
+ getSqlType(type) {
452
+ switch (type) {
453
+ case "bigint":
454
+ return "INTEGER";
455
+ // SQLite uses INTEGER for all integer sizes
456
+ case "jsonb":
457
+ return "TEXT";
458
+ // Store JSON as TEXT in SQLite
459
+ default:
460
+ return super.getSqlType(type);
461
+ }
462
+ }
435
463
  async createTable({
436
464
  tableName,
437
465
  schema
@@ -447,16 +475,64 @@ var D1Store = class extends MastraStorage {
447
475
  if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
448
476
  tableConstraints.push("UNIQUE (workflow_name, run_id)");
449
477
  }
450
- const query = createSqlBuilder().createTable(fullTableName, columnDefinitions, tableConstraints);
451
- const { sql, params } = query.build();
452
478
  try {
479
+ const query = createSqlBuilder().createTable(fullTableName, columnDefinitions, tableConstraints);
480
+ const { sql, params } = query.build();
453
481
  await this.executeQuery({ sql, params });
454
482
  this.logger.debug(`Created table ${fullTableName}`);
455
483
  } catch (error) {
456
484
  this.logger.error(`Error creating table ${fullTableName}:`, {
457
485
  message: error instanceof Error ? error.message : String(error)
458
486
  });
459
- throw new Error(`Failed to create table ${fullTableName}: ${error}`);
487
+ throw new MastraError(
488
+ {
489
+ id: "CLOUDFLARE_D1_STORAGE_CREATE_TABLE_ERROR",
490
+ domain: ErrorDomain.STORAGE,
491
+ category: ErrorCategory.THIRD_PARTY,
492
+ text: `Failed to create table ${fullTableName}: ${error instanceof Error ? error.message : String(error)}`,
493
+ details: { tableName }
494
+ },
495
+ error
496
+ );
497
+ }
498
+ }
499
+ /**
500
+ * Alters table schema to add columns if they don't exist
501
+ * @param tableName Name of the table
502
+ * @param schema Schema of the table
503
+ * @param ifNotExists Array of column names to add if they don't exist
504
+ */
505
+ async alterTable({
506
+ tableName,
507
+ schema,
508
+ ifNotExists
509
+ }) {
510
+ const fullTableName = this.getTableName(tableName);
511
+ try {
512
+ const existingColumns = await this.getTableColumns(fullTableName);
513
+ const existingColumnNames = new Set(existingColumns.map((col) => col.name.toLowerCase()));
514
+ for (const columnName of ifNotExists) {
515
+ if (!existingColumnNames.has(columnName.toLowerCase()) && schema[columnName]) {
516
+ const columnDef = schema[columnName];
517
+ const sqlType = this.getSqlType(columnDef.type);
518
+ const nullable = columnDef.nullable === false ? "NOT NULL" : "";
519
+ const defaultValue = columnDef.nullable === false ? this.getDefaultValue(columnDef.type) : "";
520
+ const alterSql = `ALTER TABLE ${fullTableName} ADD COLUMN ${columnName} ${sqlType} ${nullable} ${defaultValue}`.trim();
521
+ await this.executeQuery({ sql: alterSql, params: [] });
522
+ this.logger.debug(`Added column ${columnName} to table ${fullTableName}`);
523
+ }
524
+ }
525
+ } catch (error) {
526
+ throw new MastraError(
527
+ {
528
+ id: "CLOUDFLARE_D1_STORAGE_ALTER_TABLE_ERROR",
529
+ domain: ErrorDomain.STORAGE,
530
+ category: ErrorCategory.THIRD_PARTY,
531
+ text: `Failed to alter table ${fullTableName}: ${error instanceof Error ? error.message : String(error)}`,
532
+ details: { tableName }
533
+ },
534
+ error
535
+ );
460
536
  }
461
537
  }
462
538
  async clearTable({ tableName }) {
@@ -467,10 +543,16 @@ var D1Store = class extends MastraStorage {
467
543
  await this.executeQuery({ sql, params });
468
544
  this.logger.debug(`Cleared table ${fullTableName}`);
469
545
  } catch (error) {
470
- this.logger.error(`Error clearing table ${fullTableName}:`, {
471
- message: error instanceof Error ? error.message : String(error)
472
- });
473
- throw new Error(`Failed to clear table ${fullTableName}: ${error}`);
546
+ throw new MastraError(
547
+ {
548
+ id: "CLOUDFLARE_D1_STORAGE_CLEAR_TABLE_ERROR",
549
+ domain: ErrorDomain.STORAGE,
550
+ category: ErrorCategory.THIRD_PARTY,
551
+ text: `Failed to clear table ${fullTableName}: ${error instanceof Error ? error.message : String(error)}`,
552
+ details: { tableName }
553
+ },
554
+ error
555
+ );
474
556
  }
475
557
  }
476
558
  async processRecord(record) {
@@ -490,8 +572,16 @@ var D1Store = class extends MastraStorage {
490
572
  try {
491
573
  await this.executeQuery({ sql, params });
492
574
  } catch (error) {
493
- this.logger.error(`Error inserting into ${fullTableName}:`, { error });
494
- throw new Error(`Failed to insert into ${fullTableName}: ${error}`);
575
+ throw new MastraError(
576
+ {
577
+ id: "CLOUDFLARE_D1_STORAGE_INSERT_ERROR",
578
+ domain: ErrorDomain.STORAGE,
579
+ category: ErrorCategory.THIRD_PARTY,
580
+ text: `Failed to insert into ${fullTableName}: ${error instanceof Error ? error.message : String(error)}`,
581
+ details: { tableName }
582
+ },
583
+ error
584
+ );
495
585
  }
496
586
  }
497
587
  async load({ tableName, keys }) {
@@ -517,10 +607,16 @@ var D1Store = class extends MastraStorage {
517
607
  }
518
608
  return processedResult;
519
609
  } catch (error) {
520
- this.logger.error(`Error loading from ${fullTableName}:`, {
521
- message: error instanceof Error ? error.message : String(error)
522
- });
523
- return null;
610
+ throw new MastraError(
611
+ {
612
+ id: "CLOUDFLARE_D1_STORAGE_LOAD_ERROR",
613
+ domain: ErrorDomain.STORAGE,
614
+ category: ErrorCategory.THIRD_PARTY,
615
+ text: `Failed to load from ${fullTableName}: ${error instanceof Error ? error.message : String(error)}`,
616
+ details: { tableName }
617
+ },
618
+ error
619
+ );
524
620
  }
525
621
  }
526
622
  async getThreadById({ threadId }) {
@@ -537,12 +633,24 @@ var D1Store = class extends MastraStorage {
537
633
  metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata || "{}") : thread.metadata || {}
538
634
  };
539
635
  } catch (error) {
540
- this.logger.error(`Error processing thread ${threadId}:`, {
541
- message: error instanceof Error ? error.message : String(error)
542
- });
636
+ const mastraError = new MastraError(
637
+ {
638
+ id: "CLOUDFLARE_D1_STORAGE_GET_THREAD_BY_ID_ERROR",
639
+ domain: ErrorDomain.STORAGE,
640
+ category: ErrorCategory.THIRD_PARTY,
641
+ text: `Error processing thread ${threadId}: ${error instanceof Error ? error.message : String(error)}`,
642
+ details: { threadId }
643
+ },
644
+ error
645
+ );
646
+ this.logger?.error(mastraError.toString());
647
+ this.logger?.trackException(mastraError);
543
648
  return null;
544
649
  }
545
650
  }
651
+ /**
652
+ * @deprecated use getThreadsByResourceIdPaginated instead
653
+ */
546
654
  async getThreadsByResourceId({ resourceId }) {
547
655
  const fullTableName = this.getTableName(TABLE_THREADS);
548
656
  try {
@@ -556,12 +664,66 @@ var D1Store = class extends MastraStorage {
556
664
  metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata || "{}") : thread.metadata || {}
557
665
  }));
558
666
  } catch (error) {
559
- this.logger.error(`Error getting threads by resourceId ${resourceId}:`, {
560
- message: error instanceof Error ? error.message : String(error)
561
- });
667
+ const mastraError = new MastraError(
668
+ {
669
+ id: "CLOUDFLARE_D1_STORAGE_GET_THREADS_BY_RESOURCE_ID_ERROR",
670
+ domain: ErrorDomain.STORAGE,
671
+ category: ErrorCategory.THIRD_PARTY,
672
+ text: `Error getting threads by resourceId ${resourceId}: ${error instanceof Error ? error.message : String(error)}`,
673
+ details: { resourceId }
674
+ },
675
+ error
676
+ );
677
+ this.logger?.error(mastraError.toString());
678
+ this.logger?.trackException(mastraError);
562
679
  return [];
563
680
  }
564
681
  }
682
+ async getThreadsByResourceIdPaginated(args) {
683
+ const { resourceId, page, perPage } = args;
684
+ const fullTableName = this.getTableName(TABLE_THREADS);
685
+ const mapRowToStorageThreadType = (row) => ({
686
+ ...row,
687
+ createdAt: this.ensureDate(row.createdAt),
688
+ updatedAt: this.ensureDate(row.updatedAt),
689
+ metadata: typeof row.metadata === "string" ? JSON.parse(row.metadata || "{}") : row.metadata || {}
690
+ });
691
+ try {
692
+ const countQuery = createSqlBuilder().count().from(fullTableName).where("resourceId = ?", resourceId);
693
+ const countResult = await this.executeQuery(countQuery.build());
694
+ const total = Number(countResult?.[0]?.count ?? 0);
695
+ const selectQuery = createSqlBuilder().select("*").from(fullTableName).where("resourceId = ?", resourceId).orderBy("createdAt", "DESC").limit(perPage).offset(page * perPage);
696
+ const results = await this.executeQuery(selectQuery.build());
697
+ const threads = results.map(mapRowToStorageThreadType);
698
+ return {
699
+ threads,
700
+ total,
701
+ page,
702
+ perPage,
703
+ hasMore: page * perPage + threads.length < total
704
+ };
705
+ } catch (error) {
706
+ const mastraError = new MastraError(
707
+ {
708
+ id: "CLOUDFLARE_D1_STORAGE_GET_THREADS_BY_RESOURCE_ID_PAGINATED_ERROR",
709
+ domain: ErrorDomain.STORAGE,
710
+ category: ErrorCategory.THIRD_PARTY,
711
+ text: `Error getting threads by resourceId ${resourceId}: ${error instanceof Error ? error.message : String(error)}`,
712
+ details: { resourceId }
713
+ },
714
+ error
715
+ );
716
+ this.logger?.error(mastraError.toString());
717
+ this.logger?.trackException(mastraError);
718
+ return {
719
+ threads: [],
720
+ total: 0,
721
+ page,
722
+ perPage,
723
+ hasMore: false
724
+ };
725
+ }
726
+ }
565
727
  async saveThread({ thread }) {
566
728
  const fullTableName = this.getTableName(TABLE_THREADS);
567
729
  const threadToSave = {
@@ -588,8 +750,16 @@ var D1Store = class extends MastraStorage {
588
750
  await this.executeQuery({ sql, params });
589
751
  return thread;
590
752
  } catch (error) {
591
- this.logger.error(`Error saving thread to ${fullTableName}:`, { error });
592
- throw error;
753
+ throw new MastraError(
754
+ {
755
+ id: "CLOUDFLARE_D1_STORAGE_SAVE_THREAD_ERROR",
756
+ domain: ErrorDomain.STORAGE,
757
+ category: ErrorCategory.THIRD_PARTY,
758
+ text: `Failed to save thread to ${fullTableName}: ${error instanceof Error ? error.message : String(error)}`,
759
+ details: { threadId: thread.id }
760
+ },
761
+ error
762
+ );
593
763
  }
594
764
  }
595
765
  async updateThread({
@@ -598,19 +768,19 @@ var D1Store = class extends MastraStorage {
598
768
  metadata
599
769
  }) {
600
770
  const thread = await this.getThreadById({ threadId: id });
601
- if (!thread) {
602
- throw new Error(`Thread ${id} not found`);
603
- }
604
- const fullTableName = this.getTableName(TABLE_THREADS);
605
- const mergedMetadata = {
606
- ...typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
607
- ...metadata
608
- };
609
- const columns = ["title", "metadata", "updatedAt"];
610
- const values = [title, JSON.stringify(mergedMetadata), (/* @__PURE__ */ new Date()).toISOString()];
611
- const query = createSqlBuilder().update(fullTableName, columns, values).where("id = ?", id);
612
- const { sql, params } = query.build();
613
771
  try {
772
+ if (!thread) {
773
+ throw new Error(`Thread ${id} not found`);
774
+ }
775
+ const fullTableName = this.getTableName(TABLE_THREADS);
776
+ const mergedMetadata = {
777
+ ...typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
778
+ ...metadata
779
+ };
780
+ const columns = ["title", "metadata", "updatedAt"];
781
+ const values = [title, JSON.stringify(mergedMetadata), (/* @__PURE__ */ new Date()).toISOString()];
782
+ const query = createSqlBuilder().update(fullTableName, columns, values).where("id = ?", id);
783
+ const { sql, params } = query.build();
614
784
  await this.executeQuery({ sql, params });
615
785
  return {
616
786
  ...thread,
@@ -622,8 +792,16 @@ var D1Store = class extends MastraStorage {
622
792
  updatedAt: /* @__PURE__ */ new Date()
623
793
  };
624
794
  } catch (error) {
625
- this.logger.error("Error updating thread:", { error });
626
- throw error;
795
+ throw new MastraError(
796
+ {
797
+ id: "CLOUDFLARE_D1_STORAGE_UPDATE_THREAD_ERROR",
798
+ domain: ErrorDomain.STORAGE,
799
+ category: ErrorCategory.THIRD_PARTY,
800
+ text: `Failed to update thread ${id}: ${error instanceof Error ? error.message : String(error)}`,
801
+ details: { threadId: id }
802
+ },
803
+ error
804
+ );
627
805
  }
628
806
  }
629
807
  async deleteThread({ threadId }) {
@@ -637,17 +815,24 @@ var D1Store = class extends MastraStorage {
637
815
  const { sql: messagesSql, params: messagesParams } = deleteMessagesQuery.build();
638
816
  await this.executeQuery({ sql: messagesSql, params: messagesParams });
639
817
  } catch (error) {
640
- this.logger.error(`Error deleting thread ${threadId}:`, {
641
- message: error instanceof Error ? error.message : String(error)
642
- });
643
- throw new Error(`Failed to delete thread ${threadId}: ${error}`);
818
+ throw new MastraError(
819
+ {
820
+ id: "CLOUDFLARE_D1_STORAGE_DELETE_THREAD_ERROR",
821
+ domain: ErrorDomain.STORAGE,
822
+ category: ErrorCategory.THIRD_PARTY,
823
+ text: `Failed to delete thread ${threadId}: ${error instanceof Error ? error.message : String(error)}`,
824
+ details: { threadId }
825
+ },
826
+ error
827
+ );
644
828
  }
645
829
  }
646
- // Thread and message management methods
647
- async saveMessages({ messages }) {
830
+ async saveMessages(args) {
831
+ const { messages, format = "v1" } = args;
648
832
  if (messages.length === 0) return [];
649
833
  try {
650
834
  const now = /* @__PURE__ */ new Date();
835
+ const threadId = messages[0]?.threadId;
651
836
  for (const [i, message] of messages.entries()) {
652
837
  if (!message.id) throw new Error(`Message at index ${i} missing id`);
653
838
  if (!message.threadId) throw new Error(`Message at index ${i} missing threadId`);
@@ -666,36 +851,49 @@ var D1Store = class extends MastraStorage {
666
851
  content: typeof message.content === "string" ? message.content : JSON.stringify(message.content),
667
852
  createdAt: createdAt.toISOString(),
668
853
  role: message.role,
669
- type: message.type
854
+ type: message.type || "v2",
855
+ resourceId: message.resourceId
670
856
  };
671
857
  });
672
- await this.batchInsert({
673
- tableName: TABLE_MESSAGES,
674
- records: messagesToInsert
675
- });
858
+ await Promise.all([
859
+ this.batchUpsert({
860
+ tableName: TABLE_MESSAGES,
861
+ records: messagesToInsert
862
+ }),
863
+ // Update thread's updatedAt timestamp
864
+ this.executeQuery({
865
+ sql: `UPDATE ${this.getTableName(TABLE_THREADS)} SET updatedAt = ? WHERE id = ?`,
866
+ params: [now.toISOString(), threadId]
867
+ })
868
+ ]);
676
869
  this.logger.debug(`Saved ${messages.length} messages`);
677
- return messages;
870
+ const list = new MessageList().add(messages, "memory");
871
+ if (format === `v2`) return list.get.all.v2();
872
+ return list.get.all.v1();
678
873
  } catch (error) {
679
- this.logger.error("Error saving messages:", { message: error instanceof Error ? error.message : String(error) });
680
- throw error;
874
+ throw new MastraError(
875
+ {
876
+ id: "CLOUDFLARE_D1_STORAGE_SAVE_MESSAGES_ERROR",
877
+ domain: ErrorDomain.STORAGE,
878
+ category: ErrorCategory.THIRD_PARTY,
879
+ text: `Failed to save messages: ${error instanceof Error ? error.message : String(error)}`
880
+ },
881
+ error
882
+ );
681
883
  }
682
884
  }
683
- async getMessages({ threadId, selectBy }) {
684
- const fullTableName = this.getTableName(TABLE_MESSAGES);
685
- const limit = typeof selectBy?.last === "number" ? selectBy.last : 40;
686
- const include = selectBy?.include || [];
687
- const messages = [];
688
- try {
689
- if (include.length) {
690
- const prevMax = Math.max(...include.map((i) => i.withPreviousMessages || 0));
691
- const nextMax = Math.max(...include.map((i) => i.withNextMessages || 0));
692
- const includeIds = include.map((i) => i.id);
693
- const sql2 = `
885
+ async _getIncludedMessages(threadId, selectBy) {
886
+ const include = selectBy?.include;
887
+ if (!include) return null;
888
+ const prevMax = Math.max(...include.map((i) => i.withPreviousMessages || 0));
889
+ const nextMax = Math.max(...include.map((i) => i.withNextMessages || 0));
890
+ const includeIds = include.map((i) => i.id);
891
+ const sql = `
694
892
  WITH ordered_messages AS (
695
893
  SELECT
696
894
  *,
697
895
  ROW_NUMBER() OVER (ORDER BY createdAt DESC) AS row_num
698
- FROM ${fullTableName}
896
+ FROM ${this.getTableName(TABLE_MESSAGES)}
699
897
  WHERE thread_id = ?
700
898
  )
701
899
  SELECT
@@ -704,7 +902,7 @@ var D1Store = class extends MastraStorage {
704
902
  m.role,
705
903
  m.type,
706
904
  m.createdAt,
707
- m.thread_id AS "threadId"
905
+ m.thread_id AS threadId
708
906
  FROM ordered_messages m
709
907
  WHERE m.id IN (${includeIds.map(() => "?").join(",")})
710
908
  OR EXISTS (
@@ -718,20 +916,38 @@ var D1Store = class extends MastraStorage {
718
916
  )
719
917
  ORDER BY m.createdAt DESC
720
918
  `;
721
- const params2 = [
722
- threadId,
723
- ...includeIds,
724
- // for m.id IN (...)
725
- ...includeIds,
726
- // for target.id IN (...)
727
- prevMax,
728
- nextMax
729
- ];
730
- const includeResult = await this.executeQuery({ sql: sql2, params: params2 });
919
+ const params = [
920
+ threadId,
921
+ ...includeIds,
922
+ // for m.id IN (...)
923
+ ...includeIds,
924
+ // for target.id IN (...)
925
+ prevMax,
926
+ nextMax
927
+ ];
928
+ const messages = await this.executeQuery({ sql, params });
929
+ return messages;
930
+ }
931
+ async getMessages({
932
+ threadId,
933
+ selectBy,
934
+ format
935
+ }) {
936
+ const fullTableName = this.getTableName(TABLE_MESSAGES);
937
+ const limit = this.resolveMessageLimit({ last: selectBy?.last, defaultLimit: 40 });
938
+ const include = selectBy?.include || [];
939
+ const messages = [];
940
+ try {
941
+ if (include.length) {
942
+ const includeResult = await this._getIncludedMessages(threadId, selectBy);
731
943
  if (Array.isArray(includeResult)) messages.push(...includeResult);
732
944
  }
733
945
  const excludeIds = messages.map((m) => m.id);
734
- let query = createSqlBuilder().select(["id", "content", "role", "type", '"createdAt"', 'thread_id AS "threadId"']).from(fullTableName).where("thread_id = ?", threadId).andWhere(`id NOT IN (${excludeIds.map(() => "?").join(",")})`, ...excludeIds).orderBy("createdAt", "DESC").limit(limit);
946
+ const query = createSqlBuilder().select(["id", "content", "role", "type", "createdAt", "thread_id AS threadId"]).from(fullTableName).where("thread_id = ?", threadId);
947
+ if (excludeIds.length > 0) {
948
+ query.andWhere(`id NOT IN (${excludeIds.map(() => "?").join(",")})`, ...excludeIds);
949
+ }
950
+ query.orderBy("createdAt", "DESC").limit(limit);
735
951
  const { sql, params } = query.build();
736
952
  const result = await this.executeQuery({ sql, params });
737
953
  if (Array.isArray(result)) messages.push(...result);
@@ -745,18 +961,92 @@ var D1Store = class extends MastraStorage {
745
961
  const processedMessages = messages.map((message) => {
746
962
  const processedMsg = {};
747
963
  for (const [key, value] of Object.entries(message)) {
964
+ if (key === `type` && value === `v2`) continue;
748
965
  processedMsg[key] = this.deserializeValue(value);
749
966
  }
750
967
  return processedMsg;
751
968
  });
752
969
  this.logger.debug(`Retrieved ${messages.length} messages for thread ${threadId}`);
753
- return processedMessages;
970
+ const list = new MessageList().add(processedMessages, "memory");
971
+ if (format === `v2`) return list.get.all.v2();
972
+ return list.get.all.v1();
754
973
  } catch (error) {
755
- this.logger.error("Error retrieving messages for thread", {
756
- threadId,
757
- message: error instanceof Error ? error.message : String(error)
758
- });
759
- return [];
974
+ const mastraError = new MastraError(
975
+ {
976
+ id: "CLOUDFLARE_D1_STORAGE_GET_MESSAGES_ERROR",
977
+ domain: ErrorDomain.STORAGE,
978
+ category: ErrorCategory.THIRD_PARTY,
979
+ text: `Failed to retrieve messages for thread ${threadId}: ${error instanceof Error ? error.message : String(error)}`,
980
+ details: { threadId }
981
+ },
982
+ error
983
+ );
984
+ this.logger?.error(mastraError.toString());
985
+ this.logger?.trackException(mastraError);
986
+ throw mastraError;
987
+ }
988
+ }
989
+ async getMessagesPaginated({
990
+ threadId,
991
+ selectBy,
992
+ format
993
+ }) {
994
+ const { dateRange, page = 0, perPage = 40 } = selectBy?.pagination || {};
995
+ const { start: fromDate, end: toDate } = dateRange || {};
996
+ const fullTableName = this.getTableName(TABLE_MESSAGES);
997
+ const messages = [];
998
+ try {
999
+ if (selectBy?.include?.length) {
1000
+ const includeResult = await this._getIncludedMessages(threadId, selectBy);
1001
+ if (Array.isArray(includeResult)) messages.push(...includeResult);
1002
+ }
1003
+ const countQuery = createSqlBuilder().count().from(fullTableName).where("thread_id = ?", threadId);
1004
+ if (fromDate) {
1005
+ countQuery.andWhere("createdAt >= ?", this.serializeDate(fromDate));
1006
+ }
1007
+ if (toDate) {
1008
+ countQuery.andWhere("createdAt <= ?", this.serializeDate(toDate));
1009
+ }
1010
+ const countResult = await this.executeQuery(countQuery.build());
1011
+ const total = Number(countResult[0]?.count ?? 0);
1012
+ const query = createSqlBuilder().select(["id", "content", "role", "type", "createdAt", "thread_id AS threadId"]).from(fullTableName).where("thread_id = ?", threadId);
1013
+ if (fromDate) {
1014
+ query.andWhere("createdAt >= ?", this.serializeDate(fromDate));
1015
+ }
1016
+ if (toDate) {
1017
+ query.andWhere("createdAt <= ?", this.serializeDate(toDate));
1018
+ }
1019
+ query.orderBy("createdAt", "DESC").limit(perPage).offset(page * perPage);
1020
+ const results = await this.executeQuery(query.build());
1021
+ const list = new MessageList().add(results, "memory");
1022
+ messages.push(...format === `v2` ? list.get.all.v2() : list.get.all.v1());
1023
+ return {
1024
+ messages,
1025
+ total,
1026
+ page,
1027
+ perPage,
1028
+ hasMore: page * perPage + messages.length < total
1029
+ };
1030
+ } catch (error) {
1031
+ const mastraError = new MastraError(
1032
+ {
1033
+ id: "CLOUDFLARE_D1_STORAGE_GET_MESSAGES_PAGINATED_ERROR",
1034
+ domain: ErrorDomain.STORAGE,
1035
+ category: ErrorCategory.THIRD_PARTY,
1036
+ text: `Failed to retrieve messages for thread ${threadId}: ${error instanceof Error ? error.message : String(error)}`,
1037
+ details: { threadId }
1038
+ },
1039
+ error
1040
+ );
1041
+ this.logger?.error(mastraError.toString());
1042
+ this.logger?.trackException(mastraError);
1043
+ return {
1044
+ messages: [],
1045
+ total: 0,
1046
+ page,
1047
+ perPage,
1048
+ hasMore: false
1049
+ };
760
1050
  }
761
1051
  }
762
1052
  async persistWorkflowSnapshot({
@@ -794,23 +1084,42 @@ var D1Store = class extends MastraStorage {
794
1084
  try {
795
1085
  await this.executeQuery({ sql, params });
796
1086
  } catch (error) {
797
- this.logger.error("Error persisting workflow snapshot:", {
798
- message: error instanceof Error ? error.message : String(error)
799
- });
800
- throw error;
1087
+ throw new MastraError(
1088
+ {
1089
+ id: "CLOUDFLARE_D1_STORAGE_PERSIST_WORKFLOW_SNAPSHOT_ERROR",
1090
+ domain: ErrorDomain.STORAGE,
1091
+ category: ErrorCategory.THIRD_PARTY,
1092
+ text: `Failed to persist workflow snapshot: ${error instanceof Error ? error.message : String(error)}`,
1093
+ details: { workflowName, runId }
1094
+ },
1095
+ error
1096
+ );
801
1097
  }
802
1098
  }
803
1099
  async loadWorkflowSnapshot(params) {
804
1100
  const { workflowName, runId } = params;
805
1101
  this.logger.debug("Loading workflow snapshot", { workflowName, runId });
806
- const d = await this.load({
807
- tableName: TABLE_WORKFLOW_SNAPSHOT,
808
- keys: {
809
- workflow_name: workflowName,
810
- run_id: runId
811
- }
812
- });
813
- return d ? d.snapshot : null;
1102
+ try {
1103
+ const d = await this.load({
1104
+ tableName: TABLE_WORKFLOW_SNAPSHOT,
1105
+ keys: {
1106
+ workflow_name: workflowName,
1107
+ run_id: runId
1108
+ }
1109
+ });
1110
+ return d ? d.snapshot : null;
1111
+ } catch (error) {
1112
+ throw new MastraError(
1113
+ {
1114
+ id: "CLOUDFLARE_D1_STORAGE_LOAD_WORKFLOW_SNAPSHOT_ERROR",
1115
+ domain: ErrorDomain.STORAGE,
1116
+ category: ErrorCategory.THIRD_PARTY,
1117
+ text: `Failed to load workflow snapshot: ${error instanceof Error ? error.message : String(error)}`,
1118
+ details: { workflowName, runId }
1119
+ },
1120
+ error
1121
+ );
1122
+ }
814
1123
  }
815
1124
  /**
816
1125
  * Insert multiple records in a batch operation
@@ -845,18 +1154,84 @@ var D1Store = class extends MastraStorage {
845
1154
  }
846
1155
  this.logger.debug(`Successfully batch inserted ${records.length} records into ${tableName}`);
847
1156
  } catch (error) {
848
- this.logger.error(`Error batch inserting into ${tableName}:`, {
849
- message: error instanceof Error ? error.message : String(error)
850
- });
851
- throw new Error(`Failed to batch insert into ${tableName}: ${error}`);
1157
+ throw new MastraError(
1158
+ {
1159
+ id: "CLOUDFLARE_D1_STORAGE_BATCH_INSERT_ERROR",
1160
+ domain: ErrorDomain.STORAGE,
1161
+ category: ErrorCategory.THIRD_PARTY,
1162
+ text: `Failed to batch insert into ${tableName}: ${error instanceof Error ? error.message : String(error)}`,
1163
+ details: { tableName }
1164
+ },
1165
+ error
1166
+ );
852
1167
  }
853
1168
  }
1169
+ /**
1170
+ * Upsert multiple records in a batch operation
1171
+ * @param tableName The table to insert into
1172
+ * @param records The records to insert
1173
+ */
1174
+ async batchUpsert({
1175
+ tableName,
1176
+ records
1177
+ }) {
1178
+ if (records.length === 0) return;
1179
+ const fullTableName = this.getTableName(tableName);
1180
+ try {
1181
+ const batchSize = 50;
1182
+ for (let i = 0; i < records.length; i += batchSize) {
1183
+ const batch = records.slice(i, i + batchSize);
1184
+ const recordsToInsert = batch;
1185
+ if (recordsToInsert.length > 0) {
1186
+ const firstRecord = recordsToInsert[0];
1187
+ const columns = Object.keys(firstRecord || {});
1188
+ for (const record of recordsToInsert) {
1189
+ const values = columns.map((col) => {
1190
+ if (!record) return null;
1191
+ const value = typeof col === "string" ? record[col] : null;
1192
+ return this.serializeValue(value);
1193
+ });
1194
+ const recordToUpsert = columns.reduce(
1195
+ (acc, col) => {
1196
+ if (col !== "createdAt") acc[col] = `excluded.${col}`;
1197
+ return acc;
1198
+ },
1199
+ {}
1200
+ );
1201
+ const query = createSqlBuilder().insert(fullTableName, columns, values, ["id"], recordToUpsert);
1202
+ const { sql, params } = query.build();
1203
+ await this.executeQuery({ sql, params });
1204
+ }
1205
+ }
1206
+ this.logger.debug(
1207
+ `Processed batch ${Math.floor(i / batchSize) + 1} of ${Math.ceil(records.length / batchSize)}`
1208
+ );
1209
+ }
1210
+ this.logger.debug(`Successfully batch upserted ${records.length} records into ${tableName}`);
1211
+ } catch (error) {
1212
+ throw new MastraError(
1213
+ {
1214
+ id: "CLOUDFLARE_D1_STORAGE_BATCH_UPSERT_ERROR",
1215
+ domain: ErrorDomain.STORAGE,
1216
+ category: ErrorCategory.THIRD_PARTY,
1217
+ text: `Failed to batch upsert into ${tableName}: ${error instanceof Error ? error.message : String(error)}`,
1218
+ details: { tableName }
1219
+ },
1220
+ error
1221
+ );
1222
+ }
1223
+ }
1224
+ /**
1225
+ * @deprecated use getTracesPaginated instead
1226
+ */
854
1227
  async getTraces({
855
1228
  name,
856
1229
  scope,
857
1230
  page,
858
1231
  perPage,
859
- attributes
1232
+ attributes,
1233
+ fromDate,
1234
+ toDate
860
1235
  }) {
861
1236
  const fullTableName = this.getTableName(TABLE_TRACES);
862
1237
  try {
@@ -872,22 +1247,114 @@ var D1Store = class extends MastraStorage {
872
1247
  query.jsonLike("attributes", key, value);
873
1248
  }
874
1249
  }
875
- query.orderBy("startTime", "DESC").limit(perPage).offset((page - 1) * perPage);
1250
+ if (fromDate) {
1251
+ query.andWhere("createdAt >= ?", fromDate instanceof Date ? fromDate.toISOString() : fromDate);
1252
+ }
1253
+ if (toDate) {
1254
+ query.andWhere("createdAt <= ?", toDate instanceof Date ? toDate.toISOString() : toDate);
1255
+ }
1256
+ query.orderBy("startTime", "DESC").limit(perPage).offset(page * perPage);
876
1257
  const { sql, params } = query.build();
877
1258
  const results = await this.executeQuery({ sql, params });
878
- return isArrayOfRecords(results) ? results.map((trace) => ({
879
- ...trace,
880
- attributes: this.deserializeValue(trace.attributes, "jsonb"),
881
- status: this.deserializeValue(trace.status, "jsonb"),
882
- events: this.deserializeValue(trace.events, "jsonb"),
883
- links: this.deserializeValue(trace.links, "jsonb"),
884
- other: this.deserializeValue(trace.other, "jsonb")
885
- })) : [];
1259
+ return isArrayOfRecords(results) ? results.map(
1260
+ (trace) => ({
1261
+ ...trace,
1262
+ attributes: this.deserializeValue(trace.attributes, "jsonb"),
1263
+ status: this.deserializeValue(trace.status, "jsonb"),
1264
+ events: this.deserializeValue(trace.events, "jsonb"),
1265
+ links: this.deserializeValue(trace.links, "jsonb"),
1266
+ other: this.deserializeValue(trace.other, "jsonb")
1267
+ })
1268
+ ) : [];
886
1269
  } catch (error) {
887
- this.logger.error("Error getting traces:", { message: error instanceof Error ? error.message : String(error) });
1270
+ const mastraError = new MastraError(
1271
+ {
1272
+ id: "CLOUDFLARE_D1_STORAGE_GET_TRACES_ERROR",
1273
+ domain: ErrorDomain.STORAGE,
1274
+ category: ErrorCategory.THIRD_PARTY,
1275
+ text: `Failed to retrieve traces: ${error instanceof Error ? error.message : String(error)}`,
1276
+ details: {
1277
+ name: name ?? "",
1278
+ scope: scope ?? ""
1279
+ }
1280
+ },
1281
+ error
1282
+ );
1283
+ this.logger?.error(mastraError.toString());
1284
+ this.logger?.trackException(mastraError);
888
1285
  return [];
889
1286
  }
890
1287
  }
1288
+ async getTracesPaginated(args) {
1289
+ const { name, scope, page, perPage, attributes, fromDate, toDate } = args;
1290
+ const fullTableName = this.getTableName(TABLE_TRACES);
1291
+ try {
1292
+ const dataQuery = createSqlBuilder().select("*").from(fullTableName).where("1=1");
1293
+ const countQuery = createSqlBuilder().count().from(fullTableName).where("1=1");
1294
+ if (name) {
1295
+ dataQuery.andWhere("name LIKE ?", `%${name}%`);
1296
+ countQuery.andWhere("name LIKE ?", `%${name}%`);
1297
+ }
1298
+ if (scope) {
1299
+ dataQuery.andWhere("scope = ?", scope);
1300
+ countQuery.andWhere("scope = ?", scope);
1301
+ }
1302
+ if (attributes && Object.keys(attributes).length > 0) {
1303
+ for (const [key, value] of Object.entries(attributes)) {
1304
+ dataQuery.jsonLike("attributes", key, value);
1305
+ countQuery.jsonLike("attributes", key, value);
1306
+ }
1307
+ }
1308
+ if (fromDate) {
1309
+ const fromDateStr = fromDate instanceof Date ? fromDate.toISOString() : fromDate;
1310
+ dataQuery.andWhere("createdAt >= ?", fromDateStr);
1311
+ countQuery.andWhere("createdAt >= ?", fromDateStr);
1312
+ }
1313
+ if (toDate) {
1314
+ const toDateStr = toDate instanceof Date ? toDate.toISOString() : toDate;
1315
+ dataQuery.andWhere("createdAt <= ?", toDateStr);
1316
+ countQuery.andWhere("createdAt <= ?", toDateStr);
1317
+ }
1318
+ const countResult = await this.executeQuery(countQuery.build());
1319
+ const total = Number(countResult?.[0]?.count ?? 0);
1320
+ dataQuery.orderBy("startTime", "DESC").limit(perPage).offset(page * perPage);
1321
+ const results = await this.executeQuery(dataQuery.build());
1322
+ const traces = isArrayOfRecords(results) ? results.map(
1323
+ (trace) => ({
1324
+ ...trace,
1325
+ attributes: this.deserializeValue(trace.attributes, "jsonb"),
1326
+ status: this.deserializeValue(trace.status, "jsonb"),
1327
+ events: this.deserializeValue(trace.events, "jsonb"),
1328
+ links: this.deserializeValue(trace.links, "jsonb"),
1329
+ other: this.deserializeValue(trace.other, "jsonb")
1330
+ })
1331
+ ) : [];
1332
+ return {
1333
+ traces,
1334
+ total,
1335
+ page,
1336
+ perPage,
1337
+ hasMore: page * perPage + traces.length < total
1338
+ };
1339
+ } catch (error) {
1340
+ const mastraError = new MastraError(
1341
+ {
1342
+ id: "CLOUDFLARE_D1_STORAGE_GET_TRACES_ERROR",
1343
+ domain: ErrorDomain.STORAGE,
1344
+ category: ErrorCategory.THIRD_PARTY,
1345
+ text: `Failed to retrieve traces: ${error instanceof Error ? error.message : String(error)}`,
1346
+ details: { name: name ?? "", scope: scope ?? "" }
1347
+ },
1348
+ error
1349
+ );
1350
+ this.logger?.error(mastraError.toString());
1351
+ this.logger?.trackException(mastraError);
1352
+ return { traces: [], total: 0, page, perPage, hasMore: false };
1353
+ }
1354
+ }
1355
+ /**
1356
+ * @deprecated use getEvals instead
1357
+ */
891
1358
  async getEvalsByAgentName(agentName, type) {
892
1359
  const fullTableName = this.getTableName(TABLE_EVALS);
893
1360
  try {
@@ -917,14 +1384,221 @@ var D1Store = class extends MastraStorage {
917
1384
  };
918
1385
  }) : [];
919
1386
  } catch (error) {
920
- this.logger.error(`Error getting evals for agent ${agentName}:`, {
921
- message: error instanceof Error ? error.message : String(error)
922
- });
1387
+ const mastraError = new MastraError(
1388
+ {
1389
+ id: "CLOUDFLARE_D1_STORAGE_GET_EVALS_ERROR",
1390
+ domain: ErrorDomain.STORAGE,
1391
+ category: ErrorCategory.THIRD_PARTY,
1392
+ text: `Failed to retrieve evals for agent ${agentName}: ${error instanceof Error ? error.message : String(error)}`,
1393
+ details: { agentName }
1394
+ },
1395
+ error
1396
+ );
1397
+ this.logger?.error(mastraError.toString());
1398
+ this.logger?.trackException(mastraError);
923
1399
  return [];
924
1400
  }
925
1401
  }
926
- getWorkflowRuns(_args) {
927
- throw new Error("Method not implemented.");
1402
+ async getEvals(options) {
1403
+ const { agentName, type, page = 0, perPage = 40, fromDate, toDate } = options || {};
1404
+ const fullTableName = this.getTableName(TABLE_EVALS);
1405
+ const conditions = [];
1406
+ const queryParams = [];
1407
+ if (agentName) {
1408
+ conditions.push(`agent_name = ?`);
1409
+ queryParams.push(agentName);
1410
+ }
1411
+ if (type === "test") {
1412
+ conditions.push(`(test_info IS NOT NULL AND json_extract(test_info, '$.testPath') IS NOT NULL)`);
1413
+ } else if (type === "live") {
1414
+ conditions.push(`(test_info IS NULL OR json_extract(test_info, '$.testPath') IS NULL)`);
1415
+ }
1416
+ if (fromDate) {
1417
+ conditions.push(`createdAt >= ?`);
1418
+ queryParams.push(this.serializeDate(fromDate));
1419
+ }
1420
+ if (toDate) {
1421
+ conditions.push(`createdAt <= ?`);
1422
+ queryParams.push(this.serializeDate(toDate));
1423
+ }
1424
+ const countQueryBuilder = createSqlBuilder().count().from(fullTableName);
1425
+ if (conditions.length > 0) {
1426
+ countQueryBuilder.where(conditions.join(" AND "), ...queryParams);
1427
+ }
1428
+ const { sql: countSql, params: countParams } = countQueryBuilder.build();
1429
+ try {
1430
+ const countResult = await this.executeQuery({ sql: countSql, params: countParams, first: true });
1431
+ const total = Number(countResult?.count || 0);
1432
+ const currentOffset = page * perPage;
1433
+ if (total === 0) {
1434
+ return {
1435
+ evals: [],
1436
+ total: 0,
1437
+ page,
1438
+ perPage,
1439
+ hasMore: false
1440
+ };
1441
+ }
1442
+ const dataQueryBuilder = createSqlBuilder().select("*").from(fullTableName);
1443
+ if (conditions.length > 0) {
1444
+ dataQueryBuilder.where(conditions.join(" AND "), ...queryParams);
1445
+ }
1446
+ dataQueryBuilder.orderBy("createdAt", "DESC").limit(perPage).offset(currentOffset);
1447
+ const { sql: dataSql, params: dataParams } = dataQueryBuilder.build();
1448
+ const rows = await this.executeQuery({ sql: dataSql, params: dataParams });
1449
+ const evals = (isArrayOfRecords(rows) ? rows : []).map((row) => {
1450
+ const result = this.deserializeValue(row.result);
1451
+ const testInfo = row.test_info ? this.deserializeValue(row.test_info) : void 0;
1452
+ if (!result || typeof result !== "object" || !("score" in result)) {
1453
+ throw new Error(`Invalid MetricResult format: ${JSON.stringify(result)}`);
1454
+ }
1455
+ return {
1456
+ input: row.input,
1457
+ output: row.output,
1458
+ result,
1459
+ agentName: row.agent_name,
1460
+ metricName: row.metric_name,
1461
+ instructions: row.instructions,
1462
+ testInfo,
1463
+ globalRunId: row.global_run_id,
1464
+ runId: row.run_id,
1465
+ createdAt: row.createdAt
1466
+ };
1467
+ });
1468
+ const hasMore = currentOffset + evals.length < total;
1469
+ return {
1470
+ evals,
1471
+ total,
1472
+ page,
1473
+ perPage,
1474
+ hasMore
1475
+ };
1476
+ } catch (error) {
1477
+ throw new MastraError(
1478
+ {
1479
+ id: "CLOUDFLARE_D1_STORAGE_GET_EVALS_ERROR",
1480
+ domain: ErrorDomain.STORAGE,
1481
+ category: ErrorCategory.THIRD_PARTY,
1482
+ text: `Failed to retrieve evals for agent ${agentName}: ${error instanceof Error ? error.message : String(error)}`,
1483
+ details: { agentName: agentName ?? "", type: type ?? "" }
1484
+ },
1485
+ error
1486
+ );
1487
+ }
1488
+ }
1489
+ parseWorkflowRun(row) {
1490
+ let parsedSnapshot = row.snapshot;
1491
+ if (typeof parsedSnapshot === "string") {
1492
+ try {
1493
+ parsedSnapshot = JSON.parse(row.snapshot);
1494
+ } catch (e) {
1495
+ console.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
1496
+ }
1497
+ }
1498
+ return {
1499
+ workflowName: row.workflow_name,
1500
+ runId: row.run_id,
1501
+ snapshot: parsedSnapshot,
1502
+ createdAt: this.ensureDate(row.createdAt),
1503
+ updatedAt: this.ensureDate(row.updatedAt),
1504
+ resourceId: row.resourceId
1505
+ };
1506
+ }
1507
+ async hasColumn(table, column) {
1508
+ const sql = `PRAGMA table_info(${table});`;
1509
+ const result = await this.executeQuery({ sql, params: [] });
1510
+ if (!result || !Array.isArray(result)) return false;
1511
+ return result.some((col) => col.name === column || col.name === column.toLowerCase());
1512
+ }
1513
+ async getWorkflowRuns({
1514
+ workflowName,
1515
+ fromDate,
1516
+ toDate,
1517
+ limit,
1518
+ offset,
1519
+ resourceId
1520
+ } = {}) {
1521
+ const fullTableName = this.getTableName(TABLE_WORKFLOW_SNAPSHOT);
1522
+ try {
1523
+ const builder = createSqlBuilder().select().from(fullTableName);
1524
+ const countBuilder = createSqlBuilder().count().from(fullTableName);
1525
+ if (workflowName) builder.whereAnd("workflow_name = ?", workflowName);
1526
+ if (resourceId) {
1527
+ const hasResourceId = await this.hasColumn(fullTableName, "resourceId");
1528
+ if (hasResourceId) {
1529
+ builder.whereAnd("resourceId = ?", resourceId);
1530
+ countBuilder.whereAnd("resourceId = ?", resourceId);
1531
+ } else {
1532
+ console.warn(`[${fullTableName}] resourceId column not found. Skipping resourceId filter.`);
1533
+ }
1534
+ }
1535
+ if (fromDate) {
1536
+ builder.whereAnd("createdAt >= ?", fromDate instanceof Date ? fromDate.toISOString() : fromDate);
1537
+ countBuilder.whereAnd("createdAt >= ?", fromDate instanceof Date ? fromDate.toISOString() : fromDate);
1538
+ }
1539
+ if (toDate) {
1540
+ builder.whereAnd("createdAt <= ?", toDate instanceof Date ? toDate.toISOString() : toDate);
1541
+ countBuilder.whereAnd("createdAt <= ?", toDate instanceof Date ? toDate.toISOString() : toDate);
1542
+ }
1543
+ builder.orderBy("createdAt", "DESC");
1544
+ if (typeof limit === "number") builder.limit(limit);
1545
+ if (typeof offset === "number") builder.offset(offset);
1546
+ const { sql, params } = builder.build();
1547
+ let total = 0;
1548
+ if (limit !== void 0 && offset !== void 0) {
1549
+ const { sql: countSql, params: countParams } = countBuilder.build();
1550
+ const countResult = await this.executeQuery({ sql: countSql, params: countParams, first: true });
1551
+ total = Number(countResult?.count ?? 0);
1552
+ }
1553
+ const results = await this.executeQuery({ sql, params });
1554
+ const runs = (isArrayOfRecords(results) ? results : []).map((row) => this.parseWorkflowRun(row));
1555
+ return { runs, total: total || runs.length };
1556
+ } catch (error) {
1557
+ throw new MastraError(
1558
+ {
1559
+ id: "CLOUDFLARE_D1_STORAGE_GET_WORKFLOW_RUNS_ERROR",
1560
+ domain: ErrorDomain.STORAGE,
1561
+ category: ErrorCategory.THIRD_PARTY,
1562
+ text: `Failed to retrieve workflow runs: ${error instanceof Error ? error.message : String(error)}`,
1563
+ details: { workflowName: workflowName ?? "", resourceId: resourceId ?? "" }
1564
+ },
1565
+ error
1566
+ );
1567
+ }
1568
+ }
1569
+ async getWorkflowRunById({
1570
+ runId,
1571
+ workflowName
1572
+ }) {
1573
+ const fullTableName = this.getTableName(TABLE_WORKFLOW_SNAPSHOT);
1574
+ try {
1575
+ const conditions = [];
1576
+ const params = [];
1577
+ if (runId) {
1578
+ conditions.push("run_id = ?");
1579
+ params.push(runId);
1580
+ }
1581
+ if (workflowName) {
1582
+ conditions.push("workflow_name = ?");
1583
+ params.push(workflowName);
1584
+ }
1585
+ const whereClause = conditions.length > 0 ? "WHERE " + conditions.join(" AND ") : "";
1586
+ const sql = `SELECT * FROM ${fullTableName} ${whereClause} ORDER BY createdAt DESC LIMIT 1`;
1587
+ const result = await this.executeQuery({ sql, params, first: true });
1588
+ if (!result) return null;
1589
+ return this.parseWorkflowRun(result);
1590
+ } catch (error) {
1591
+ throw new MastraError(
1592
+ {
1593
+ id: "CLOUDFLARE_D1_STORAGE_GET_WORKFLOW_RUN_BY_ID_ERROR",
1594
+ domain: ErrorDomain.STORAGE,
1595
+ category: ErrorCategory.THIRD_PARTY,
1596
+ text: `Failed to retrieve workflow run by ID: ${error instanceof Error ? error.message : String(error)}`,
1597
+ details: { runId, workflowName: workflowName ?? "" }
1598
+ },
1599
+ error
1600
+ );
1601
+ }
928
1602
  }
929
1603
  /**
930
1604
  * Close the database connection
@@ -933,6 +1607,10 @@ var D1Store = class extends MastraStorage {
933
1607
  async close() {
934
1608
  this.logger.debug("Closing D1 connection");
935
1609
  }
1610
+ async updateMessages(_args) {
1611
+ this.logger.error("updateMessages is not yet implemented in CloudflareD1Store");
1612
+ throw new Error("Method not implemented");
1613
+ }
936
1614
  };
937
1615
 
938
1616
  export { D1Store };