@mastra/dynamodb 0.11.1-alpha.0 → 0.11.1-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { DynamoDBClient, DescribeTableCommand } from '@aws-sdk/client-dynamodb';
2
2
  import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
3
3
  import { MessageList } from '@mastra/core/agent';
4
+ import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
4
5
  import { MastraStorage, TABLE_TRACES, TABLE_EVALS, TABLE_WORKFLOW_SNAPSHOT, TABLE_MESSAGES, TABLE_THREADS } from '@mastra/core/storage';
5
6
  import { Entity, Service } from 'electrodb';
6
7
 
@@ -564,22 +565,33 @@ var DynamoDBStore = class extends MastraStorage {
564
565
  hasInitialized = null;
565
566
  constructor({ name, config }) {
566
567
  super({ name });
567
- if (!config.tableName || typeof config.tableName !== "string" || config.tableName.trim() === "") {
568
- throw new Error("DynamoDBStore: config.tableName must be provided and cannot be empty.");
569
- }
570
- if (!/^[a-zA-Z0-9_.-]{3,255}$/.test(config.tableName)) {
571
- throw new Error(
572
- `DynamoDBStore: config.tableName "${config.tableName}" contains invalid characters or is not between 3 and 255 characters long.`
568
+ try {
569
+ if (!config.tableName || typeof config.tableName !== "string" || config.tableName.trim() === "") {
570
+ throw new Error("DynamoDBStore: config.tableName must be provided and cannot be empty.");
571
+ }
572
+ if (!/^[a-zA-Z0-9_.-]{3,255}$/.test(config.tableName)) {
573
+ throw new Error(
574
+ `DynamoDBStore: config.tableName "${config.tableName}" contains invalid characters or is not between 3 and 255 characters long.`
575
+ );
576
+ }
577
+ const dynamoClient = new DynamoDBClient({
578
+ region: config.region || "us-east-1",
579
+ endpoint: config.endpoint,
580
+ credentials: config.credentials
581
+ });
582
+ this.tableName = config.tableName;
583
+ this.client = DynamoDBDocumentClient.from(dynamoClient);
584
+ this.service = getElectroDbService(this.client, this.tableName);
585
+ } catch (error) {
586
+ throw new MastraError(
587
+ {
588
+ id: "STORAGE_DYNAMODB_STORE_CONSTRUCTOR_FAILED",
589
+ domain: ErrorDomain.STORAGE,
590
+ category: ErrorCategory.USER
591
+ },
592
+ error
573
593
  );
574
594
  }
575
- const dynamoClient = new DynamoDBClient({
576
- region: config.region || "us-east-1",
577
- endpoint: config.endpoint,
578
- credentials: config.credentials
579
- });
580
- this.tableName = config.tableName;
581
- this.client = DynamoDBDocumentClient.from(dynamoClient);
582
- this.service = getElectroDbService(this.client, this.tableName);
583
595
  }
584
596
  /**
585
597
  * This method is modified for DynamoDB with ElectroDB single-table design.
@@ -603,7 +615,15 @@ var DynamoDBStore = class extends MastraStorage {
603
615
  this.logger.debug(`Table ${this.tableName} exists and is accessible`);
604
616
  } catch (error) {
605
617
  this.logger.error("Error validating table access", { tableName: this.tableName, error });
606
- throw error;
618
+ throw new MastraError(
619
+ {
620
+ id: "STORAGE_DYNAMODB_STORE_VALIDATE_TABLE_ACCESS_FAILED",
621
+ domain: ErrorDomain.STORAGE,
622
+ category: ErrorCategory.THIRD_PARTY,
623
+ details: { tableName: this.tableName }
624
+ },
625
+ error
626
+ );
607
627
  }
608
628
  }
609
629
  /**
@@ -622,7 +642,15 @@ var DynamoDBStore = class extends MastraStorage {
622
642
  if (error.name === "ResourceNotFoundException") {
623
643
  return false;
624
644
  }
625
- throw error;
645
+ throw new MastraError(
646
+ {
647
+ id: "STORAGE_DYNAMODB_STORE_VALIDATE_TABLE_EXISTS_FAILED",
648
+ domain: ErrorDomain.STORAGE,
649
+ category: ErrorCategory.THIRD_PARTY,
650
+ details: { tableName: this.tableName }
651
+ },
652
+ error
653
+ );
626
654
  }
627
655
  }
628
656
  /**
@@ -637,7 +665,15 @@ var DynamoDBStore = class extends MastraStorage {
637
665
  try {
638
666
  await this.hasInitialized;
639
667
  } catch (error) {
640
- throw error;
668
+ throw new MastraError(
669
+ {
670
+ id: "STORAGE_DYNAMODB_STORE_INIT_FAILED",
671
+ domain: ErrorDomain.STORAGE,
672
+ category: ErrorCategory.THIRD_PARTY,
673
+ details: { tableName: this.tableName }
674
+ },
675
+ error
676
+ );
641
677
  }
642
678
  }
643
679
  /**
@@ -683,7 +719,13 @@ var DynamoDBStore = class extends MastraStorage {
683
719
  this.logger.debug("DynamoDB clearTable called", { tableName });
684
720
  const entityName = this.getEntityNameForTable(tableName);
685
721
  if (!entityName || !this.service.entities[entityName]) {
686
- throw new Error(`No entity defined for ${tableName}`);
722
+ throw new MastraError({
723
+ id: "STORAGE_DYNAMODB_STORE_CLEAR_TABLE_INVALID_ARGS",
724
+ domain: ErrorDomain.STORAGE,
725
+ category: ErrorCategory.USER,
726
+ text: "No entity defined for tableName",
727
+ details: { tableName }
728
+ });
687
729
  }
688
730
  try {
689
731
  const result = await this.service.entities[entityName].scan.go({ pages: "all" });
@@ -731,35 +773,67 @@ var DynamoDBStore = class extends MastraStorage {
731
773
  }
732
774
  this.logger.debug(`Successfully cleared all records for ${tableName}`);
733
775
  } catch (error) {
734
- this.logger.error("Failed to clear table", { tableName, error });
735
- throw error;
776
+ throw new MastraError(
777
+ {
778
+ id: "STORAGE_DYNAMODB_STORE_CLEAR_TABLE_FAILED",
779
+ domain: ErrorDomain.STORAGE,
780
+ category: ErrorCategory.THIRD_PARTY,
781
+ details: { tableName }
782
+ },
783
+ error
784
+ );
736
785
  }
737
786
  }
738
787
  /**
739
788
  * Insert a record into the specified "table" (entity)
740
789
  */
741
- async insert({ tableName, record }) {
790
+ async insert({
791
+ tableName,
792
+ record
793
+ }) {
742
794
  this.logger.debug("DynamoDB insert called", { tableName });
743
795
  const entityName = this.getEntityNameForTable(tableName);
744
796
  if (!entityName || !this.service.entities[entityName]) {
745
- throw new Error(`No entity defined for ${tableName}`);
797
+ throw new MastraError({
798
+ id: "STORAGE_DYNAMODB_STORE_INSERT_INVALID_ARGS",
799
+ domain: ErrorDomain.STORAGE,
800
+ category: ErrorCategory.USER,
801
+ text: "No entity defined for tableName",
802
+ details: { tableName }
803
+ });
746
804
  }
747
805
  try {
748
806
  const dataToSave = { entity: entityName, ...this.preprocessRecord(record) };
749
807
  await this.service.entities[entityName].create(dataToSave).go();
750
808
  } catch (error) {
751
- this.logger.error("Failed to insert record", { tableName, error });
752
- throw error;
809
+ throw new MastraError(
810
+ {
811
+ id: "STORAGE_DYNAMODB_STORE_INSERT_FAILED",
812
+ domain: ErrorDomain.STORAGE,
813
+ category: ErrorCategory.THIRD_PARTY,
814
+ details: { tableName }
815
+ },
816
+ error
817
+ );
753
818
  }
754
819
  }
755
820
  /**
756
821
  * Insert multiple records as a batch
757
822
  */
758
- async batchInsert({ tableName, records }) {
823
+ async batchInsert({
824
+ tableName,
825
+ records
826
+ }) {
759
827
  this.logger.debug("DynamoDB batchInsert called", { tableName, count: records.length });
760
828
  const entityName = this.getEntityNameForTable(tableName);
761
829
  if (!entityName || !this.service.entities[entityName]) {
762
- throw new Error(`No entity defined for ${tableName}`);
830
+ throw new MastraError({
831
+ id: "STORAGE_DYNAMODB_STORE_BATCH_INSERT_INVALID_ARGS",
832
+ domain: ErrorDomain.STORAGE,
833
+ category: ErrorCategory.USER,
834
+ text: "No entity defined for tableName",
835
+ details: { tableName }
836
+ });
763
837
  }
764
838
  const recordsToSave = records.map((rec) => ({ entity: entityName, ...this.preprocessRecord(rec) }));
765
839
  const batchSize = 25;
@@ -780,18 +854,34 @@ var DynamoDBStore = class extends MastraStorage {
780
854
  }
781
855
  }
782
856
  } catch (error) {
783
- this.logger.error("Failed to batch insert records", { tableName, error });
784
- throw error;
857
+ throw new MastraError(
858
+ {
859
+ id: "STORAGE_DYNAMODB_STORE_BATCH_INSERT_FAILED",
860
+ domain: ErrorDomain.STORAGE,
861
+ category: ErrorCategory.THIRD_PARTY,
862
+ details: { tableName }
863
+ },
864
+ error
865
+ );
785
866
  }
786
867
  }
787
868
  /**
788
869
  * Load a record by its keys
789
870
  */
790
- async load({ tableName, keys }) {
871
+ async load({
872
+ tableName,
873
+ keys
874
+ }) {
791
875
  this.logger.debug("DynamoDB load called", { tableName, keys });
792
876
  const entityName = this.getEntityNameForTable(tableName);
793
877
  if (!entityName || !this.service.entities[entityName]) {
794
- throw new Error(`No entity defined for ${tableName}`);
878
+ throw new MastraError({
879
+ id: "STORAGE_DYNAMODB_STORE_LOAD_INVALID_ARGS",
880
+ domain: ErrorDomain.STORAGE,
881
+ category: ErrorCategory.USER,
882
+ text: "No entity defined for tableName",
883
+ details: { tableName }
884
+ });
795
885
  }
796
886
  try {
797
887
  const keyObject = { entity: entityName, ...keys };
@@ -802,8 +892,15 @@ var DynamoDBStore = class extends MastraStorage {
802
892
  let data = result.data;
803
893
  return data;
804
894
  } catch (error) {
805
- this.logger.error("Failed to load record", { tableName, keys, error });
806
- throw error;
895
+ throw new MastraError(
896
+ {
897
+ id: "STORAGE_DYNAMODB_STORE_LOAD_FAILED",
898
+ domain: ErrorDomain.STORAGE,
899
+ category: ErrorCategory.THIRD_PARTY,
900
+ details: { tableName }
901
+ },
902
+ error
903
+ );
807
904
  }
808
905
  }
809
906
  // Thread operations
@@ -824,8 +921,15 @@ var DynamoDBStore = class extends MastraStorage {
824
921
  // metadata is already transformed by the entity's getter
825
922
  };
826
923
  } catch (error) {
827
- this.logger.error("Failed to get thread by ID", { threadId, error });
828
- throw error;
924
+ throw new MastraError(
925
+ {
926
+ id: "STORAGE_DYNAMODB_STORE_GET_THREAD_BY_ID_FAILED",
927
+ domain: ErrorDomain.STORAGE,
928
+ category: ErrorCategory.THIRD_PARTY,
929
+ details: { threadId }
930
+ },
931
+ error
932
+ );
829
933
  }
830
934
  }
831
935
  async getThreadsByResourceId({ resourceId }) {
@@ -844,8 +948,15 @@ var DynamoDBStore = class extends MastraStorage {
844
948
  // metadata is already transformed by the entity's getter
845
949
  }));
846
950
  } catch (error) {
847
- this.logger.error("Failed to get threads by resource ID", { resourceId, error });
848
- throw error;
951
+ throw new MastraError(
952
+ {
953
+ id: "STORAGE_DYNAMODB_STORE_GET_THREADS_BY_RESOURCE_ID_FAILED",
954
+ domain: ErrorDomain.STORAGE,
955
+ category: ErrorCategory.THIRD_PARTY,
956
+ details: { resourceId }
957
+ },
958
+ error
959
+ );
849
960
  }
850
961
  }
