@mastra/cloudflare-d1 1.0.0-beta.6 → 1.0.0-beta.8

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