@mastra/pg 1.31.0-alpha.3 → 1.31.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/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ErrorCategory, ErrorDomain, MastraError } from "@mastra/core/error";
2
2
  import * as coreStorage from "@mastra/core/storage";
3
- import { AgentsStorage, BRANCH_SPAN_TYPES, BackgroundTasksStorage, BlobStore, ChannelsStorage, DATASETS_SCHEMA, DATASET_ITEMS_SCHEMA, DATASET_VERSIONS_SCHEMA, DatasetsStorage, EXPERIMENTS_SCHEMA, EXPERIMENT_RESULTS_SCHEMA, EntityType, ExperimentsStorage, FactoryStorage, FavoritesStorage, KNOWLEDGE_SEMANTIC_OUTBOX_SCHEMA, KnowledgeConflictError, KnowledgeNotFoundError, KnowledgeStorage, MCPClientsStorage, MCPServersStorage, METRIC_DISTINCT_COLUMNS, MastraCompositeStore, MemoryStorage, NotificationsStorage, OBSERVATIONAL_MEMORY_TABLE_SCHEMA, ObservabilityStorage, PromptBlocksStorage, SchedulesStorage, ScorerDefinitionsStorage, ScoresStorage, SkillsStorage, TABLE_AGENTS, TABLE_AGENT_VERSIONS, TABLE_BACKGROUND_TASKS, TABLE_CHANNEL_CONFIG, TABLE_CHANNEL_INSTALLATIONS, TABLE_CONFIGS, TABLE_DATASETS, TABLE_DATASET_ITEMS, TABLE_DATASET_VERSIONS, TABLE_EXPERIMENTS, TABLE_EXPERIMENT_RESULTS, TABLE_FAVORITES, TABLE_KNOWLEDGE_ACTIVITY, TABLE_KNOWLEDGE_MENTIONS, TABLE_KNOWLEDGE_NODES, TABLE_KNOWLEDGE_RECORDS, TABLE_KNOWLEDGE_SEMANTIC_OUTBOX, TABLE_MCP_CLIENTS, TABLE_MCP_CLIENT_VERSIONS, TABLE_MCP_SERVERS, TABLE_MCP_SERVER_VERSIONS, TABLE_MESSAGES, TABLE_NOTIFICATIONS, TABLE_PROMPT_BLOCKS, TABLE_PROMPT_BLOCK_VERSIONS, TABLE_RESOURCES, TABLE_SCHEDULES, TABLE_SCHEDULE_TRIGGERS, TABLE_SCHEMAS, TABLE_SCORERS, TABLE_SCORER_DEFINITIONS, TABLE_SCORER_DEFINITION_VERSIONS, TABLE_SKILLS, TABLE_SKILL_BLOBS, TABLE_SKILL_VERSIONS, TABLE_SPANS, TABLE_THREADS, TABLE_THREAD_STATE, TABLE_TOOL_PROVIDER_CONNECTIONS, TABLE_WORKFLOW_DEFINITIONS, TABLE_WORKFLOW_SNAPSHOT, TABLE_WORKSPACES, TABLE_WORKSPACE_VERSIONS, ThreadStateStorage, ToolProviderConnectionsStorage, TraceStatus, UniqueViolationError, WorkflowDefinitionsStorage, WorkflowsStorage, WorkspacesStorage, calculatePagination, canonicalizeKnowledgeScope, createKnowledgeUlid, createStorageErrorId, createVectorErrorId, ensureDate, executeRetentionPrune, getDefaultValue, getSqlType, hasErrorCode, isKnowledgeScopeVisible, knowledgeScopeKey, knowledgeSemanticDocumentId, knowledgeSemanticIdempotencyKey, listBranchesArgsSchema, listFeedbackArgsSchema, listLogsArgsSchema, listMetricsArgsSchema, listScoresArgsSchema, listTracesArgsSchema, matchesExpectedWorkflowStatus, mergeWorkflowStepResult, normalizePerPage, normalizeScheduleTarget, parseDuration, parseKnowledgeNodeCursor, parseKnowledgeWikilinks, resolveListOrderBy, resolveRetentionTargets, retentionCutoffMs, runRetentionBatches, safelyParseJSON, storageMessageMatchesMetadataFilter, toTraceSpans, transformScoreRow, validateStorageMetadataFilter } from "@mastra/core/storage";
3
+ import { AgentsStorage, BRANCH_SPAN_TYPES, BackgroundTasksStorage, BlobStore, ChannelsStorage, DATASETS_SCHEMA, DATASET_ITEMS_SCHEMA, DATASET_VERSIONS_SCHEMA, DatasetsStorage, EXPERIMENTS_SCHEMA, EXPERIMENT_RESULTS_SCHEMA, EntityType, ExperimentsStorage, FactoryStorage, FavoritesStorage, KNOWLEDGE_SEMANTIC_OUTBOX_SCHEMA, KnowledgeConflictError, KnowledgeNotFoundError, KnowledgeStorage, MCPClientsStorage, MCPServersStorage, METRIC_DISTINCT_COLUMNS, MastraCompositeStore, MemoryStorage, NotificationsStorage, OBSERVATIONAL_MEMORY_TABLE_SCHEMA, ObservabilityStorage, PromptBlocksStorage, RUN_FENCING_TABLE_SCHEMAS, RunFenceConflictError, SchedulesStorage, ScorerDefinitionsStorage, ScoresStorage, SkillsStorage, TABLE_AGENTS, TABLE_AGENT_VERSIONS, TABLE_BACKGROUND_TASKS, TABLE_CHANNEL_CONFIG, TABLE_CHANNEL_INSTALLATIONS, TABLE_CONFIGS, TABLE_DATASETS, TABLE_DATASET_ITEMS, TABLE_DATASET_VERSIONS, TABLE_EXPERIMENTS, TABLE_EXPERIMENT_RESULTS, TABLE_FAVORITES, TABLE_KNOWLEDGE_ACTIVITY, TABLE_KNOWLEDGE_MENTIONS, TABLE_KNOWLEDGE_NODES, TABLE_KNOWLEDGE_RECORDS, TABLE_KNOWLEDGE_SEMANTIC_OUTBOX, TABLE_MCP_CLIENTS, TABLE_MCP_CLIENT_VERSIONS, TABLE_MCP_SERVERS, TABLE_MCP_SERVER_VERSIONS, TABLE_MEMORY_RUN_FENCES, TABLE_MESSAGES, TABLE_NOTIFICATIONS, TABLE_PROMPT_BLOCKS, TABLE_PROMPT_BLOCK_VERSIONS, TABLE_RESOURCES, TABLE_SCHEDULES, TABLE_SCHEDULE_TRIGGERS, TABLE_SCHEMAS, TABLE_SCORERS, TABLE_SCORER_DEFINITIONS, TABLE_SCORER_DEFINITION_VERSIONS, TABLE_SKILLS, TABLE_SKILL_BLOBS, TABLE_SKILL_VERSIONS, TABLE_SPANS, TABLE_THREADS, TABLE_THREAD_STATE, TABLE_TOOL_PROVIDER_CONNECTIONS, TABLE_WORKFLOW_DEFINITIONS, TABLE_WORKFLOW_RUN_OWNERS, TABLE_WORKFLOW_SNAPSHOT, TABLE_WORKSPACES, TABLE_WORKSPACE_VERSIONS, ThreadStateStorage, ToolProviderConnectionsStorage, TraceStatus, UniqueViolationError, WorkflowDefinitionsStorage, WorkflowsStorage, WorkspacesStorage, calculatePagination, canonicalizeKnowledgeScope, createKnowledgeUlid, createStorageErrorId, createVectorErrorId, ensureDate, executeRetentionPrune, getDefaultValue, getSqlType, hasErrorCode, isKnowledgeScopeVisible, isRunFenceConflictError, knowledgeScopeKey, knowledgeSemanticDocumentId, knowledgeSemanticIdempotencyKey, listBranchesArgsSchema, listFeedbackArgsSchema, listLogsArgsSchema, listMetricsArgsSchema, listScoresArgsSchema, listTracesArgsSchema, matchesExpectedWorkflowStatus, matchesRunFence, mergeWorkflowStepResult, normalizePerPage, normalizeScheduleTarget, parseDuration, parseKnowledgeNodeCursor, parseKnowledgeWikilinks, resolveListOrderBy, resolveRetentionTargets, resolveRunFence, retentionCutoffMs, runRetentionBatches, safelyParseJSON, storageMessageMatchesMetadataFilter, toTraceSpans, transformScoreRow, validateStorageMetadataFilter } from "@mastra/core/storage";
4
4
  import { parseFieldKey, parseSqlIdentifier } from "@mastra/core/utils";