851
962
  async saveThread({ thread }) {
@@ -871,8 +982,15 @@ var DynamoDBStore = class extends MastraStorage {
871
982
  metadata: thread.metadata
872
983
  };
873
984
  } catch (error) {
874
- this.logger.error("Failed to save thread", { threadId: thread.id, error });
875
- throw error;
985
+ throw new MastraError(
986
+ {
987
+ id: "STORAGE_DYNAMODB_STORE_SAVE_THREAD_FAILED",
988
+ domain: ErrorDomain.STORAGE,
989
+ category: ErrorCategory.THIRD_PARTY,
990
+ details: { threadId: thread.id }
991
+ },
992
+ error
993
+ );
876
994
  }
877
995
  }
878
996
  async updateThread({
@@ -904,8 +1022,15 @@ var DynamoDBStore = class extends MastraStorage {
904
1022
  updatedAt: now
905
1023
  };
906
1024
  } catch (error) {
907
- this.logger.error("Failed to update thread", { threadId: id, error });
908
- throw error;
1025
+ throw new MastraError(
1026
+ {
1027
+ id: "STORAGE_DYNAMODB_STORE_UPDATE_THREAD_FAILED",
1028
+ domain: ErrorDomain.STORAGE,
1029
+ category: ErrorCategory.THIRD_PARTY,
1030
+ details: { threadId: id }
1031
+ },
1032
+ error
1033
+ );
909
1034
  }
910
1035
  }
