@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.
@@ -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.2",
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.9",
44
- "@mastra/core": "0.10.2"
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' } });
@@ -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 to each record
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;