5
5
  import { MastraVector, validateTopK, validateUpsertInput } from "@mastra/core/vector";
6
6
  import { Mutex } from "async-mutex";
@@ -3189,9 +3189,10 @@ var PgDB = class extends MastraBase {
3189
3189
  }
3190
3190
  await flush();
3191
3191
  }
3192
- async insert({ tableName, record }) {
3192
+ /** `client` lets a caller run the insert inside its own transaction. */
3193
+ async insert({ tableName, record }, client = this.client) {
3193
3194
  try {
3194
- await this.executeInsert(this.client, {
3195
+ await this.executeInsert(client, {
3195
3196
  tableName,
3196
3197
  record
3197
3198
  });
@@ -4025,13 +4026,15 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
4025
4026
  indexName: tableName,
4026
4027
  schemaName: getSchemaName$6(this.schemaName)
4027
4028
  });
4029
+ const parsedColumn = `"${parseSqlIdentifier(column, "column name")}"`;
4028
4030
  const sql = `
4029
4031
  DELETE FROM ${fullTableName}
4030
4032
  WHERE ctid IN (
4031
4033
  SELECT ctid FROM ${fullTableName}
4032
- WHERE ${`"${parseSqlIdentifier(column, "column name")}"`} < $1
4034
+ WHERE ${parsedColumn} < $1
4033
4035
  LIMIT $2
4034
4036
  )
4037
+ AND ${parsedColumn} < $1
4035
4038
  `;
4036
4039
  return (await this.client.query(sql, [cutoff, limit])).rowCount ?? 0;
4037
4040
  }
@@ -4494,6 +4497,7 @@ var AgentsPG = class AgentsPG extends AgentsStorage {
4494
4497
  }
4495
4498
  async create(input) {
4496
4499
  const { agent } = input;
4500
+ let draftInserted = false;
4497
4501
  try {
4498
4502
  const agentsTable = getTableName$5({
4499
4503
  indexName: TABLE_AGENTS,
@@ -4520,6 +4524,7 @@ var AgentsPG = class AgentsPG extends AgentsStorage {
4520
4524
  nowIso,
4521
4525
  nowIso
4522
4526
  ]);
4527
+ draftInserted = true;
4523
4528
  const { id: _id, authorId: _authorId, visibility: _visibility, metadata: _metadata, ...snapshotConfig } = agent;
4524
4529
  const versionId = crypto.randomUUID();
4525
4530
  await this.createVersion({
@@ -4542,14 +4547,14 @@ var AgentsPG = class AgentsPG extends AgentsStorage {
4542
4547
  updatedAt: now
4543
4548
  };
4544
4549
  } catch (error) {
4545
- if (error instanceof MastraError) throw error;
4546
- try {
4550
+ if (draftInserted) try {
4547
4551
  const agentsTable = getTableName$5({
4548
4552
  indexName: TABLE_AGENTS,
4549
4553
  schemaName: getSchemaName$5(this.#schema)
4550
4554
  });
4551
4555
  await this.#db.client.none(`DELETE FROM ${agentsTable} WHERE id = $1 AND status = 'draft' AND "activeVersionId" IS NULL`, [agent.id]);
4552
4556
  } catch {}
4557
+ if (error instanceof MastraError) throw error;
4553
4558
  throw new MastraError({
4554
4559
  id: createStorageErrorId("PG", "CREATE_AGENT", "FAILED"),
4555
4560
  domain: ErrorDomain.STORAGE,
@@ -10545,6 +10550,7 @@ var MCPClientsPG = class MCPClientsPG extends MCPClientsStorage {
10545
10550
  }
10546
10551
  async create(input) {
10547
10552
  const { mcpClient } = input;
10553
+ let draftInserted = false;
10548
10554
  try {
10549
10555
  const tableName = getTableName$5({
10550
10556
  indexName: TABLE_MCP_CLIENTS,
@@ -10567,6 +10573,7 @@ var MCPClientsPG = class MCPClientsPG extends MCPClientsStorage {
10567
10573
  nowIso,
10568
10574
  nowIso
10569
10575
  ]);
10576
+ draftInserted = true;
10570
10577
  const { id: _id, authorId: _authorId, metadata: _metadata, ...snapshotConfig } = mcpClient;
10571
10578
  const versionId = crypto.randomUUID();
10572
10579
  await this.createVersion({
@@ -10587,7 +10594,7 @@ var MCPClientsPG = class MCPClientsPG extends MCPClientsStorage {
10587
10594
  updatedAt: now
10588
10595
  };
10589
10596
  } catch (error) {
10590
- try {
10597
+ if (draftInserted) try {
10591
10598
  const tableName = getTableName$5({
10592
10599
  indexName: TABLE_MCP_CLIENTS,
10593
10600
  schemaName: getSchemaName$5(this.#schema)
@@ -11113,6 +11120,7 @@ var MCPServersPG = class MCPServersPG extends MCPServersStorage {
11113
11120
  }
11114
11121
  async create(input) {
11115
11122
  const { mcpServer } = input;
11123
+ let draftInserted = false;
11116
11124
  try {
11117
11125
  const tableName = getTableName$5({
11118
11126
  indexName: TABLE_MCP_SERVERS,
@@ -11134,6 +11142,7 @@ var MCPServersPG = class MCPServersPG extends MCPServersStorage {
11134
11142
  nowIso,
11135
11143
  nowIso
11136
11144
  ]);
11145
+ draftInserted = true;
11137
11146
  const { id: _id, authorId: _authorId, metadata: _metadata, ...snapshotConfig } = mcpServer;
11138
11147
  const versionId = crypto.randomUUID();
11139
11148
  await this.createVersion({
@@ -11154,7 +11163,7 @@ var MCPServersPG = class MCPServersPG extends MCPServersStorage {
11154
11163
  updatedAt: now
11155
11164
  };
11156
11165
  } catch (error) {
11157
- try {
11166
+ if (draftInserted) try {
11158
11167
  const tableName = getTableName$5({
11159
11168
  indexName: TABLE_MCP_SERVERS,
11160
11169
  schemaName: getSchemaName$5(this.#schema)
@@ -11578,6 +11587,38 @@ var MCPServersPG = class MCPServersPG extends MCPServersStorage {
11578
11587
  }
11579
11588
  };
11580
11589
  //#endregion
11590
+ //#region src/storage/db/run-fencing.ts
11591
+ /**
11592
+ * Epoch milliseconds on the database clock. `clock_timestamp()` rather than
11593
+ * `now()`: a statement can wait on a row lock, and lease math must use the
11594
+ * time it actually runs, not when its transaction started.
11595
+ */
11596
+ const DB_NOW_MS = `(EXTRACT(EPOCH FROM clock_timestamp()) * 1000)::bigint`;
11597
+ /**
11598
+ * The check half of a fenced write; call it first inside the write's
11599
+ * transaction. `FOR SHARE` holds the claim row until commit, so a claim that
11600
+ * updates the row waits for the write to finish, and every later write sees
11601
+ * the new claim.
11602
+ */
11603
+ async function assertRunFence(t, claimsTable, fence, operation) {
11604
+ const row = await t.oneOrNone(`SELECT generation, "ownerId" FROM ${claimsTable} WHERE "runId" = $1 FOR SHARE`, [fence.runId]);
11605
+ if (!matchesRunFence(row && {
11606
+ generation: Number(row.generation),
11607
+ ownerId: row.ownerId
11608
+ }, fence)) throw new RunFenceConflictError(fence, operation);
11609
+ }
11610
+ /**
11611
+ * Runs `write` directly when it carries no fence, or in a transaction behind
11612
+ * the fence check when it does.
11613
+ */
11614
+ function withRunFence(client, claimsTable, fence, operation, write) {
11615
+ if (!fence) return write(client);
11616
+ return client.tx(async (t) => {
11617
+ await assertRunFence(t, claimsTable, fence, operation);
11618
+ return write(t);
11619
+ });
11620
+ }
11621
+ //#endregion
11581
11622
  //#region src/storage/domains/memory/index.ts
11582
11623
  /**
11583
11624
  * Local constant for the observational memory table name.
@@ -11678,6 +11719,10 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
11678
11719
  * and are indexed for fast batched deletes. Cascade order is enforced in
11679
11720
  * `prune()` (children before threads), not here. Observational memory has no
11680
11721
  * timestamp anchor and is deliberately excluded.
11722
+ *
11723
+ * Run fences anchor on `retiredAt` (epoch ms), which stays NULL until the
11724
+ * execution holding the fence settles, so a running execution's fence is
11725
+ * never pruned.
11681
11726
  */
11682
11727
  static retentionTables = {
11683
11728
  messages: {
@@ -11694,6 +11739,12 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
11694
11739
  table: TABLE_THREADS,
11695
11740
  column: "createdAtZ",
11696
11741
  indexed: true
11742
+ },
11743
+ runFences: {
11744
+ table: TABLE_MEMORY_RUN_FENCES,
11745
+ column: "retiredAt",
11746
+ indexed: true,
11747
+ anchorType: "epoch-ms"
11697
11748
  }
11698
11749
  };
11699
11750
  #db;
@@ -11757,6 +11808,10 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
11757
11808
  });
11758
11809
  await this.#db.createIndexFromStatement("idx_om_lookup_key", `CREATE INDEX IF NOT EXISTS idx_om_lookup_key ON ${omTableName} ("lookupKey")`);
11759
11810
  }
11811
+ await this.#db.createTable({
11812
+ tableName: TABLE_MEMORY_RUN_FENCES,
11813
+ schema: RUN_FENCING_TABLE_SCHEMAS[TABLE_MEMORY_RUN_FENCES]
11814
+ });
11760
11815
  await this.createDefaultIndexes();
11761
11816
  await this.createCustomIndexes();
11762
11817
  }
@@ -11834,6 +11889,12 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
11834
11889
  statements.push(`CREATE INDEX IF NOT EXISTS "${idxPrefix}idx_om_lookup_key" ON ${fullOmTableName} ("lookupKey");`);
11835
11890
  }
11836
11891
  for (const idx of MemoryPG.getDefaultIndexDefs(schemaPrefix)) statements.push(generateIndexSQL(idx, schemaName));
11892
+ statements.push(generateTableSQL({
11893
+ tableName: TABLE_MEMORY_RUN_FENCES,
11894
+ schema: RUN_FENCING_TABLE_SCHEMAS[TABLE_MEMORY_RUN_FENCES],
11895
+ schemaName,
11896
+ includeAllConstraints: true
11897
+ }));
11837
11898
  return statements;
11838
11899
  }
11839
11900
  /**
@@ -11869,6 +11930,61 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
11869
11930
  await this.#db.clearTable({ tableName: TABLE_MESSAGES });
11870
11931
  await this.#db.clearTable({ tableName: TABLE_THREADS });
11871
11932
  await this.#db.clearTable({ tableName: TABLE_RESOURCES });
11933
+ await this.#db.clearTable({ tableName: TABLE_MEMORY_RUN_FENCES });
11934
+ }
11935
+ supportsRunFencing() {
11936
+ return true;
11937
+ }
11938
+ #runFencesTable() {
11939
+ return getTableName$3({
11940
+ indexName: TABLE_MEMORY_RUN_FENCES,
11941
+ schemaName: getSchemaName$3(this.#schema)
11942
+ });
11943
+ }
11944
+ /** Runs `write` behind the run's fence check when it carries a fence. */
11945
+ #fenced(fence, operation, write) {
11946
+ return withRunFence(this.#db.client, this.#runFencesTable(), resolveRunFence(this, fence), operation, write);
11947
+ }
11948
+ async raiseRunFence(fence) {
11949
+ try {
11950
+ return await this.#db.client.oneOrNone(`INSERT INTO ${this.#runFencesTable()} AS f ("runId", generation, "ownerId")
11951
+ VALUES ($1, $2, $3)
11952
+ ON CONFLICT ("runId") DO UPDATE
11953
+ SET generation = EXCLUDED.generation, "ownerId" = EXCLUDED."ownerId", "retiredAt" = NULL
11954
+ WHERE f.generation < EXCLUDED.generation
11955
+ OR (f.generation = EXCLUDED.generation AND f."ownerId" = EXCLUDED."ownerId")
11956
+ RETURNING "runId"`, [
11957
+ fence.runId,
11958
+ fence.generation,
11959
+ fence.ownerId
11960
+ ]) !== null;
11961
+ } catch (error) {
11962
+ throw new MastraError({
11963
+ id: createStorageErrorId("PG", "RAISE_RUN_FENCE", "FAILED"),
11964
+ domain: ErrorDomain.STORAGE,
11965
+ category: ErrorCategory.THIRD_PARTY,
11966
+ details: { runId: fence.runId }
11967
+ }, error);
11968
+ }
11969
+ }
11970
+ async retireRunFence(fence) {
11971
+ try {
11972
+ return await this.#db.client.oneOrNone(`UPDATE ${this.#runFencesTable()}
11973
+ SET "retiredAt" = ${DB_NOW_MS}
11974
+ WHERE "runId" = $1 AND generation = $2 AND "ownerId" = $3
11975
+ RETURNING "runId"`, [
11976
+ fence.runId,
11977
+ fence.generation,
11978
+ fence.ownerId
11979
+ ]) !== null;
11980
+ } catch (error) {
11981
+ throw new MastraError({
11982
+ id: createStorageErrorId("PG", "RETIRE_RUN_FENCE", "FAILED"),
11983
+ domain: ErrorDomain.STORAGE,
11984
+ category: ErrorCategory.THIRD_PARTY,
11985
+ details: { runId: fence.runId }
11986
+ }, error);
11987
+ }
11872
11988
  }
11873
11989
  /**
11874
11990
  * Deletes rows older than the configured `maxAge` per table, in bounded,
@@ -11889,7 +12005,8 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
11889
12005
  order: [
11890
12006
  "messages",
11891
12007
  "resources",
11892
- "threads"
12008
+ "threads",
12009
+ "runFences"
11893
12010
  ]
11894
12011
  });
11895
12012
  const results = await runPrune({
@@ -12147,7 +12264,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
12147
12264
  throw mastraError;
12148
12265
  }
12149
12266
  }
12150
- async saveThread({ thread }) {
12267
+ async saveThread({ thread, fence }) {
12151
12268
  try {
12152
12269
  const tableName = getTableName$3({
12153
12270
  indexName: TABLE_THREADS,
@@ -12156,7 +12273,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
12156
12273
  const createdAt = toUtcISOString(thread.createdAt);
12157
12274
  const updatedAt = toUtcISOString(thread.updatedAt);
12158
12275
  const metadataJson = thread.metadata ? toPgJson(thread.metadata) : null;
12159
- await this.#db.client.none(`INSERT INTO ${tableName} (
12276
+ await this.#fenced(fence, "saveThread", (q) => q.none(`INSERT INTO ${tableName} (
12160
12277
  id,
12161
12278
  "resourceId",
12162
12279
  title,
@@ -12182,12 +12299,13 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
12182
12299
  createdAt,
12183
12300
  updatedAt,
12184
12301
  updatedAt
12185
- ]);
12302
+ ]));
12186
12303
  return {
12187
12304
  ...thread,
12188
12305
  metadata: metadataJson ? JSON.parse(metadataJson) : thread.metadata
12189
12306
  };
12190
12307
  } catch (error) {
12308
+ if (isRunFenceConflictError(error)) throw error;
12191
12309
  throw new MastraError({
12192
12310
  id: createStorageErrorId("PG", "SAVE_THREAD", "FAILED"),
12193
12311
  domain: ErrorDomain.STORAGE,
@@ -12196,29 +12314,30 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
12196
12314
  }, error);
12197
12315
  }
12198
12316
  }
12199
- async updateThread({ id, title, metadata }) {
12317
+ async updateThread({ id, title, metadata, fence }) {
12200
12318
  const threadTableName = getTableName$3({
12201
12319
  indexName: TABLE_THREADS,
12202
12320
  schemaName: getSchemaName$3(this.#schema)
12203
12321
  });
12204
- const existingThread = await this.#getThreadById(this.#db.client, { threadId: id });
12205
- if (!existingThread) throw new MastraError({
12206
- id: createStorageErrorId("PG", "UPDATE_THREAD", "FAILED"),
12207
- domain: ErrorDomain.STORAGE,
12208
- category: ErrorCategory.USER,
12209
- text: `Thread ${id} not found`,
12210
- details: {
12211
- threadId: id,
12212
- title: title ?? null
12213
- }
12214
- });
12215
- const mergedMetadata = {
12216
- ...existingThread.metadata,
12217
- ...metadata
12218
- };
12219
12322
  try {
12220
- const nowStr = toUtcISOString(/* @__PURE__ */ new Date());
12221
- const thread = await this.#db.client.one(`UPDATE ${threadTableName}
12323
+ return await this.#fenced(fence, "updateThread", async (q) => {
12324
+ const existingThread = await this.#getThreadById(q, { threadId: id });
12325
+ if (!existingThread) throw new MastraError({
12326
+ id: createStorageErrorId("PG", "UPDATE_THREAD", "FAILED"),
12327
+ domain: ErrorDomain.STORAGE,
12328
+ category: ErrorCategory.USER,
12329
+ text: `Thread ${id} not found`,
12330
+ details: {
12331
+ threadId: id,
12332
+ title: title ?? null
12333
+ }
12334
+ });
12335
+ const mergedMetadata = {
12336
+ ...existingThread.metadata,
12337
+ ...metadata
12338
+ };
12339
+ const nowStr = toUtcISOString(/* @__PURE__ */ new Date());
12340
+ const thread = await q.one(`UPDATE ${threadTableName}
12222
12341
  SET
12223
12342
  title = COALESCE($1, title),
12224
12343
  metadata = $2,
@@ -12227,21 +12346,23 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
12227
12346
  WHERE id = $5
12228
12347
  RETURNING *
12229
12348
  `, [
12230
- title ?? null,
12231
- mergedMetadata,
12232
- nowStr,
12233
- nowStr,
12234
- id
12235
- ]);
12236
- return {
12237
- id: thread.id,
12238
- resourceId: thread.resourceId,
12239
- title: thread.title,
12240
- metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
12241
- createdAt: thread.createdAtZ || thread.createdAt,
12242
- updatedAt: thread.updatedAtZ || thread.updatedAt
12243
- };
12349
+ title ?? null,
12350
+ mergedMetadata,
12351
+ nowStr,
12352
+ nowStr,
12353
+ id
12354
+ ]);
12355
+ return {
12356
+ id: thread.id,
12357
+ resourceId: thread.resourceId,
12358
+ title: thread.title,
12359
+ metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
12360
+ createdAt: thread.createdAtZ || thread.createdAt,
12361
+ updatedAt: thread.updatedAtZ || thread.updatedAt
12362
+ };
12363
+ });
12244
12364
  } catch (error) {
12365
+ if (error instanceof MastraError || isRunFenceConflictError(error)) throw error;
12245
12366
  throw new MastraError({
12246
12367
  id: createStorageErrorId("PG", "UPDATE_THREAD", "FAILED"),
12247
12368
  domain: ErrorDomain.STORAGE,
@@ -12794,8 +12915,9 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
12794
12915
  throw mastraError;
12795
12916
  }
12796
12917
  }
12797
- async saveMessages({ messages }) {
12918
+ async saveMessages({ messages, fence: explicitFence }) {
12798
12919
  if (messages.length === 0) return { messages: [] };
12920
+ const fence = resolveRunFence(this, explicitFence);
12799
12921
  const threadId = messages[0]?.threadId;
12800
12922
  if (!threadId) throw new MastraError({
12801
12923
  id: createStorageErrorId("PG", "SAVE_MESSAGES", "FAILED"),
@@ -12823,6 +12945,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
12823
12945
  });
12824
12946
  const messagesToSave = dedupeMessagesForSave(messages);
12825
12947
  await this.#db.client.tx(async (t) => {
12948
+ if (fence) await assertRunFence(t, this.#runFencesTable(), fence, "saveMessages");
12826
12949
  for (let offset = 0; offset < messagesToSave.length; offset += MAX_MESSAGES_PER_INSERT) {
12827
12950
  const batch = messagesToSave.slice(offset, offset + MAX_MESSAGES_PER_INSERT);
12828
12951
  const values = [];
@@ -12869,7 +12992,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
12869
12992
  });
12870
12993
  return { messages: new MessageList().add(messagesWithParsedContent, "memory").get.all.db() };
12871
12994
  } catch (error) {
12872
- if (error instanceof MastraError) throw error;
12995
+ if (error instanceof MastraError || isRunFenceConflictError(error)) throw error;
12873
12996
  throw new MastraError({
12874
12997
  id: createStorageErrorId("PG", "SAVE_MESSAGES", "FAILED"),
12875
12998
  domain: ErrorDomain.STORAGE,
@@ -12878,8 +13001,9 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
12878
13001
  }, error);
12879
13002
  }
12880
13003
  }
12881
- async updateMessages({ messages }) {
13004
+ async updateMessages({ messages, fence: explicitFence }) {
12882
13005
  if (messages.length === 0) return [];
13006
+ const fence = resolveRunFence(this, explicitFence);
12883
13007
  const messageIds = messages.map((m) => m.id);
12884
13008
  const selectQuery = `SELECT id, content, role, type, "createdAt", "createdAtZ", thread_id AS "threadId", "resourceId" FROM ${getTableName$3({
12885
13009
  indexName: TABLE_MESSAGES,
@@ -12895,6 +13019,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
12895
13019
  });
12896
13020
  const threadIdsToUpdate = /* @__PURE__ */ new Set();
12897
13021
  await this.#db.client.tx(async (t) => {
13022
+ if (fence) await assertRunFence(t, this.#runFencesTable(), fence, "updateMessages");
12898
13023
  const queries = [];
12899
13024
  const columnMapping = { threadId: "thread_id" };
12900
13025
  for (const existingMessage of existingMessages) {
@@ -12955,8 +13080,9 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
12955
13080
  return message;
12956
13081
  });
12957
13082
  }
12958
- async deleteMessages(messageIds) {
13083
+ async deleteMessages(messageIds, options) {
12959
13084
  if (!messageIds || messageIds.length === 0) return;
13085
+ const fence = resolveRunFence(this, options?.fence);
12960
13086
  try {
12961
13087
  const messageTableName = getTableName$3({
12962
13088
  indexName: TABLE_MESSAGES,
@@ -12967,12 +13093,14 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
12967
13093
  schemaName: getSchemaName$3(this.#schema)
12968
13094
  });
12969
13095
  await this.#db.client.tx(async (t) => {
13096
+ if (fence) await assertRunFence(t, this.#runFencesTable(), fence, "deleteMessages");
12970
13097
  const placeholders = messageIds.map((_, idx) => `$${idx + 1}`).join(",");
12971
13098
  const threadIds = (await t.manyOrNone(`SELECT DISTINCT thread_id FROM ${messageTableName} WHERE id IN (${placeholders})`, messageIds))?.map((msg) => msg.thread_id).filter(Boolean) || [];
12972
13099
  await t.none(`DELETE FROM ${messageTableName} WHERE id IN (${placeholders})`, messageIds);
12973
13100
  if (threadIds.length > 0) await t.none(`UPDATE ${threadTableName} SET "updatedAt" = NOW(), "updatedAtZ" = NOW() WHERE id IN (${inPlaceholders(threadIds.length)})`, threadIds);
12974
13101
  });
12975
13102
  } catch (error) {
13103
+ if (isRunFenceConflictError(error)) throw error;
12976
13104
  throw new MastraError({
12977
13105
  id: createStorageErrorId("PG", "DELETE_MESSAGES", "FAILED"),
12978
13106
  domain: ErrorDomain.STORAGE,
@@ -13000,6 +13128,9 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
13000
13128
  };
13001
13129
  }
13002
13130
  async saveResource({ resource }) {
13131
+ return this.#insertResource(this.#db.client, resource);
13132
+ }
13133
+ async #insertResource(q, resource) {
13003
13134
  const createdAt = toUtcISOString(resource.createdAt);
13004
13135
  const updatedAt = toUtcISOString(resource.updatedAt);
13005
13136
  const metadataJson = toPgJson(resource.metadata);
@@ -13011,7 +13142,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
13011
13142
  createdAt,
13012
13143
  updatedAt
13013
13144
  }
13014
- });
13145
+ }, q);
13015
13146
  return {
13016
13147
  ...resource,
13017
13148
  metadata: metadataJson ? JSON.parse(metadataJson) : resource.metadata
@@ -13022,9 +13153,11 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
13022
13153
  indexName: TABLE_RESOURCES,
13023
13154
  schemaName: getSchemaName$3(this.#schema)
13024
13155
  });
13156
+ const fence = resolveRunFence(this, void 0);
13025
13157
  const callbackError = {};
13026
13158
  try {
13027
13159
  return await this.#db.client.tx(async (t) => {
13160
+ if (fence) await assertRunFence(t, this.#runFencesTable(), fence, "mergeResourceWorkingMemory");
13028
13161
  const now = (/* @__PURE__ */ new Date()).toISOString();
13029
13162
  await t.none(`INSERT INTO ${tableName} (id, metadata, "createdAt", "createdAtZ", "updatedAt", "updatedAtZ")
13030
13163
  VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (id) DO NOTHING`, [
@@ -13068,8 +13201,15 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
13068
13201
  }, error);
13069
13202
  }
13070
13203
  }
13071
- async updateResource({ resourceId, workingMemory, metadata }) {
13072
- const existingResource = await this.#getResourceById(this.#db.client, resourceId);
13204
+ async updateResource({ resourceId, workingMemory, metadata, fence }) {
13205
+ return this.#fenced(fence, "updateResource", (q) => this.#updateResource(q, {
13206
+ resourceId,
13207
+ workingMemory,
13208
+ metadata
13209
+ }));
13210
+ }
13211
+ async #updateResource(q, { resourceId, workingMemory, metadata }) {
13212
+ const existingResource = await this.#getResourceById(q, resourceId);
13073
13213
  if (!existingResource) {
13074
13214
  const newResource = {
13075
13215
  id: resourceId,
@@ -13078,7 +13218,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
13078
13218
  createdAt: /* @__PURE__ */ new Date(),
13079
13219
  updatedAt: /* @__PURE__ */ new Date()
13080
13220
  };
13081
- return this.saveResource({ resource: newResource });
13221
+ return this.#insertResource(q, newResource);
13082
13222
  }
13083
13223
  const updatedResource = {
13084
13224
  ...existingResource,
@@ -13114,7 +13254,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
13114
13254
  updates.push(`"updatedAtZ" = $${paramIndex++}`);
13115
13255
  values.push(updatedAtStr);
13116
13256
  values.push(resourceId);
13117
- await this.#db.client.none(`UPDATE ${tableName} SET ${updates.join(", ")} WHERE id = $${paramIndex}`, values);
13257
+ await q.none(`UPDATE ${tableName} SET ${updates.join(", ")} WHERE id = $${paramIndex}`, values);
13118
13258
  return metadataJson ? {
13119
13259
  ...updatedResource,
13120
13260
  metadata: JSON.parse(metadataJson)
@@ -13253,9 +13393,11 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
13253
13393
  * serializes generation creation across processes instead. It is
13254
13394
  * transaction-scoped so it also works behind transaction-pooling proxies.
13255
13395
  */
13256
- async #withOMLookupKeyLock(tableName, lookupKey, fn) {
13396
+ async #withOMLookupKeyLock(tableName, lookupKey, fn, fencedOperation) {
13397
+ const fence = fencedOperation ? resolveRunFence(this, void 0) : void 0;
13257
13398
  return this.#db.client.tx(async (t) => {
13258
13399
  await t.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [`${tableName}:${lookupKey}`]);
13400
+ if (fence && fencedOperation) await assertRunFence(t, this.#runFencesTable(), fence, fencedOperation);
13259
13401
  return fn(t);
13260
13402
  });
13261
13403
  }
@@ -13552,7 +13694,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
13552
13694
  const lastObservedAtStr = input.lastObservedAt.toISOString();
13553
13695
  const nowStr = now.toISOString();
13554
13696
  const observedMessageIdsJson = input.observedMessageIds ? toPgJson(input.observedMessageIds) : null;
13555
- if ((await this.#db.client.query(`UPDATE ${tableName} SET
13697
+ if ((await this.#fenced(void 0, "updateActiveObservations", (q) => q.query(`UPDATE ${tableName} SET
13556
13698
  "activeObservations" = $1,
13557
13699
  "lastObservedAt" = $2,
13558
13700
  "lastObservedAtZ" = $3,
@@ -13572,7 +13714,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
13572
13714
  nowStr,
13573
13715
  nowStr,
13574
13716
  input.id
13575
- ])).rowCount === 0) throw new MastraError({
13717
+ ]))).rowCount === 0) throw new MastraError({
13576
13718
  id: createStorageErrorId("PG", "UPDATE_ACTIVE_OBSERVATIONS", "NOT_FOUND"),
13577
13719
  text: `Observational memory record not found: ${input.id}`,
13578
13720
  domain: ErrorDomain.STORAGE,
@@ -13580,7 +13722,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
13580
13722
  details: { id: input.id }
13581
13723
  });
13582
13724
  } catch (error) {
13583
- if (error instanceof MastraError) throw error;
13725
+ if (error instanceof MastraError || isRunFenceConflictError(error)) throw error;
13584
13726
  throw new MastraError({
13585
13727
  id: createStorageErrorId("PG", "UPDATE_ACTIVE_OBSERVATIONS", "FAILED"),
13586
13728
  domain: ErrorDomain.STORAGE,
@@ -13600,8 +13742,9 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
13600
13742
  const newer = await this.#getNewerOMGeneration(t, tableName, lookupKey, input.currentRecord.generationCount);
13601
13743
  if (newer) return newer;
13602
13744
  return this.#insertReflectionGeneration(t, tableName, lookupKey, input);
13603
- });
13745
+ }, "createReflectionGeneration");
13604
13746
  } catch (error) {
13747
+ if (isRunFenceConflictError(error)) throw error;
13605
13748
  throw new MastraError({
13606
13749
  id: createStorageErrorId("PG", "CREATE_REFLECTION_GENERATION", "FAILED"),
13607
13750
  domain: ErrorDomain.STORAGE,
@@ -13961,7 +14104,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
13961
14104
  extractionFailures: input.chunk.extractionFailures
13962
14105
  };
13963
14106
  const lastBufferedAtTime = input.lastBufferedAtTime ? input.lastBufferedAtTime.toISOString() : null;
13964
- if ((await this.#db.client.query(`UPDATE ${tableName} SET
14107
+ if ((await this.#fenced(void 0, "updateBufferedObservations", (q) => q.query(`UPDATE ${tableName} SET
13965
14108
  "bufferedObservationChunks" = CASE
13966
14109
  WHEN EXISTS (
13967
14110
  SELECT 1
@@ -13980,7 +14123,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
13980
14123
  nowStr,
13981
14124
  nowStr,
13982
14125
  input.id
13983
- ])).rowCount === 0) throw new MastraError({
14126
+ ]))).rowCount === 0) throw new MastraError({
13984
14127
  id: createStorageErrorId("PG", "UPDATE_BUFFERED_OBSERVATIONS", "NOT_FOUND"),
13985
14128
  text: `Observational memory record not found: ${input.id}`,
13986
14129
  domain: ErrorDomain.STORAGE,
@@ -13988,7 +14131,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
13988
14131
  details: { id: input.id }
13989
14132
  });
13990
14133
  } catch (error) {
13991
- if (error instanceof MastraError) throw error;
14134
+ if (error instanceof MastraError || isRunFenceConflictError(error)) throw error;
13992
14135
  throw new MastraError({
13993
14136
  id: createStorageErrorId("PG", "UPDATE_BUFFERED_OBSERVATIONS", "FAILED"),
13994
14137
  domain: ErrorDomain.STORAGE,
@@ -14070,7 +14213,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
14070
14213
  const lastObservedAt = input.lastObservedAt ?? (latestChunk?.lastObservedAt ? new Date(latestChunk.lastObservedAt) : /* @__PURE__ */ new Date());
14071
14214
  const lastObservedAtStr = lastObservedAt.toISOString();
14072
14215
  const boundary = `\n\n--- message boundary (${lastObservedAt.toISOString()}) ---\n\n`;
14073
- if ((await this.#db.client.query(`UPDATE ${tableName} SET
14216
+ if ((await this.#fenced(void 0, "swapBufferedToActive", (q) => q.query(`UPDATE ${tableName} SET
14074
14217
  "activeObservations" = CASE
14075
14218
  WHEN "activeObservations" IS NOT NULL AND "activeObservations" != ''
14076
14219
  THEN "activeObservations" || $10 || $1
@@ -14096,7 +14239,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
14096
14239
  nowStr,
14097
14240
  input.id,
14098
14241
  boundary
14099
- ])).rowCount === 0) return {
14242
+ ]))).rowCount === 0) return {
14100
14243
  chunksActivated: 0,
14101
14244
  messageTokensActivated: 0,
14102
14245
  observationTokensActivated: 0,
@@ -14124,7 +14267,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
14124
14267
  currentTask: latestChunkHints?.currentTask ?? void 0
14125
14268
  };
14126
14269
  } catch (error) {
14127
- if (error instanceof MastraError) throw error;
14270
+ if (error instanceof MastraError || isRunFenceConflictError(error)) throw error;
14128
14271
  throw new MastraError({
14129
14272
  id: createStorageErrorId("PG", "SWAP_BUFFERED_TO_ACTIVE", "FAILED"),
14130
14273
  domain: ErrorDomain.STORAGE,
@@ -14140,7 +14283,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
14140
14283
  schemaName: getSchemaName$3(this.#schema)
14141
14284
  });
14142
14285
  const nowStr = (/* @__PURE__ */ new Date()).toISOString();
14143
- if ((await this.#db.client.query(`UPDATE ${tableName} SET
14286
+ if ((await this.#fenced(void 0, "updateBufferedReflection", (q) => q.query(`UPDATE ${tableName} SET
14144
14287
  "bufferedReflection" = CASE
14145
14288
  WHEN "bufferedReflection" IS NOT NULL AND "bufferedReflection" != ''
14146
14289
  THEN "bufferedReflection" || E'\\n\\n' || $1
@@ -14159,7 +14302,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
14159
14302
  nowStr,
14160
14303
  nowStr,
14161
14304
  input.id
14162
- ])).rowCount === 0) throw new MastraError({
14305
+ ]))).rowCount === 0) throw new MastraError({
14163
14306
  id: createStorageErrorId("PG", "UPDATE_BUFFERED_REFLECTION", "NOT_FOUND"),
14164
14307
  text: `Observational memory record not found: ${input.id}`,
14165
14308
  domain: ErrorDomain.STORAGE,
@@ -14167,7 +14310,7 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
14167
14310
  details: { id: input.id }
14168
14311
  });
14169
14312
  } catch (error) {
14170
- if (error instanceof MastraError) throw error;
14313
+ if (error instanceof MastraError || isRunFenceConflictError(error)) throw error;
14171
14314
  throw new MastraError({
14172
14315
  id: createStorageErrorId("PG", "UPDATE_BUFFERED_REFLECTION", "FAILED"),
14173
14316
  domain: ErrorDomain.STORAGE,
@@ -14224,9 +14367,9 @@ var MemoryPG = class MemoryPG extends MemoryStorage {
14224
14367
  input.currentRecord.id
14225
14368
  ]);
14226
14369
  return newRecord;
14227
- });
14370
+ }, "swapBufferedReflectionToActive");
14228
14371
  } catch (error) {
14229
- if (error instanceof MastraError) throw error;
14372
+ if (error instanceof MastraError || isRunFenceConflictError(error)) throw error;
14230
14373
  throw new MastraError({
14231
14374
  id: createStorageErrorId("PG", "SWAP_BUFFERED_REFLECTION_TO_ACTIVE", "FAILED"),
14232
14375
  domain: ErrorDomain.STORAGE,
@@ -21222,6 +21365,7 @@ var PromptBlocksPG = class PromptBlocksPG extends PromptBlocksStorage {
21222
21365
  }
21223
21366
  async create(input) {
21224
21367
  const { promptBlock } = input;
21368
+ let draftInserted = false;
21225
21369
  try {
21226
21370
  const tableName = getTableName$5({
21227
21371
  indexName: TABLE_PROMPT_BLOCKS,
@@ -21243,6 +21387,7 @@ var PromptBlocksPG = class PromptBlocksPG extends PromptBlocksStorage {
21243
21387
  nowIso,
21244
21388
  nowIso
21245
21389
  ]);
21390
+ draftInserted = true;
21246
21391
  const { id: _id, authorId: _authorId, metadata: _metadata, ...snapshotConfig } = promptBlock;
21247
21392
  const versionId = crypto.randomUUID();
21248
21393
  await this.createVersion({
@@ -21263,14 +21408,14 @@ var PromptBlocksPG = class PromptBlocksPG extends PromptBlocksStorage {
21263
21408
  updatedAt: now
21264
21409
  };
21265
21410
  } catch (error) {
21266
- if (error instanceof MastraError) throw error;
21267
- try {
21411
+ if (draftInserted) try {
21268
21412
  const tableName = getTableName$5({
21269
21413
  indexName: TABLE_PROMPT_BLOCKS,
21270
21414
  schemaName: getSchemaName$5(this.#schema)
21271
21415
  });
21272
21416
  await this.#db.client.none(`DELETE FROM ${tableName} WHERE id = $1 AND status = 'draft' AND "activeVersionId" IS NULL`, [promptBlock.id]);
21273
21417
  } catch {}
21418
+ if (error instanceof MastraError) throw error;
21274
21419
  throw new MastraError({
21275
21420
  id: createStorageErrorId("PG", "CREATE_PROMPT_BLOCK", "FAILED"),
21276
21421
  domain: ErrorDomain.STORAGE,
@@ -22166,6 +22311,7 @@ var ScorerDefinitionsPG = class ScorerDefinitionsPG extends ScorerDefinitionsSto
22166
22311
  }
22167
22312
  async create(input) {
22168
22313
  const { scorerDefinition } = input;
22314
+ let draftInserted = false;
22169
22315
  try {
22170
22316
  const tableName = getTableName$5({
22171
22317
  indexName: TABLE_SCORER_DEFINITIONS,
@@ -22189,6 +22335,7 @@ var ScorerDefinitionsPG = class ScorerDefinitionsPG extends ScorerDefinitionsSto
22189
22335
  nowIso,
22190
22336
  nowIso
22191
22337
  ]);
22338
+ draftInserted = true;
22192
22339
  const { id: _id, authorId: _authorId, organizationId: _organizationId, projectId: _projectId, metadata: _metadata, ...snapshotConfig } = scorerDefinition;
22193
22340
  const versionId = crypto.randomUUID();
22194
22341
  await this.createVersion({
@@ -22211,7 +22358,7 @@ var ScorerDefinitionsPG = class ScorerDefinitionsPG extends ScorerDefinitionsSto
22211
22358
  updatedAt: now
22212
22359
  };
22213
22360
  } catch (error) {
22214
- try {
22361
+ if (draftInserted) try {
22215
22362
  const tableName = getTableName$5({
22216
22363
  indexName: TABLE_SCORER_DEFINITIONS,
22217
22364
  schemaName: getSchemaName$5(this.#schema)
@@ -23229,6 +23376,7 @@ var SkillsPG = class SkillsPG extends SkillsStorage {
23229
23376
  }
23230
23377
  async create(input) {
23231
23378
  const { skill } = input;
23379
+ let draftInserted = false;
23232
23380
  try {
23233
23381
  const tableName = getTableName$5({
23234
23382
  indexName: TABLE_SKILLS,
@@ -23252,6 +23400,7 @@ var SkillsPG = class SkillsPG extends SkillsStorage {
23252
23400
  nowIso,
23253
23401
  nowIso
23254
23402
  ]);
23403
+ draftInserted = true;
23255
23404
  const { id: _id, authorId: _authorId, visibility: _visibility, ...snapshotConfig } = skill;
23256
23405
  const versionId = crypto.randomUUID();
23257
23406
  await this.createVersion({
@@ -23273,7 +23422,7 @@ var SkillsPG = class SkillsPG extends SkillsStorage {
23273
23422
  updatedAt: now
23274
23423
  };
23275
23424
  } catch (error) {
23276
- try {
23425
+ if (draftInserted) try {
23277
23426
  const tableName = getTableName$5({
23278
23427
  indexName: TABLE_SKILLS,
23279
23428
  schemaName: getSchemaName$5(this.#schema)
@@ -24418,6 +24567,17 @@ function getTableName({ indexName, schemaName }) {
24418
24567
  const quotedIndexName = `"${indexName}"`;
24419
24568
  return schemaName ? `${schemaName}.${quotedIndexName}` : quotedIndexName;
24420
24569
  }
24570
+ const RUN_OWNER_COLUMNS = `generation, "ownerId", "leaseExpiresAt", ${DB_NOW_MS} AS "nowMs"`;
24571
+ function toRunOwnershipRecord(runId, row) {
24572
+ const leaseExpiresAt = row.leaseExpiresAt === null ? null : Number(row.leaseExpiresAt);
24573
+ return {
24574
+ runId,
24575
+ generation: Number(row.generation),
24576
+ ownerId: row.ownerId,
24577
+ leaseExpiresAt: leaseExpiresAt === null ? null : new Date(leaseExpiresAt),
24578
+ live: leaseExpiresAt !== null && leaseExpiresAt > Number(row.nowMs)
24579
+ };
24580
+ }
24421
24581
  /** Base name (before any schema prefix) of the expression index backing the status filter. */
24422
24582
  const WORKFLOW_SNAPSHOT_STATUS_INDEX = "mastra_workflow_snapshot_name_status_createdat_idx";
24423
24583
  /**
@@ -24502,12 +24662,24 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
24502
24662
  * Workflow run snapshots accumulate as runs execute. Anchored on the
24503
24663
  * timezone-aware `updatedAtZ` mirror column (last activity) so suspended or
24504
24664
  * long-running runs are not pruned by start age.
24665
+ *
24666
+ * Run ownership rows are anchored on `updatedAt` (epoch ms), which every
24667
+ * claim, renewal and release refreshes, so a live execution's row is never
24668
+ * older than its renew interval.
24505
24669
  */
24506
- static retentionTables = { workflowSnapshot: {
24507
- table: TABLE_WORKFLOW_SNAPSHOT,
24508
- column: "updatedAtZ",
24509
- indexed: true
24510
- } };
24670
+ static retentionTables = {
24671
+ workflowSnapshot: {
24672
+ table: TABLE_WORKFLOW_SNAPSHOT,
24673
+ column: "updatedAtZ",
24674
+ indexed: true
24675
+ },
24676
+ runOwnership: {
24677
+ table: TABLE_WORKFLOW_RUN_OWNERS,
24678
+ column: "updatedAt",
24679
+ indexed: true,
24680
+ anchorType: "epoch-ms"
24681
+ }
24682
+ };
24511
24683
  constructor(config) {
24512
24684
  super();
24513
24685
  const { client, readClient, schemaName, skipDefaultIndexes, indexes } = resolvePgConfig(config);
@@ -24564,6 +24736,12 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
24564
24736
  for (const idx of WorkflowsPG.getDefaultIndexDefs(schemaPrefix)) statements.push(generateIndexSQL(idx, schemaName));
24565
24737
  statements.push(`${workflowSnapshotStatusIndexSQL(workflowSnapshotStatusIndexName(parsedSchema), schemaName)};`);
24566
24738
  statements.push(`${workflowSnapshotThreadIdIndexSQL(workflowSnapshotThreadIdIndexName(parsedSchema), schemaName)};`);
24739
+ statements.push(generateTableSQL({
24740
+ tableName: TABLE_WORKFLOW_RUN_OWNERS,
24741
+ schema: RUN_FENCING_TABLE_SCHEMAS[TABLE_WORKFLOW_RUN_OWNERS],
24742
+ schemaName,
24743
+ includeAllConstraints: true
24744
+ }));
24567
24745
  return statements;
24568
24746
  }
24569
24747
  /**
@@ -24607,6 +24785,10 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
24607
24785
  schema: TABLE_SCHEMAS[TABLE_WORKFLOW_SNAPSHOT],
24608
24786
  ifNotExists: ["resourceId"]
24609
24787
  });
24788
+ await this.#db.createTable({
24789
+ tableName: TABLE_WORKFLOW_RUN_OWNERS,
24790
+ schema: RUN_FENCING_TABLE_SCHEMAS[TABLE_WORKFLOW_RUN_OWNERS]
24791
+ });
24610
24792
  await this.createDefaultIndexes();
24611
24793
  await this.createCustomIndexes();
24612
24794
  }
@@ -24634,13 +24816,13 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
24634
24816
  }
24635
24817
  }
24636
24818
  }
24637
- /** Delete workflow run snapshots older than the `workflowSnapshot` policy's `maxAge`, batched. */
24819
+ /** Delete workflow run snapshots and run ownership rows older than their policy's `maxAge`, batched. */
24638
24820
  async prune(policies, options) {
24639
24821
  await this.ensureRetentionIndexes(policies);
24640
24822
  const targets = resolveTargets({
24641
24823
  policies,
24642
24824
  descriptor: WorkflowsPG.retentionTables,
24643
- order: ["workflowSnapshot"]
24825
+ order: ["workflowSnapshot", "runOwnership"]
24644
24826
  });
24645
24827
  return runPrune({
24646
24828
  db: this.#db,
@@ -24662,10 +24844,126 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
24662
24844
  }
24663
24845
  async dangerouslyClearAll() {
24664
24846
  await this.#db.clearTable({ tableName: TABLE_WORKFLOW_SNAPSHOT });
24847
+ await this.#db.clearTable({ tableName: TABLE_WORKFLOW_RUN_OWNERS });
24665
24848
  }
24666
- async updateWorkflowResults({ workflowName, runId, stepId, result, requestContext, state }) {
24849
+ supportsRunFencing() {
24850
+ return true;
24851
+ }
24852
+ #runOwnersTable() {
24853
+ return getTableName({
24854
+ indexName: TABLE_WORKFLOW_RUN_OWNERS,
24855
+ schemaName: getSchemaName(this.#schema)
24856
+ });
24857
+ }
24858
+ async #readRunOwner(q, runId) {
24859
+ const row = await q.oneOrNone(`SELECT ${RUN_OWNER_COLUMNS} FROM ${this.#runOwnersTable()} WHERE "runId" = $1`, [runId]);
24860
+ return row ? toRunOwnershipRecord(runId, row) : null;
24861
+ }
24862
+ #ownershipError(operation, runId, error) {
24863
+ return new MastraError({
24864
+ id: createStorageErrorId("PG", operation, "FAILED"),
24865
+ domain: ErrorDomain.STORAGE,
24866
+ category: ErrorCategory.THIRD_PARTY,
24867
+ details: { runId }
24868
+ }, error);
24869
+ }
24870
+ async claimRunOwnership({ runId, ownerId, leaseMs, force, expectedGeneration }) {
24871
+ const table = this.#runOwnersTable();
24872
+ try {
24873
+ return await this.#db.client.tx(async (t) => {
24874
+ if (!await t.oneOrNone(`SELECT 1 FROM ${table} WHERE "runId" = $1 FOR UPDATE`, [runId])) {
24875
+ if (expectedGeneration !== void 0 && expectedGeneration !== 0) return {
24876
+ acquired: false,
24877
+ record: null
24878
+ };
24879
+ const inserted = await t.oneOrNone(`INSERT INTO ${table} ("runId", generation, "ownerId", "leaseExpiresAt", "updatedAt")
24880
+ VALUES ($1, GREATEST(1, ${DB_NOW_MS}), $2, ${DB_NOW_MS} + $3, ${DB_NOW_MS})
24881
+ ON CONFLICT ("runId") DO NOTHING
24882
+ RETURNING ${RUN_OWNER_COLUMNS}`, [
24883
+ runId,
24884
+ ownerId,
24885
+ leaseMs
24886
+ ]);
24887
+ if (inserted) return {
24888
+ acquired: true,
24889
+ record: toRunOwnershipRecord(runId, inserted)
24890
+ };
24891
+ }
24892
+ const claimed = await t.oneOrNone(`UPDATE ${table}
24893
+ SET generation = generation + 1, "ownerId" = $2, "leaseExpiresAt" = ${DB_NOW_MS} + $3,
24894
+ "updatedAt" = ${DB_NOW_MS}
24895
+ WHERE "runId" = $1
24896
+ AND ($4::bigint IS NULL OR generation = $4)
24897
+ AND ($5::boolean OR "leaseExpiresAt" IS NULL OR "leaseExpiresAt" <= ${DB_NOW_MS})
24898
+ RETURNING ${RUN_OWNER_COLUMNS}`, [
24899
+ runId,
24900
+ ownerId,
24901
+ leaseMs,
24902
+ expectedGeneration ?? null,
24903
+ force === true
24904
+ ]);
24905
+ if (claimed) return {
24906
+ acquired: true,
24907
+ record: toRunOwnershipRecord(runId, claimed)
24908
+ };
24909
+ return {
24910
+ acquired: false,
24911
+ record: await this.#readRunOwner(t, runId)
24912
+ };
24913
+ });
24914
+ } catch (error) {
24915
+ throw this.#ownershipError("CLAIM_RUN_OWNERSHIP", runId, error);
24916
+ }
24917
+ }
24918
+ async renewRunOwnership({ leaseMs, ...fence }) {
24919
+ try {
24920
+ const renewed = await this.#db.client.oneOrNone(`UPDATE ${this.#runOwnersTable()}
24921
+ SET "leaseExpiresAt" = ${DB_NOW_MS} + $4, "updatedAt" = ${DB_NOW_MS}
24922
+ WHERE "runId" = $1 AND generation = $2 AND "ownerId" = $3 AND "leaseExpiresAt" IS NOT NULL
24923
+ RETURNING ${RUN_OWNER_COLUMNS}`, [
24924
+ fence.runId,
24925
+ fence.generation,
24926
+ fence.ownerId,
24927
+ leaseMs
24928
+ ]);
24929
+ if (renewed) return {
24930
+ renewed: true,
24931
+ record: toRunOwnershipRecord(fence.runId, renewed)
24932
+ };
24933
+ return {
24934
+ renewed: false,
24935
+ record: await this.#readRunOwner(this.#db.client, fence.runId)
24936
+ };
24937
+ } catch (error) {
24938
+ throw this.#ownershipError("RENEW_RUN_OWNERSHIP", fence.runId, error);
24939
+ }
24940
+ }
24941
+ async releaseRunOwnership(fence) {
24942
+ try {
24943
+ return await this.#db.client.oneOrNone(`UPDATE ${this.#runOwnersTable()}
24944
+ SET "leaseExpiresAt" = NULL, "updatedAt" = ${DB_NOW_MS}
24945
+ WHERE "runId" = $1 AND generation = $2 AND "ownerId" = $3
24946
+ RETURNING "runId"`, [
24947
+ fence.runId,
24948
+ fence.generation,
24949
+ fence.ownerId
24950
+ ]) !== null;
24951
+ } catch (error) {
24952
+ throw this.#ownershipError("RELEASE_RUN_OWNERSHIP", fence.runId, error);
24953
+ }
24954
+ }
24955
+ async getRunOwnership({ runId }) {
24956
+ try {
24957
+ return await this.#readRunOwner(this.#db.client, runId);
24958
+ } catch (error) {
24959
+ throw this.#ownershipError("GET_RUN_OWNERSHIP", runId, error);
24960
+ }
24961
+ }
24962
+ async updateWorkflowResults({ workflowName, runId, stepId, result, requestContext, state, fence: explicitFence }) {
24963
+ const fence = resolveRunFence(this, explicitFence, runId);
24667
24964
  try {
24668
24965
  return await this.#db.client.tx(async (t) => {
24966
+ if (fence) await assertRunFence(t, this.#runOwnersTable(), fence, "updateWorkflowResults");
24669
24967
  const tableName = getTableName({
24670
24968
  indexName: TABLE_WORKFLOW_SNAPSHOT,
24671
24969
  schemaName: getSchemaName(this.#schema)
@@ -24715,6 +25013,7 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
24715
25013
  return snapshot.context;
24716
25014
  });
24717
25015
  } catch (error) {
25016
+ if (isRunFenceConflictError(error)) throw error;
24718
25017
  throw new MastraError({
24719
25018
  id: createStorageErrorId("PG", "UPDATE_WORKFLOW_RESULTS", "FAILED"),
24720
25019
  domain: ErrorDomain.STORAGE,
@@ -24727,9 +25026,11 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
24727
25026
  }, error);
24728
25027
  }
24729
25028
  }
24730
- async updateWorkflowState({ workflowName, runId, opts }) {
25029
+ async updateWorkflowState({ workflowName, runId, opts, fence: explicitFence }) {
25030
+ const fence = resolveRunFence(this, explicitFence, runId);
24731
25031
  try {
24732
25032
  return await this.#db.client.tx(async (t) => {
25033
+ if (fence) await assertRunFence(t, this.#runOwnersTable(), fence, "updateWorkflowState");
24733
25034
  const tableName = getTableName({
24734
25035
  indexName: TABLE_WORKFLOW_SNAPSHOT,
24735
25036
  schemaName: getSchemaName(this.#schema)
@@ -24759,6 +25060,7 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
24759
25060
  return updatedSnapshot;
24760
25061
  });
24761
25062
  } catch (error) {
25063
+ if (isRunFenceConflictError(error)) throw error;
24762
25064
  throw new MastraError({
24763
25065
  id: createStorageErrorId("PG", "UPDATE_WORKFLOW_STATE", "FAILED"),
24764
25066
  domain: ErrorDomain.STORAGE,
@@ -24770,13 +25072,13 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
24770
25072
  }, error);
24771
25073
  }
24772
25074
  }
24773
- async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, createdAt, updatedAt }) {
25075
+ async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, createdAt, updatedAt, fence }) {
24774
25076
  try {
24775
25077
  const now = /* @__PURE__ */ new Date();
24776
25078
  const createdAtValue = createdAt ? createdAt : now;
24777
25079
  const updatedAtValue = updatedAt ? updatedAt : now;
24778
25080
  const sanitizedSnapshot = toPgJson(snapshot);
24779
- await this.#db.client.none(`INSERT INTO ${getTableName({
25081
+ await withRunFence(this.#db.client, this.#runOwnersTable(), resolveRunFence(this, fence, runId), "persistWorkflowSnapshot", (q) => q.none(`INSERT INTO ${getTableName({
24780
25082
  indexName: TABLE_WORKFLOW_SNAPSHOT,
24781
25083
  schemaName: getSchemaName(this.#schema)
24782
25084
  })} AS t
@@ -24792,8 +25094,9 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
24792
25094
  updatedAtValue,
24793
25095
  createdAtValue,
24794
25096
  updatedAtValue
24795
- ]);
25097
+ ]));
24796
25098
  } catch (error) {
25099
+ if (isRunFenceConflictError(error)) throw error;
24797
25100
  throw new MastraError({
24798
25101
  id: createStorageErrorId("PG", "PERSIST_WORKFLOW_SNAPSHOT", "FAILED"),
24799
25102
  domain: ErrorDomain.STORAGE,
@@ -24859,13 +25162,14 @@ var WorkflowsPG = class WorkflowsPG extends WorkflowsStorage {
24859
25162
  }, error);
24860
25163
  }
24861
25164
  }
24862
- async deleteWorkflowRunById({ runId, workflowName }) {
25165
+ async deleteWorkflowRunById({ runId, workflowName, fence }) {
24863
25166
  try {
24864
- await this.#db.client.none(`DELETE FROM ${getTableName({
25167
+ await withRunFence(this.#db.client, this.#runOwnersTable(), resolveRunFence(this, fence, runId), "deleteWorkflowRunById", (q) => q.none(`DELETE FROM ${getTableName({
24865
25168
  indexName: TABLE_WORKFLOW_SNAPSHOT,
24866
25169
  schemaName: getSchemaName(this.#schema)
24867
- })} WHERE run_id = $1 AND workflow_name = $2`, [runId, workflowName]);
25170
+ })} WHERE run_id = $1 AND workflow_name = $2`, [runId, workflowName]));
24868
25171
  } catch (error) {
25172
+ if (isRunFenceConflictError(error)) throw error;
24869
25173
  throw new MastraError({
24870
25174
  id: createStorageErrorId("PG", "DELETE_WORKFLOW_RUN_BY_ID", "FAILED"),
24871
25175
  domain: ErrorDomain.STORAGE,
@@ -25077,6 +25381,7 @@ var WorkspacesPG = class WorkspacesPG extends WorkspacesStorage {
25077
25381
  }
25078
25382
  async create(input) {
25079
25383
  const { workspace } = input;
25384
+ let draftInserted = false;
25080
25385
  try {
25081
25386
  const tableName = getTableName$5({
25082
25387
  indexName: TABLE_WORKSPACES,
@@ -25098,6 +25403,7 @@ var WorkspacesPG = class WorkspacesPG extends WorkspacesStorage {
25098
25403
  nowIso,
25099
25404
  nowIso
25100
25405
  ]);
25406
+ draftInserted = true;
25101
25407
  const { id: _id, authorId: _authorId, metadata: _metadata, ...snapshotConfig } = workspace;
25102
25408
  const versionId = crypto.randomUUID();
25103
25409
  await this.createVersion({
@@ -25118,7 +25424,7 @@ var WorkspacesPG = class WorkspacesPG extends WorkspacesStorage {
25118
25424
  updatedAt: now
25119
25425
  };
25120
25426
  } catch (error) {
25121
- try {
25427
+ if (draftInserted) try {
25122
25428
  const tableName = getTableName$5({
25123
25429
  indexName: TABLE_WORKSPACES,
25124
25430
  schemaName: getSchemaName$5(this.#schema)