911
1036
  async deleteThread({ threadId }) {
@@ -913,8 +1038,15 @@ var DynamoDBStore = class extends MastraStorage {
913
1038
  try {
914
1039
  await this.service.entities.thread.delete({ entity: "thread", id: threadId }).go();
915
1040
  } catch (error) {
916
- this.logger.error("Failed to delete thread", { threadId, error });
917
- throw error;
1041
+ throw new MastraError(
1042
+ {
1043
+ id: "STORAGE_DYNAMODB_STORE_DELETE_THREAD_FAILED",
1044
+ domain: ErrorDomain.STORAGE,
1045
+ category: ErrorCategory.THIRD_PARTY,
1046
+ details: { threadId }
1047
+ },
1048
+ error
1049
+ );
918
1050
  }
919
1051
  }
920
1052
  async getMessages({
@@ -926,8 +1058,9 @@ var DynamoDBStore = class extends MastraStorage {
926
1058
  this.logger.debug("Getting messages", { threadId, selectBy });
927
1059
  try {
928
1060
  const query = this.service.entities.message.query.byThread({ entity: "message", threadId });
929
- if (selectBy?.last && typeof selectBy.last === "number") {
930
- const results2 = await query.go({ limit: selectBy.last, order: "desc" });
1061
+ const limit = this.resolveMessageLimit({ last: selectBy?.last, defaultLimit: Number.MAX_SAFE_INTEGER });
1062
+ if (limit !== Number.MAX_SAFE_INTEGER) {
1063
+ const results2 = await query.go({ limit, order: "desc" });
931
1064
  const list2 = new MessageList({ threadId, resourceId }).add(
932
1065
  results2.data.map((data) => this.parseMessageData(data)),
933
1066
  "memory"
@@ -943,8 +1076,15 @@ var DynamoDBStore = class extends MastraStorage {
943
1076
  if (format === `v2`) return list.get.all.v2();
944
1077
  return list.get.all.v1();
945
1078
  } catch (error) {
946
- this.logger.error("Failed to get messages", { threadId, error });
947
- throw error;
1079
+ throw new MastraError(
1080
+ {
1081
+ id: "STORAGE_DYNAMODB_STORE_GET_MESSAGES_FAILED",
1082
+ domain: ErrorDomain.STORAGE,
1083
+ category: ErrorCategory.THIRD_PARTY,
1084
+ details: { threadId }
1085
+ },
1086
+ error
1087
+ );
948
1088
  }
949
1089
  }
950
1090
  async saveMessages(args) {
@@ -992,7 +1132,7 @@ var DynamoDBStore = class extends MastraStorage {
992
1132
  this.logger.error("Missing entity property in message data for create", { messageData });
993
1133
  throw new Error("Internal error: Missing entity property during saveMessages");
994
1134
  }
995
- await this.service.entities.message.create(messageData).go();
1135
+ await this.service.entities.message.put(messageData).go();
996
1136
  }
997
1137
  }),
998
1138
  // Update thread's updatedAt timestamp
@@ -1004,8 +1144,15 @@ var DynamoDBStore = class extends MastraStorage {
1004
1144
  if (format === `v1`) return list.get.all.v1();
1005
1145
  return list.get.all.v2();
1006
1146
  } catch (error) {
1007
- this.logger.error("Failed to save messages", { error });
1008
- throw error;
1147
+ throw new MastraError(
1148
+ {
1149
+ id: "STORAGE_DYNAMODB_STORE_SAVE_MESSAGES_FAILED",
1150
+ domain: ErrorDomain.STORAGE,
1151
+ category: ErrorCategory.THIRD_PARTY,
1152
+ details: { count: messages.length }
1153
+ },
1154
+ error
1155
+ );
1009
1156
  }
1010
1157
  }
1011
1158
  // Helper function to parse message data (handle JSON fields)
@@ -1051,8 +1198,14 @@ var DynamoDBStore = class extends MastraStorage {
1051
1198
  } while (cursor && pagesFetched < startPage);
1052
1199
  return items;
1053
1200
  } catch (error) {
1054
- this.logger.error("Failed to get traces", { error });
1055
- throw error;
1201
+ throw new MastraError(
1202
+ {
1203
+ id: "STORAGE_DYNAMODB_STORE_GET_TRACES_FAILED",
1204
+ domain: ErrorDomain.STORAGE,
1205
+ category: ErrorCategory.THIRD_PARTY
1206
+ },
1207
+ error
1208
+ );
1056
1209
  }
1057
1210
  }
1058
1211
  async batchTraceInsert({ records }) {
@@ -1068,8 +1221,15 @@ var DynamoDBStore = class extends MastraStorage {
1068
1221
  // Pass records with 'entity' included
1069
1222
  });
1070
1223
  } catch (error) {
1071
- this.logger.error("Failed to batch insert traces", { error });
1072
- throw error;
1224
+ throw new MastraError(
1225
+ {
1226
+ id: "STORAGE_DYNAMODB_STORE_BATCH_TRACE_INSERT_FAILED",
1227
+ domain: ErrorDomain.STORAGE,
1228
+ category: ErrorCategory.THIRD_PARTY,
1229
+ details: { count: records.length }
1230
+ },
1231
+ error
1232
+ );
1073
1233
  }
1074
1234
  }
