@mastra/dynamodb 0.14.5 → 0.14.6-alpha.0

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.
@@ -1,334 +0,0 @@
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 { StepResult, 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
- updateWorkflowResults(
38
- {
39
- // workflowName,
40
- // runId,
41
- // stepId,
42
- // result,
43
- // runtimeContext,
44
- }: {
45
- workflowName: string;
46
- runId: string;
47
- stepId: string;
48
- result: StepResult<any, any, any, any>;
49
- runtimeContext: Record<string, any>;
50
- },
51
- ): Promise<Record<string, StepResult<any, any, any, any>>> {
52
- throw new Error('Method not implemented.');
53
- }
54
- updateWorkflowState(
55
- {
56
- // workflowName,
57
- // runId,
58
- // opts,
59
- }: {
60
- workflowName: string;
61
- runId: string;
62
- opts: {
63
- status: string;
64
- result?: StepResult<any, any, any, any>;
65
- error?: string;
66
- suspendedPaths?: Record<string, number[]>;
67
- waitingPaths?: Record<string, number[]>;
68
- };
69
- },
70
- ): Promise<WorkflowRunState | undefined> {
71
- throw new Error('Method not implemented.');
72
- }
73
-
74
- // Workflow operations
75
- async persistWorkflowSnapshot({
76
- workflowName,
77
- runId,
78
- snapshot,
79
- }: {
80
- workflowName: string;
81
- runId: string;
82
- snapshot: WorkflowRunState;
83
- }): Promise<void> {
84
- this.logger.debug('Persisting workflow snapshot', { workflowName, runId });
85
-
86
- try {
87
- const resourceId = 'resourceId' in snapshot ? snapshot.resourceId : undefined;
88
- const now = new Date().toISOString();
89
- // Prepare data including the 'entity' type
90
- const data = {
91
- entity: 'workflow_snapshot', // Add entity type
92
- workflow_name: workflowName,
93
- run_id: runId,
94
- snapshot: JSON.stringify(snapshot), // Stringify the snapshot object
95
- createdAt: now,
96
- updatedAt: now,
97
- resourceId,
98
- };
99
- // Use upsert instead of create to handle both create and update cases
100
- await this.service.entities.workflow_snapshot.upsert(data).go();
101
- } catch (error) {
102
- throw new MastraError(
103
- {
104
- id: 'STORAGE_DYNAMODB_STORE_PERSIST_WORKFLOW_SNAPSHOT_FAILED',
105
- domain: ErrorDomain.STORAGE,
106
- category: ErrorCategory.THIRD_PARTY,
107
- details: { workflowName, runId },
108
- },
109
- error,
110
- );
111
- }
112
- }
113
-
114
- async loadWorkflowSnapshot({
115
- workflowName,
116
- runId,
117
- }: {
118
- workflowName: string;
119
- runId: string;
120
- }): Promise<WorkflowRunState | null> {
121
- this.logger.debug('Loading workflow snapshot', { workflowName, runId });
122
-
123
- try {
124
- // Provide *all* composite key components for the primary index ('entity', 'workflow_name', 'run_id')
125
- const result = await this.service.entities.workflow_snapshot
126
- .get({
127
- entity: 'workflow_snapshot', // Add entity type
128
- workflow_name: workflowName,
129
- run_id: runId,
130
- })
131
- .go();
132
-
133
- if (!result.data?.snapshot) {
134
- // Check snapshot exists
135
- return null;
136
- }
137
-
138
- // Parse the snapshot string
139
- return result.data.snapshot as WorkflowRunState;
140
- } catch (error) {
141
- throw new MastraError(
142
- {
143
- id: 'STORAGE_DYNAMODB_STORE_LOAD_WORKFLOW_SNAPSHOT_FAILED',
144
- domain: ErrorDomain.STORAGE,
145
- category: ErrorCategory.THIRD_PARTY,
146
- details: { workflowName, runId },
147
- },
148
- error,
149
- );
150
- }
151
- }
152
-
153
- async getWorkflowRuns(args?: {
154
- workflowName?: string;
155
- fromDate?: Date;
156
- toDate?: Date;
157
- limit?: number;
158
- offset?: number;
159
- resourceId?: string;
160
- }): Promise<WorkflowRuns> {
161
- this.logger.debug('Getting workflow runs', { args });
162
-
163
- try {
164
- // Default values
165
- const limit = args?.limit || 10;
166
- const offset = args?.offset || 0;
167
-
168
- let query;
169
-
170
- if (args?.workflowName) {
171
- // Query by workflow name using the primary index
172
- // Provide *all* composite key components for the PK ('entity', 'workflow_name')
173
- query = this.service.entities.workflow_snapshot.query.primary({
174
- entity: 'workflow_snapshot', // Add entity type
175
- workflow_name: args.workflowName,
176
- });
177
- } else {
178
- // If no workflow name, we need to scan
179
- // This is not ideal for production with large datasets
180
- this.logger.warn('Performing a scan operation on workflow snapshots - consider using a more specific query');
181
- query = this.service.entities.workflow_snapshot.scan; // Scan still uses the service entity
182
- }
183
-
184
- const allMatchingSnapshots: WorkflowSnapshotDBItem[] = [];
185
- let cursor: string | null = null;
186
- const DYNAMODB_PAGE_SIZE = 100; // Sensible page size for fetching
187
-
188
- do {
189
- const pageResults: { data: WorkflowSnapshotDBItem[]; cursor: string | null } = await query.go({
190
- limit: DYNAMODB_PAGE_SIZE,
191
- cursor,
192
- });
193
-
194
- if (pageResults.data && pageResults.data.length > 0) {
195
- let pageFilteredData: WorkflowSnapshotDBItem[] = pageResults.data;
196
-
197
- // Apply date filters if specified
198
- if (args?.fromDate || args?.toDate) {
199
- pageFilteredData = pageFilteredData.filter((snapshot: WorkflowSnapshotDBItem) => {
200
- const createdAt = new Date(snapshot.createdAt);
201
- if (args.fromDate && createdAt < args.fromDate) {
202
- return false;
203
- }
204
- if (args.toDate && createdAt > args.toDate) {
205
- return false;
206
- }
207
- return true;
208
- });
209
- }
210
-
211
- // Filter by resourceId if specified
212
- if (args?.resourceId) {
213
- pageFilteredData = pageFilteredData.filter((snapshot: WorkflowSnapshotDBItem) => {
214
- return snapshot.resourceId === args.resourceId;
215
- });
216
- }
217
- allMatchingSnapshots.push(...pageFilteredData);
218
- }
219
-
220
- cursor = pageResults.cursor;
221
- } while (cursor);
222
-
223
- if (!allMatchingSnapshots.length) {
224
- return { runs: [], total: 0 };
225
- }
226
-
227
- // Apply offset and limit to the accumulated filtered results
228
- const total = allMatchingSnapshots.length;
229
- const paginatedData = allMatchingSnapshots.slice(offset, offset + limit);
230
-
231
- // Format and return the results
232
- const runs = paginatedData.map((snapshot: WorkflowSnapshotDBItem) => formatWorkflowRun(snapshot));
233
-
234
- return {
235
- runs,
236
- total,
237
- };
238
- } catch (error) {
239
- throw new MastraError(
240
- {
241
- id: 'STORAGE_DYNAMODB_STORE_GET_WORKFLOW_RUNS_FAILED',
242
- domain: ErrorDomain.STORAGE,
243
- category: ErrorCategory.THIRD_PARTY,
244
- details: { workflowName: args?.workflowName || '', resourceId: args?.resourceId || '' },
245
- },
246
- error,
247
- );
248
- }
249
- }
250
-
251
- async getWorkflowRunById(args: { runId: string; workflowName?: string }): Promise<WorkflowRun | null> {
252
- const { runId, workflowName } = args;
253
- this.logger.debug('Getting workflow run by ID', { runId, workflowName });
254
-
255
- console.log('workflowName', workflowName);
256
- console.log('runId', runId);
257
-
258
- try {
259
- // If we have a workflowName, we can do a direct get using the primary key
260
- if (workflowName) {
261
- this.logger.debug('WorkflowName provided, using direct GET operation.');
262
- const result = await this.service.entities.workflow_snapshot
263
- .get({
264
- entity: 'workflow_snapshot', // Entity type for PK
265
- workflow_name: workflowName,
266
- run_id: runId,
267
- })
268
- .go();
269
-
270
- console.log('result', result);
271
-
272
- if (!result.data) {
273
- return null;
274
- }
275
-
276
- const snapshot = result.data.snapshot;
277
- return {
278
- workflowName: result.data.workflow_name,
279
- runId: result.data.run_id,
280
- snapshot,
281
- createdAt: new Date(result.data.createdAt),
282
- updatedAt: new Date(result.data.updatedAt),
283
- resourceId: result.data.resourceId,
284
- };
285
- }
286
-
287
- // Otherwise, if workflowName is not provided, use the GSI on runId.
288
- // This is more efficient than a full table scan.
289
- this.logger.debug(
290
- '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.',
291
- );
292
-
293
- // IMPORTANT: This assumes a GSI (e.g., named 'byRunId') exists on the workflowSnapshot entity
294
- // with 'run_id' as its partition key. This GSI must be:
295
- // 1. Defined in your ElectroDB model (e.g., in stores/dynamodb/src/entities/index.ts).
296
- // 2. Provisioned in the actual DynamoDB table (e.g., via CDK/CloudFormation).
297
- // The query key object includes 'entity' as it's good practice with ElectroDB and single-table design,
298
- // aligning with how other GSIs are queried in this file.
299
- const result = await this.service.entities.workflow_snapshot.query
300
- .gsi2({ entity: 'workflow_snapshot', run_id: runId }) // Replace 'byRunId' with your actual GSI name
301
- .go();
302
-
303
- // If the GSI query returns multiple items (e.g., if run_id is not globally unique across all snapshots),
304
- // this will take the first one. The original scan logic also effectively took the first match found.
305
- // If run_id is guaranteed unique, result.data should contain at most one item.
306
- const matchingRunDbItem: WorkflowSnapshotDBItem | null =
307
- result.data && result.data.length > 0 ? result.data[0] : null;
308
-
309
- if (!matchingRunDbItem) {
310
- return null;
311
- }
312
-
313
- const snapshot = matchingRunDbItem.snapshot;
314
- return {
315
- workflowName: matchingRunDbItem.workflow_name,
316
- runId: matchingRunDbItem.run_id,
317
- snapshot,
318
- createdAt: new Date(matchingRunDbItem.createdAt),
319
- updatedAt: new Date(matchingRunDbItem.updatedAt),
320
- resourceId: matchingRunDbItem.resourceId,
321
- };
322
- } catch (error) {
323
- throw new MastraError(
324
- {
325
- id: 'STORAGE_DYNAMODB_STORE_GET_WORKFLOW_RUN_BY_ID_FAILED',
326
- domain: ErrorDomain.STORAGE,
327
- category: ErrorCategory.THIRD_PARTY,
328
- details: { runId, workflowName: args?.workflowName || '' },
329
- },
330
- error,
331
- );
332
- }
333
- }
334
- }