@mastra/cloudflare-d1 0.0.0-vnextWorkflows-20250422142014 → 0.0.0-workflow-deno-20250616130925

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,9 @@
1
+ import { MessageList } from '@mastra/core/agent';
1
2
  import { MastraStorage, TABLE_THREADS, TABLE_MESSAGES, TABLE_WORKFLOW_SNAPSHOT, TABLE_TRACES, TABLE_EVALS } from '@mastra/core/storage';
2
3
  import Cloudflare from 'cloudflare';
4
+ import { parseSqlIdentifier } from '@mastra/core/utils';
3
5
 
4
6
  // src/storage/index.ts
5
-
6
- // src/storage/sql-builder.ts
7
7
  var SqlBuilder = class {
8
8
  sql = "";
9
9
  params = [];
@@ -13,12 +13,15 @@ var SqlBuilder = class {
13
13
  if (!columns || Array.isArray(columns) && columns.length === 0) {
14
14
  this.sql = "SELECT *";
15
15
  } else {
16
- this.sql = `SELECT ${Array.isArray(columns) ? columns.join(", ") : columns}`;
16
+ const cols = Array.isArray(columns) ? columns : [columns];
17
+ const parsedCols = cols.map((col) => parseSelectIdentifier(col));
18
+ this.sql = `SELECT ${parsedCols.join(", ")}`;
17
19
  }
18
20
  return this;
19
21
  }
20
22
  from(table) {
21
- this.sql += ` FROM ${table}`;
23
+ const parsedTableName = parseSqlIdentifier(table, "table name");
24
+ this.sql += ` FROM ${parsedTableName}`;
22
25
  return this;
23
26
  }
24
27
  /**
@@ -55,7 +58,11 @@ var SqlBuilder = class {
55
58
  return this;
56
59
  }
57
60
  orderBy(column, direction = "ASC") {
58
- this.sql += ` ORDER BY ${column} ${direction}`;
61
+ const parsedColumn = parseSqlIdentifier(column, "column name");
62
+ if (!["ASC", "DESC"].includes(direction)) {
63
+ throw new Error(`Invalid sort direction: ${direction}`);
64
+ }
65
+ this.sql += ` ORDER BY ${parsedColumn} ${direction}`;
59
66
  return this;
60
67
  }
61
68
  limit(count) {
@@ -68,6 +75,10 @@ var SqlBuilder = class {
68
75
  this.params.push(count);
69
76
  return this;
70
77
  }
78
+ count() {
79
+ this.sql += "SELECT COUNT(*) AS count";
80
+ return this;
81
+ }
71
82
  /**
72
83
  * Insert a row, or update specific columns on conflict (upsert).
73
84
  * @param table Table name
@@ -77,27 +88,33 @@ var SqlBuilder = class {
77
88
  * @param updateMap Object mapping columns to update to their new value (e.g. { name: 'excluded.name' })
78
89
  */
79
90
  insert(table, columns, values, conflictColumns, updateMap) {
80
- const placeholders = columns.map(() => "?").join(", ");
91
+ const parsedTableName = parseSqlIdentifier(table, "table name");
92
+ const parsedColumns = columns.map((col) => parseSqlIdentifier(col, "column name"));
93
+ const placeholders = parsedColumns.map(() => "?").join(", ");
81
94
  if (conflictColumns && updateMap) {
95
+ const parsedConflictColumns = conflictColumns.map((col) => parseSqlIdentifier(col, "column name"));
82
96
  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}`;
97
+ this.sql = `INSERT INTO ${parsedTableName} (${parsedColumns.join(", ")}) VALUES (${placeholders}) ON CONFLICT(${parsedConflictColumns.join(", ")}) DO UPDATE SET ${updateClause}`;
84
98
  this.params.push(...values);
85
99
  return this;
86
100
  }
87
- this.sql = `INSERT INTO ${table} (${columns.join(", ")}) VALUES (${placeholders})`;
101
+ this.sql = `INSERT INTO ${parsedTableName} (${parsedColumns.join(", ")}) VALUES (${placeholders})`;
88
102
  this.params.push(...values);
89
103
  return this;
90
104
  }
91
105
  // Update operations
92
106
  update(table, columns, values) {
93
- const setClause = columns.map((col) => `${col} = ?`).join(", ");
94
- this.sql = `UPDATE ${table} SET ${setClause}`;
107
+ const parsedTableName = parseSqlIdentifier(table, "table name");
108
+ const parsedColumns = columns.map((col) => parseSqlIdentifier(col, "column name"));
109
+ const setClause = parsedColumns.map((col) => `${col} = ?`).join(", ");
110
+ this.sql = `UPDATE ${parsedTableName} SET ${setClause}`;
95
111
  this.params.push(...values);
96
112
  return this;
97
113
  }
98
114
  // Delete operations
99
115
  delete(table) {
100
- this.sql = `DELETE FROM ${table}`;
116
+ const parsedTableName = parseSqlIdentifier(table, "table name");
117
+ this.sql = `DELETE FROM ${parsedTableName}`;
101
118
  return this;
102
119
  }
103
120
  /**
@@ -108,9 +125,16 @@ var SqlBuilder = class {
108
125
  * @returns The builder instance
109
126
  */
110
127
  createTable(table, columnDefinitions, tableConstraints) {
111
- const columns = columnDefinitions.join(", ");
128
+ const parsedTableName = parseSqlIdentifier(table, "table name");
129
+ const parsedColumnDefinitions = columnDefinitions.map((def) => {
130
+ const colName = def.split(/\s+/)[0];
131
+ if (!colName) throw new Error("Empty column name in definition");
132
+ parseSqlIdentifier(colName, "column name");
133
+ return def;
134
+ });
135
+ const columns = parsedColumnDefinitions.join(", ");
112
136
  const constraints = tableConstraints && tableConstraints.length > 0 ? ", " + tableConstraints.join(", ") : "";
113
- this.sql = `CREATE TABLE IF NOT EXISTS ${table} (${columns}${constraints})`;
137
+ this.sql = `CREATE TABLE IF NOT EXISTS ${parsedTableName} (${columns}${constraints})`;
114
138
  return this;
115
139
  }
116
140
  /**
@@ -133,13 +157,10 @@ var SqlBuilder = class {
133
157
  * @returns The builder instance
134
158
  */
135
159
  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);
160
+ const parsedIndexName = parseSqlIdentifier(indexName, "index name");
161
+ const parsedTableName = parseSqlIdentifier(tableName, "table name");
162
+ const parsedColumnName = parseSqlIdentifier(columnName, "column name");
163
+ this.sql = `CREATE ${indexType ? indexType + " " : ""}INDEX IF NOT EXISTS ${parsedIndexName} ON ${parsedTableName}(${parsedColumnName})`;
143
164
  return this;
144
165
  }
145
166
  /**
@@ -149,11 +170,12 @@ var SqlBuilder = class {
149
170
  * @param exact If true, will not add % wildcards
150
171
  */
151
172
  like(column, value, exact = false) {
173
+ const parsedColumnName = parseSqlIdentifier(column, "column name");
152
174
  const likeValue = exact ? value : `%${value}%`;
153
175
  if (this.whereAdded) {
154
- this.sql += ` AND ${column} LIKE ?`;
176
+ this.sql += ` AND ${parsedColumnName} LIKE ?`;
155
177
  } else {
156
- this.sql += ` WHERE ${column} LIKE ?`;
178
+ this.sql += ` WHERE ${parsedColumnName} LIKE ?`;
157
179
  this.whereAdded = true;
158
180
  }
159
181
  this.params.push(likeValue);
@@ -166,11 +188,13 @@ var SqlBuilder = class {
166
188
  * @param value The value to match
167
189
  */
168
190
  jsonLike(column, key, value) {
169
- const jsonPattern = `%"${key}":"${value}"%`;
191
+ const parsedColumnName = parseSqlIdentifier(column, "column name");
192
+ const parsedKey = parseSqlIdentifier(key, "key name");
193
+ const jsonPattern = `%"${parsedKey}":"${value}"%`;
170
194
  if (this.whereAdded) {
171
- this.sql += ` AND ${column} LIKE ?`;
195
+ this.sql += ` AND ${parsedColumnName} LIKE ?`;
172
196
  } else {
173
- this.sql += ` WHERE ${column} LIKE ?`;
197
+ this.sql += ` WHERE ${parsedColumnName} LIKE ?`;
174
198
  this.whereAdded = true;
175
199
  }
176
200
  this.params.push(jsonPattern);
@@ -200,6 +224,15 @@ var SqlBuilder = class {
200
224
  function createSqlBuilder() {
201
225
  return new SqlBuilder();
202
226
  }
227
+ var SQL_IDENTIFIER_PATTERN = /^[a-zA-Z0-9_]+(\s+AS\s+[a-zA-Z0-9_]+)?$/;
228
+ function parseSelectIdentifier(column) {
229
+ if (column !== "*" && !SQL_IDENTIFIER_PATTERN.test(column)) {
230
+ throw new Error(
231
+ `Invalid column name: "${column}". Must be "*" or a valid identifier (letters, numbers, underscores), optionally with "AS alias".`
232
+ );
233
+ }
234
+ return column;
235
+ }
203
236
 
204
237
  // src/storage/index.ts
205
238
  function isArrayOfRecords(value) {
@@ -218,6 +251,9 @@ var D1Store = class extends MastraStorage {
218
251
  */
219
252
  constructor(config) {
220
253
  super({ name: "D1" });
254
+ if (config.tablePrefix && !/^[a-zA-Z0-9_]*$/.test(config.tablePrefix)) {
255
+ throw new Error("Invalid tablePrefix: only letters, numbers, and underscores are allowed.");
256
+ }
221
257
  this.tablePrefix = config.tablePrefix || "";
222
258
  if ("binding" in config) {
223
259
  if (!config.binding) {
@@ -244,30 +280,6 @@ var D1Store = class extends MastraStorage {
244
280
  formatSqlParams(params) {
245
281
  return params.map((p) => p === void 0 || p === null ? null : p);
246
282
  }
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
283
  async executeWorkersBindingQuery({
272
284
  sql,
273
285
  params = [],
@@ -371,34 +383,25 @@ var D1Store = class extends MastraStorage {
371
383
  throw new Error(`D1 query error: ${error.message}`);
372
384
  }
373
385
  }
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";
386
+ // Helper to get existing table columns
387
+ async getTableColumns(tableName) {
388
+ try {
389
+ const sql = `PRAGMA table_info(${tableName})`;
390
+ const result = await this.executeQuery({ sql, params: [] });
391
+ if (!result || !Array.isArray(result)) {
392
+ return [];
393
+ }
394
+ return result.map((row) => ({
395
+ name: row.name,
396
+ type: row.type
397
+ }));
398
+ } catch (error) {
399
+ this.logger.error(`Error getting table columns for ${tableName}:`, {
400
+ message: error instanceof Error ? error.message : String(error)
401
+ });
402
+ return [];
391
403
  }
392
404
  }
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
405
  // Helper to serialize objects to JSON strings
403
406
  serializeValue(value) {
404
407
  if (value === null || value === void 0) return null;
@@ -432,6 +435,18 @@ var D1Store = class extends MastraStorage {
432
435
  }
433
436
  return value;
434
437
  }
438
+ getSqlType(type) {
439
+ switch (type) {
440
+ case "bigint":
441
+ return "INTEGER";
442
+ // SQLite uses INTEGER for all integer sizes
443
+ case "jsonb":
444
+ return "TEXT";
445
+ // Store JSON as TEXT in SQLite
446
+ default:
447
+ return super.getSqlType(type);
448
+ }
449
+ }
435
450
  async createTable({
436
451
  tableName,
437
452
  schema
@@ -459,6 +474,39 @@ var D1Store = class extends MastraStorage {
459
474
  throw new Error(`Failed to create table ${fullTableName}: ${error}`);
460
475
  }
461
476
  }
477
+ /**
478
+ * Alters table schema to add columns if they don't exist
479
+ * @param tableName Name of the table
480
+ * @param schema Schema of the table
481
+ * @param ifNotExists Array of column names to add if they don't exist
482
+ */
483
+ async alterTable({
484
+ tableName,
485
+ schema,
486
+ ifNotExists
487
+ }) {
488
+ const fullTableName = this.getTableName(tableName);
489
+ try {
490
+ const existingColumns = await this.getTableColumns(fullTableName);
491
+ const existingColumnNames = new Set(existingColumns.map((col) => col.name.toLowerCase()));
492
+ for (const columnName of ifNotExists) {
493
+ if (!existingColumnNames.has(columnName.toLowerCase()) && schema[columnName]) {
494
+ const columnDef = schema[columnName];
495
+ const sqlType = this.getSqlType(columnDef.type);
496
+ const nullable = columnDef.nullable === false ? "NOT NULL" : "";
497
+ const defaultValue = columnDef.nullable === false ? this.getDefaultValue(columnDef.type) : "";
498
+ const alterSql = `ALTER TABLE ${fullTableName} ADD COLUMN ${columnName} ${sqlType} ${nullable} ${defaultValue}`.trim();
499
+ await this.executeQuery({ sql: alterSql, params: [] });
500
+ this.logger.debug(`Added column ${columnName} to table ${fullTableName}`);
501
+ }
502
+ }
503
+ } catch (error) {
504
+ this.logger.error(`Error altering table ${fullTableName}:`, {
505
+ message: error instanceof Error ? error.message : String(error)
506
+ });
507
+ throw new Error(`Failed to alter table ${fullTableName}: ${error}`);
508
+ }
509
+ }
462
510
  async clearTable({ tableName }) {
463
511
  const fullTableName = this.getTableName(tableName);
464
512
  try {
@@ -490,7 +538,8 @@ var D1Store = class extends MastraStorage {
490
538
  try {
491
539
  await this.executeQuery({ sql, params });
492
540
  } catch (error) {
493
- this.logger.error(`Error inserting into ${fullTableName}:`, { error });
541
+ const message = error instanceof Error ? error.message : String(error);
542
+ this.logger.error(`Error inserting into ${fullTableName}:`, { message });
494
543
  throw new Error(`Failed to insert into ${fullTableName}: ${error}`);
495
544
  }
496
545
  }
@@ -543,6 +592,9 @@ var D1Store = class extends MastraStorage {
543
592
  return null;
544
593
  }
545
594
  }
595
+ /**
596
+ * @deprecated use getThreadsByResourceIdPaginated instead
597
+ */
546
598
  async getThreadsByResourceId({ resourceId }) {
547
599
  const fullTableName = this.getTableName(TABLE_THREADS);
548
600
  try {
@@ -562,6 +614,29 @@ var D1Store = class extends MastraStorage {
562
614
  return [];
563
615
  }
564
616
  }
617
+ async getThreadsByResourceIdPaginated(args) {
618
+ const { resourceId, page, perPage } = args;
619
+ const fullTableName = this.getTableName(TABLE_THREADS);
620
+ const mapRowToStorageThreadType = (row) => ({
621
+ ...row,
622
+ createdAt: this.ensureDate(row.createdAt),
623
+ updatedAt: this.ensureDate(row.updatedAt),
624
+ metadata: typeof row.metadata === "string" ? JSON.parse(row.metadata || "{}") : row.metadata || {}
625
+ });
626
+ const countQuery = createSqlBuilder().count().from(fullTableName).where("resourceId = ?", resourceId);
627
+ const countResult = await this.executeQuery(countQuery.build());
628
+ const total = Number(countResult?.[0]?.count ?? 0);
629
+ const selectQuery = createSqlBuilder().select("*").from(fullTableName).where("resourceId = ?", resourceId).orderBy("createdAt", "DESC").limit(perPage).offset(page * perPage);
630
+ const results = await this.executeQuery(selectQuery.build());
631
+ const threads = results.map(mapRowToStorageThreadType);
632
+ return {
633
+ threads,
634
+ total,
635
+ page,
636
+ perPage,
637
+ hasMore: page * perPage + threads.length < total
638
+ };
639
+ }
565
640
  async saveThread({ thread }) {
566
641
  const fullTableName = this.getTableName(TABLE_THREADS);
567
642
  const threadToSave = {
@@ -588,7 +663,8 @@ var D1Store = class extends MastraStorage {
588
663
  await this.executeQuery({ sql, params });
589
664
  return thread;
590
665
  } catch (error) {
591
- this.logger.error(`Error saving thread to ${fullTableName}:`, { error });
666
+ const message = error instanceof Error ? error.message : String(error);
667
+ this.logger.error(`Error saving thread to ${fullTableName}:`, { message });
592
668
  throw error;
593
669
  }
594
670
  }
@@ -622,7 +698,8 @@ var D1Store = class extends MastraStorage {
622
698
  updatedAt: /* @__PURE__ */ new Date()
623
699
  };
624
700
  } catch (error) {
625
- this.logger.error("Error updating thread:", { error });
701
+ const message = error instanceof Error ? error.message : String(error);
702
+ this.logger.error("Error updating thread:", { message });
626
703
  throw error;
627
704
  }
628
705
  }
@@ -643,11 +720,12 @@ var D1Store = class extends MastraStorage {
643
720
  throw new Error(`Failed to delete thread ${threadId}: ${error}`);
644
721
  }
645
722
  }
646
- // Thread and message management methods
647
- async saveMessages({ messages }) {
723
+ async saveMessages(args) {
724
+ const { messages, format = "v1" } = args;
648
725
  if (messages.length === 0) return [];
649
726
  try {
650
727
  const now = /* @__PURE__ */ new Date();
728
+ const threadId = messages[0]?.threadId;
651
729
  for (const [i, message] of messages.entries()) {
652
730
  if (!message.id) throw new Error(`Message at index ${i} missing id`);
653
731
  if (!message.threadId) throw new Error(`Message at index ${i} missing threadId`);
@@ -666,36 +744,42 @@ var D1Store = class extends MastraStorage {
666
744
  content: typeof message.content === "string" ? message.content : JSON.stringify(message.content),
667
745
  createdAt: createdAt.toISOString(),
668
746
  role: message.role,
669
- type: message.type
747
+ type: message.type || "v2",
748
+ resourceId: message.resourceId
670
749
  };
671
750
  });
672
- await this.batchInsert({
673
- tableName: TABLE_MESSAGES,
674
- records: messagesToInsert
675
- });
751
+ await Promise.all([
752
+ this.batchInsert({
753
+ tableName: TABLE_MESSAGES,
754
+ records: messagesToInsert
755
+ }),
756
+ // Update thread's updatedAt timestamp
757
+ this.executeQuery({
758
+ sql: `UPDATE ${this.getTableName(TABLE_THREADS)} SET updatedAt = ? WHERE id = ?`,
759
+ params: [now.toISOString(), threadId]
760
+ })
761
+ ]);
676
762
  this.logger.debug(`Saved ${messages.length} messages`);
677
- return messages;
763
+ const list = new MessageList().add(messages, "memory");
764
+ if (format === `v2`) return list.get.all.v2();
765
+ return list.get.all.v1();
678
766
  } catch (error) {
679
767
  this.logger.error("Error saving messages:", { message: error instanceof Error ? error.message : String(error) });
680
768
  throw error;
681
769
  }
682
770
  }
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 = `
771
+ async _getIncludedMessages(threadId, selectBy) {
772
+ const include = selectBy?.include;
773
+ if (!include) return null;
774
+ const prevMax = Math.max(...include.map((i) => i.withPreviousMessages || 0));
775
+ const nextMax = Math.max(...include.map((i) => i.withNextMessages || 0));
776
+ const includeIds = include.map((i) => i.id);
777
+ const sql = `
694
778
  WITH ordered_messages AS (
695
779
  SELECT
696
780
  *,
697
781
  ROW_NUMBER() OVER (ORDER BY createdAt DESC) AS row_num
698
- FROM ${fullTableName}
782
+ FROM ${this.getTableName(TABLE_MESSAGES)}
699
783
  WHERE thread_id = ?
700
784
  )
701
785
  SELECT
@@ -704,7 +788,7 @@ var D1Store = class extends MastraStorage {
704
788
  m.role,
705
789
  m.type,
706
790
  m.createdAt,
707
- m.thread_id AS "threadId"
791
+ m.thread_id AS threadId
708
792
  FROM ordered_messages m
709
793
  WHERE m.id IN (${includeIds.map(() => "?").join(",")})
710
794
  OR EXISTS (
@@ -718,20 +802,38 @@ var D1Store = class extends MastraStorage {
718
802
  )
719
803
  ORDER BY m.createdAt DESC
720
804
  `;
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 });
805
+ const params = [
806
+ threadId,
807
+ ...includeIds,
808
+ // for m.id IN (...)
809
+ ...includeIds,
810
+ // for target.id IN (...)
811
+ prevMax,
812
+ nextMax
813
+ ];
814
+ const messages = await this.executeQuery({ sql, params });
815
+ return messages;
816
+ }
817
+ async getMessages({
818
+ threadId,
819
+ selectBy,
820
+ format
821
+ }) {
822
+ const fullTableName = this.getTableName(TABLE_MESSAGES);
823
+ const limit = typeof selectBy?.last === "number" ? selectBy.last : 40;
824
+ const include = selectBy?.include || [];
825
+ const messages = [];
826
+ try {
827
+ if (include.length) {
828
+ const includeResult = await this._getIncludedMessages(threadId, selectBy);
731
829
  if (Array.isArray(includeResult)) messages.push(...includeResult);
732
830
  }
733
831
  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);
832
+ const query = createSqlBuilder().select(["id", "content", "role", "type", "createdAt", "thread_id AS threadId"]).from(fullTableName).where("thread_id = ?", threadId);
833
+ if (excludeIds.length > 0) {
834
+ query.andWhere(`id NOT IN (${excludeIds.map(() => "?").join(",")})`, ...excludeIds);
835
+ }
836
+ query.orderBy("createdAt", "DESC").limit(limit);
735
837
  const { sql, params } = query.build();
736
838
  const result = await this.executeQuery({ sql, params });
737
839
  if (Array.isArray(result)) messages.push(...result);
@@ -745,12 +847,15 @@ var D1Store = class extends MastraStorage {
745
847
  const processedMessages = messages.map((message) => {
746
848
  const processedMsg = {};
747
849
  for (const [key, value] of Object.entries(message)) {
850
+ if (key === `type` && value === `v2`) continue;
748
851
  processedMsg[key] = this.deserializeValue(value);
749
852
  }
750
853
  return processedMsg;
751
854
  });
752
855
  this.logger.debug(`Retrieved ${messages.length} messages for thread ${threadId}`);
753
- return processedMessages;
856
+ const list = new MessageList().add(processedMessages, "memory");
857
+ if (format === `v2`) return list.get.all.v2();
858
+ return list.get.all.v1();
754
859
  } catch (error) {
755
860
  this.logger.error("Error retrieving messages for thread", {
756
861
  threadId,
@@ -759,6 +864,47 @@ var D1Store = class extends MastraStorage {
759
864
  return [];
760
865
  }
761
866
  }
867
+ async getMessagesPaginated({
868
+ threadId,
869
+ selectBy,
870
+ format
871
+ }) {
872
+ const { dateRange, page = 0, perPage = 40 } = selectBy?.pagination || {};
873
+ const { start: fromDate, end: toDate } = dateRange || {};
874
+ const fullTableName = this.getTableName(TABLE_MESSAGES);
875
+ const messages = [];
876
+ if (selectBy?.include?.length) {
877
+ const includeResult = await this._getIncludedMessages(threadId, selectBy);
878
+ if (Array.isArray(includeResult)) messages.push(...includeResult);
879
+ }
880
+ const countQuery = createSqlBuilder().count().from(fullTableName).where("thread_id = ?", threadId);
881
+ if (fromDate) {
882
+ countQuery.andWhere("createdAt >= ?", this.serializeDate(fromDate));
883
+ }
884
+ if (toDate) {
885
+ countQuery.andWhere("createdAt <= ?", this.serializeDate(toDate));
886
+ }
887
+ const countResult = await this.executeQuery(countQuery.build());
888
+ const total = Number(countResult[0]?.count ?? 0);
889
+ const query = createSqlBuilder().select(["id", "content", "role", "type", "createdAt", "thread_id AS threadId"]).from(fullTableName).where("thread_id = ?", threadId);
890
+ if (fromDate) {
891
+ query.andWhere("createdAt >= ?", this.serializeDate(fromDate));
892
+ }
893
+ if (toDate) {
894
+ query.andWhere("createdAt <= ?", this.serializeDate(toDate));
895
+ }
896
+ query.orderBy("createdAt", "DESC").limit(perPage).offset(page * perPage);
897
+ const results = await this.executeQuery(query.build());
898
+ const list = new MessageList().add(results, "memory");
899
+ messages.push(...format === `v2` ? list.get.all.v2() : list.get.all.v1());
900
+ return {
901
+ messages,
902
+ total,
903
+ page,
904
+ perPage,
905
+ hasMore: page * perPage + messages.length < total
906
+ };
907
+ }
762
908
  async persistWorkflowSnapshot({
763
909
  workflowName,
764
910
  runId,
@@ -851,12 +997,17 @@ var D1Store = class extends MastraStorage {
851
997
  throw new Error(`Failed to batch insert into ${tableName}: ${error}`);
852
998
  }
853
999
  }
1000
+ /**
1001
+ * @deprecated use getTracesPaginated instead
1002
+ */
854
1003
  async getTraces({
855
1004
  name,
856
1005
  scope,
857
1006
  page,
858
1007
  perPage,
859
- attributes
1008
+ attributes,
1009
+ fromDate,
1010
+ toDate
860
1011
  }) {
861
1012
  const fullTableName = this.getTableName(TABLE_TRACES);
862
1013
  try {
@@ -872,22 +1023,89 @@ var D1Store = class extends MastraStorage {
872
1023
  query.jsonLike("attributes", key, value);
873
1024
  }
874
1025
  }
875
- query.orderBy("startTime", "DESC").limit(perPage).offset((page - 1) * perPage);
1026
+ if (fromDate) {
1027
+ query.andWhere("createdAt >= ?", fromDate instanceof Date ? fromDate.toISOString() : fromDate);
1028
+ }
1029
+ if (toDate) {
1030
+ query.andWhere("createdAt <= ?", toDate instanceof Date ? toDate.toISOString() : toDate);
1031
+ }
1032
+ query.orderBy("startTime", "DESC").limit(perPage).offset(page * perPage);
876
1033
  const { sql, params } = query.build();
877
1034
  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
- })) : [];
1035
+ return isArrayOfRecords(results) ? results.map(
1036
+ (trace) => ({
1037
+ ...trace,
1038
+ attributes: this.deserializeValue(trace.attributes, "jsonb"),
1039
+ status: this.deserializeValue(trace.status, "jsonb"),
1040
+ events: this.deserializeValue(trace.events, "jsonb"),
1041
+ links: this.deserializeValue(trace.links, "jsonb"),
1042
+ other: this.deserializeValue(trace.other, "jsonb")
1043
+ })
1044
+ ) : [];
886
1045
  } catch (error) {
887
1046
  this.logger.error("Error getting traces:", { message: error instanceof Error ? error.message : String(error) });
888
1047
  return [];
889
1048
  }
890
1049
  }