1075
1235
  // Workflow operations
@@ -1095,8 +1255,15 @@ var DynamoDBStore = class extends MastraStorage {
1095
1255
  };
1096
1256
  await this.service.entities.workflowSnapshot.upsert(data).go();
1097
1257
  } catch (error) {
1098
- this.logger.error("Failed to persist workflow snapshot", { workflowName, runId, error });
1099
- throw error;
1258
+ throw new MastraError(
1259
+ {
1260
+ id: "STORAGE_DYNAMODB_STORE_PERSIST_WORKFLOW_SNAPSHOT_FAILED",
1261
+ domain: ErrorDomain.STORAGE,
1262
+ category: ErrorCategory.THIRD_PARTY,
1263
+ details: { workflowName, runId }
1264
+ },
1265
+ error
1266
+ );
1100
1267
  }
1101
1268
  }
1102
1269
  async loadWorkflowSnapshot({
@@ -1116,8 +1283,15 @@ var DynamoDBStore = class extends MastraStorage {
1116
1283
  }
1117
1284
  return result.data.snapshot;
1118
1285
  } catch (error) {
1119
- this.logger.error("Failed to load workflow snapshot", { workflowName, runId, error });
1120
- throw error;
1286
+ throw new MastraError(
1287
+ {
1288
+ id: "STORAGE_DYNAMODB_STORE_LOAD_WORKFLOW_SNAPSHOT_FAILED",
1289
+ domain: ErrorDomain.STORAGE,
1290
+ category: ErrorCategory.THIRD_PARTY,
1291
+ details: { workflowName, runId }
1292
+ },
1293
+ error
1294
+ );
1121
1295
  }
1122
1296
  }
