@mastra/dynamodb 1.0.1 → 1.0.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.
- package/CHANGELOG.md +18 -0
- package/LICENSE.md +15 -0
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/reference-storage-dynamodb.md +20 -20
- package/dist/index.cjs +164 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +164 -52
- package/dist/index.js.map +1 -1
- package/dist/storage/domains/workflows/index.d.ts +9 -0
- package/dist/storage/domains/workflows/index.d.ts.map +1 -1
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -2142,6 +2142,8 @@ function formatWorkflowRun(snapshotData) {
|
|
|
2142
2142
|
resourceId: snapshotData.resourceId
|
|
2143
2143
|
};
|
|
2144
2144
|
}
|
|
2145
|
+
var MAX_RETRIES = 5;
|
|
2146
|
+
var BASE_DELAY_MS = 50;
|
|
2145
2147
|
var WorkflowStorageDynamoDB = class extends WorkflowsStorage {
|
|
2146
2148
|
service;
|
|
2147
2149
|
ttlConfig;
|
|
@@ -2151,9 +2153,41 @@ var WorkflowStorageDynamoDB = class extends WorkflowsStorage {
|
|
|
2151
2153
|
this.service = resolved.service;
|
|
2152
2154
|
this.ttlConfig = resolved.ttl;
|
|
2153
2155
|
}
|
|
2156
|
+
supportsConcurrentUpdates() {
|
|
2157
|
+
return true;
|
|
2158
|
+
}
|
|
2154
2159
|
async dangerouslyClearAll() {
|
|
2155
2160
|
await deleteTableData(this.service, TABLE_WORKFLOW_SNAPSHOT);
|
|
2156
2161
|
}
|
|
2162
|
+
/**
|
|
2163
|
+
* Helper to check if an error is a DynamoDB conditional check failure
|
|
2164
|
+
*/
|
|
2165
|
+
isConditionalCheckFailed(error) {
|
|
2166
|
+
if (error && typeof error === "object") {
|
|
2167
|
+
const err = error;
|
|
2168
|
+
if (err.name === "ConditionalCheckFailedException" || err.code === "ConditionalCheckFailedException" || (err.__type?.includes("ConditionalCheckFailedException") ?? false)) {
|
|
2169
|
+
return true;
|
|
2170
|
+
}
|
|
2171
|
+
if (err.message?.includes("conditional request failed")) {
|
|
2172
|
+
return true;
|
|
2173
|
+
}
|
|
2174
|
+
if (err.cause && typeof err.cause === "object") {
|
|
2175
|
+
const cause = err.cause;
|
|
2176
|
+
if (cause.name === "ConditionalCheckFailedException" || cause.code === "ConditionalCheckFailedException") {
|
|
2177
|
+
return true;
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
return false;
|
|
2182
|
+
}
|
|
2183
|
+
/**
|
|
2184
|
+
* Helper to delay with exponential backoff and jitter
|
|
2185
|
+
*/
|
|
2186
|
+
async delay(attempt) {
|
|
2187
|
+
const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
2188
|
+
const jitter = Math.random() * backoff * 0.5;
|
|
2189
|
+
await new Promise((resolve) => setTimeout(resolve, backoff + jitter));
|
|
2190
|
+
}
|
|
2157
2191
|
async updateWorkflowResults({
|
|
2158
2192
|
workflowName,
|
|
2159
2193
|
runId,
|
|
@@ -2161,69 +2195,147 @@ var WorkflowStorageDynamoDB = class extends WorkflowsStorage {
|
|
|
2161
2195
|
result,
|
|
2162
2196
|
requestContext
|
|
2163
2197
|
}) {
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2198
|
+
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
2199
|
+
try {
|
|
2200
|
+
const existingRecord = await this.service.entities.workflow_snapshot.get({
|
|
2201
|
+
entity: "workflow_snapshot",
|
|
2202
|
+
workflow_name: workflowName,
|
|
2203
|
+
run_id: runId
|
|
2204
|
+
}).go();
|
|
2205
|
+
const now = /* @__PURE__ */ new Date();
|
|
2206
|
+
let snapshot;
|
|
2207
|
+
let previousUpdatedAt;
|
|
2208
|
+
if (!existingRecord.data) {
|
|
2209
|
+
snapshot = {
|
|
2210
|
+
context: {},
|
|
2211
|
+
activePaths: [],
|
|
2212
|
+
timestamp: Date.now(),
|
|
2213
|
+
suspendedPaths: {},
|
|
2214
|
+
activeStepsPath: {},
|
|
2215
|
+
resumeLabels: {},
|
|
2216
|
+
serializedStepGraph: [],
|
|
2217
|
+
status: "pending",
|
|
2218
|
+
value: {},
|
|
2219
|
+
waitingPaths: {},
|
|
2220
|
+
runId,
|
|
2221
|
+
requestContext: {}
|
|
2222
|
+
};
|
|
2223
|
+
} else {
|
|
2224
|
+
snapshot = existingRecord.data.snapshot;
|
|
2225
|
+
previousUpdatedAt = existingRecord.data.updatedAt;
|
|
2226
|
+
}
|
|
2227
|
+
snapshot.context[stepId] = result;
|
|
2228
|
+
snapshot.requestContext = { ...snapshot.requestContext, ...requestContext };
|
|
2229
|
+
const data = {
|
|
2230
|
+
entity: "workflow_snapshot",
|
|
2231
|
+
workflow_name: workflowName,
|
|
2232
|
+
run_id: runId,
|
|
2233
|
+
snapshot: JSON.stringify(snapshot),
|
|
2234
|
+
createdAt: existingRecord.data?.createdAt ?? now.toISOString(),
|
|
2235
|
+
updatedAt: now.toISOString(),
|
|
2236
|
+
...getTtlProps("workflow_snapshot", this.ttlConfig)
|
|
2181
2237
|
};
|
|
2182
|
-
|
|
2183
|
-
|
|
2238
|
+
if (previousUpdatedAt) {
|
|
2239
|
+
await this.service.entities.workflow_snapshot.upsert(data).where((attr, op) => op.eq(attr.updatedAt, previousUpdatedAt)).go();
|
|
2240
|
+
} else {
|
|
2241
|
+
await this.service.entities.workflow_snapshot.create(data).where((attr, op) => op.notExists(attr.run_id)).go();
|
|
2242
|
+
}
|
|
2243
|
+
return snapshot.context;
|
|
2244
|
+
} catch (error) {
|
|
2245
|
+
if (this.isConditionalCheckFailed(error)) {
|
|
2246
|
+
if (attempt < MAX_RETRIES - 1) {
|
|
2247
|
+
this.logger.debug(
|
|
2248
|
+
`Optimistic locking conflict in updateWorkflowResults, retrying (attempt ${attempt + 1})`
|
|
2249
|
+
);
|
|
2250
|
+
await this.delay(attempt);
|
|
2251
|
+
continue;
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
if (error instanceof MastraError) throw error;
|
|
2255
|
+
throw new MastraError(
|
|
2256
|
+
{
|
|
2257
|
+
id: createStorageErrorId("DYNAMODB", "UPDATE_WORKFLOW_RESULTS", "FAILED"),
|
|
2258
|
+
domain: ErrorDomain.STORAGE,
|
|
2259
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
2260
|
+
details: { workflowName, runId, stepId }
|
|
2261
|
+
},
|
|
2262
|
+
error
|
|
2263
|
+
);
|
|
2184
2264
|
}
|
|
2185
|
-
snapshot.context[stepId] = result;
|
|
2186
|
-
snapshot.requestContext = { ...snapshot.requestContext, ...requestContext };
|
|
2187
|
-
await this.persistWorkflowSnapshot({ workflowName, runId, snapshot });
|
|
2188
|
-
return snapshot.context;
|
|
2189
|
-
} catch (error) {
|
|
2190
|
-
if (error instanceof MastraError) throw error;
|
|
2191
|
-
throw new MastraError(
|
|
2192
|
-
{
|
|
2193
|
-
id: createStorageErrorId("DYNAMODB", "UPDATE_WORKFLOW_RESULTS", "FAILED"),
|
|
2194
|
-
domain: ErrorDomain.STORAGE,
|
|
2195
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
2196
|
-
details: { workflowName, runId, stepId }
|
|
2197
|
-
},
|
|
2198
|
-
error
|
|
2199
|
-
);
|
|
2200
2265
|
}
|
|
2266
|
+
throw new MastraError(
|
|
2267
|
+
{
|
|
2268
|
+
id: createStorageErrorId("DYNAMODB", "UPDATE_WORKFLOW_RESULTS", "MAX_RETRIES_EXCEEDED"),
|
|
2269
|
+
domain: ErrorDomain.STORAGE,
|
|
2270
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
2271
|
+
details: { workflowName, runId, stepId }
|
|
2272
|
+
},
|
|
2273
|
+
new Error("Max retries exceeded for optimistic locking")
|
|
2274
|
+
);
|
|
2201
2275
|
}
|
|
2202
2276
|
async updateWorkflowState({
|
|
2203
2277
|
workflowName,
|
|
2204
2278
|
runId,
|
|
2205
2279
|
opts
|
|
2206
2280
|
}) {
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2281
|
+
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
2282
|
+
try {
|
|
2283
|
+
const existingRecord = await this.service.entities.workflow_snapshot.get({
|
|
2284
|
+
entity: "workflow_snapshot",
|
|
2285
|
+
workflow_name: workflowName,
|
|
2286
|
+
run_id: runId
|
|
2287
|
+
}).go();
|
|
2288
|
+
if (!existingRecord.data) {
|
|
2289
|
+
return void 0;
|
|
2290
|
+
}
|
|
2291
|
+
const existingSnapshot = existingRecord.data.snapshot;
|
|
2292
|
+
if (!existingSnapshot || !existingSnapshot.context) {
|
|
2293
|
+
return void 0;
|
|
2294
|
+
}
|
|
2295
|
+
const previousUpdatedAt = existingRecord.data.updatedAt;
|
|
2296
|
+
const updatedSnapshot = { ...existingSnapshot, ...opts };
|
|
2297
|
+
const now = /* @__PURE__ */ new Date();
|
|
2298
|
+
const data = {
|
|
2299
|
+
entity: "workflow_snapshot",
|
|
2300
|
+
workflow_name: workflowName,
|
|
2301
|
+
run_id: runId,
|
|
2302
|
+
snapshot: JSON.stringify(updatedSnapshot),
|
|
2303
|
+
createdAt: existingRecord.data.createdAt,
|
|
2304
|
+
updatedAt: now.toISOString(),
|
|
2305
|
+
resourceId: existingRecord.data.resourceId,
|
|
2306
|
+
...getTtlProps("workflow_snapshot", this.ttlConfig)
|
|
2307
|
+
};
|
|
2308
|
+
await this.service.entities.workflow_snapshot.upsert(data).where((attr, op) => op.eq(attr.updatedAt, previousUpdatedAt)).go();
|
|
2309
|
+
return updatedSnapshot;
|
|
2310
|
+
} catch (error) {
|
|
2311
|
+
if (this.isConditionalCheckFailed(error)) {
|
|
2312
|
+
if (attempt < MAX_RETRIES - 1) {
|
|
2313
|
+
this.logger.debug(`Optimistic locking conflict in updateWorkflowState, retrying (attempt ${attempt + 1})`);
|
|
2314
|
+
await this.delay(attempt);
|
|
2315
|
+
continue;
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
if (error instanceof MastraError) throw error;
|
|
2319
|
+
throw new MastraError(
|
|
2320
|
+
{
|
|
2321
|
+
id: createStorageErrorId("DYNAMODB", "UPDATE_WORKFLOW_STATE", "FAILED"),
|
|
2322
|
+
domain: ErrorDomain.STORAGE,
|
|
2323
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
2324
|
+
details: { workflowName, runId }
|
|
2325
|
+
},
|
|
2326
|
+
error
|
|
2327
|
+
);
|
|
2211
2328
|
}
|
|
2212
|
-
const updatedSnapshot = { ...existingSnapshot, ...opts };
|
|
2213
|
-
await this.persistWorkflowSnapshot({ workflowName, runId, snapshot: updatedSnapshot });
|
|
2214
|
-
return updatedSnapshot;
|
|
2215
|
-
} catch (error) {
|
|
2216
|
-
if (error instanceof MastraError) throw error;
|
|
2217
|
-
throw new MastraError(
|
|
2218
|
-
{
|
|
2219
|
-
id: createStorageErrorId("DYNAMODB", "UPDATE_WORKFLOW_STATE", "FAILED"),
|
|
2220
|
-
domain: ErrorDomain.STORAGE,
|
|
2221
|
-
category: ErrorCategory.THIRD_PARTY,
|
|
2222
|
-
details: { workflowName, runId }
|
|
2223
|
-
},
|
|
2224
|
-
error
|
|
2225
|
-
);
|
|
2226
2329
|
}
|
|
2330
|
+
throw new MastraError(
|
|
2331
|
+
{
|
|
2332
|
+
id: createStorageErrorId("DYNAMODB", "UPDATE_WORKFLOW_STATE", "MAX_RETRIES_EXCEEDED"),
|
|
2333
|
+
domain: ErrorDomain.STORAGE,
|
|
2334
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
2335
|
+
details: { workflowName, runId }
|
|
2336
|
+
},
|
|
2337
|
+
new Error("Max retries exceeded for optimistic locking")
|
|
2338
|
+
);
|
|
2227
2339
|
}
|
|
2228
2340
|
// Workflow operations
|
|
2229
2341
|
async persistWorkflowSnapshot({
|