@mastra/dynamodb 1.4.1 → 1.4.2-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.
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/entities/index.d.ts +79 -0
- package/dist/entities/index.d.ts.map +1 -1
- package/dist/entities/run-fencing.d.ts +92 -0
- package/dist/entities/run-fencing.d.ts.map +1 -0
- package/dist/index.cjs +424 -51
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +425 -52
- package/dist/index.js.map +1 -1
- package/dist/storage/domains/memory/index.d.ts +16 -5
- package/dist/storage/domains/memory/index.d.ts.map +1 -1
- package/dist/storage/domains/run-fencing.d.ts +41 -0
- package/dist/storage/domains/run-fencing.d.ts.map +1 -0
- package/dist/storage/domains/utils.d.ts +2 -1
- package/dist/storage/domains/utils.d.ts.map +1 -1
- package/dist/storage/domains/workflows/index.d.ts +17 -5
- package/dist/storage/domains/workflows/index.d.ts.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -499,6 +499,80 @@ const resourceEntity = new electrodb.Entity({
|
|
|
499
499
|
} }
|
|
500
500
|
});
|
|
501
501
|
//#endregion
|
|
502
|
+
//#region src/entities/run-fencing.ts
|
|
503
|
+
/**
|
|
504
|
+
* The current claim on a durable workflow run. `leaseExpiresAt` holds the
|
|
505
|
+
* lease's expiry in epoch milliseconds and is removed when the owner releases
|
|
506
|
+
* the run; `generation` and `ownerId` are kept. `updatedAt` is the epoch
|
|
507
|
+
* milliseconds of the last claim, renewal or release.
|
|
508
|
+
*/
|
|
509
|
+
const workflowRunOwnerEntity = new electrodb.Entity({
|
|
510
|
+
model: {
|
|
511
|
+
entity: "workflow_run_owner",
|
|
512
|
+
version: "1",
|
|
513
|
+
service: "mastra"
|
|
514
|
+
},
|
|
515
|
+
attributes: {
|
|
516
|
+
entity: {
|
|
517
|
+
type: "string",
|
|
518
|
+
required: true
|
|
519
|
+
},
|
|
520
|
+
run_id: {
|
|
521
|
+
type: "string",
|
|
522
|
+
required: true
|
|
523
|
+
},
|
|
524
|
+
generation: { type: "number" },
|
|
525
|
+
ownerId: { type: "string" },
|
|
526
|
+
leaseExpiresAt: { type: "number" },
|
|
527
|
+
updatedAt: { type: "number" }
|
|
528
|
+
},
|
|
529
|
+
indexes: { primary: {
|
|
530
|
+
pk: {
|
|
531
|
+
field: "pk",
|
|
532
|
+
composite: ["entity", "run_id"]
|
|
533
|
+
},
|
|
534
|
+
sk: {
|
|
535
|
+
field: "sk",
|
|
536
|
+
composite: ["entity"]
|
|
537
|
+
}
|
|
538
|
+
} }
|
|
539
|
+
});
|
|
540
|
+
/**
|
|
541
|
+
* The newest fence memory has seen for a run. Fenced memory writes check it.
|
|
542
|
+
* `retiredAt` is set, in epoch milliseconds, once the execution holding the
|
|
543
|
+
* fence settles, and removed when a fence is raised again.
|
|
544
|
+
*/
|
|
545
|
+
const memoryRunFenceEntity = new electrodb.Entity({
|
|
546
|
+
model: {
|
|
547
|
+
entity: "memory_run_fence",
|
|
548
|
+
version: "1",
|
|
549
|
+
service: "mastra"
|
|
550
|
+
},
|
|
551
|
+
attributes: {
|
|
552
|
+
entity: {
|
|
553
|
+
type: "string",
|
|
554
|
+
required: true
|
|
555
|
+
},
|
|
556
|
+
run_id: {
|
|
557
|
+
type: "string",
|
|
558
|
+
required: true
|
|
559
|
+
},
|
|
560
|
+
generation: { type: "number" },
|
|
561
|
+
ownerId: { type: "string" },
|
|
562
|
+
retiredAt: { type: "number" }
|
|
563
|
+
},
|
|
564
|
+
indexes: { primary: {
|
|
565
|
+
pk: {
|
|
566
|
+
field: "pk",
|
|
567
|
+
composite: ["entity", "run_id"]
|
|
568
|
+
},
|
|
569
|
+
sk: {
|
|
570
|
+
field: "sk",
|
|
571
|
+
composite: ["entity"]
|
|
572
|
+
}
|
|
573
|
+
} }
|
|
574
|
+
});
|
|
575
|
+
//#endregion
|
|
502
576
|
//#region src/entities/score.ts
|
|
503
577
|
const scoreEntity = new electrodb.Entity({
|
|
504
578
|
model: {
|
|
@@ -1129,7 +1203,9 @@ function getElectroDbService(client, tableName) {
|
|
|
1129
1203
|
workflow_snapshot: workflowSnapshotEntity,
|
|
1130
1204
|
resource: resourceEntity,
|
|
1131
1205
|
score: scoreEntity,
|
|
1132
|
-
background_task: backgroundTaskEntity
|
|
1206
|
+
background_task: backgroundTaskEntity,
|
|
1207
|
+
workflow_run_owner: workflowRunOwnerEntity,
|
|
1208
|
+
memory_run_fence: memoryRunFenceEntity
|
|
1133
1209
|
}, {
|
|
1134
1210
|
client,
|
|
1135
1211
|
table: tableName
|
|
@@ -1167,7 +1243,9 @@ const ENTITY_MAP = {
|
|
|
1167
1243
|
[_mastra_core_storage.TABLE_RESOURCES]: "resource",
|
|
1168
1244
|
[_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT]: "workflow_snapshot",
|
|
1169
1245
|
[_mastra_core_storage.TABLE_SCORERS]: "score",
|
|
1170
|
-
[_mastra_core_storage.TABLE_BACKGROUND_TASKS]: "background_task"
|
|
1246
|
+
[_mastra_core_storage.TABLE_BACKGROUND_TASKS]: "background_task",
|
|
1247
|
+
[_mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS]: "workflow_run_owner",
|
|
1248
|
+
[_mastra_core_storage.TABLE_MEMORY_RUN_FENCES]: "memory_run_fence"
|
|
1171
1249
|
};
|
|
1172
1250
|
/**
|
|
1173
1251
|
* Gets the primary key fields for an entity based on the scanned item
|
|
@@ -1186,6 +1264,10 @@ function getDeleteKey(entityName, item) {
|
|
|
1186
1264
|
key.workflow_name = item.workflow_name;
|
|
1187
1265
|
key.run_id = item.run_id;
|
|
1188
1266
|
break;
|
|
1267
|
+
case "workflow_run_owner":
|
|
1268
|
+
case "memory_run_fence":
|
|
1269
|
+
key.run_id = item.run_id;
|
|
1270
|
+
break;
|
|
1189
1271
|
default: key.id = item.id;
|
|
1190
1272
|
}
|
|
1191
1273
|
return key;
|
|
@@ -1521,6 +1603,91 @@ function getTtlProps(entityName, ttlConfig, customTtlSeconds) {
|
|
|
1521
1603
|
return { [getTtlAttributeName(entityName, ttlConfig)]: ttlValue };
|
|
1522
1604
|
}
|
|
1523
1605
|
//#endregion
|
|
1606
|
+
//#region src/storage/domains/run-fencing.ts
|
|
1607
|
+
const MAX_CONFLICT_RETRIES = 8;
|
|
1608
|
+
const BASE_DELAY_MS$1 = 20;
|
|
1609
|
+
async function backoff(attempt) {
|
|
1610
|
+
const delay = BASE_DELAY_MS$1 * 2 ** attempt;
|
|
1611
|
+
await new Promise((resolve) => setTimeout(resolve, delay + Math.random() * delay * .5));
|
|
1612
|
+
}
|
|
1613
|
+
function hasErrorName(error, errorName) {
|
|
1614
|
+
let current = error;
|
|
1615
|
+
for (let depth = 0; depth < 4 && current && typeof current === "object"; depth++) {
|
|
1616
|
+
const { name, code } = current;
|
|
1617
|
+
if (name === errorName || code === errorName) return true;
|
|
1618
|
+
current = current.cause;
|
|
1619
|
+
}
|
|
1620
|
+
return false;
|
|
1621
|
+
}
|
|
1622
|
+
function isTransactionConflict(error) {
|
|
1623
|
+
return hasErrorName(error, "TransactionConflictException");
|
|
1624
|
+
}
|
|
1625
|
+
/** Whether a write failed its condition, either directly or wrapped by ElectroDB. */
|
|
1626
|
+
function isConditionalCheckFailed(error) {
|
|
1627
|
+
return hasErrorName(error, "ConditionalCheckFailedException");
|
|
1628
|
+
}
|
|
1629
|
+
/**
|
|
1630
|
+
* Runs a single-item write, retrying while a transaction is writing the same
|
|
1631
|
+
* item. DynamoDB rejects such a write with TransactionConflictException
|
|
1632
|
+
* instead of waiting for the transaction, and the SDK does not retry it.
|
|
1633
|
+
*/
|
|
1634
|
+
async function retryOnTransactionConflict(write) {
|
|
1635
|
+
for (let attempt = 0;; attempt++) try {
|
|
1636
|
+
return await write();
|
|
1637
|
+
} catch (error) {
|
|
1638
|
+
if (attempt >= MAX_CONFLICT_RETRIES || !isTransactionConflict(error)) throw error;
|
|
1639
|
+
await backoff(attempt);
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
/**
|
|
1643
|
+
* Rejects a fenced write that turns out to have nothing to write when its
|
|
1644
|
+
* fence is no longer current, as the write itself would have been rejected.
|
|
1645
|
+
*/
|
|
1646
|
+
async function assertRunFence(service, check) {
|
|
1647
|
+
if (!check) return;
|
|
1648
|
+
const { claims, fence, operation } = check;
|
|
1649
|
+
const { data } = await service.entities[claims].get({
|
|
1650
|
+
entity: claims,
|
|
1651
|
+
run_id: fence.runId
|
|
1652
|
+
}).go({ consistent: true });
|
|
1653
|
+
if (!(0, _mastra_core_storage.matchesRunFence)(data, fence)) throw new _mastra_core_storage.RunFenceConflictError(fence, operation);
|
|
1654
|
+
}
|
|
1655
|
+
/**
|
|
1656
|
+
* Runs a write, fenced when `check` is set. A fenced write is a transaction
|
|
1657
|
+
* whose first item is a condition check on the run's claim, matching it on
|
|
1658
|
+
* the fence. A fence that is no longer current fails the check, and the write
|
|
1659
|
+
* throws `RunFenceConflictError` without writing anything. Transactions are
|
|
1660
|
+
* serializable with the single-item writes that claim a run, so a takeover
|
|
1661
|
+
* lands either before the transaction (failing its check) or after it.
|
|
1662
|
+
*
|
|
1663
|
+
* A fenced write whose own condition fails throws an error named
|
|
1664
|
+
* `ConditionalCheckFailedException`, as the unfenced write would.
|
|
1665
|
+
*/
|
|
1666
|
+
async function goWithRunFence(service, check, write) {
|
|
1667
|
+
if (!check) {
|
|
1668
|
+
await write.go();
|
|
1669
|
+
return;
|
|
1670
|
+
}
|
|
1671
|
+
const { claims, fence, operation } = check;
|
|
1672
|
+
for (let attempt = 0;; attempt++) {
|
|
1673
|
+
const result = await service.transaction.write((entities) => [entities[claims].check({
|
|
1674
|
+
entity: claims,
|
|
1675
|
+
run_id: fence.runId
|
|
1676
|
+
}).where((attr, op) => `${op.eq(attr.generation, fence.generation)} AND ${op.eq(attr.ownerId, fence.ownerId)}`).commit(), write.commit()]).go();
|
|
1677
|
+
if (!result.canceled) return;
|
|
1678
|
+
const [claim, target] = result.data;
|
|
1679
|
+
if (claim?.code === "ConditionalCheckFailed") throw new _mastra_core_storage.RunFenceConflictError(fence, operation);
|
|
1680
|
+
if (target?.code === "ConditionalCheckFailed") {
|
|
1681
|
+
const error = new Error(target.message ?? "The conditional request failed");
|
|
1682
|
+
error.name = "ConditionalCheckFailedException";
|
|
1683
|
+
throw error;
|
|
1684
|
+
}
|
|
1685
|
+
const reasons = result.data.map((item) => item?.code ?? "None");
|
|
1686
|
+
if (attempt >= MAX_CONFLICT_RETRIES || !reasons.includes("TransactionConflict")) throw new Error(`DynamoDB canceled the fenced ${operation} transaction: ${reasons.join(", ")}`);
|
|
1687
|
+
await backoff(attempt);
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
//#endregion
|
|
1524
1691
|
//#region src/storage/domains/memory/index.ts
|
|
1525
1692
|
var MemoryStorageDynamoDB = class extends _mastra_core_storage.MemoryStorage {
|
|
1526
1693
|
supportsPartialThreadUpdate = true;
|
|
@@ -1532,15 +1699,81 @@ var MemoryStorageDynamoDB = class extends _mastra_core_storage.MemoryStorage {
|
|
|
1532
1699
|
this.service = resolved.service;
|
|
1533
1700
|
this.ttlConfig = resolved.ttl;
|
|
1534
1701
|
}
|
|
1702
|
+
supportsRunFencing() {
|
|
1703
|
+
return true;
|
|
1704
|
+
}
|
|
1535
1705
|
async dangerouslyClearAll() {
|
|
1536
1706
|
await deleteTableData(this.service, _mastra_core_storage.TABLE_THREADS);
|
|
1537
1707
|
await deleteTableData(this.service, _mastra_core_storage.TABLE_MESSAGES);
|
|
1538
1708
|
await deleteTableData(this.service, _mastra_core_storage.TABLE_RESOURCES);
|
|
1709
|
+
await deleteTableData(this.service, _mastra_core_storage.TABLE_MEMORY_RUN_FENCES);
|
|
1539
1710
|
}
|
|
1540
|
-
async
|
|
1711
|
+
async raiseRunFence(fence) {
|
|
1712
|
+
const fences = this.service.entities.memory_run_fence;
|
|
1713
|
+
const key = {
|
|
1714
|
+
entity: "memory_run_fence",
|
|
1715
|
+
run_id: fence.runId
|
|
1716
|
+
};
|
|
1717
|
+
try {
|
|
1718
|
+
await retryOnTransactionConflict(() => fences.update(key).set({
|
|
1719
|
+
generation: fence.generation,
|
|
1720
|
+
ownerId: fence.ownerId
|
|
1721
|
+
}).remove(["retiredAt"]).where((attr, op) => `${op.notExists(attr.generation)} OR ${op.lt(attr.generation, fence.generation)} OR (${op.eq(attr.generation, fence.generation)} AND ${op.eq(attr.ownerId, fence.ownerId)})`).go());
|
|
1722
|
+
return true;
|
|
1723
|
+
} catch (error) {
|
|
1724
|
+
try {
|
|
1725
|
+
if (!isConditionalCheckFailed(error)) throw error;
|
|
1726
|
+
const { data } = await fences.get(key).go({ consistent: true });
|
|
1727
|
+
return (0, _mastra_core_storage.matchesRunFence)(data, fence);
|
|
1728
|
+
} catch (cause) {
|
|
1729
|
+
throw new _mastra_core_error.MastraError({
|
|
1730
|
+
id: (0, _mastra_core_storage.createStorageErrorId)("DYNAMODB", "RAISE_RUN_FENCE", "FAILED"),
|
|
1731
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
1732
|
+
category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
|
|
1733
|
+
details: { runId: fence.runId }
|
|
1734
|
+
}, cause);
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
async retireRunFence(fence) {
|
|
1739
|
+
try {
|
|
1740
|
+
await retryOnTransactionConflict(() => this.service.entities.memory_run_fence.patch({
|
|
1741
|
+
entity: "memory_run_fence",
|
|
1742
|
+
run_id: fence.runId
|
|
1743
|
+
}).set({ retiredAt: Date.now() }).where((attr, op) => `${op.eq(attr.generation, fence.generation)} AND ${op.eq(attr.ownerId, fence.ownerId)}`).go());
|
|
1744
|
+
return true;
|
|
1745
|
+
} catch (error) {
|
|
1746
|
+
if (isConditionalCheckFailed(error)) return false;
|
|
1747
|
+
throw new _mastra_core_error.MastraError({
|
|
1748
|
+
id: (0, _mastra_core_storage.createStorageErrorId)("DYNAMODB", "RETIRE_RUN_FENCE", "FAILED"),
|
|
1749
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
1750
|
+
category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
|
|
1751
|
+
details: { runId: fence.runId }
|
|
1752
|
+
}, error);
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
/** The check a write must pass: its own fence, otherwise the one in scope. */
|
|
1756
|
+
#runFenceCheck(fence, operation) {
|
|
1757
|
+
const resolved = (0, _mastra_core_storage.resolveRunFence)(this, fence);
|
|
1758
|
+
return resolved && {
|
|
1759
|
+
claims: "memory_run_fence",
|
|
1760
|
+
fence: resolved,
|
|
1761
|
+
operation
|
|
1762
|
+
};
|
|
1763
|
+
}
|
|
1764
|
+
/** Bumps a thread's updatedAt after a write to its messages. */
|
|
1765
|
+
#touchThread(threadId, check) {
|
|
1766
|
+
return goWithRunFence(this.service, check, this.service.entities.thread.update({
|
|
1767
|
+
entity: "thread",
|
|
1768
|
+
id: threadId
|
|
1769
|
+
}).set({ updatedAt: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
1770
|
+
}
|
|
1771
|
+
async deleteMessages(messageIds, options) {
|
|
1541
1772
|
if (!messageIds || messageIds.length === 0) return;
|
|
1542
1773
|
this.logger.debug("Deleting messages", { count: messageIds.length });
|
|
1774
|
+
const check = this.#runFenceCheck(options?.fence, "deleteMessages");
|
|
1543
1775
|
try {
|
|
1776
|
+
let deleted = 0;
|
|
1544
1777
|
const threadIds = /* @__PURE__ */ new Set();
|
|
1545
1778
|
const batchSize = 25;
|
|
1546
1779
|
for (let i = 0; i < messageIds.length; i += batchSize) {
|
|
@@ -1553,18 +1786,17 @@ var MemoryStorageDynamoDB = class extends _mastra_core_storage.MemoryStorage {
|
|
|
1553
1786
|
}));
|
|
1554
1787
|
for (const message of messagesToDelete) if (message) {
|
|
1555
1788
|
if (message.threadId) threadIds.add(message.threadId);
|
|
1556
|
-
await this.service.entities.message.delete({
|
|
1789
|
+
await goWithRunFence(this.service, check, this.service.entities.message.delete({
|
|
1557
1790
|
entity: "message",
|
|
1558
1791
|
id: message.id
|
|
1559
|
-
})
|
|
1792
|
+
}));
|
|
1793
|
+
deleted++;
|
|
1560
1794
|
}
|
|
1561
1795
|
}
|
|
1562
|
-
|
|
1563
|
-
for (const threadId of threadIds) await this
|
|
1564
|
-
entity: "thread",
|
|
1565
|
-
id: threadId
|
|
1566
|
-
}).set({ updatedAt: now }).go();
|
|
1796
|
+
if (deleted === 0) await assertRunFence(this.service, check);
|
|
1797
|
+
for (const threadId of threadIds) await this.#touchThread(threadId, check);
|
|
1567
1798
|
} catch (error) {
|
|
1799
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
1568
1800
|
throw new _mastra_core_error.MastraError({
|
|
1569
1801
|
id: (0, _mastra_core_storage.createStorageErrorId)("DYNAMODB", "DELETE_MESSAGES", "FAILED"),
|
|
1570
1802
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
@@ -1618,7 +1850,7 @@ var MemoryStorageDynamoDB = class extends _mastra_core_storage.MemoryStorage {
|
|
|
1618
1850
|
}, error);
|
|
1619
1851
|
}
|
|
1620
1852
|
}
|
|
1621
|
-
async saveThread({ thread }) {
|
|
1853
|
+
async saveThread({ thread, fence }) {
|
|
1622
1854
|
this.logger.debug("Saving thread", { threadId: thread.id });
|
|
1623
1855
|
const now = /* @__PURE__ */ new Date();
|
|
1624
1856
|
const threadData = {
|
|
@@ -1632,7 +1864,7 @@ var MemoryStorageDynamoDB = class extends _mastra_core_storage.MemoryStorage {
|
|
|
1632
1864
|
...getTtlProps("thread", this.ttlConfig)
|
|
1633
1865
|
};
|
|
1634
1866
|
try {
|
|
1635
|
-
await this.service.entities.thread.upsert(threadData)
|
|
1867
|
+
await goWithRunFence(this.service, this.#runFenceCheck(fence, "saveThread"), this.service.entities.thread.upsert(threadData));
|
|
1636
1868
|
return {
|
|
1637
1869
|
id: thread.id,
|
|
1638
1870
|
resourceId: thread.resourceId,
|
|
@@ -1642,6 +1874,7 @@ var MemoryStorageDynamoDB = class extends _mastra_core_storage.MemoryStorage {
|
|
|
1642
1874
|
metadata: thread.metadata
|
|
1643
1875
|
};
|
|
1644
1876
|
} catch (error) {
|
|
1877
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
1645
1878
|
throw new _mastra_core_error.MastraError({
|
|
1646
1879
|
id: (0, _mastra_core_storage.createStorageErrorId)("DYNAMODB", "SAVE_THREAD", "FAILED"),
|
|
1647
1880
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
@@ -1650,8 +1883,9 @@ var MemoryStorageDynamoDB = class extends _mastra_core_storage.MemoryStorage {
|
|
|
1650
1883
|
}, error);
|
|
1651
1884
|
}
|
|
1652
1885
|
}
|
|
1653
|
-
async updateThread({ id, title, metadata }) {
|
|
1886
|
+
async updateThread({ id, title, metadata, fence }) {
|
|
1654
1887
|
this.logger.debug("Updating thread", { threadId: id });
|
|
1888
|
+
const check = this.#runFenceCheck(fence, "updateThread");
|
|
1655
1889
|
try {
|
|
1656
1890
|
const existingThread = await this.getThreadById({ threadId: id });
|
|
1657
1891
|
if (!existingThread) throw new Error(`Thread not found: ${id}`);
|
|
@@ -1665,10 +1899,10 @@ var MemoryStorageDynamoDB = class extends _mastra_core_storage.MemoryStorage {
|
|
|
1665
1899
|
};
|
|
1666
1900
|
updateData.metadata = JSON.stringify(mergedMetadata);
|
|
1667
1901
|
}
|
|
1668
|
-
await this.service.entities.thread.update({
|
|
1902
|
+
await goWithRunFence(this.service, check, this.service.entities.thread.update({
|
|
1669
1903
|
entity: "thread",
|
|
1670
1904
|
id
|
|
1671
|
-
}).set(updateData)
|
|
1905
|
+
}).set(updateData));
|
|
1672
1906
|
return {
|
|
1673
1907
|
...existingThread,
|
|
1674
1908
|
title: title || existingThread.title,
|
|
@@ -1679,6 +1913,7 @@ var MemoryStorageDynamoDB = class extends _mastra_core_storage.MemoryStorage {
|
|
|
1679
1913
|
updatedAt: now
|
|
1680
1914
|
};
|
|
1681
1915
|
} catch (error) {
|
|
1916
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
1682
1917
|
throw new _mastra_core_error.MastraError({
|
|
1683
1918
|
id: (0, _mastra_core_storage.createStorageErrorId)("DYNAMODB", "UPDATE_THREAD", "FAILED"),
|
|
1684
1919
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
@@ -1926,6 +2161,7 @@ var MemoryStorageDynamoDB = class extends _mastra_core_storage.MemoryStorage {
|
|
|
1926
2161
|
...getTtlProps("message", this.ttlConfig)
|
|
1927
2162
|
};
|
|
1928
2163
|
});
|
|
2164
|
+
const check = this.#runFenceCheck(args.fence, "saveMessages");
|
|
1929
2165
|
try {
|
|
1930
2166
|
const savedMessageIds = [];
|
|
1931
2167
|
for (const messageData of messagesToSave) {
|
|
@@ -1934,14 +2170,14 @@ var MemoryStorageDynamoDB = class extends _mastra_core_storage.MemoryStorage {
|
|
|
1934
2170
|
throw new Error("Internal error: Missing entity property during saveMessages");
|
|
1935
2171
|
}
|
|
1936
2172
|
try {
|
|
1937
|
-
await this.service.entities.message.put(messageData)
|
|
2173
|
+
await goWithRunFence(this.service, check, this.service.entities.message.put(messageData));
|
|
1938
2174
|
savedMessageIds.push(messageData.id);
|
|
1939
2175
|
} catch (error) {
|
|
1940
|
-
for (const savedId of savedMessageIds) try {
|
|
1941
|
-
await this.service.entities.message.delete({
|
|
2176
|
+
for (const savedId of (0, _mastra_core_storage.isRunFenceConflictError)(error) ? [] : savedMessageIds) try {
|
|
2177
|
+
await goWithRunFence(this.service, check, this.service.entities.message.delete({
|
|
1942
2178
|
entity: "message",
|
|
1943
2179
|
id: savedId
|
|
1944
|
-
})
|
|
2180
|
+
}));
|
|
1945
2181
|
} catch (rollbackError) {
|
|
1946
2182
|
this.logger.error("Failed to rollback message during save error", {
|
|
1947
2183
|
messageId: savedId,
|
|
@@ -1951,12 +2187,10 @@ var MemoryStorageDynamoDB = class extends _mastra_core_storage.MemoryStorage {
|
|
|
1951
2187
|
throw error;
|
|
1952
2188
|
}
|
|
1953
2189
|
}
|
|
1954
|
-
await this
|
|
1955
|
-
entity: "thread",
|
|
1956
|
-
id: threadId
|
|
1957
|
-
}).set({ updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).go();
|
|
2190
|
+
await this.#touchThread(threadId, check);
|
|
1958
2191
|
return { messages: new _mastra_core_agent.MessageList().add(messages, "memory").get.all.db() };
|
|
1959
2192
|
} catch (error) {
|
|
2193
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
1960
2194
|
throw new _mastra_core_error.MastraError({
|
|
1961
2195
|
id: (0, _mastra_core_storage.createStorageErrorId)("DYNAMODB", "SAVE_MESSAGES", "FAILED"),
|
|
1962
2196
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
@@ -2110,6 +2344,7 @@ var MemoryStorageDynamoDB = class extends _mastra_core_storage.MemoryStorage {
|
|
|
2110
2344
|
const { messages } = args;
|
|
2111
2345
|
this.logger.debug("Updating messages", { count: messages.length });
|
|
2112
2346
|
if (!messages.length) return [];
|
|
2347
|
+
const check = this.#runFenceCheck(args.fence, "updateMessages");
|
|
2113
2348
|
const updatedMessages = [];
|
|
2114
2349
|
const affectedThreadIds = /* @__PURE__ */ new Set();
|
|
2115
2350
|
try {
|
|
@@ -2145,22 +2380,21 @@ var MemoryStorageDynamoDB = class extends _mastra_core_storage.MemoryStorage {
|
|
|
2145
2380
|
if ("parts" in updates.content && updates.content.parts !== void 0) newContent.parts = updates.content.parts;
|
|
2146
2381
|
updatePayload.content = JSON.stringify(newContent);
|
|
2147
2382
|
}
|
|
2148
|
-
await this.service.entities.message.update({
|
|
2383
|
+
await goWithRunFence(this.service, check, this.service.entities.message.update({
|
|
2149
2384
|
entity: "message",
|
|
2150
2385
|
id
|
|
2151
|
-
}).set(updatePayload)
|
|
2386
|
+
}).set(updatePayload));
|
|
2152
2387
|
const updatedMessage = await this.service.entities.message.get({
|
|
2153
2388
|
entity: "message",
|
|
2154
2389
|
id
|
|
2155
2390
|
}).go();
|
|
2156
2391
|
if (updatedMessage.data) updatedMessages.push(this.parseMessageData(updatedMessage.data));
|
|
2157
2392
|
}
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
id: threadId
|
|
2161
|
-
}).set({ updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).go();
|
|
2393
|
+
if (affectedThreadIds.size === 0) await assertRunFence(this.service, check);
|
|
2394
|
+
for (const threadId of affectedThreadIds) await this.#touchThread(threadId, check);
|
|
2162
2395
|
return updatedMessages;
|
|
2163
2396
|
} catch (error) {
|
|
2397
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
2164
2398
|
throw new _mastra_core_error.MastraError({
|
|
2165
2399
|
id: (0, _mastra_core_storage.createStorageErrorId)("DYNAMODB", "UPDATE_MESSAGES", "FAILED"),
|
|
2166
2400
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
@@ -2194,6 +2428,9 @@ var MemoryStorageDynamoDB = class extends _mastra_core_storage.MemoryStorage {
|
|
|
2194
2428
|
}
|
|
2195
2429
|
}
|
|
2196
2430
|
async saveResource({ resource }) {
|
|
2431
|
+
return this.#saveResource(resource, void 0);
|
|
2432
|
+
}
|
|
2433
|
+
async #saveResource(resource, check) {
|
|
2197
2434
|
this.logger.debug("Saving resource", { resourceId: resource.id });
|
|
2198
2435
|
const now = /* @__PURE__ */ new Date();
|
|
2199
2436
|
const resourceData = {
|
|
@@ -2206,7 +2443,7 @@ var MemoryStorageDynamoDB = class extends _mastra_core_storage.MemoryStorage {
|
|
|
2206
2443
|
...getTtlProps("resource", this.ttlConfig)
|
|
2207
2444
|
};
|
|
2208
2445
|
try {
|
|
2209
|
-
await this.service.entities.resource.upsert(resourceData)
|
|
2446
|
+
await goWithRunFence(this.service, check, this.service.entities.resource.upsert(resourceData));
|
|
2210
2447
|
return {
|
|
2211
2448
|
id: resource.id,
|
|
2212
2449
|
workingMemory: resource.workingMemory,
|
|
@@ -2215,6 +2452,7 @@ var MemoryStorageDynamoDB = class extends _mastra_core_storage.MemoryStorage {
|
|
|
2215
2452
|
updatedAt: now
|
|
2216
2453
|
};
|
|
2217
2454
|
} catch (error) {
|
|
2455
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
2218
2456
|
throw new _mastra_core_error.MastraError({
|
|
2219
2457
|
id: (0, _mastra_core_storage.createStorageErrorId)("DYNAMODB", "SAVE_RESOURCE", "FAILED"),
|
|
2220
2458
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
@@ -2223,8 +2461,9 @@ var MemoryStorageDynamoDB = class extends _mastra_core_storage.MemoryStorage {
|
|
|
2223
2461
|
}, error);
|
|
2224
2462
|
}
|
|
2225
2463
|
}
|
|
2226
|
-
async updateResource({ resourceId, workingMemory, metadata }) {
|
|
2464
|
+
async updateResource({ resourceId, workingMemory, metadata, fence }) {
|
|
2227
2465
|
this.logger.debug("Updating resource", { resourceId });
|
|
2466
|
+
const check = this.#runFenceCheck(fence, "updateResource");
|
|
2228
2467
|
try {
|
|
2229
2468
|
const existingResource = await this.getResourceById({ resourceId });
|
|
2230
2469
|
if (!existingResource) {
|
|
@@ -2235,7 +2474,7 @@ var MemoryStorageDynamoDB = class extends _mastra_core_storage.MemoryStorage {
|
|
|
2235
2474
|
createdAt: /* @__PURE__ */ new Date(),
|
|
2236
2475
|
updatedAt: /* @__PURE__ */ new Date()
|
|
2237
2476
|
};
|
|
2238
|
-
return this
|
|
2477
|
+
return this.#saveResource(newResource, check);
|
|
2239
2478
|
}
|
|
2240
2479
|
const now = /* @__PURE__ */ new Date();
|
|
2241
2480
|
const updateData = { updatedAt: now.toISOString() };
|
|
@@ -2247,10 +2486,10 @@ var MemoryStorageDynamoDB = class extends _mastra_core_storage.MemoryStorage {
|
|
|
2247
2486
|
};
|
|
2248
2487
|
updateData.metadata = JSON.stringify(mergedMetadata);
|
|
2249
2488
|
}
|
|
2250
|
-
await this.service.entities.resource.update({
|
|
2489
|
+
await goWithRunFence(this.service, check, this.service.entities.resource.update({
|
|
2251
2490
|
entity: "resource",
|
|
2252
2491
|
id: resourceId
|
|
2253
|
-
}).set(updateData)
|
|
2492
|
+
}).set(updateData));
|
|
2254
2493
|
return {
|
|
2255
2494
|
...existingResource,
|
|
2256
2495
|
workingMemory: workingMemory !== void 0 ? workingMemory : existingResource.workingMemory,
|
|
@@ -2261,6 +2500,7 @@ var MemoryStorageDynamoDB = class extends _mastra_core_storage.MemoryStorage {
|
|
|
2261
2500
|
updatedAt: now
|
|
2262
2501
|
};
|
|
2263
2502
|
} catch (error) {
|
|
2503
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
2264
2504
|
throw new _mastra_core_error.MastraError({
|
|
2265
2505
|
id: (0, _mastra_core_storage.createStorageErrorId)("DYNAMODB", "UPDATE_RESOURCE", "FAILED"),
|
|
2266
2506
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
@@ -2582,6 +2822,15 @@ function formatWorkflowRun(snapshotData) {
|
|
|
2582
2822
|
resourceId: snapshotData.resourceId
|
|
2583
2823
|
};
|
|
2584
2824
|
}
|
|
2825
|
+
function toRunOwnershipRecord(item) {
|
|
2826
|
+
return {
|
|
2827
|
+
runId: item.run_id,
|
|
2828
|
+
generation: Number(item.generation),
|
|
2829
|
+
ownerId: item.ownerId,
|
|
2830
|
+
leaseExpiresAt: item.leaseExpiresAt === void 0 ? null : new Date(item.leaseExpiresAt),
|
|
2831
|
+
live: item.leaseExpiresAt !== void 0 && item.leaseExpiresAt > Date.now()
|
|
2832
|
+
};
|
|
2833
|
+
}
|
|
2585
2834
|
const MAX_RETRIES = 5;
|
|
2586
2835
|
const BASE_DELAY_MS = 50;
|
|
2587
2836
|
var WorkflowStorageDynamoDB = class extends _mastra_core_storage.WorkflowsStorage {
|
|
@@ -2596,8 +2845,126 @@ var WorkflowStorageDynamoDB = class extends _mastra_core_storage.WorkflowsStorag
|
|
|
2596
2845
|
supportsConcurrentUpdates() {
|
|
2597
2846
|
return true;
|
|
2598
2847
|
}
|
|
2848
|
+
supportsRunFencing() {
|
|
2849
|
+
return true;
|
|
2850
|
+
}
|
|
2599
2851
|
async dangerouslyClearAll() {
|
|
2600
2852
|
await deleteTableData(this.service, _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT);
|
|
2853
|
+
await deleteTableData(this.service, _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS);
|
|
2854
|
+
}
|
|
2855
|
+
#runFenceCheck(fence, runId, operation) {
|
|
2856
|
+
const resolved = (0, _mastra_core_storage.resolveRunFence)(this, fence, runId);
|
|
2857
|
+
return resolved && {
|
|
2858
|
+
claims: "workflow_run_owner",
|
|
2859
|
+
fence: resolved,
|
|
2860
|
+
operation
|
|
2861
|
+
};
|
|
2862
|
+
}
|
|
2863
|
+
#runOwnerKey(runId) {
|
|
2864
|
+
return {
|
|
2865
|
+
entity: "workflow_run_owner",
|
|
2866
|
+
run_id: runId
|
|
2867
|
+
};
|
|
2868
|
+
}
|
|
2869
|
+
async #readRunOwner(runId) {
|
|
2870
|
+
const { data } = await this.service.entities.workflow_run_owner.get(this.#runOwnerKey(runId)).go({ consistent: true });
|
|
2871
|
+
return data ? toRunOwnershipRecord(data) : null;
|
|
2872
|
+
}
|
|
2873
|
+
#ownershipError(operation, runId, error) {
|
|
2874
|
+
return new _mastra_core_error.MastraError({
|
|
2875
|
+
id: (0, _mastra_core_storage.createStorageErrorId)("DYNAMODB", operation, "FAILED"),
|
|
2876
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
2877
|
+
category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
|
|
2878
|
+
details: { runId }
|
|
2879
|
+
}, error);
|
|
2880
|
+
}
|
|
2881
|
+
async claimRunOwnership({ runId, ownerId, leaseMs, force, expectedGeneration }) {
|
|
2882
|
+
try {
|
|
2883
|
+
const owners = this.service.entities.workflow_run_owner;
|
|
2884
|
+
const now = Date.now();
|
|
2885
|
+
if (!expectedGeneration) {
|
|
2886
|
+
try {
|
|
2887
|
+
const item = {
|
|
2888
|
+
...this.#runOwnerKey(runId),
|
|
2889
|
+
generation: Math.max(1, now),
|
|
2890
|
+
ownerId,
|
|
2891
|
+
leaseExpiresAt: now + leaseMs,
|
|
2892
|
+
updatedAt: now
|
|
2893
|
+
};
|
|
2894
|
+
await retryOnTransactionConflict(() => owners.create(item).go());
|
|
2895
|
+
return {
|
|
2896
|
+
acquired: true,
|
|
2897
|
+
record: toRunOwnershipRecord(item)
|
|
2898
|
+
};
|
|
2899
|
+
} catch (error) {
|
|
2900
|
+
if (!this.isConditionalCheckFailed(error)) throw error;
|
|
2901
|
+
}
|
|
2902
|
+
if (expectedGeneration === 0) return {
|
|
2903
|
+
acquired: false,
|
|
2904
|
+
record: await this.#readRunOwner(runId)
|
|
2905
|
+
};
|
|
2906
|
+
}
|
|
2907
|
+
try {
|
|
2908
|
+
const { data } = await retryOnTransactionConflict(() => owners.patch(this.#runOwnerKey(runId)).set({
|
|
2909
|
+
ownerId,
|
|
2910
|
+
leaseExpiresAt: now + leaseMs,
|
|
2911
|
+
updatedAt: now
|
|
2912
|
+
}).add({ generation: 1 }).where((attr, op) => [expectedGeneration !== void 0 ? op.eq(attr.generation, expectedGeneration) : "", force ? "" : `(${op.notExists(attr.leaseExpiresAt)} OR ${op.lte(attr.leaseExpiresAt, now)})`].filter(Boolean).join(" AND ")).go({ response: "all_new" }));
|
|
2913
|
+
return {
|
|
2914
|
+
acquired: true,
|
|
2915
|
+
record: toRunOwnershipRecord(data)
|
|
2916
|
+
};
|
|
2917
|
+
} catch (error) {
|
|
2918
|
+
if (!this.isConditionalCheckFailed(error)) throw error;
|
|
2919
|
+
return {
|
|
2920
|
+
acquired: false,
|
|
2921
|
+
record: await this.#readRunOwner(runId)
|
|
2922
|
+
};
|
|
2923
|
+
}
|
|
2924
|
+
} catch (error) {
|
|
2925
|
+
throw this.#ownershipError("CLAIM_RUN_OWNERSHIP", runId, error);
|
|
2926
|
+
}
|
|
2927
|
+
}
|
|
2928
|
+
async renewRunOwnership({ leaseMs, ...fence }) {
|
|
2929
|
+
try {
|
|
2930
|
+
const { data } = await retryOnTransactionConflict(() => {
|
|
2931
|
+
const now = Date.now();
|
|
2932
|
+
return this.service.entities.workflow_run_owner.patch(this.#runOwnerKey(fence.runId)).set({
|
|
2933
|
+
leaseExpiresAt: now + leaseMs,
|
|
2934
|
+
updatedAt: now
|
|
2935
|
+
}).where((attr, op) => `${op.eq(attr.generation, fence.generation)} AND ${op.eq(attr.ownerId, fence.ownerId)} AND ${op.exists(attr.leaseExpiresAt)}`).go({ response: "all_new" });
|
|
2936
|
+
});
|
|
2937
|
+
return {
|
|
2938
|
+
renewed: true,
|
|
2939
|
+
record: toRunOwnershipRecord(data)
|
|
2940
|
+
};
|
|
2941
|
+
} catch (error) {
|
|
2942
|
+
if (!this.isConditionalCheckFailed(error)) throw this.#ownershipError("RENEW_RUN_OWNERSHIP", fence.runId, error);
|
|
2943
|
+
try {
|
|
2944
|
+
return {
|
|
2945
|
+
renewed: false,
|
|
2946
|
+
record: await this.#readRunOwner(fence.runId)
|
|
2947
|
+
};
|
|
2948
|
+
} catch (readError) {
|
|
2949
|
+
throw this.#ownershipError("RENEW_RUN_OWNERSHIP", fence.runId, readError);
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
2952
|
+
}
|
|
2953
|
+
async releaseRunOwnership(fence) {
|
|
2954
|
+
try {
|
|
2955
|
+
await retryOnTransactionConflict(() => this.service.entities.workflow_run_owner.patch(this.#runOwnerKey(fence.runId)).set({ updatedAt: Date.now() }).remove(["leaseExpiresAt"]).where((attr, op) => `${op.eq(attr.generation, fence.generation)} AND ${op.eq(attr.ownerId, fence.ownerId)}`).go());
|
|
2956
|
+
return true;
|
|
2957
|
+
} catch (error) {
|
|
2958
|
+
if (this.isConditionalCheckFailed(error)) return false;
|
|
2959
|
+
throw this.#ownershipError("RELEASE_RUN_OWNERSHIP", fence.runId, error);
|
|
2960
|
+
}
|
|
2961
|
+
}
|
|
2962
|
+
async getRunOwnership({ runId }) {
|
|
2963
|
+
try {
|
|
2964
|
+
return await this.#readRunOwner(runId);
|
|
2965
|
+
} catch (error) {
|
|
2966
|
+
throw this.#ownershipError("GET_RUN_OWNERSHIP", runId, error);
|
|
2967
|
+
}
|
|
2601
2968
|
}
|
|
2602
2969
|
/**
|
|
2603
2970
|
* Helper to check if an error is a DynamoDB conditional check failure
|
|
@@ -2622,7 +2989,8 @@ var WorkflowStorageDynamoDB = class extends _mastra_core_storage.WorkflowsStorag
|
|
|
2622
2989
|
const jitter = Math.random() * backoff * .5;
|
|
2623
2990
|
await new Promise((resolve) => setTimeout(resolve, backoff + jitter));
|
|
2624
2991
|
}
|
|
2625
|
-
async updateWorkflowResults({ workflowName, runId, stepId, result, requestContext, state }) {
|
|
2992
|
+
async updateWorkflowResults({ workflowName, runId, stepId, result, requestContext, state, fence }) {
|
|
2993
|
+
const check = this.#runFenceCheck(fence, runId, "updateWorkflowResults");
|
|
2626
2994
|
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) try {
|
|
2627
2995
|
const existingRecord = await this.service.entities.workflow_snapshot.get({
|
|
2628
2996
|
entity: "workflow_snapshot",
|
|
@@ -2665,8 +3033,8 @@ var WorkflowStorageDynamoDB = class extends _mastra_core_storage.WorkflowsStorag
|
|
|
2665
3033
|
updatedAt: now.toISOString(),
|
|
2666
3034
|
...getTtlProps("workflow_snapshot", this.ttlConfig)
|
|
2667
3035
|
};
|
|
2668
|
-
|
|
2669
|
-
|
|
3036
|
+
const write = previousUpdatedAt ? this.service.entities.workflow_snapshot.upsert(data).where((attr, op) => op.eq(attr.updatedAt, previousUpdatedAt)) : this.service.entities.workflow_snapshot.create(data).where((attr, op) => op.notExists(attr.run_id));
|
|
3037
|
+
await goWithRunFence(this.service, check, write);
|
|
2670
3038
|
return snapshot.context;
|
|
2671
3039
|
} catch (error) {
|
|
2672
3040
|
if (this.isConditionalCheckFailed(error)) {
|
|
@@ -2699,19 +3067,21 @@ var WorkflowStorageDynamoDB = class extends _mastra_core_storage.WorkflowsStorag
|
|
|
2699
3067
|
}
|
|
2700
3068
|
}, /* @__PURE__ */ new Error("Max retries exceeded for optimistic locking"));
|
|
2701
3069
|
}
|
|
2702
|
-
async updateWorkflowState({ workflowName, runId, opts }) {
|
|
3070
|
+
async updateWorkflowState({ workflowName, runId, opts, fence }) {
|
|
3071
|
+
const check = this.#runFenceCheck(fence, runId, "updateWorkflowState");
|
|
2703
3072
|
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) try {
|
|
2704
3073
|
const existingRecord = await this.service.entities.workflow_snapshot.get({
|
|
2705
3074
|
entity: "workflow_snapshot",
|
|
2706
3075
|
workflow_name: workflowName,
|
|
2707
3076
|
run_id: runId
|
|
2708
3077
|
}).go();
|
|
2709
|
-
|
|
2710
|
-
const existingSnapshot = existingRecord.data.snapshot;
|
|
2711
|
-
if (!existingSnapshot || !existingSnapshot.context) return;
|
|
2712
|
-
const previousUpdatedAt = existingRecord.data.updatedAt;
|
|
3078
|
+
const existingSnapshot = existingRecord.data?.snapshot;
|
|
2713
3079
|
const { expectedStatus, ...state } = opts;
|
|
2714
|
-
if (!(0, _mastra_core_storage.matchesExpectedWorkflowStatus)(existingSnapshot.status, expectedStatus))
|
|
3080
|
+
if (!existingSnapshot?.context || !(0, _mastra_core_storage.matchesExpectedWorkflowStatus)(existingSnapshot.status, expectedStatus)) {
|
|
3081
|
+
await assertRunFence(this.service, check);
|
|
3082
|
+
return;
|
|
3083
|
+
}
|
|
3084
|
+
const previousUpdatedAt = existingRecord.data.updatedAt;
|
|
2715
3085
|
const updatedSnapshot = {
|
|
2716
3086
|
...existingSnapshot,
|
|
2717
3087
|
...state
|
|
@@ -2727,7 +3097,7 @@ var WorkflowStorageDynamoDB = class extends _mastra_core_storage.WorkflowsStorag
|
|
|
2727
3097
|
resourceId: existingRecord.data.resourceId,
|
|
2728
3098
|
...getTtlProps("workflow_snapshot", this.ttlConfig)
|
|
2729
3099
|
};
|
|
2730
|
-
await this.service.entities.workflow_snapshot.upsert(data).where((attr, op) => op.eq(attr.updatedAt, previousUpdatedAt))
|
|
3100
|
+
await goWithRunFence(this.service, check, this.service.entities.workflow_snapshot.upsert(data).where((attr, op) => op.eq(attr.updatedAt, previousUpdatedAt)));
|
|
2731
3101
|
return updatedSnapshot;
|
|
2732
3102
|
} catch (error) {
|
|
2733
3103
|
if (this.isConditionalCheckFailed(error)) {
|
|
@@ -2758,11 +3128,12 @@ var WorkflowStorageDynamoDB = class extends _mastra_core_storage.WorkflowsStorag
|
|
|
2758
3128
|
}
|
|
2759
3129
|
}, /* @__PURE__ */ new Error("Max retries exceeded for optimistic locking"));
|
|
2760
3130
|
}
|
|
2761
|
-
async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, createdAt, updatedAt }) {
|
|
3131
|
+
async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, createdAt, updatedAt, fence }) {
|
|
2762
3132
|
this.logger.debug("Persisting workflow snapshot", {
|
|
2763
3133
|
workflowName,
|
|
2764
3134
|
runId
|
|
2765
3135
|
});
|
|
3136
|
+
const check = this.#runFenceCheck(fence, runId, "persistWorkflowSnapshot");
|
|
2766
3137
|
try {
|
|
2767
3138
|
const now = /* @__PURE__ */ new Date();
|
|
2768
3139
|
const createdAtValue = (createdAt ?? now).toISOString();
|
|
@@ -2778,10 +3149,10 @@ var WorkflowStorageDynamoDB = class extends _mastra_core_storage.WorkflowsStorag
|
|
|
2778
3149
|
...getTtlProps("workflow_snapshot", this.ttlConfig)
|
|
2779
3150
|
};
|
|
2780
3151
|
try {
|
|
2781
|
-
await this.service.entities.workflow_snapshot.create(data).where((attr, op) => op.notExists(attr.run_id))
|
|
3152
|
+
await goWithRunFence(this.service, check, this.service.entities.workflow_snapshot.create(data).where((attr, op) => op.notExists(attr.run_id)));
|
|
2782
3153
|
} catch (error) {
|
|
2783
3154
|
if (!this.isConditionalCheckFailed(error)) throw error;
|
|
2784
|
-
await this.service.entities.workflow_snapshot.update({
|
|
3155
|
+
await goWithRunFence(this.service, check, this.service.entities.workflow_snapshot.update({
|
|
2785
3156
|
entity: "workflow_snapshot",
|
|
2786
3157
|
workflow_name: workflowName,
|
|
2787
3158
|
run_id: runId
|
|
@@ -2790,9 +3161,10 @@ var WorkflowStorageDynamoDB = class extends _mastra_core_storage.WorkflowsStorag
|
|
|
2790
3161
|
updatedAt: updatedAtValue,
|
|
2791
3162
|
resourceId,
|
|
2792
3163
|
...getTtlProps("workflow_snapshot", this.ttlConfig)
|
|
2793
|
-
})
|
|
3164
|
+
}));
|
|
2794
3165
|
}
|
|
2795
3166
|
} catch (error) {
|
|
3167
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
2796
3168
|
throw new _mastra_core_error.MastraError({
|
|
2797
3169
|
id: (0, _mastra_core_storage.createStorageErrorId)("DYNAMODB", "PERSIST_WORKFLOW_SNAPSHOT", "FAILED"),
|
|
2798
3170
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
@@ -2951,18 +3323,19 @@ var WorkflowStorageDynamoDB = class extends _mastra_core_storage.WorkflowsStorag
|
|
|
2951
3323
|
}, error);
|
|
2952
3324
|
}
|
|
2953
3325
|
}
|
|
2954
|
-
async deleteWorkflowRunById({ runId, workflowName }) {
|
|
3326
|
+
async deleteWorkflowRunById({ runId, workflowName, fence }) {
|
|
2955
3327
|
this.logger.debug("Deleting workflow run by ID", {
|
|
2956
3328
|
runId,
|
|
2957
3329
|
workflowName
|
|
2958
3330
|
});
|
|
2959
3331
|
try {
|
|
2960
|
-
await this.service.entities.workflow_snapshot.delete({
|
|
3332
|
+
await goWithRunFence(this.service, this.#runFenceCheck(fence, runId, "deleteWorkflowRunById"), this.service.entities.workflow_snapshot.delete({
|
|
2961
3333
|
entity: "workflow_snapshot",
|
|
2962
3334
|
workflow_name: workflowName,
|
|
2963
3335
|
run_id: runId
|
|
2964
|
-
})
|
|
3336
|
+
}));
|
|
2965
3337
|
} catch (error) {
|
|
3338
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
2966
3339
|
throw new _mastra_core_error.MastraError({
|
|
2967
3340
|
id: (0, _mastra_core_storage.createStorageErrorId)("DYNAMODB", "DELETE_WORKFLOW_RUN_BY_ID", "FAILED"),
|
|
2968
3341
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|