1123
1297
  async getWorkflowRuns(args) {
@@ -1178,8 +1352,15 @@ var DynamoDBStore = class extends MastraStorage {
1178
1352
  total
1179
1353
  };
1180
1354
  } catch (error) {
1181
- this.logger.error("Failed to get workflow runs", { error });
1182
- throw error;
1355
+ throw new MastraError(
1356
+ {
1357
+ id: "STORAGE_DYNAMODB_STORE_GET_WORKFLOW_RUNS_FAILED",
1358
+ domain: ErrorDomain.STORAGE,
1359
+ category: ErrorCategory.THIRD_PARTY,
1360
+ details: { workflowName: args?.workflowName || "", resourceId: args?.resourceId || "" }
1361
+ },
1362
+ error
1363
+ );
1183
1364
  }
1184
1365
  }
1185
1366
  async getWorkflowRunById(args) {
@@ -1225,8 +1406,15 @@ var DynamoDBStore = class extends MastraStorage {
1225
1406
  resourceId: matchingRunDbItem.resourceId
1226
1407
  };
1227
1408
  } catch (error) {
1228
- this.logger.error("Failed to get workflow run by ID", { runId, workflowName, error });
1229
- throw error;
1409
+ throw new MastraError(
1410
+ {
1411
+ id: "STORAGE_DYNAMODB_STORE_GET_WORKFLOW_RUN_BY_ID_FAILED",
1412
+ domain: ErrorDomain.STORAGE,
1413
+ category: ErrorCategory.THIRD_PARTY,
1414
+ details: { runId, workflowName: args?.workflowName || "" }
1415
+ },
1416
+ error
1417
+ );
1230
1418
  }
