@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/_tsup-dts-rollup.d.cts +10 -7
- package/dist/_tsup-dts-rollup.d.ts +10 -7
- package/dist/index.cjs +314 -92
- package/dist/index.js +290 -68
- package/package.json +3 -3
- package/src/storage/index.test.ts +77 -0
- package/src/storage/index.ts +308 -75
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
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
735
|
-
|
|
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({
|
|
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
|
|
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
|
-
|
|
752
|
-
|
|
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({
|
|
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
|
|
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
|
-
|
|
784
|
-
|
|
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({
|
|
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
|
|
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
|
-
|
|
806
|
-
|
|
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
|
-
|
|
828
|
-
|
|
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
|
-
|
|
848
|
-
|
|
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
|
-
|
|
875
|
-
|
|
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
|
-
|
|
908
|
-
|
|
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
|
-
|
|
917
|
-
|
|
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
|
-
|
|
930
|
-
|
|
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
|
-
|
|
947
|
-
|
|
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.
|
|
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
|
-
|
|
1008
|
-
|
|
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
|
-
|
|
1055
|
-
|
|
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
|
-
|
|
1072
|
-
|
|
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
|
-
|
|
1099
|
-
|
|
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
|
-
|
|
1120
|
-
|
|
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
|
-
|
|
1182
|
-
|
|
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
|
-
|
|
1229
|
-
|
|
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
|
-
|
|
1308
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
1331
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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', () => {
|