@mastra/dynamodb 0.13.0 → 0.13.1
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 +998 -182
- package/dist/_tsup-dts-rollup.d.ts +998 -182
- package/dist/index.cjs +1979 -542
- package/dist/index.js +1980 -543
- package/package.json +5 -5
- package/src/entities/index.ts +5 -1
- package/src/entities/resource.ts +57 -0
- package/src/entities/score.ts +285 -0
- package/src/storage/domains/legacy-evals/index.ts +243 -0
- package/src/storage/domains/memory/index.ts +894 -0
- package/src/storage/domains/operations/index.ts +433 -0
- package/src/storage/domains/score/index.ts +285 -0
- package/src/storage/domains/traces/index.ts +286 -0
- package/src/storage/domains/workflows/index.ts +297 -0
- package/src/storage/index.test.ts +1346 -1409
- package/src/storage/index.ts +161 -1062
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { ErrorCategory, ErrorDomain, MastraError } from '@mastra/core/error';
|
|
2
|
+
import { WorkflowsStorage } from '@mastra/core/storage';
|
|
3
|
+
import type { WorkflowRun, WorkflowRuns } from '@mastra/core/storage';
|
|
4
|
+
import type { WorkflowRunState } from '@mastra/core/workflows';
|
|
5
|
+
import type { Service } from 'electrodb';
|
|
6
|
+
|
|
7
|
+
// Define the structure for workflow snapshot items retrieved from DynamoDB
|
|
8
|
+
interface WorkflowSnapshotDBItem {
|
|
9
|
+
entity: string; // Typically 'workflow_snapshot'
|
|
10
|
+
workflow_name: string;
|
|
11
|
+
run_id: string;
|
|
12
|
+
snapshot: WorkflowRunState; // Should be WorkflowRunState after ElectroDB get attribute processing
|
|
13
|
+
createdAt: string; // ISO Date string
|
|
14
|
+
updatedAt: string; // ISO Date string
|
|
15
|
+
resourceId?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function formatWorkflowRun(snapshotData: WorkflowSnapshotDBItem): WorkflowRun {
|
|
19
|
+
return {
|
|
20
|
+
workflowName: snapshotData.workflow_name,
|
|
21
|
+
runId: snapshotData.run_id,
|
|
22
|
+
snapshot: snapshotData.snapshot as WorkflowRunState,
|
|
23
|
+
createdAt: new Date(snapshotData.createdAt),
|
|
24
|
+
updatedAt: new Date(snapshotData.updatedAt),
|
|
25
|
+
resourceId: snapshotData.resourceId,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class WorkflowStorageDynamoDB extends WorkflowsStorage {
|
|
30
|
+
private service: Service<Record<string, any>>;
|
|
31
|
+
constructor({ service }: { service: Service<Record<string, any>> }) {
|
|
32
|
+
super();
|
|
33
|
+
|
|
34
|
+
this.service = service;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Workflow operations
|
|
38
|
+
async persistWorkflowSnapshot({
|
|
39
|
+
workflowName,
|
|
40
|
+
runId,
|
|
41
|
+
snapshot,
|
|
42
|
+
}: {
|
|
43
|
+
workflowName: string;
|
|
44
|
+
runId: string;
|
|
45
|
+
snapshot: WorkflowRunState;
|
|
46
|
+
}): Promise<void> {
|
|
47
|
+
this.logger.debug('Persisting workflow snapshot', { workflowName, runId });
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const resourceId = 'resourceId' in snapshot ? snapshot.resourceId : undefined;
|
|
51
|
+
const now = new Date().toISOString();
|
|
52
|
+
// Prepare data including the 'entity' type
|
|
53
|
+
const data = {
|
|
54
|
+
entity: 'workflow_snapshot', // Add entity type
|
|
55
|
+
workflow_name: workflowName,
|
|
56
|
+
run_id: runId,
|
|
57
|
+
snapshot: JSON.stringify(snapshot), // Stringify the snapshot object
|
|
58
|
+
createdAt: now,
|
|
59
|
+
updatedAt: now,
|
|
60
|
+
resourceId,
|
|
61
|
+
};
|
|
62
|
+
// Use upsert instead of create to handle both create and update cases
|
|
63
|
+
await this.service.entities.workflow_snapshot.upsert(data).go();
|
|
64
|
+
} catch (error) {
|
|
65
|
+
throw new MastraError(
|
|
66
|
+
{
|
|
67
|
+
id: 'STORAGE_DYNAMODB_STORE_PERSIST_WORKFLOW_SNAPSHOT_FAILED',
|
|
68
|
+
domain: ErrorDomain.STORAGE,
|
|
69
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
70
|
+
details: { workflowName, runId },
|
|
71
|
+
},
|
|
72
|
+
error,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async loadWorkflowSnapshot({
|
|
78
|
+
workflowName,
|
|
79
|
+
runId,
|
|
80
|
+
}: {
|
|
81
|
+
workflowName: string;
|
|
82
|
+
runId: string;
|
|
83
|
+
}): Promise<WorkflowRunState | null> {
|
|
84
|
+
this.logger.debug('Loading workflow snapshot', { workflowName, runId });
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
// Provide *all* composite key components for the primary index ('entity', 'workflow_name', 'run_id')
|
|
88
|
+
const result = await this.service.entities.workflow_snapshot
|
|
89
|
+
.get({
|
|
90
|
+
entity: 'workflow_snapshot', // Add entity type
|
|
91
|
+
workflow_name: workflowName,
|
|
92
|
+
run_id: runId,
|
|
93
|
+
})
|
|
94
|
+
.go();
|
|
95
|
+
|
|
96
|
+
if (!result.data?.snapshot) {
|
|
97
|
+
// Check snapshot exists
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Parse the snapshot string
|
|
102
|
+
return result.data.snapshot as WorkflowRunState;
|
|
103
|
+
} catch (error) {
|
|
104
|
+
throw new MastraError(
|
|
105
|
+
{
|
|
106
|
+
id: 'STORAGE_DYNAMODB_STORE_LOAD_WORKFLOW_SNAPSHOT_FAILED',
|
|
107
|
+
domain: ErrorDomain.STORAGE,
|
|
108
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
109
|
+
details: { workflowName, runId },
|
|
110
|
+
},
|
|
111
|
+
error,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async getWorkflowRuns(args?: {
|
|
117
|
+
workflowName?: string;
|
|
118
|
+
fromDate?: Date;
|
|
119
|
+
toDate?: Date;
|
|
120
|
+
limit?: number;
|
|
121
|
+
offset?: number;
|
|
122
|
+
resourceId?: string;
|
|
123
|
+
}): Promise<WorkflowRuns> {
|
|
124
|
+
this.logger.debug('Getting workflow runs', { args });
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
// Default values
|
|
128
|
+
const limit = args?.limit || 10;
|
|
129
|
+
const offset = args?.offset || 0;
|
|
130
|
+
|
|
131
|
+
let query;
|
|
132
|
+
|
|
133
|
+
if (args?.workflowName) {
|
|
134
|
+
// Query by workflow name using the primary index
|
|
135
|
+
// Provide *all* composite key components for the PK ('entity', 'workflow_name')
|
|
136
|
+
query = this.service.entities.workflow_snapshot.query.primary({
|
|
137
|
+
entity: 'workflow_snapshot', // Add entity type
|
|
138
|
+
workflow_name: args.workflowName,
|
|
139
|
+
});
|
|
140
|
+
} else {
|
|
141
|
+
// If no workflow name, we need to scan
|
|
142
|
+
// This is not ideal for production with large datasets
|
|
143
|
+
this.logger.warn('Performing a scan operation on workflow snapshots - consider using a more specific query');
|
|
144
|
+
query = this.service.entities.workflow_snapshot.scan; // Scan still uses the service entity
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const allMatchingSnapshots: WorkflowSnapshotDBItem[] = [];
|
|
148
|
+
let cursor: string | null = null;
|
|
149
|
+
const DYNAMODB_PAGE_SIZE = 100; // Sensible page size for fetching
|
|
150
|
+
|
|
151
|
+
do {
|
|
152
|
+
const pageResults: { data: WorkflowSnapshotDBItem[]; cursor: string | null } = await query.go({
|
|
153
|
+
limit: DYNAMODB_PAGE_SIZE,
|
|
154
|
+
cursor,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
if (pageResults.data && pageResults.data.length > 0) {
|
|
158
|
+
let pageFilteredData: WorkflowSnapshotDBItem[] = pageResults.data;
|
|
159
|
+
|
|
160
|
+
// Apply date filters if specified
|
|
161
|
+
if (args?.fromDate || args?.toDate) {
|
|
162
|
+
pageFilteredData = pageFilteredData.filter((snapshot: WorkflowSnapshotDBItem) => {
|
|
163
|
+
const createdAt = new Date(snapshot.createdAt);
|
|
164
|
+
if (args.fromDate && createdAt < args.fromDate) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
if (args.toDate && createdAt > args.toDate) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
return true;
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Filter by resourceId if specified
|
|
175
|
+
if (args?.resourceId) {
|
|
176
|
+
pageFilteredData = pageFilteredData.filter((snapshot: WorkflowSnapshotDBItem) => {
|
|
177
|
+
return snapshot.resourceId === args.resourceId;
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
allMatchingSnapshots.push(...pageFilteredData);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
cursor = pageResults.cursor;
|
|
184
|
+
} while (cursor);
|
|
185
|
+
|
|
186
|
+
if (!allMatchingSnapshots.length) {
|
|
187
|
+
return { runs: [], total: 0 };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Apply offset and limit to the accumulated filtered results
|
|
191
|
+
const total = allMatchingSnapshots.length;
|
|
192
|
+
const paginatedData = allMatchingSnapshots.slice(offset, offset + limit);
|
|
193
|
+
|
|
194
|
+
// Format and return the results
|
|
195
|
+
const runs = paginatedData.map((snapshot: WorkflowSnapshotDBItem) => formatWorkflowRun(snapshot));
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
runs,
|
|
199
|
+
total,
|
|
200
|
+
};
|
|
201
|
+
} catch (error) {
|
|
202
|
+
throw new MastraError(
|
|
203
|
+
{
|
|
204
|
+
id: 'STORAGE_DYNAMODB_STORE_GET_WORKFLOW_RUNS_FAILED',
|
|
205
|
+
domain: ErrorDomain.STORAGE,
|
|
206
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
207
|
+
details: { workflowName: args?.workflowName || '', resourceId: args?.resourceId || '' },
|
|
208
|
+
},
|
|
209
|
+
error,
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async getWorkflowRunById(args: { runId: string; workflowName?: string }): Promise<WorkflowRun | null> {
|
|
215
|
+
const { runId, workflowName } = args;
|
|
216
|
+
this.logger.debug('Getting workflow run by ID', { runId, workflowName });
|
|
217
|
+
|
|
218
|
+
console.log('workflowName', workflowName);
|
|
219
|
+
console.log('runId', runId);
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
// If we have a workflowName, we can do a direct get using the primary key
|
|
223
|
+
if (workflowName) {
|
|
224
|
+
this.logger.debug('WorkflowName provided, using direct GET operation.');
|
|
225
|
+
const result = await this.service.entities.workflow_snapshot
|
|
226
|
+
.get({
|
|
227
|
+
entity: 'workflow_snapshot', // Entity type for PK
|
|
228
|
+
workflow_name: workflowName,
|
|
229
|
+
run_id: runId,
|
|
230
|
+
})
|
|
231
|
+
.go();
|
|
232
|
+
|
|
233
|
+
console.log('result', result);
|
|
234
|
+
|
|
235
|
+
if (!result.data) {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const snapshot = result.data.snapshot;
|
|
240
|
+
return {
|
|
241
|
+
workflowName: result.data.workflow_name,
|
|
242
|
+
runId: result.data.run_id,
|
|
243
|
+
snapshot,
|
|
244
|
+
createdAt: new Date(result.data.createdAt),
|
|
245
|
+
updatedAt: new Date(result.data.updatedAt),
|
|
246
|
+
resourceId: result.data.resourceId,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Otherwise, if workflowName is not provided, use the GSI on runId.
|
|
251
|
+
// This is more efficient than a full table scan.
|
|
252
|
+
this.logger.debug(
|
|
253
|
+
'WorkflowName not provided. Attempting to find workflow run by runId using GSI. Ensure GSI (e.g., "byRunId") is defined on the workflowSnapshot entity with run_id as its key and provisioned in DynamoDB.',
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
// IMPORTANT: This assumes a GSI (e.g., named 'byRunId') exists on the workflowSnapshot entity
|
|
257
|
+
// with 'run_id' as its partition key. This GSI must be:
|
|
258
|
+
// 1. Defined in your ElectroDB model (e.g., in stores/dynamodb/src/entities/index.ts).
|
|
259
|
+
// 2. Provisioned in the actual DynamoDB table (e.g., via CDK/CloudFormation).
|
|
260
|
+
// The query key object includes 'entity' as it's good practice with ElectroDB and single-table design,
|
|
261
|
+
// aligning with how other GSIs are queried in this file.
|
|
262
|
+
const result = await this.service.entities.workflow_snapshot.query
|
|
263
|
+
.gsi2({ entity: 'workflow_snapshot', run_id: runId }) // Replace 'byRunId' with your actual GSI name
|
|
264
|
+
.go();
|
|
265
|
+
|
|
266
|
+
// If the GSI query returns multiple items (e.g., if run_id is not globally unique across all snapshots),
|
|
267
|
+
// this will take the first one. The original scan logic also effectively took the first match found.
|
|
268
|
+
// If run_id is guaranteed unique, result.data should contain at most one item.
|
|
269
|
+
const matchingRunDbItem: WorkflowSnapshotDBItem | null =
|
|
270
|
+
result.data && result.data.length > 0 ? result.data[0] : null;
|
|
271
|
+
|
|
272
|
+
if (!matchingRunDbItem) {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const snapshot = matchingRunDbItem.snapshot;
|
|
277
|
+
return {
|
|
278
|
+
workflowName: matchingRunDbItem.workflow_name,
|
|
279
|
+
runId: matchingRunDbItem.run_id,
|
|
280
|
+
snapshot,
|
|
281
|
+
createdAt: new Date(matchingRunDbItem.createdAt),
|
|
282
|
+
updatedAt: new Date(matchingRunDbItem.updatedAt),
|
|
283
|
+
resourceId: matchingRunDbItem.resourceId,
|
|
284
|
+
};
|
|
285
|
+
} catch (error) {
|
|
286
|
+
throw new MastraError(
|
|
287
|
+
{
|
|
288
|
+
id: 'STORAGE_DYNAMODB_STORE_GET_WORKFLOW_RUN_BY_ID_FAILED',
|
|
289
|
+
domain: ErrorDomain.STORAGE,
|
|
290
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
291
|
+
details: { runId, workflowName: args?.workflowName || '' },
|
|
292
|
+
},
|
|
293
|
+
error,
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|