@mastra/dynamodb 0.11.1-alpha.1 → 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.
@@ -12,6 +12,7 @@ import type { StorageGetMessagesArg } from '@mastra/core/storage';
12
12
  import type { StorageGetTracesArg } from '@mastra/core/storage';
13
13
  import type { StorageThreadType } from '@mastra/core/memory';
14
14
  import type { TABLE_NAMES } from '@mastra/core/storage';
15
+ import type { TABLE_RESOURCES } from '@mastra/core/storage';
15
16
  import type { Trace } from '@mastra/core/telemetry';
16
17
  import type { WorkflowRun } from '@mastra/core/storage';
17
18
  import type { WorkflowRuns } from '@mastra/core/storage';
@@ -89,27 +90,27 @@ declare class DynamoDBStore extends MastraStorage {
89
90
  * Clear all items from a logical "table" (entity type)
90
91
  */
91
92
  clearTable({ tableName }: {
92
- tableName: TABLE_NAMES;
93
+ tableName: SUPPORTED_TABLE_NAMES;
93
94
  }): Promise<void>;
94
95
  /**
95
96
  * Insert a record into the specified "table" (entity)
96
97
  */
97
- insert({ tableName, record }: {
98
- tableName: TABLE_NAMES;
98
+ insert({ tableName, record, }: {
99
+ tableName: SUPPORTED_TABLE_NAMES;
99
100
  record: Record<string, any>;
100
101
  }): Promise<void>;
101
102
  /**
102
103
  * Insert multiple records as a batch
103
104
  */
104
- batchInsert({ tableName, records }: {
105
- tableName: TABLE_NAMES;
105
+ batchInsert({ tableName, records, }: {
106
+ tableName: SUPPORTED_TABLE_NAMES;
106
107
  records: Record<string, any>[];
107
108
  }): Promise<void>;
108
109
  /**
109
110
  * Load a record by its keys
110
111
  */
111
- load<R>({ tableName, keys }: {
112
- tableName: TABLE_NAMES;
112
+ load<R>({ tableName, keys, }: {
113
+ tableName: SUPPORTED_TABLE_NAMES;
113
114
  keys: Record<string, string>;
114
115
  }): Promise<R | null>;
115
116
  getThreadById({ threadId }: {
@@ -887,6 +888,8 @@ export declare const messageEntity: Entity<string, string, string, {
887
888
  };
888
889
  }>;
889
890
 
891
+ declare type SUPPORTED_TABLE_NAMES = Exclude<TABLE_NAMES, typeof TABLE_RESOURCES>;
892
+
890
893
  export declare const threadEntity: Entity<string, string, string, {
891
894
  model: {
892
895
  entity: string;
@@ -12,6 +12,7 @@ import type { StorageGetMessagesArg } from '@mastra/core/storage';
12
12
  import type { StorageGetTracesArg } from '@mastra/core/storage';
13
13
  import type { StorageThreadType } from '@mastra/core/memory';
14
14
  import type { TABLE_NAMES } from '@mastra/core/storage';
15
+ import type { TABLE_RESOURCES } from '@mastra/core/storage';
15
16
  import type { Trace } from '@mastra/core/telemetry';
16
17
  import type { WorkflowRun } from '@mastra/core/storage';
17
18
  import type { WorkflowRuns } from '@mastra/core/storage';
@@ -89,27 +90,27 @@ declare class DynamoDBStore extends MastraStorage {
89
90
  * Clear all items from a logical "table" (entity type)
90
91
  */
91
92
  clearTable({ tableName }: {
92
- tableName: TABLE_NAMES;
93
+ tableName: SUPPORTED_TABLE_NAMES;
93
94
  }): Promise<void>;
94
95
  /**
95
96
  * Insert a record into the specified "table" (entity)
96
97
  */
97
- insert({ tableName, record }: {
98
- tableName: TABLE_NAMES;
98
+ insert({ tableName, record, }: {
99
+ tableName: SUPPORTED_TABLE_NAMES;
99
100
  record: Record<string, any>;
100
101
  }): Promise<void>;
101
102
  /**
102
103
  * Insert multiple records as a batch
103
104
  */
104
- batchInsert({ tableName, records }: {
105
- tableName: TABLE_NAMES;
105
+ batchInsert({ tableName, records, }: {
106
+ tableName: SUPPORTED_TABLE_NAMES;
106
107
  records: Record<string, any>[];
107
108
  }): Promise<void>;
108
109
  /**
109
110
  * Load a record by its keys
110
111
  */
111
- load<R>({ tableName, keys }: {
112
- tableName: TABLE_NAMES;
112
+ load<R>({ tableName, keys, }: {
113
+ tableName: SUPPORTED_TABLE_NAMES;
113
114
  keys: Record<string, string>;
114
115
  }): Promise<R | null>;
115
116
  getThreadById({ threadId }: {
@@ -887,6 +888,8 @@ export declare const messageEntity: Entity<string, string, string, {
887
888
  };
888
889
  }>;
889
890
 
891
+ declare type SUPPORTED_TABLE_NAMES = Exclude<TABLE_NAMES, typeof TABLE_RESOURCES>;
892
+
890
893
  export declare const threadEntity: Entity<string, string, string, {
891
894
  model: {
892
895
  entity: string;
package/dist/index.cjs CHANGED
@@ -789,7 +789,10 @@ var DynamoDBStore = class extends storage.MastraStorage {
789
789
  /**
790
790
  * Insert a record into the specified "table" (entity)
791
791
  */
792
- async insert({ tableName, record }) {
792
+ async insert({
793
+ tableName,
794
+ record
795
+ }) {
793
796
  this.logger.debug("DynamoDB insert called", { tableName });
794
797
  const entityName = this.getEntityNameForTable(tableName);
795
798
  if (!entityName || !this.service.entities[entityName]) {
@@ -819,7 +822,10 @@ var DynamoDBStore = class extends storage.MastraStorage {
819
822
  /**
820
823
  * Insert multiple records as a batch
821
824
  */
822
- async batchInsert({ tableName, records }) {
825
+ async batchInsert({
826
+ tableName,
827
+ records
828
+ }) {
823
829
  this.logger.debug("DynamoDB batchInsert called", { tableName, count: records.length });
824
830
  const entityName = this.getEntityNameForTable(tableName);
825
831
  if (!entityName || !this.service.entities[entityName]) {
@@ -864,7 +870,10 @@ var DynamoDBStore = class extends storage.MastraStorage {
864
870
  /**
865
871
  * Load a record by its keys
866
872
  */
867
- async load({ tableName, keys }) {
873
+ async load({
874
+ tableName,
875
+ keys
876
+ }) {
868
877
  this.logger.debug("DynamoDB load called", { tableName, keys });
869
878
  const entityName = this.getEntityNameForTable(tableName);
870
879
  if (!entityName || !this.service.entities[entityName]) {
@@ -1125,7 +1134,7 @@ var DynamoDBStore = class extends storage.MastraStorage {
1125
1134
  this.logger.error("Missing entity property in message data for create", { messageData });
1126
1135
  throw new Error("Internal error: Missing entity property during saveMessages");
1127
1136
  }
1128
- await this.service.entities.message.create(messageData).go();
1137
+ await this.service.entities.message.put(messageData).go();
1129
1138
  }
1130
1139
  }),
1131
1140
  // Update thread's updatedAt timestamp
package/dist/index.js CHANGED
@@ -787,7 +787,10 @@ var DynamoDBStore = class extends MastraStorage {
787
787
  /**
788
788
  * Insert a record into the specified "table" (entity)
789
789
  */
790
- async insert({ tableName, record }) {
790
+ async insert({
791
+ tableName,
792
+ record
793
+ }) {
791
794
  this.logger.debug("DynamoDB insert called", { tableName });
792
795
  const entityName = this.getEntityNameForTable(tableName);
793
796
  if (!entityName || !this.service.entities[entityName]) {
@@ -817,7 +820,10 @@ var DynamoDBStore = class extends MastraStorage {
817
820
  /**
818
821
  * Insert multiple records as a batch
819
822
  */
820
- async batchInsert({ tableName, records }) {
823
+ async batchInsert({
824
+ tableName,
825
+ records
826
+ }) {
821
827
  this.logger.debug("DynamoDB batchInsert called", { tableName, count: records.length });
822
828
  const entityName = this.getEntityNameForTable(tableName);
823
829
  if (!entityName || !this.service.entities[entityName]) {
@@ -862,7 +868,10 @@ var DynamoDBStore = class extends MastraStorage {
862
868
  /**
863
869
  * Load a record by its keys
864
870
  */
865
- async load({ tableName, keys }) {
871
+ async load({
872
+ tableName,
873
+ keys
874
+ }) {
866
875
  this.logger.debug("DynamoDB load called", { tableName, keys });
867
876
  const entityName = this.getEntityNameForTable(tableName);
868
877
  if (!entityName || !this.service.entities[entityName]) {
@@ -1123,7 +1132,7 @@ var DynamoDBStore = class extends MastraStorage {
1123
1132
  this.logger.error("Missing entity property in message data for create", { messageData });
1124
1133
  throw new Error("Internal error: Missing entity property during saveMessages");
1125
1134
  }
1126
- await this.service.entities.message.create(messageData).go();
1135
+ await this.service.entities.message.put(messageData).go();
1127
1136
  }
1128
1137
  }),
1129
1138
  // Update thread's updatedAt timestamp
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/dynamodb",
3
- "version": "0.11.1-alpha.1",
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.1",
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', () => {
@@ -22,6 +22,7 @@ import type {
22
22
  StorageGetTracesArg,
23
23
  PaginationInfo,
24
24
  StorageColumn,
25
+ TABLE_RESOURCES,
25
26
  } from '@mastra/core/storage';
26
27
  import type { Trace } from '@mastra/core/telemetry';
27
28
  import type { WorkflowRunState } from '@mastra/core/workflows';
@@ -38,6 +39,8 @@ export interface DynamoDBStoreConfig {
38
39
  };
39
40
  }
40
41
 
42
+ type SUPPORTED_TABLE_NAMES = Exclude<TABLE_NAMES, typeof TABLE_RESOURCES>;
43
+
41
44
  // Define a type for our service that allows string indexing
42
45
  type MastraService = Service<Record<string, any>> & {
43
46
  [key: string]: any;
@@ -263,7 +266,7 @@ export class DynamoDBStore extends MastraStorage {
263
266
  /**
264
267
  * Clear all items from a logical "table" (entity type)
265
268
  */
266
- async clearTable({ tableName }: { tableName: TABLE_NAMES }): Promise<void> {
269
+ async clearTable({ tableName }: { tableName: SUPPORTED_TABLE_NAMES }): Promise<void> {
267
270
  this.logger.debug('DynamoDB clearTable called', { tableName });
268
271
 
269
272
  const entityName = this.getEntityNameForTable(tableName);
@@ -359,7 +362,13 @@ export class DynamoDBStore extends MastraStorage {
359
362
  /**
360
363
  * Insert a record into the specified "table" (entity)
361
364
  */
362
- async insert({ tableName, record }: { tableName: TABLE_NAMES; record: Record<string, any> }): Promise<void> {
365
+ async insert({
366
+ tableName,
367
+ record,
368
+ }: {
369
+ tableName: SUPPORTED_TABLE_NAMES;
370
+ record: Record<string, any>;
371
+ }): Promise<void> {
363
372
  this.logger.debug('DynamoDB insert called', { tableName });
364
373
 
365
374
  const entityName = this.getEntityNameForTable(tableName);
@@ -393,7 +402,13 @@ export class DynamoDBStore extends MastraStorage {
393
402
  /**
394
403
  * Insert multiple records as a batch
395
404
  */
396
- async batchInsert({ tableName, records }: { tableName: TABLE_NAMES; records: Record<string, any>[] }): Promise<void> {
405
+ async batchInsert({
406
+ tableName,
407
+ records,
408
+ }: {
409
+ tableName: SUPPORTED_TABLE_NAMES;
410
+ records: Record<string, any>[];
411
+ }): Promise<void> {
397
412
  this.logger.debug('DynamoDB batchInsert called', { tableName, count: records.length });
398
413
 
399
414
  const entityName = this.getEntityNameForTable(tableName);
@@ -449,7 +464,13 @@ export class DynamoDBStore extends MastraStorage {
449
464
  /**
450
465
  * Load a record by its keys
451
466
  */
452
- async load<R>({ tableName, keys }: { tableName: TABLE_NAMES; keys: Record<string, string> }): Promise<R | null> {
467
+ async load<R>({
468
+ tableName,
469
+ keys,
470
+ }: {
471
+ tableName: SUPPORTED_TABLE_NAMES;
472
+ keys: Record<string, string>;
473
+ }): Promise<R | null> {
453
474
  this.logger.debug('DynamoDB load called', { tableName, keys });
454
475
 
455
476
  const entityName = this.getEntityNameForTable(tableName);
@@ -797,7 +818,7 @@ export class DynamoDBStore extends MastraStorage {
797
818
  this.logger.error('Missing entity property in message data for create', { messageData });
798
819
  throw new Error('Internal error: Missing entity property during saveMessages');
799
820
  }
800
- await this.service.entities.message.create(messageData).go();
821
+ await this.service.entities.message.put(messageData).go();
801
822
  }
802
823
  }),
803
824
  // Update thread's updatedAt timestamp
@@ -1192,8 +1213,8 @@ export class DynamoDBStore extends MastraStorage {
1192
1213
  }
1193
1214
 
1194
1215
  // Helper methods for entity/table mapping
1195
- private getEntityNameForTable(tableName: TABLE_NAMES): string | null {
1196
- const mapping: Record<TABLE_NAMES, string> = {
1216
+ private getEntityNameForTable(tableName: SUPPORTED_TABLE_NAMES): string | null {
1217
+ const mapping: Record<SUPPORTED_TABLE_NAMES, string> = {
1197
1218
  [TABLE_THREADS]: 'thread',
1198
1219
  [TABLE_MESSAGES]: 'message',
1199
1220
  [TABLE_WORKFLOW_SNAPSHOT]: 'workflowSnapshot',