@mastra/dynamodb 0.11.1-alpha.1 → 0.12.0-alpha.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 +10 -7
- package/dist/_tsup-dts-rollup.d.ts +10 -7
- package/dist/index.cjs +13 -4
- package/dist/index.js +13 -4
- package/package.json +6 -6
- package/src/storage/index.test.ts +77 -0
- package/src/storage/index.ts +28 -7
|
@@ -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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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({
|
|
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({
|
|
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({
|
|
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.
|
|
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({
|
|
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({
|
|
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({
|
|
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.
|
|
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.
|
|
3
|
+
"version": "0.12.0-alpha.3",
|
|
4
4
|
"description": "DynamoDB storage adapter for Mastra",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"electrodb": "^3.4.3"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
|
-
"@mastra/core": ">=0.10.
|
|
31
|
+
"@mastra/core": ">=0.10.7-0 <0.11.0-0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@microsoft/api-extractor": "^7.52.8",
|
|
@@ -36,13 +36,13 @@
|
|
|
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
|
-
"@
|
|
44
|
-
"@
|
|
45
|
-
"@internal/
|
|
43
|
+
"@mastra/core": "0.10.7-alpha.3",
|
|
44
|
+
"@internal/storage-test-utils": "0.0.9",
|
|
45
|
+
"@internal/lint": "0.0.13"
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
48
|
"build": "tsup src/index.ts --format esm,cjs --experimental-dts --clean --treeshake=smallest --splitting",
|
|
@@ -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', () => {
|
package/src/storage/index.ts
CHANGED
|
@@ -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:
|
|
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({
|
|
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({
|
|
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>({
|
|
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.
|
|
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:
|
|
1196
|
-
const mapping: Record<
|
|
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',
|