1231
1419
  }
1232
1420
  // Helper function to format workflow run
@@ -1304,18 +1492,46 @@ var DynamoDBStore = class extends MastraStorage {
1304
1492
  }
1305
1493
  });
1306
1494
  } catch (error) {
1307
- this.logger.error("Failed to get evals by agent name", { agentName, type, error });
1308
- throw error;
1495
+ throw new MastraError(
1496
+ {
1497
+ id: "STORAGE_DYNAMODB_STORE_GET_EVALS_BY_AGENT_NAME_FAILED",
1498
+ domain: ErrorDomain.STORAGE,
1499
+ category: ErrorCategory.THIRD_PARTY,
1500
+ details: { agentName }
1501
+ },
1502
+ error
1503
+ );
1309
1504
  }
1310
1505
  }
1311
1506
  async getTracesPaginated(_args) {
1312
- throw new Error("Method not implemented.");
1507
+ throw new MastraError(
1508
+ {
1509
+ id: "STORAGE_DYNAMODB_STORE_GET_TRACES_PAGINATED_FAILED",
1510
+ domain: ErrorDomain.STORAGE,
1511
+ category: ErrorCategory.THIRD_PARTY
1512
+ },
1513
+ new Error("Method not implemented.")
1514
+ );
1313
1515
  }
