@mastra/spanner 1.9.3 → 1.9.4-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/index.cjs +325 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +326 -20
- package/dist/index.js.map +1 -1
- package/dist/storage/db/run-fencing.d.ts +23 -0
- package/dist/storage/db/run-fencing.d.ts.map +1 -0
- package/dist/storage/domains/memory/index.d.ts +20 -9
- package/dist/storage/domains/memory/index.d.ts.map +1 -1
- package/dist/storage/domains/workflows/index.d.ts +22 -7
- package/dist/storage/domains/workflows/index.d.ts.map +1 -1
- package/package.json +4 -4
package/dist/docs/SKILL.md
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -6657,6 +6657,59 @@ var MCPServersSpanner = class MCPServersSpanner extends _mastra_core_storage.MCP
|
|
|
6657
6657
|
}
|
|
6658
6658
|
};
|
|
6659
6659
|
//#endregion
|
|
6660
|
+
//#region src/storage/db/run-fencing.ts
|
|
6661
|
+
/** Epoch milliseconds on the database clock, read when the statement runs. */
|
|
6662
|
+
const DB_NOW_MS = "UNIX_MILLIS(CURRENT_TIMESTAMP())";
|
|
6663
|
+
/**
|
|
6664
|
+
* The check half of a fenced write; call it first inside the write's
|
|
6665
|
+
* read-write transaction. Reads in a read-write transaction lock the rows they
|
|
6666
|
+
* return until commit, so a claim that updates the row either waits for the
|
|
6667
|
+
* write to commit or aborts it, and the retried write sees the new claim.
|
|
6668
|
+
*/
|
|
6669
|
+
async function assertRunFence(tx, claimsTable, fence, operation) {
|
|
6670
|
+
const [rows] = await tx.run({
|
|
6671
|
+
sql: `SELECT generation, ${quoteIdent("ownerId", "column name")} AS ownerId
|
|
6672
|
+
FROM ${quoteIdent(claimsTable, "table name")}
|
|
6673
|
+
WHERE ${quoteIdent("runId", "column name")} = @runId`,
|
|
6674
|
+
params: { runId: fence.runId },
|
|
6675
|
+
json: true
|
|
6676
|
+
});
|
|
6677
|
+
const row = rows[0];
|
|
6678
|
+
if (!(0, _mastra_core_storage.matchesRunFence)(row ? {
|
|
6679
|
+
generation: Number(row.generation),
|
|
6680
|
+
ownerId: String(row.ownerId)
|
|
6681
|
+
} : null, fence)) throw new _mastra_core_storage.RunFenceConflictError(fence, operation);
|
|
6682
|
+
}
|
|
6683
|
+
/**
|
|
6684
|
+
* Runs `fn` in a read-write transaction and commits it, retrying the whole
|
|
6685
|
+
* transaction when Spanner aborts it for conflicting with another one.
|
|
6686
|
+
*/
|
|
6687
|
+
function inTransaction(db, fn) {
|
|
6688
|
+
return db.runWithAbortRetry(() => db.database.runTransactionAsync(async (tx) => {
|
|
6689
|
+
try {
|
|
6690
|
+
const result = await fn(tx);
|
|
6691
|
+
await tx.commit();
|
|
6692
|
+
return result;
|
|
6693
|
+
} catch (err) {
|
|
6694
|
+
await tx.rollback().catch((rollbackErr) => {
|
|
6695
|
+
throw new AggregateError([err, rollbackErr], "Transaction and rollback both failed");
|
|
6696
|
+
});
|
|
6697
|
+
throw err;
|
|
6698
|
+
}
|
|
6699
|
+
}));
|
|
6700
|
+
}
|
|
6701
|
+
/**
|
|
6702
|
+
* Runs `write` directly when it carries no fence, or in a transaction behind
|
|
6703
|
+
* the fence check when it does.
|
|
6704
|
+
*/
|
|
6705
|
+
function withRunFence(db, claimsTable, fence, operation, write) {
|
|
6706
|
+
if (!fence) return write(void 0);
|
|
6707
|
+
return inTransaction(db, async (tx) => {
|
|
6708
|
+
await assertRunFence(tx, claimsTable, fence, operation);
|
|
6709
|
+
return write(tx);
|
|
6710
|
+
});
|
|
6711
|
+
}
|
|
6712
|
+
//#endregion
|
|
6660
6713
|
//#region src/storage/domains/memory/index.ts
|
|
6661
6714
|
function buildSpannerMessageMetadataFilter(metadataFilter) {
|
|
6662
6715
|
if (!metadataFilter) return {
|
|
@@ -6720,7 +6773,7 @@ var MemorySpanner = class MemorySpanner extends _mastra_core_storage.MemoryStora
|
|
|
6720
6773
|
this.skipDefaultIndexes = skipDefaultIndexes;
|
|
6721
6774
|
this.indexes = indexes?.filter((idx) => MemorySpanner.MANAGED_TABLES.includes(idx.table));
|
|
6722
6775
|
}
|
|
6723
|
-
/** Creates the threads/messages/resources tables and any indexes. */
|
|
6776
|
+
/** Creates the threads/messages/resources and run fence tables and any indexes. */
|
|
6724
6777
|
async init() {
|
|
6725
6778
|
await this.db.createTable({
|
|
6726
6779
|
tableName: _mastra_core_storage.TABLE_THREADS,
|
|
@@ -6734,6 +6787,10 @@ var MemorySpanner = class MemorySpanner extends _mastra_core_storage.MemoryStora
|
|
|
6734
6787
|
tableName: _mastra_core_storage.TABLE_RESOURCES,
|
|
6735
6788
|
schema: _mastra_core_storage.TABLE_SCHEMAS[_mastra_core_storage.TABLE_RESOURCES]
|
|
6736
6789
|
});
|
|
6790
|
+
await this.db.createTable({
|
|
6791
|
+
tableName: _mastra_core_storage.TABLE_MEMORY_RUN_FENCES,
|
|
6792
|
+
schema: _mastra_core_storage.RUN_FENCING_TABLE_SCHEMAS[_mastra_core_storage.TABLE_MEMORY_RUN_FENCES]
|
|
6793
|
+
});
|
|
6737
6794
|
await this.createDefaultIndexes();
|
|
6738
6795
|
await this.createCustomIndexes();
|
|
6739
6796
|
}
|
|
@@ -6759,11 +6816,92 @@ var MemorySpanner = class MemorySpanner extends _mastra_core_storage.MemoryStora
|
|
|
6759
6816
|
if (!this.indexes || this.indexes.length === 0) return;
|
|
6760
6817
|
await this.db.createIndexes(this.indexes);
|
|
6761
6818
|
}
|
|
6762
|
-
/** Removes every row from threads/messages/resources tables. Intended for tests. */
|
|
6819
|
+
/** Removes every row from threads/messages/resources and run fence tables. Intended for tests. */
|
|
6763
6820
|
async dangerouslyClearAll() {
|
|
6764
6821
|
await this.db.clearTable({ tableName: _mastra_core_storage.TABLE_MESSAGES });
|
|
6765
6822
|
await this.db.clearTable({ tableName: _mastra_core_storage.TABLE_THREADS });
|
|
6766
6823
|
await this.db.clearTable({ tableName: _mastra_core_storage.TABLE_RESOURCES });
|
|
6824
|
+
await this.db.clearTable({ tableName: _mastra_core_storage.TABLE_MEMORY_RUN_FENCES });
|
|
6825
|
+
}
|
|
6826
|
+
supportsRunFencing() {
|
|
6827
|
+
return true;
|
|
6828
|
+
}
|
|
6829
|
+
async raiseRunFence(fence) {
|
|
6830
|
+
const table = quoteIdent(_mastra_core_storage.TABLE_MEMORY_RUN_FENCES, "table name");
|
|
6831
|
+
const runIdCol = quoteIdent("runId", "column name");
|
|
6832
|
+
const ownerIdCol = quoteIdent("ownerId", "column name");
|
|
6833
|
+
const retiredAtCol = quoteIdent("retiredAt", "column name");
|
|
6834
|
+
try {
|
|
6835
|
+
return await inTransaction(this.db, async (tx) => {
|
|
6836
|
+
const [rows] = await tx.run({
|
|
6837
|
+
sql: `SELECT generation, ${ownerIdCol} AS ownerId FROM ${table} WHERE ${runIdCol} = @runId`,
|
|
6838
|
+
params: { runId: fence.runId },
|
|
6839
|
+
json: true
|
|
6840
|
+
});
|
|
6841
|
+
const row = rows[0];
|
|
6842
|
+
if (!row) {
|
|
6843
|
+
await tx.runUpdate({
|
|
6844
|
+
sql: `INSERT INTO ${table} (${runIdCol}, generation, ${ownerIdCol}) VALUES (@runId, @generation, @ownerId)`,
|
|
6845
|
+
params: {
|
|
6846
|
+
runId: fence.runId,
|
|
6847
|
+
generation: fence.generation,
|
|
6848
|
+
ownerId: fence.ownerId
|
|
6849
|
+
}
|
|
6850
|
+
});
|
|
6851
|
+
return true;
|
|
6852
|
+
}
|
|
6853
|
+
const generation = Number(row.generation);
|
|
6854
|
+
const raises = generation < fence.generation;
|
|
6855
|
+
const reaffirms = generation === fence.generation && String(row.ownerId) === fence.ownerId;
|
|
6856
|
+
if (!raises && !reaffirms) return false;
|
|
6857
|
+
await tx.runUpdate({
|
|
6858
|
+
sql: `UPDATE ${table} SET generation = @generation, ${ownerIdCol} = @ownerId, ${retiredAtCol} = NULL
|
|
6859
|
+
WHERE ${runIdCol} = @runId`,
|
|
6860
|
+
params: {
|
|
6861
|
+
runId: fence.runId,
|
|
6862
|
+
generation: fence.generation,
|
|
6863
|
+
ownerId: fence.ownerId
|
|
6864
|
+
}
|
|
6865
|
+
});
|
|
6866
|
+
return true;
|
|
6867
|
+
});
|
|
6868
|
+
} catch (error) {
|
|
6869
|
+
throw new _mastra_core_error.MastraError({
|
|
6870
|
+
id: (0, _mastra_core_storage.createStorageErrorId)("SPANNER", "RAISE_RUN_FENCE", "FAILED"),
|
|
6871
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
6872
|
+
category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
|
|
6873
|
+
details: { runId: fence.runId }
|
|
6874
|
+
}, error);
|
|
6875
|
+
}
|
|
6876
|
+
}
|
|
6877
|
+
async retireRunFence(fence) {
|
|
6878
|
+
const table = quoteIdent(_mastra_core_storage.TABLE_MEMORY_RUN_FENCES, "table name");
|
|
6879
|
+
try {
|
|
6880
|
+
return await inTransaction(this.db, async (tx) => {
|
|
6881
|
+
const [count] = await tx.runUpdate({
|
|
6882
|
+
sql: `UPDATE ${table} SET ${quoteIdent("retiredAt", "column name")} = ${DB_NOW_MS}
|
|
6883
|
+
WHERE ${quoteIdent("runId", "column name")} = @runId AND generation = @generation
|
|
6884
|
+
AND ${quoteIdent("ownerId", "column name")} = @ownerId`,
|
|
6885
|
+
params: {
|
|
6886
|
+
runId: fence.runId,
|
|
6887
|
+
generation: fence.generation,
|
|
6888
|
+
ownerId: fence.ownerId
|
|
6889
|
+
}
|
|
6890
|
+
});
|
|
6891
|
+
return count === 1;
|
|
6892
|
+
});
|
|
6893
|
+
} catch (error) {
|
|
6894
|
+
throw new _mastra_core_error.MastraError({
|
|
6895
|
+
id: (0, _mastra_core_storage.createStorageErrorId)("SPANNER", "RETIRE_RUN_FENCE", "FAILED"),
|
|
6896
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
6897
|
+
category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
|
|
6898
|
+
details: { runId: fence.runId }
|
|
6899
|
+
}, error);
|
|
6900
|
+
}
|
|
6901
|
+
}
|
|
6902
|
+
/** The fence check for a write already running in a read-write transaction. */
|
|
6903
|
+
async #assertFence(tx, fence, operation) {
|
|
6904
|
+
if (fence) await assertRunFence(tx, _mastra_core_storage.TABLE_MEMORY_RUN_FENCES, fence, operation);
|
|
6767
6905
|
}
|
|
6768
6906
|
/**
|
|
6769
6907
|
* Fetches a thread row by id, or `null` when absent.
|
|
@@ -6932,9 +7070,9 @@ var MemorySpanner = class MemorySpanner extends _mastra_core_storage.MemoryStora
|
|
|
6932
7070
|
}
|
|
6933
7071
|
}
|
|
6934
7072
|
/** Upserts a thread row by id (`INSERT OR UPDATE` semantics). */
|
|
6935
|
-
async saveThread({ thread }) {
|
|
7073
|
+
async saveThread({ thread, fence }) {
|
|
6936
7074
|
try {
|
|
6937
|
-
await this.db.upsert({
|
|
7075
|
+
await withRunFence(this.db, _mastra_core_storage.TABLE_MEMORY_RUN_FENCES, (0, _mastra_core_storage.resolveRunFence)(this, fence), "saveThread", (tx) => this.db.upsert({
|
|
6938
7076
|
tableName: _mastra_core_storage.TABLE_THREADS,
|
|
6939
7077
|
record: {
|
|
6940
7078
|
id: thread.id,
|
|
@@ -6943,10 +7081,12 @@ var MemorySpanner = class MemorySpanner extends _mastra_core_storage.MemoryStora
|
|
|
6943
7081
|
metadata: thread.metadata ?? {},
|
|
6944
7082
|
createdAt: thread.createdAt,
|
|
6945
7083
|
updatedAt: thread.updatedAt
|
|
6946
|
-
}
|
|
6947
|
-
|
|
7084
|
+
},
|
|
7085
|
+
transaction: tx
|
|
7086
|
+
}));
|
|
6948
7087
|
return thread;
|
|
6949
7088
|
} catch (error) {
|
|
7089
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
6950
7090
|
throw new _mastra_core_error.MastraError({
|
|
6951
7091
|
id: (0, _mastra_core_storage.createStorageErrorId)("SPANNER", "SAVE_THREAD", "FAILED"),
|
|
6952
7092
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
@@ -6956,7 +7096,8 @@ var MemorySpanner = class MemorySpanner extends _mastra_core_storage.MemoryStora
|
|
|
6956
7096
|
}
|
|
6957
7097
|
}
|
|
6958
7098
|
/** Updates a thread's title and merges the metadata payload. */
|
|
6959
|
-
async updateThread({ id, title, metadata }) {
|
|
7099
|
+
async updateThread({ id, title, metadata, fence: explicitFence }) {
|
|
7100
|
+
const fence = (0, _mastra_core_storage.resolveRunFence)(this, explicitFence);
|
|
6960
7101
|
const tableThreads = quoteIdent(_mastra_core_storage.TABLE_THREADS, "table name");
|
|
6961
7102
|
const now = /* @__PURE__ */ new Date();
|
|
6962
7103
|
let merged = {};
|
|
@@ -6964,6 +7105,7 @@ var MemorySpanner = class MemorySpanner extends _mastra_core_storage.MemoryStora
|
|
|
6964
7105
|
try {
|
|
6965
7106
|
await this.db.runWithAbortRetry(() => this.database.runTransactionAsync(async (tx) => {
|
|
6966
7107
|
try {
|
|
7108
|
+
await this.#assertFence(tx, fence, "updateThread");
|
|
6967
7109
|
const [rows] = await tx.run({
|
|
6968
7110
|
sql: `SELECT * FROM ${tableThreads} WHERE id = @id LIMIT 1`,
|
|
6969
7111
|
params: { id },
|
|
@@ -7430,8 +7572,9 @@ var MemorySpanner = class MemorySpanner extends _mastra_core_storage.MemoryStora
|
|
|
7430
7572
|
}
|
|
7431
7573
|
}
|
|
7432
7574
|
/** Upserts a batch of messages and bumps every touched thread's `updatedAt` in a single transaction. */
|
|
7433
|
-
async saveMessages({ messages }) {
|
|
7575
|
+
async saveMessages({ messages, fence: explicitFence }) {
|
|
7434
7576
|
if (messages.length === 0) return { messages: [] };
|
|
7577
|
+
const fence = (0, _mastra_core_storage.resolveRunFence)(this, explicitFence);
|
|
7435
7578
|
const threadIds = /* @__PURE__ */ new Set();
|
|
7436
7579
|
for (const message of messages) {
|
|
7437
7580
|
if (!message.threadId) throw new _mastra_core_error.MastraError({
|
|
@@ -7457,6 +7600,7 @@ var MemorySpanner = class MemorySpanner extends _mastra_core_storage.MemoryStora
|
|
|
7457
7600
|
try {
|
|
7458
7601
|
await this.db.runWithAbortRetry(() => this.database.runTransactionAsync(async (tx) => {
|
|
7459
7602
|
try {
|
|
7603
|
+
await this.#assertFence(tx, fence, "saveMessages");
|
|
7460
7604
|
for (const message of messages) {
|
|
7461
7605
|
if (!message.resourceId) throw new Error("Expected to find a resourceId for message");
|
|
7462
7606
|
const sql = `INSERT OR UPDATE INTO ${tableMessages} (id, ${quoteIdent("thread_id", "column name")}, content, ${quoteIdent("createdAt", "column name")}, role, type, ${quoteIdent("resourceId", "column name")})
|
|
@@ -7494,6 +7638,7 @@ var MemorySpanner = class MemorySpanner extends _mastra_core_storage.MemoryStora
|
|
|
7494
7638
|
}));
|
|
7495
7639
|
return { messages: new _mastra_core_agent.MessageList().add(messages, "memory").get.all.db() };
|
|
7496
7640
|
} catch (error) {
|
|
7641
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
7497
7642
|
throw new _mastra_core_error.MastraError({
|
|
7498
7643
|
id: (0, _mastra_core_storage.createStorageErrorId)("SPANNER", "SAVE_MESSAGES", "FAILED"),
|
|
7499
7644
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
@@ -7503,8 +7648,9 @@ var MemorySpanner = class MemorySpanner extends _mastra_core_storage.MemoryStora
|
|
|
7503
7648
|
}
|
|
7504
7649
|
}
|
|
7505
7650
|
/** Merges partial updates onto existing messages and bumps affected thread `updatedAt`. */
|
|
7506
|
-
async updateMessages({ messages }) {
|
|
7651
|
+
async updateMessages({ messages, fence: explicitFence }) {
|
|
7507
7652
|
if (!messages || messages.length === 0) return [];
|
|
7653
|
+
const fence = (0, _mastra_core_storage.resolveRunFence)(this, explicitFence);
|
|
7508
7654
|
const messageIds = messages.map((m) => m.id);
|
|
7509
7655
|
const params = {};
|
|
7510
7656
|
const placeholders = messageIds.map((id, i) => {
|
|
@@ -7522,6 +7668,7 @@ var MemorySpanner = class MemorySpanner extends _mastra_core_storage.MemoryStora
|
|
|
7522
7668
|
try {
|
|
7523
7669
|
await this.db.runWithAbortRetry(() => this.database.runTransactionAsync(async (tx) => {
|
|
7524
7670
|
try {
|
|
7671
|
+
await this.#assertFence(tx, fence, "updateMessages");
|
|
7525
7672
|
const [existingRows] = await tx.run({
|
|
7526
7673
|
sql: selectSql,
|
|
7527
7674
|
params,
|
|
@@ -7597,6 +7744,7 @@ var MemorySpanner = class MemorySpanner extends _mastra_core_storage.MemoryStora
|
|
|
7597
7744
|
}
|
|
7598
7745
|
}));
|
|
7599
7746
|
} catch (error) {
|
|
7747
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
7600
7748
|
throw new _mastra_core_error.MastraError({
|
|
7601
7749
|
id: (0, _mastra_core_storage.createStorageErrorId)("SPANNER", "UPDATE_MESSAGES", "FAILED"),
|
|
7602
7750
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
@@ -7620,8 +7768,9 @@ var MemorySpanner = class MemorySpanner extends _mastra_core_storage.MemoryStora
|
|
|
7620
7768
|
});
|
|
7621
7769
|
}
|
|
7622
7770
|
/** Deletes a batch of messages and bumps the affected threads' `updatedAt`. */
|
|
7623
|
-
async deleteMessages(messageIds) {
|
|
7771
|
+
async deleteMessages(messageIds, options) {
|
|
7624
7772
|
if (!messageIds || messageIds.length === 0) return;
|
|
7773
|
+
const fence = (0, _mastra_core_storage.resolveRunFence)(this, options?.fence);
|
|
7625
7774
|
try {
|
|
7626
7775
|
const messageTableName = quoteIdent(_mastra_core_storage.TABLE_MESSAGES, "table name");
|
|
7627
7776
|
const threadTableName = quoteIdent(_mastra_core_storage.TABLE_THREADS, "table name");
|
|
@@ -7639,6 +7788,7 @@ var MemorySpanner = class MemorySpanner extends _mastra_core_storage.MemoryStora
|
|
|
7639
7788
|
const threadIds = threadRows.map((r) => r.thread_id).filter(Boolean);
|
|
7640
7789
|
await this.db.runWithAbortRetry(() => this.database.runTransactionAsync(async (tx) => {
|
|
7641
7790
|
try {
|
|
7791
|
+
await this.#assertFence(tx, fence, "deleteMessages");
|
|
7642
7792
|
await tx.runUpdate({
|
|
7643
7793
|
sql: `DELETE FROM ${messageTableName} WHERE id IN (${placeholders})`,
|
|
7644
7794
|
params
|
|
@@ -7656,6 +7806,7 @@ var MemorySpanner = class MemorySpanner extends _mastra_core_storage.MemoryStora
|
|
|
7656
7806
|
}
|
|
7657
7807
|
}));
|
|
7658
7808
|
} catch (error) {
|
|
7809
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
7659
7810
|
throw new _mastra_core_error.MastraError({
|
|
7660
7811
|
id: (0, _mastra_core_storage.createStorageErrorId)("SPANNER", "DELETE_MESSAGES", "FAILED"),
|
|
7661
7812
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
@@ -7714,13 +7865,15 @@ var MemorySpanner = class MemorySpanner extends _mastra_core_storage.MemoryStora
|
|
|
7714
7865
|
return resource;
|
|
7715
7866
|
}
|
|
7716
7867
|
/** Updates a resource's `workingMemory` and merges its `metadata`. */
|
|
7717
|
-
async updateResource({ resourceId, workingMemory, metadata }) {
|
|
7868
|
+
async updateResource({ resourceId, workingMemory, metadata, fence: explicitFence }) {
|
|
7869
|
+
const fence = (0, _mastra_core_storage.resolveRunFence)(this, explicitFence);
|
|
7718
7870
|
const tableResources = quoteIdent(_mastra_core_storage.TABLE_RESOURCES, "table name");
|
|
7719
7871
|
const now = /* @__PURE__ */ new Date();
|
|
7720
7872
|
let updated = null;
|
|
7721
7873
|
try {
|
|
7722
7874
|
await this.db.runWithAbortRetry(() => this.database.runTransactionAsync(async (tx) => {
|
|
7723
7875
|
try {
|
|
7876
|
+
await this.#assertFence(tx, fence, "updateResource");
|
|
7724
7877
|
const [rows] = await tx.run({
|
|
7725
7878
|
sql: `SELECT * FROM ${tableResources} WHERE id = @id LIMIT 1`,
|
|
7726
7879
|
params: { id: resourceId },
|
|
@@ -7784,6 +7937,7 @@ var MemorySpanner = class MemorySpanner extends _mastra_core_storage.MemoryStora
|
|
|
7784
7937
|
}));
|
|
7785
7938
|
return updated;
|
|
7786
7939
|
} catch (error) {
|
|
7940
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
7787
7941
|
const mastraError = new _mastra_core_error.MastraError({
|
|
7788
7942
|
id: (0, _mastra_core_storage.createStorageErrorId)("SPANNER", "UPDATE_RESOURCE", "FAILED"),
|
|
7789
7943
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
@@ -12291,6 +12445,30 @@ var WorkflowDefinitionsSpanner = class WorkflowDefinitionsSpanner extends _mastr
|
|
|
12291
12445
|
};
|
|
12292
12446
|
//#endregion
|
|
12293
12447
|
//#region src/storage/domains/workflows/index.ts
|
|
12448
|
+
const RUN_OWNERS_TABLE = quoteIdent(_mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS, "table name");
|
|
12449
|
+
const RUN_ID = quoteIdent("runId", "column name");
|
|
12450
|
+
const OWNER_ID = quoteIdent("ownerId", "column name");
|
|
12451
|
+
const LEASE_EXPIRES_AT = quoteIdent("leaseExpiresAt", "column name");
|
|
12452
|
+
const UPDATED_AT = quoteIdent("updatedAt", "column name");
|
|
12453
|
+
/** Reads a run's ownership record, judging the lease against the database clock. */
|
|
12454
|
+
async function readRunOwner(q, runId) {
|
|
12455
|
+
const [rows] = await q.run({
|
|
12456
|
+
sql: `SELECT generation, ${OWNER_ID} AS ownerId, ${LEASE_EXPIRES_AT} AS leaseExpiresAt, ${DB_NOW_MS} AS nowMs
|
|
12457
|
+
FROM ${RUN_OWNERS_TABLE} WHERE ${RUN_ID} = @runId`,
|
|
12458
|
+
params: { runId },
|
|
12459
|
+
json: true
|
|
12460
|
+
});
|
|
12461
|
+
const row = rows[0];
|
|
12462
|
+
if (!row) return null;
|
|
12463
|
+
const leaseExpiresAt = row.leaseExpiresAt === null ? null : Number(row.leaseExpiresAt);
|
|
12464
|
+
return {
|
|
12465
|
+
runId,
|
|
12466
|
+
generation: Number(row.generation),
|
|
12467
|
+
ownerId: String(row.ownerId),
|
|
12468
|
+
leaseExpiresAt: leaseExpiresAt === null ? null : new Date(leaseExpiresAt),
|
|
12469
|
+
live: leaseExpiresAt !== null && leaseExpiresAt > Number(row.nowMs)
|
|
12470
|
+
};
|
|
12471
|
+
}
|
|
12294
12472
|
/**
|
|
12295
12473
|
* Spanner-backed storage for workflow run snapshots, including persistence,
|
|
12296
12474
|
* incremental updates, and listing of historical runs.
|
|
@@ -12371,12 +12549,19 @@ var WorkflowsSpanner = class WorkflowsSpanner extends _mastra_core_storage.Workf
|
|
|
12371
12549
|
const indexes = this.getDefaultIndexDefinitions().filter((idx) => hasStatus || !idx.columns.some((c) => c.startsWith("snapshotStatus")));
|
|
12372
12550
|
await this.db.createIndexes(indexes);
|
|
12373
12551
|
}
|
|
12374
|
-
/**
|
|
12552
|
+
/**
|
|
12553
|
+
* Creates the workflow snapshot and run ownership tables, the snapshotStatus
|
|
12554
|
+
* generated column, and indexes.
|
|
12555
|
+
*/
|
|
12375
12556
|
async init() {
|
|
12376
12557
|
await this.db.createTable({
|
|
12377
12558
|
tableName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
|
|
12378
12559
|
schema: _mastra_core_storage.TABLE_SCHEMAS[_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT]
|
|
12379
12560
|
});
|
|
12561
|
+
await this.db.createTable({
|
|
12562
|
+
tableName: _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS,
|
|
12563
|
+
schema: _mastra_core_storage.RUN_FENCING_TABLE_SCHEMAS[_mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS]
|
|
12564
|
+
});
|
|
12380
12565
|
await this.ensureStatusColumn();
|
|
12381
12566
|
await this.createDefaultIndexes();
|
|
12382
12567
|
await this.createCustomIndexes();
|
|
@@ -12425,9 +12610,121 @@ var WorkflowsSpanner = class WorkflowsSpanner extends _mastra_core_storage.Workf
|
|
|
12425
12610
|
if (!this.indexes || this.indexes.length === 0) return;
|
|
12426
12611
|
await this.db.createIndexes(this.indexes);
|
|
12427
12612
|
}
|
|
12428
|
-
/** Removes every workflow snapshot row. Intended for tests. */
|
|
12613
|
+
/** Removes every workflow snapshot and run ownership row. Intended for tests. */
|
|
12429
12614
|
async dangerouslyClearAll() {
|
|
12430
12615
|
await this.db.clearTable({ tableName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT });
|
|
12616
|
+
await this.db.clearTable({ tableName: _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS });
|
|
12617
|
+
}
|
|
12618
|
+
supportsRunFencing() {
|
|
12619
|
+
return true;
|
|
12620
|
+
}
|
|
12621
|
+
#ownershipError(operation, runId, error) {
|
|
12622
|
+
return new _mastra_core_error.MastraError({
|
|
12623
|
+
id: (0, _mastra_core_storage.createStorageErrorId)("SPANNER", operation, "FAILED"),
|
|
12624
|
+
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
12625
|
+
category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
|
|
12626
|
+
details: { runId }
|
|
12627
|
+
}, error);
|
|
12628
|
+
}
|
|
12629
|
+
async claimRunOwnership({ runId, ownerId, leaseMs, force, expectedGeneration }) {
|
|
12630
|
+
try {
|
|
12631
|
+
return await inTransaction(this.db, async (tx) => {
|
|
12632
|
+
const existing = await readRunOwner(tx, runId);
|
|
12633
|
+
if (!existing) {
|
|
12634
|
+
if (expectedGeneration !== void 0 && expectedGeneration !== 0) return {
|
|
12635
|
+
acquired: false,
|
|
12636
|
+
record: null
|
|
12637
|
+
};
|
|
12638
|
+
await tx.runUpdate({
|
|
12639
|
+
sql: `INSERT INTO ${RUN_OWNERS_TABLE} (${RUN_ID}, generation, ${OWNER_ID}, ${LEASE_EXPIRES_AT}, ${UPDATED_AT})
|
|
12640
|
+
VALUES (@runId, ${DB_NOW_MS}, @ownerId, ${DB_NOW_MS} + @leaseMs, ${DB_NOW_MS})`,
|
|
12641
|
+
params: {
|
|
12642
|
+
runId,
|
|
12643
|
+
ownerId,
|
|
12644
|
+
leaseMs
|
|
12645
|
+
}
|
|
12646
|
+
});
|
|
12647
|
+
return {
|
|
12648
|
+
acquired: true,
|
|
12649
|
+
record: await readRunOwner(tx, runId)
|
|
12650
|
+
};
|
|
12651
|
+
}
|
|
12652
|
+
if (!(expectedGeneration === void 0 || existing.generation === expectedGeneration) || !force && existing.live) return {
|
|
12653
|
+
acquired: false,
|
|
12654
|
+
record: existing
|
|
12655
|
+
};
|
|
12656
|
+
await tx.runUpdate({
|
|
12657
|
+
sql: `UPDATE ${RUN_OWNERS_TABLE}
|
|
12658
|
+
SET generation = generation + 1, ${OWNER_ID} = @ownerId, ${LEASE_EXPIRES_AT} = ${DB_NOW_MS} + @leaseMs,
|
|
12659
|
+
${UPDATED_AT} = ${DB_NOW_MS}
|
|
12660
|
+
WHERE ${RUN_ID} = @runId`,
|
|
12661
|
+
params: {
|
|
12662
|
+
runId,
|
|
12663
|
+
ownerId,
|
|
12664
|
+
leaseMs
|
|
12665
|
+
}
|
|
12666
|
+
});
|
|
12667
|
+
return {
|
|
12668
|
+
acquired: true,
|
|
12669
|
+
record: await readRunOwner(tx, runId)
|
|
12670
|
+
};
|
|
12671
|
+
});
|
|
12672
|
+
} catch (error) {
|
|
12673
|
+
throw this.#ownershipError("CLAIM_RUN_OWNERSHIP", runId, error);
|
|
12674
|
+
}
|
|
12675
|
+
}
|
|
12676
|
+
async renewRunOwnership({ leaseMs, ...fence }) {
|
|
12677
|
+
try {
|
|
12678
|
+
return await inTransaction(this.db, async (tx) => {
|
|
12679
|
+
const [count] = await tx.runUpdate({
|
|
12680
|
+
sql: `UPDATE ${RUN_OWNERS_TABLE}
|
|
12681
|
+
SET ${LEASE_EXPIRES_AT} = ${DB_NOW_MS} + @leaseMs, ${UPDATED_AT} = ${DB_NOW_MS}
|
|
12682
|
+
WHERE ${RUN_ID} = @runId AND generation = @generation AND ${OWNER_ID} = @ownerId
|
|
12683
|
+
AND ${LEASE_EXPIRES_AT} IS NOT NULL`,
|
|
12684
|
+
params: {
|
|
12685
|
+
leaseMs,
|
|
12686
|
+
runId: fence.runId,
|
|
12687
|
+
generation: fence.generation,
|
|
12688
|
+
ownerId: fence.ownerId
|
|
12689
|
+
}
|
|
12690
|
+
});
|
|
12691
|
+
const record = await readRunOwner(tx, fence.runId);
|
|
12692
|
+
return count === 1 && record ? {
|
|
12693
|
+
renewed: true,
|
|
12694
|
+
record
|
|
12695
|
+
} : {
|
|
12696
|
+
renewed: false,
|
|
12697
|
+
record
|
|
12698
|
+
};
|
|
12699
|
+
});
|
|
12700
|
+
} catch (error) {
|
|
12701
|
+
throw this.#ownershipError("RENEW_RUN_OWNERSHIP", fence.runId, error);
|
|
12702
|
+
}
|
|
12703
|
+
}
|
|
12704
|
+
async releaseRunOwnership(fence) {
|
|
12705
|
+
try {
|
|
12706
|
+
return await inTransaction(this.db, async (tx) => {
|
|
12707
|
+
const [count] = await tx.runUpdate({
|
|
12708
|
+
sql: `UPDATE ${RUN_OWNERS_TABLE} SET ${LEASE_EXPIRES_AT} = NULL, ${UPDATED_AT} = ${DB_NOW_MS}
|
|
12709
|
+
WHERE ${RUN_ID} = @runId AND generation = @generation AND ${OWNER_ID} = @ownerId`,
|
|
12710
|
+
params: {
|
|
12711
|
+
runId: fence.runId,
|
|
12712
|
+
generation: fence.generation,
|
|
12713
|
+
ownerId: fence.ownerId
|
|
12714
|
+
}
|
|
12715
|
+
});
|
|
12716
|
+
return count === 1;
|
|
12717
|
+
});
|
|
12718
|
+
} catch (error) {
|
|
12719
|
+
throw this.#ownershipError("RELEASE_RUN_OWNERSHIP", fence.runId, error);
|
|
12720
|
+
}
|
|
12721
|
+
}
|
|
12722
|
+
async getRunOwnership({ runId }) {
|
|
12723
|
+
try {
|
|
12724
|
+
return await readRunOwner(this.database, runId);
|
|
12725
|
+
} catch (error) {
|
|
12726
|
+
throw this.#ownershipError("GET_RUN_OWNERSHIP", runId, error);
|
|
12727
|
+
}
|
|
12431
12728
|
}
|
|
12432
12729
|
/** Decodes a raw Spanner snapshot row into the public `WorkflowRun` shape. */
|
|
12433
12730
|
parseWorkflowRun(row) {
|
|
@@ -12451,12 +12748,14 @@ var WorkflowsSpanner = class WorkflowsSpanner extends _mastra_core_storage.Workf
|
|
|
12451
12748
|
};
|
|
12452
12749
|
}
|
|
12453
12750
|
/** Upserts a workflow run snapshot, preserving the original `createdAt` on update. */
|
|
12454
|
-
async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, createdAt, updatedAt }) {
|
|
12751
|
+
async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, createdAt, updatedAt, fence: explicitFence }) {
|
|
12752
|
+
const fence = (0, _mastra_core_storage.resolveRunFence)(this, explicitFence, runId);
|
|
12455
12753
|
const now = /* @__PURE__ */ new Date();
|
|
12456
12754
|
const table = quoteIdent(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT, "table name");
|
|
12457
12755
|
try {
|
|
12458
12756
|
await this.db.runWithAbortRetry(() => this.database.runTransactionAsync(async (tx) => {
|
|
12459
12757
|
try {
|
|
12758
|
+
if (fence) await assertRunFence(tx, _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS, fence, "persistWorkflowSnapshot");
|
|
12460
12759
|
const [rows] = await tx.run({
|
|
12461
12760
|
sql: `SELECT ${quoteIdent("createdAt", "column name")}, ${quoteIdent("resourceId", "column name")} FROM ${table}
|
|
12462
12761
|
WHERE workflow_name = @workflow_name AND run_id = @run_id`,
|
|
@@ -12490,6 +12789,7 @@ var WorkflowsSpanner = class WorkflowsSpanner extends _mastra_core_storage.Workf
|
|
|
12490
12789
|
}
|
|
12491
12790
|
}));
|
|
12492
12791
|
} catch (error) {
|
|
12792
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
12493
12793
|
throw new _mastra_core_error.MastraError({
|
|
12494
12794
|
id: (0, _mastra_core_storage.createStorageErrorId)("SPANNER", "PERSIST_WORKFLOW_SNAPSHOT", "FAILED"),
|
|
12495
12795
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
@@ -12526,12 +12826,14 @@ var WorkflowsSpanner = class WorkflowsSpanner extends _mastra_core_storage.Workf
|
|
|
12526
12826
|
}
|
|
12527
12827
|
}
|
|
12528
12828
|
/** Merges a step result and request-context delta into a snapshot inside an RW transaction. */
|
|
12529
|
-
async updateWorkflowResults({ workflowName, runId, stepId, result, requestContext, state }) {
|
|
12829
|
+
async updateWorkflowResults({ workflowName, runId, stepId, result, requestContext, state, fence: explicitFence }) {
|
|
12830
|
+
const fence = (0, _mastra_core_storage.resolveRunFence)(this, explicitFence, runId);
|
|
12530
12831
|
const table = quoteIdent(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT, "table name");
|
|
12531
12832
|
try {
|
|
12532
12833
|
let mergedContext = {};
|
|
12533
12834
|
await this.db.runWithAbortRetry(() => this.database.runTransactionAsync(async (tx) => {
|
|
12534
12835
|
try {
|
|
12836
|
+
if (fence) await assertRunFence(tx, _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS, fence, "updateWorkflowResults");
|
|
12535
12837
|
const [rows] = await tx.run({
|
|
12536
12838
|
sql: `SELECT snapshot, ${quoteIdent("createdAt", "column name")} FROM ${table}
|
|
12537
12839
|
WHERE workflow_name = @workflow_name AND run_id = @run_id`,
|
|
@@ -12591,6 +12893,7 @@ var WorkflowsSpanner = class WorkflowsSpanner extends _mastra_core_storage.Workf
|
|
|
12591
12893
|
}));
|
|
12592
12894
|
return mergedContext;
|
|
12593
12895
|
} catch (error) {
|
|
12896
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
12594
12897
|
throw new _mastra_core_error.MastraError({
|
|
12595
12898
|
id: (0, _mastra_core_storage.createStorageErrorId)("SPANNER", "UPDATE_WORKFLOW_RESULTS", "FAILED"),
|
|
12596
12899
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|
|
@@ -12604,13 +12907,15 @@ var WorkflowsSpanner = class WorkflowsSpanner extends _mastra_core_storage.Workf
|
|
|
12604
12907
|
}
|
|
12605
12908
|
}
|
|
12606
12909
|
/** Applies a top-level snapshot patch (e.g. status, value) inside an RW transaction. */
|
|
12607
|
-
async updateWorkflowState({ workflowName, runId, opts }) {
|
|
12910
|
+
async updateWorkflowState({ workflowName, runId, opts, fence: explicitFence }) {
|
|
12911
|
+
const fence = (0, _mastra_core_storage.resolveRunFence)(this, explicitFence, runId);
|
|
12608
12912
|
const table = quoteIdent(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT, "table name");
|
|
12609
12913
|
try {
|
|
12610
12914
|
let updated;
|
|
12611
12915
|
await this.db.runWithAbortRetry(() => this.database.runTransactionAsync(async (tx) => {
|
|
12612
12916
|
updated = void 0;
|
|
12613
12917
|
try {
|
|
12918
|
+
if (fence) await assertRunFence(tx, _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS, fence, "updateWorkflowState");
|
|
12614
12919
|
const [rows] = await tx.run({
|
|
12615
12920
|
sql: `SELECT snapshot FROM ${table} WHERE workflow_name = @workflow_name AND run_id = @run_id`,
|
|
12616
12921
|
params: {
|
|
@@ -12721,17 +13026,18 @@ var WorkflowsSpanner = class WorkflowsSpanner extends _mastra_core_storage.Workf
|
|
|
12721
13026
|
}
|
|
12722
13027
|
}
|
|
12723
13028
|
/** Deletes the snapshot row for a `(workflowName, runId)` pair. */
|
|
12724
|
-
async deleteWorkflowRunById({ runId, workflowName }) {
|
|
13029
|
+
async deleteWorkflowRunById({ runId, workflowName, fence }) {
|
|
12725
13030
|
const table = quoteIdent(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT, "table name");
|
|
12726
13031
|
try {
|
|
12727
|
-
await this.db.runDml({
|
|
13032
|
+
await withRunFence(this.db, _mastra_core_storage.TABLE_WORKFLOW_RUN_OWNERS, (0, _mastra_core_storage.resolveRunFence)(this, fence, runId), "deleteWorkflowRunById", (tx) => this.db.runDml({
|
|
12728
13033
|
sql: `DELETE FROM ${table} WHERE workflow_name = @workflow_name AND run_id = @run_id`,
|
|
12729
13034
|
params: {
|
|
12730
13035
|
workflow_name: workflowName,
|
|
12731
13036
|
run_id: runId
|
|
12732
13037
|
}
|
|
12733
|
-
});
|
|
13038
|
+
}, tx));
|
|
12734
13039
|
} catch (error) {
|
|
13040
|
+
if ((0, _mastra_core_storage.isRunFenceConflictError)(error)) throw error;
|
|
12735
13041
|
throw new _mastra_core_error.MastraError({
|
|
12736
13042
|
id: (0, _mastra_core_storage.createStorageErrorId)("SPANNER", "DELETE_WORKFLOW_RUN_BY_ID", "FAILED"),
|
|
12737
13043
|
domain: _mastra_core_error.ErrorDomain.STORAGE,
|