1050
+ async getTracesPaginated(args) {
1051
+ const { name, scope, page, perPage, attributes, fromDate, toDate } = args;
1052
+ const fullTableName = this.getTableName(TABLE_TRACES);
1053
+ try {
1054
+ const dataQuery = createSqlBuilder().select("*").from(fullTableName).where("1=1");
1055
+ const countQuery = createSqlBuilder().count().from(fullTableName).where("1=1");
1056
+ if (name) {
1057
+ dataQuery.andWhere("name LIKE ?", `%${name}%`);
1058
+ countQuery.andWhere("name LIKE ?", `%${name}%`);
1059
+ }
1060
+ if (scope) {
1061
+ dataQuery.andWhere("scope = ?", scope);
1062
+ countQuery.andWhere("scope = ?", scope);
1063
+ }
1064
+ if (attributes && Object.keys(attributes).length > 0) {
1065
+ for (const [key, value] of Object.entries(attributes)) {
1066
+ dataQuery.jsonLike("attributes", key, value);
1067
+ countQuery.jsonLike("attributes", key, value);
1068
+ }
1069
+ }
1070
+ if (fromDate) {
1071
+ const fromDateStr = fromDate instanceof Date ? fromDate.toISOString() : fromDate;
1072
+ dataQuery.andWhere("createdAt >= ?", fromDateStr);
1073
+ countQuery.andWhere("createdAt >= ?", fromDateStr);
1074
+ }
1075
+ if (toDate) {
1076
+ const toDateStr = toDate instanceof Date ? toDate.toISOString() : toDate;
1077
+ dataQuery.andWhere("createdAt <= ?", toDateStr);
1078
+ countQuery.andWhere("createdAt <= ?", toDateStr);
1079
+ }
1080
+ const countResult = await this.executeQuery(countQuery.build());
1081
+ const total = Number(countResult?.[0]?.count ?? 0);
1082
+ dataQuery.orderBy("startTime", "DESC").limit(perPage).offset(page * perPage);
1083
+ const results = await this.executeQuery(dataQuery.build());
1084
+ const traces = isArrayOfRecords(results) ? results.map(
1085
+ (trace) => ({
1086
+ ...trace,
1087
+ attributes: this.deserializeValue(trace.attributes, "jsonb"),
1088
+ status: this.deserializeValue(trace.status, "jsonb"),
1089
+ events: this.deserializeValue(trace.events, "jsonb"),
1090
+ links: this.deserializeValue(trace.links, "jsonb"),
1091
+ other: this.deserializeValue(trace.other, "jsonb")
1092
+ })
1093
+ ) : [];
1094
+ return {
1095
+ traces,
1096
+ total,
1097
+ page,
1098
+ perPage,
1099
+ hasMore: page * perPage + traces.length < total
1100
+ };
1101
+ } catch (error) {
1102
+ this.logger.error("Error getting traces:", { message: error instanceof Error ? error.message : String(error) });
1103
+ return { traces: [], total: 0, page, perPage, hasMore: false };
1104
+ }
1105
+ }
1106
+ /**
1107
+ * @deprecated use getEvals instead
1108
+ */
891
1109
  async getEvalsByAgentName(agentName, type) {
892
1110
  const fullTableName = this.getTableName(TABLE_EVALS);
893
1111
  try {
@@ -923,8 +1141,181 @@ var D1Store = class extends MastraStorage {
923
1141
  return [];
924
1142
  }
925
1143
  }
926
- getWorkflowRuns(_args) {
927
- throw new Error("Method not implemented.");
1144
+ async getEvals(options) {
1145
+ const { agentName, type, page = 0, perPage = 40, fromDate, toDate } = options || {};
1146
+ const fullTableName = this.getTableName(TABLE_EVALS);
1147
+ const conditions = [];
1148
+ const queryParams = [];
1149
+ if (agentName) {
1150
+ conditions.push(`agent_name = ?`);
1151
+ queryParams.push(agentName);
1152
+ }
1153
+ if (type === "test") {
1154
+ conditions.push(`(test_info IS NOT NULL AND json_extract(test_info, '$.testPath') IS NOT NULL)`);
1155
+ } else if (type === "live") {
1156
+ conditions.push(`(test_info IS NULL OR json_extract(test_info, '$.testPath') IS NULL)`);
1157
+ }
1158
+ if (fromDate) {
1159
+ conditions.push(`createdAt >= ?`);
1160
+ queryParams.push(this.serializeDate(fromDate));
1161
+ }
1162
+ if (toDate) {
1163
+ conditions.push(`createdAt <= ?`);
1164
+ queryParams.push(this.serializeDate(toDate));
1165
+ }
1166
+ const countQueryBuilder = createSqlBuilder().count().from(fullTableName);
1167
+ if (conditions.length > 0) {
1168
+ countQueryBuilder.where(conditions.join(" AND "), ...queryParams);
1169
+ }
1170
+ const { sql: countSql, params: countParams } = countQueryBuilder.build();
1171
+ const countResult = await this.executeQuery({ sql: countSql, params: countParams, first: true });
1172
+ const total = Number(countResult?.count || 0);
1173
+ const currentOffset = page * perPage;
1174
+ if (total === 0) {
1175
+ return {
1176
+ evals: [],
1177
+ total: 0,
1178
+ page,
1179
+ perPage,
1180
+ hasMore: false
1181
+ };
1182
+ }
1183
+ const dataQueryBuilder = createSqlBuilder().select("*").from(fullTableName);
1184
+ if (conditions.length > 0) {
1185
+ dataQueryBuilder.where(conditions.join(" AND "), ...queryParams);
1186
+ }
1187
+ dataQueryBuilder.orderBy("createdAt", "DESC").limit(perPage).offset(currentOffset);
1188
+ const { sql: dataSql, params: dataParams } = dataQueryBuilder.build();
1189
+ const rows = await this.executeQuery({ sql: dataSql, params: dataParams });
1190
+ const evals = (isArrayOfRecords(rows) ? rows : []).map((row) => {
1191
+ const result = this.deserializeValue(row.result);
1192
+ const testInfo = row.test_info ? this.deserializeValue(row.test_info) : void 0;
1193
+ if (!result || typeof result !== "object" || !("score" in result)) {
1194
+ throw new Error(`Invalid MetricResult format: ${JSON.stringify(result)}`);
1195
+ }
1196
+ return {
1197
+ input: row.input,
1198
+ output: row.output,
1199
+ result,
1200
+ agentName: row.agent_name,
1201
+ metricName: row.metric_name,
1202
+ instructions: row.instructions,
1203
+ testInfo,
1204
+ globalRunId: row.global_run_id,
1205
+ runId: row.run_id,
1206
+ createdAt: row.createdAt
1207
+ };
1208
+ });
1209
+ const hasMore = currentOffset + evals.length < total;
1210
+ return {
1211
+ evals,
1212
+ total,
1213
+ page,
1214
+ perPage,
1215
+ hasMore
1216
+ };
1217
+ }
1218
+ parseWorkflowRun(row) {
1219
+ let parsedSnapshot = row.snapshot;
1220
+ if (typeof parsedSnapshot === "string") {
1221
+ try {
1222
+ parsedSnapshot = JSON.parse(row.snapshot);
1223
+ } catch (e) {
1224
+ console.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
1225
+ }
1226
+ }
1227
+ return {
1228
+ workflowName: row.workflow_name,
1229
+ runId: row.run_id,
1230
+ snapshot: parsedSnapshot,
1231
+ createdAt: this.ensureDate(row.createdAt),
1232
+ updatedAt: this.ensureDate(row.updatedAt),
1233
+ resourceId: row.resourceId
1234
+ };
1235
+ }
1236
+ async hasColumn(table, column) {
1237
+ const sql = `PRAGMA table_info(${table});`;
1238
+ const result = await this.executeQuery({ sql, params: [] });
1239
+ if (!result || !Array.isArray(result)) return false;
1240
+ return result.some((col) => col.name === column || col.name === column.toLowerCase());
1241
+ }
1242
+ async getWorkflowRuns({
1243
+ workflowName,
1244
+ fromDate,
1245
+ toDate,
1246
+ limit,
1247
+ offset,
1248
+ resourceId
1249
+ } = {}) {
1250
+ const fullTableName = this.getTableName(TABLE_WORKFLOW_SNAPSHOT);
1251
+ try {
1252
+ const builder = createSqlBuilder().select().from(fullTableName);
1253
+ const countBuilder = createSqlBuilder().count().from(fullTableName);
1254
+ if (workflowName) builder.whereAnd("workflow_name = ?", workflowName);
1255
+ if (resourceId) {
1256
+ const hasResourceId = await this.hasColumn(fullTableName, "resourceId");
1257
+ if (hasResourceId) {
1258
+ builder.whereAnd("resourceId = ?", resourceId);
1259
+ countBuilder.whereAnd("resourceId = ?", resourceId);
1260
+ } else {
1261
+ console.warn(`[${fullTableName}] resourceId column not found. Skipping resourceId filter.`);
1262
+ }
1263
+ }
1264
+ if (fromDate) {
1265
+ builder.whereAnd("createdAt >= ?", fromDate instanceof Date ? fromDate.toISOString() : fromDate);
1266
+ countBuilder.whereAnd("createdAt >= ?", fromDate instanceof Date ? fromDate.toISOString() : fromDate);
1267
+ }
1268
+ if (toDate) {
1269
+ builder.whereAnd("createdAt <= ?", toDate instanceof Date ? toDate.toISOString() : toDate);
1270
+ countBuilder.whereAnd("createdAt <= ?", toDate instanceof Date ? toDate.toISOString() : toDate);
1271
+ }
1272
+ builder.orderBy("createdAt", "DESC");
1273
+ if (typeof limit === "number") builder.limit(limit);
1274
+ if (typeof offset === "number") builder.offset(offset);
1275
+ const { sql, params } = builder.build();
1276
+ let total = 0;
1277
+ if (limit !== void 0 && offset !== void 0) {
1278
+ const { sql: countSql, params: countParams } = countBuilder.build();
1279
+ const countResult = await this.executeQuery({ sql: countSql, params: countParams, first: true });
1280
+ total = Number(countResult?.count ?? 0);
1281
+ }
1282
+ const results = await this.executeQuery({ sql, params });
1283
+ const runs = (isArrayOfRecords(results) ? results : []).map((row) => this.parseWorkflowRun(row));
1284
+ return { runs, total: total || runs.length };
1285
+ } catch (error) {
1286
+ this.logger.error("Error getting workflow runs:", {
1287
+ message: error instanceof Error ? error.message : String(error)
1288
+ });
1289
+ throw error;
1290
+ }
1291
+ }
1292
+ async getWorkflowRunById({
1293
+ runId,
1294
+ workflowName
1295
+ }) {
1296
+ const fullTableName = this.getTableName(TABLE_WORKFLOW_SNAPSHOT);
1297
+ try {
1298
+ const conditions = [];
1299
+ const params = [];
1300
+ if (runId) {
1301
+ conditions.push("run_id = ?");
1302
+ params.push(runId);
1303
+ }
1304
+ if (workflowName) {
1305
+ conditions.push("workflow_name = ?");
1306
+ params.push(workflowName);
1307
+ }
1308
+ const whereClause = conditions.length > 0 ? "WHERE " + conditions.join(" AND ") : "";
1309
+ const sql = `SELECT * FROM ${fullTableName} ${whereClause} ORDER BY createdAt DESC LIMIT 1`;
1310
+ const result = await this.executeQuery({ sql, params, first: true });
1311
+ if (!result) return null;
1312
+ return this.parseWorkflowRun(result);
1313
+ } catch (error) {
1314
+ this.logger.error("Error getting workflow run by ID:", {
1315
+ message: error instanceof Error ? error.message : String(error)
1316
+ });
1317
+ throw error;
1318
+ }
928
1319
  }
929
1320
  /**
930
1321
  * Close the database connection