@mastra/dynamodb 0.10.2 → 0.10.3
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 +5 -0
- package/dist/_tsup-dts-rollup.d.ts +5 -0
- package/dist/index.cjs +19 -2
- package/dist/index.js +19 -2
- package/package.json +3 -3
- package/src/storage/index.test.ts +91 -0
- package/src/storage/index.ts +27 -4
|
@@ -70,6 +70,11 @@ declare class DynamoDBStore extends MastraStorage {
|
|
|
70
70
|
* Handles resetting the stored promise on failure to allow retries.
|
|
71
71
|
*/
|
|
72
72
|
private _performInitializationAndStore;
|
|
73
|
+
/**
|
|
74
|
+
* Pre-processes a record to ensure Date objects are converted to ISO strings
|
|
75
|
+
* This is necessary because ElectroDB validation happens before setters are applied
|
|
76
|
+
*/
|
|
77
|
+
private preprocessRecord;
|
|
73
78
|
/**
|
|
74
79
|
* Clear all items from a logical "table" (entity type)
|
|
75
80
|
*/
|
|
@@ -70,6 +70,11 @@ declare class DynamoDBStore extends MastraStorage {
|
|
|
70
70
|
* Handles resetting the stored promise on failure to allow retries.
|
|
71
71
|
*/
|
|
72
72
|
private _performInitializationAndStore;
|
|
73
|
+
/**
|
|
74
|
+
* Pre-processes a record to ensure Date objects are converted to ISO strings
|
|
75
|
+
* This is necessary because ElectroDB validation happens before setters are applied
|
|
76
|
+
*/
|
|
77
|
+
private preprocessRecord;
|
|
73
78
|
/**
|
|
74
79
|
* Clear all items from a logical "table" (entity type)
|
|
75
80
|
*/
|
package/dist/index.cjs
CHANGED
|
@@ -659,6 +659,23 @@ var DynamoDBStore = class extends storage.MastraStorage {
|
|
|
659
659
|
throw err;
|
|
660
660
|
});
|
|
661
661
|
}
|
|
662
|
+
/**
|
|
663
|
+
* Pre-processes a record to ensure Date objects are converted to ISO strings
|
|
664
|
+
* This is necessary because ElectroDB validation happens before setters are applied
|
|
665
|
+
*/
|
|
666
|
+
preprocessRecord(record) {
|
|
667
|
+
const processed = { ...record };
|
|
668
|
+
if (processed.createdAt instanceof Date) {
|
|
669
|
+
processed.createdAt = processed.createdAt.toISOString();
|
|
670
|
+
}
|
|
671
|
+
if (processed.updatedAt instanceof Date) {
|
|
672
|
+
processed.updatedAt = processed.updatedAt.toISOString();
|
|
673
|
+
}
|
|
674
|
+
if (processed.created_at instanceof Date) {
|
|
675
|
+
processed.created_at = processed.created_at.toISOString();
|
|
676
|
+
}
|
|
677
|
+
return processed;
|
|
678
|
+
}
|
|
662
679
|
/**
|
|
663
680
|
* Clear all items from a logical "table" (entity type)
|
|
664
681
|
*/
|
|
@@ -728,7 +745,7 @@ var DynamoDBStore = class extends storage.MastraStorage {
|
|
|
728
745
|
throw new Error(`No entity defined for ${tableName}`);
|
|
729
746
|
}
|
|
730
747
|
try {
|
|
731
|
-
const dataToSave = { entity: entityName, ...record };
|
|
748
|
+
const dataToSave = { entity: entityName, ...this.preprocessRecord(record) };
|
|
732
749
|
await this.service.entities[entityName].create(dataToSave).go();
|
|
733
750
|
} catch (error) {
|
|
734
751
|
this.logger.error("Failed to insert record", { tableName, error });
|
|
@@ -744,7 +761,7 @@ var DynamoDBStore = class extends storage.MastraStorage {
|
|
|
744
761
|
if (!entityName || !this.service.entities[entityName]) {
|
|
745
762
|
throw new Error(`No entity defined for ${tableName}`);
|
|
746
763
|
}
|
|
747
|
-
const recordsToSave = records.map((rec) => ({ entity: entityName, ...rec }));
|
|
764
|
+
const recordsToSave = records.map((rec) => ({ entity: entityName, ...this.preprocessRecord(rec) }));
|
|
748
765
|
const batchSize = 25;
|
|
749
766
|
const batches = [];
|
|
750
767
|
for (let i = 0; i < recordsToSave.length; i += batchSize) {
|
package/dist/index.js
CHANGED
|
@@ -657,6 +657,23 @@ var DynamoDBStore = class extends MastraStorage {
|
|
|
657
657
|
throw err;
|
|
658
658
|
});
|
|
659
659
|
}
|
|
660
|
+
/**
|
|
661
|
+
* Pre-processes a record to ensure Date objects are converted to ISO strings
|
|
662
|
+
* This is necessary because ElectroDB validation happens before setters are applied
|
|
663
|
+
*/
|
|
664
|
+
preprocessRecord(record) {
|
|
665
|
+
const processed = { ...record };
|
|
666
|
+
if (processed.createdAt instanceof Date) {
|
|
667
|
+
processed.createdAt = processed.createdAt.toISOString();
|
|
668
|
+
}
|
|
669
|
+
if (processed.updatedAt instanceof Date) {
|
|
670
|
+
processed.updatedAt = processed.updatedAt.toISOString();
|
|
671
|
+
}
|
|
672
|
+
if (processed.created_at instanceof Date) {
|
|
673
|
+
processed.created_at = processed.created_at.toISOString();
|
|
674
|
+
}
|
|
675
|
+
return processed;
|
|
676
|
+
}
|
|
660
677
|
/**
|
|
661
678
|
* Clear all items from a logical "table" (entity type)
|
|
662
679
|
*/
|
|
@@ -726,7 +743,7 @@ var DynamoDBStore = class extends MastraStorage {
|
|
|
726
743
|
throw new Error(`No entity defined for ${tableName}`);
|
|
727
744
|
}
|
|
728
745
|
try {
|
|
729
|
-
const dataToSave = { entity: entityName, ...record };
|
|
746
|
+
const dataToSave = { entity: entityName, ...this.preprocessRecord(record) };
|
|
730
747
|
await this.service.entities[entityName].create(dataToSave).go();
|
|
731
748
|
} catch (error) {
|
|
732
749
|
this.logger.error("Failed to insert record", { tableName, error });
|
|
@@ -742,7 +759,7 @@ var DynamoDBStore = class extends MastraStorage {
|
|
|
742
759
|
if (!entityName || !this.service.entities[entityName]) {
|
|
743
760
|
throw new Error(`No entity defined for ${tableName}`);
|
|
744
761
|
}
|
|
745
|
-
const recordsToSave = records.map((rec) => ({ entity: entityName, ...rec }));
|
|
762
|
+
const recordsToSave = records.map((rec) => ({ entity: entityName, ...this.preprocessRecord(rec) }));
|
|
746
763
|
const batchSize = 25;
|
|
747
764
|
const batches = [];
|
|
748
765
|
for (let i = 0; i < recordsToSave.length; i += batchSize) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/dynamodb",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.3",
|
|
4
4
|
"description": "DynamoDB storage adapter for Mastra",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
"tsup": "^8.4.0",
|
|
41
41
|
"typescript": "^5.8.2",
|
|
42
42
|
"vitest": "^3.0.9",
|
|
43
|
-
"@internal/lint": "0.0.
|
|
44
|
-
"@mastra/core": "0.10.
|
|
43
|
+
"@internal/lint": "0.0.10",
|
|
44
|
+
"@mastra/core": "0.10.3"
|
|
45
45
|
},
|
|
46
46
|
"scripts": {
|
|
47
47
|
"build": "tsup src/index.ts --format esm,cjs --experimental-dts --clean --treeshake=smallest --splitting",
|
|
@@ -644,6 +644,37 @@ describe('DynamoDBStore Integration Tests', () => {
|
|
|
644
644
|
expect(allTraces.length).toBe(3);
|
|
645
645
|
});
|
|
646
646
|
|
|
647
|
+
test('should handle Date objects for createdAt/updatedAt fields in batchTraceInsert', async () => {
|
|
648
|
+
// This test specifically verifies the bug from the issue where Date objects
|
|
649
|
+
// were passed instead of ISO strings and ElectroDB validation failed
|
|
650
|
+
const now = new Date();
|
|
651
|
+
const traceWithDateObjects = {
|
|
652
|
+
id: `trace-${randomUUID()}`,
|
|
653
|
+
parentSpanId: `span-${randomUUID()}`,
|
|
654
|
+
traceId: `traceid-${randomUUID()}`,
|
|
655
|
+
name: 'test-trace-with-dates',
|
|
656
|
+
scope: 'default-tracer',
|
|
657
|
+
kind: 1,
|
|
658
|
+
startTime: now.getTime(),
|
|
659
|
+
endTime: now.getTime() + 100,
|
|
660
|
+
status: JSON.stringify({ code: 0 }),
|
|
661
|
+
attributes: JSON.stringify({ key: 'value' }),
|
|
662
|
+
events: JSON.stringify([]),
|
|
663
|
+
links: JSON.stringify([]),
|
|
664
|
+
// These are Date objects, not ISO strings - this should be handled by ElectroDB attribute setters
|
|
665
|
+
createdAt: now,
|
|
666
|
+
updatedAt: now,
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
// This should not throw a validation error due to Date object type
|
|
670
|
+
await expect(store.batchTraceInsert({ records: [traceWithDateObjects] })).resolves.not.toThrow();
|
|
671
|
+
|
|
672
|
+
// Verify the trace was saved correctly
|
|
673
|
+
const allTraces = await store.getTraces({ name: 'test-trace-with-dates', page: 1, perPage: 10 });
|
|
674
|
+
expect(allTraces.length).toBe(1);
|
|
675
|
+
expect(allTraces[0].name).toBe('test-trace-with-dates');
|
|
676
|
+
});
|
|
677
|
+
|
|
647
678
|
test('should retrieve traces filtered by name using GSI', async () => {
|
|
648
679
|
const trace1 = sampleTrace('trace-filter-name', 'scope-X');
|
|
649
680
|
const trace2 = sampleTrace('trace-filter-name', 'scope-Y', Date.now() + 10);
|
|
@@ -725,6 +756,40 @@ describe('DynamoDBStore Integration Tests', () => {
|
|
|
725
756
|
};
|
|
726
757
|
};
|
|
727
758
|
|
|
759
|
+
test('should handle Date objects for createdAt/updatedAt fields in eval batchInsert', async () => {
|
|
760
|
+
// Test that eval entity properly handles Date objects in createdAt/updatedAt fields
|
|
761
|
+
const now = new Date();
|
|
762
|
+
const evalWithDateObjects = {
|
|
763
|
+
entity: 'eval',
|
|
764
|
+
agent_name: 'test-agent-dates',
|
|
765
|
+
input: 'Test input',
|
|
766
|
+
output: 'Test output',
|
|
767
|
+
result: JSON.stringify({ score: 0.95 }),
|
|
768
|
+
metric_name: 'test-metric',
|
|
769
|
+
instructions: 'Test instructions',
|
|
770
|
+
global_run_id: `global-${randomUUID()}`,
|
|
771
|
+
run_id: `run-${randomUUID()}`,
|
|
772
|
+
created_at: now, // Date object instead of ISO string
|
|
773
|
+
// These are Date objects, not ISO strings - should be handled by ElectroDB attribute setters
|
|
774
|
+
createdAt: now,
|
|
775
|
+
updatedAt: now,
|
|
776
|
+
metadata: JSON.stringify({ test: 'meta' }),
|
|
777
|
+
};
|
|
778
|
+
|
|
779
|
+
// This should not throw a validation error due to Date object type
|
|
780
|
+
await expect(
|
|
781
|
+
store.batchInsert({
|
|
782
|
+
tableName: TABLE_EVALS,
|
|
783
|
+
records: [evalWithDateObjects],
|
|
784
|
+
}),
|
|
785
|
+
).resolves.not.toThrow();
|
|
786
|
+
|
|
787
|
+
// Verify the eval was saved correctly
|
|
788
|
+
const evals = await store.getEvalsByAgentName('test-agent-dates');
|
|
789
|
+
expect(evals.length).toBe(1);
|
|
790
|
+
expect(evals[0].agentName).toBe('test-agent-dates');
|
|
791
|
+
});
|
|
792
|
+
|
|
728
793
|
test('should retrieve evals by agent name using GSI and filter by type', async () => {
|
|
729
794
|
const agent1 = 'eval-agent-1';
|
|
730
795
|
const agent2 = 'eval-agent-2';
|
|
@@ -1067,6 +1132,32 @@ describe('DynamoDBStore Integration Tests', () => {
|
|
|
1067
1132
|
}
|
|
1068
1133
|
});
|
|
1069
1134
|
|
|
1135
|
+
test('insert() should handle Date objects for createdAt/updatedAt fields', async () => {
|
|
1136
|
+
// Test that individual insert method properly handles Date objects in date fields
|
|
1137
|
+
const now = new Date();
|
|
1138
|
+
const recordWithDates = {
|
|
1139
|
+
id: `thread-${randomUUID()}`,
|
|
1140
|
+
resourceId: `resource-${randomUUID()}`,
|
|
1141
|
+
title: 'Thread with Date Objects',
|
|
1142
|
+
// These are Date objects, not ISO strings - should be handled by preprocessing
|
|
1143
|
+
createdAt: now,
|
|
1144
|
+
updatedAt: now,
|
|
1145
|
+
metadata: JSON.stringify({ test: 'with-dates' }),
|
|
1146
|
+
};
|
|
1147
|
+
|
|
1148
|
+
// This should not throw a validation error due to Date object type
|
|
1149
|
+
await expect(genericStore.insert({ tableName: TABLE_THREADS, record: recordWithDates })).resolves.not.toThrow();
|
|
1150
|
+
|
|
1151
|
+
// Verify the record was saved correctly
|
|
1152
|
+
const loaded = await genericStore.load<StorageThreadType>({
|
|
1153
|
+
tableName: TABLE_THREADS,
|
|
1154
|
+
keys: { id: recordWithDates.id },
|
|
1155
|
+
});
|
|
1156
|
+
expect(loaded).not.toBeNull();
|
|
1157
|
+
expect(loaded?.id).toBe(recordWithDates.id);
|
|
1158
|
+
expect(loaded?.title).toBe('Thread with Date Objects');
|
|
1159
|
+
});
|
|
1160
|
+
|
|
1070
1161
|
test('load() should return null for non-existent record', async () => {
|
|
1071
1162
|
// Use the genericStore instance
|
|
1072
1163
|
const loaded = await genericStore.load({ tableName: TABLE_THREADS, keys: { id: 'non-existent-generic' } });
|
package/src/storage/index.ts
CHANGED
|
@@ -180,6 +180,29 @@ export class DynamoDBStore extends MastraStorage {
|
|
|
180
180
|
});
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
+
/**
|
|
184
|
+
* Pre-processes a record to ensure Date objects are converted to ISO strings
|
|
185
|
+
* This is necessary because ElectroDB validation happens before setters are applied
|
|
186
|
+
*/
|
|
187
|
+
private preprocessRecord(record: Record<string, any>): Record<string, any> {
|
|
188
|
+
const processed = { ...record };
|
|
189
|
+
|
|
190
|
+
// Convert Date objects to ISO strings for date fields
|
|
191
|
+
// This prevents ElectroDB validation errors that occur when Date objects are passed
|
|
192
|
+
// to string-typed attributes, even when the attribute has a setter that converts dates
|
|
193
|
+
if (processed.createdAt instanceof Date) {
|
|
194
|
+
processed.createdAt = processed.createdAt.toISOString();
|
|
195
|
+
}
|
|
196
|
+
if (processed.updatedAt instanceof Date) {
|
|
197
|
+
processed.updatedAt = processed.updatedAt.toISOString();
|
|
198
|
+
}
|
|
199
|
+
if (processed.created_at instanceof Date) {
|
|
200
|
+
processed.created_at = processed.created_at.toISOString();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return processed;
|
|
204
|
+
}
|
|
205
|
+
|
|
183
206
|
/**
|
|
184
207
|
* Clear all items from a logical "table" (entity type)
|
|
185
208
|
*/
|
|
@@ -275,8 +298,8 @@ export class DynamoDBStore extends MastraStorage {
|
|
|
275
298
|
}
|
|
276
299
|
|
|
277
300
|
try {
|
|
278
|
-
// Add the entity type to the record before creating
|
|
279
|
-
const dataToSave = { entity: entityName, ...record };
|
|
301
|
+
// Add the entity type to the record and preprocess before creating
|
|
302
|
+
const dataToSave = { entity: entityName, ...this.preprocessRecord(record) };
|
|
280
303
|
await this.service.entities[entityName].create(dataToSave).go();
|
|
281
304
|
} catch (error) {
|
|
282
305
|
this.logger.error('Failed to insert record', { tableName, error });
|
|
@@ -295,8 +318,8 @@ export class DynamoDBStore extends MastraStorage {
|
|
|
295
318
|
throw new Error(`No entity defined for ${tableName}`);
|
|
296
319
|
}
|
|
297
320
|
|
|
298
|
-
// Add entity type
|
|
299
|
-
const recordsToSave = records.map(rec => ({ entity: entityName, ...rec }));
|
|
321
|
+
// Add entity type and preprocess each record
|
|
322
|
+
const recordsToSave = records.map(rec => ({ entity: entityName, ...this.preprocessRecord(rec) }));
|
|
300
323
|
|
|
301
324
|
// ElectroDB has batch limits of 25 items, so we need to chunk
|
|
302
325
|
const batchSize = 25;
|