1314
1516
  async getThreadsByResourceIdPaginated(_args) {
1315
- throw new Error("Method not implemented.");
1517
+ throw new MastraError(
1518
+ {
1519
+ id: "STORAGE_DYNAMODB_STORE_GET_THREADS_BY_RESOURCE_ID_PAGINATED_FAILED",
1520
+ domain: ErrorDomain.STORAGE,
1521
+ category: ErrorCategory.THIRD_PARTY
1522
+ },
1523
+ new Error("Method not implemented.")
1524
+ );
1316
1525
  }
1317
1526
  async getMessagesPaginated(_args) {
1318
- throw new Error("Method not implemented.");
1527
+ throw new MastraError(
1528
+ {
1529
+ id: "STORAGE_DYNAMODB_STORE_GET_MESSAGES_PAGINATED_FAILED",
1530
+ domain: ErrorDomain.STORAGE,
1531
+ category: ErrorCategory.THIRD_PARTY
1532
+ },
1533
+ new Error("Method not implemented.")
1534
+ );
1319
1535
  }
1320
1536
  /**
1321
1537
  * Closes the DynamoDB client connection and cleans up resources.
@@ -1327,8 +1543,14 @@ var DynamoDBStore = class extends MastraStorage {
1327
1543
  this.client.destroy();
1328
1544
  this.logger.debug("DynamoDB client closed successfully for store:", { name: this.name });
1329
1545
  } catch (error) {
1330
- this.logger.error("Error closing DynamoDB client for store:", { name: this.name, error });
1331
- throw error;
1546
+ throw new MastraError(
1547
+ {
1548
+ id: "STORAGE_DYNAMODB_STORE_CLOSE_FAILED",
1549
+ domain: ErrorDomain.STORAGE,
1550
+ category: ErrorCategory.THIRD_PARTY
1551
+ },
1552
+ error
1553
+ );
1332
1554
  }
1333
1555
  }
1334
1556
  async updateMessages(_args) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/dynamodb",
3
- "version": "0.11.1-alpha.0",
3
+ "version": "0.11.1-alpha.2",
4
4
  "description": "DynamoDB storage adapter for Mastra",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -36,12 +36,12 @@
36
36
  "@vitest/coverage-v8": "3.2.3",
37
37
  "@vitest/ui": "3.2.3",
38
38
  "axios": "^1.10.0",
39
- "eslint": "^9.28.0",
39
+ "eslint": "^9.29.0",
40
40
  "tsup": "^8.5.0",
41
41
  "typescript": "^5.8.3",
42
42
  "vitest": "^3.2.3",
43
43
  "@internal/lint": "0.0.13",
44
- "@mastra/core": "0.10.7-alpha.0",
44
+ "@mastra/core": "0.10.7-alpha.2",
45
45
  "@internal/storage-test-utils": "0.0.9"
46
46
  },
47
47
  "scripts": {
@@ -11,6 +11,7 @@ import {
11
11
  waitUntilTableExists,
12
12
  waitUntilTableNotExists,
13
13
  } from '@aws-sdk/client-dynamodb';
14
+ import { createSampleMessageV2, createSampleThread } from '@internal/storage-test-utils';
14
15
  import type { MastraMessageV1, StorageThreadType, WorkflowRun, WorkflowRunState } from '@mastra/core';
15
16
  import type { MastraMessageV2 } from '@mastra/core/agent';
16
17
  import { TABLE_EVALS, TABLE_THREADS, TABLE_WORKFLOW_SNAPSHOT } from '@mastra/core/storage';
@@ -555,6 +556,82 @@ describe('DynamoDBStore Integration Tests', () => {
555
556
  expect(retrieved[0]?.content).toBe('Large Message 0');
556
557
  expect(retrieved[29]?.content).toBe('Large Message 29');
557
558
  });
559
+
560
+ test('should upsert messages: duplicate id+threadId results in update, not duplicate row', async () => {
561
+ const thread = await createSampleThread();
562
+ await store.saveThread({ thread });
563
+ const baseMessage = createSampleMessageV2({
564
+ threadId: thread.id,
565
+ createdAt: new Date(),
566
+ content: { content: 'Original' },
567
+ resourceId: thread.resourceId,
568
+ });
569
+
570
+ // Insert the message for the first time
571
+ await store.saveMessages({ messages: [baseMessage], format: 'v2' });
572
+
573
+ // // Insert again with the same id and threadId but different content
574
+ const updatedMessage = {
575
+ ...createSampleMessageV2({
576
+ threadId: thread.id,
577
+ createdAt: new Date(),
578
+ content: { content: 'Updated' },
579
+ resourceId: thread.resourceId,
580
+ }),
581
+ id: baseMessage.id,
582
+ };
583
+
584
+ await store.saveMessages({ messages: [updatedMessage], format: 'v2' });
585
+
586
+ // Retrieve messages for the thread
587
+ const retrievedMessages = await store.getMessages({ threadId: thread.id, format: 'v2' });
588
+
589
+ // Only one message should exist for that id+threadId
590
+ expect(retrievedMessages.filter(m => m.id === baseMessage.id)).toHaveLength(1);
591
+
592
+ // The content should be the updated one
593
+ expect(retrievedMessages.find(m => m.id === baseMessage.id)?.content.content).toBe('Updated');
594
+ });
595
+
596
+ test('should upsert messages: duplicate id and different threadid', async () => {
597
+ const thread1 = await createSampleThread();
598
+ const thread2 = await createSampleThread();
599
+ await store.saveThread({ thread: thread1 });
600
+ await store.saveThread({ thread: thread2 });
601
+
602
+ const message = createSampleMessageV2({
603
+ threadId: thread1.id,
604
+ createdAt: new Date(),
605
+ content: { content: 'Thread1 Content' },
606
+ resourceId: thread1.resourceId,
607
+ });
608
+
609
+ // Insert message into thread1
610
+ await store.saveMessages({ messages: [message], format: 'v2' });
611
+
612
+ // Attempt to insert a message with the same id but different threadId
613
+ const conflictingMessage = {
614
+ ...createSampleMessageV2({
615
+ threadId: thread2.id, // different thread
616
+ content: { content: 'Thread2 Content' },
617
+ resourceId: thread2.resourceId,
618
+ }),
619
+ id: message.id,
620
+ };
621
+
622
+ // Save should save the message to the new thread
623
+ await store.saveMessages({ messages: [conflictingMessage], format: 'v2' });
624
+
625
+ // Retrieve messages for both threads
626
+ const thread1Messages = await store.getMessages({ threadId: thread1.id, format: 'v2' });
627
+ const thread2Messages = await store.getMessages({ threadId: thread2.id, format: 'v2' });
628
+
629
+ // Thread 1 should NOT have the message with that id
630
+ expect(thread1Messages.find(m => m.id === message.id)).toBeUndefined();
631
+
632
+ // Thread 2 should have the message with that id
633
+ expect(thread2Messages.find(m => m.id === message.id)?.content.content).toBe('Thread2 Content');
634
+ });
558
635
  });
559
636
 
560
637
  describe('Single-Table Design', () => {