@mastra/spanner 1.9.4 → 1.9.5-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/index.js CHANGED
@@ -272,6 +272,51 @@ var SpannerDB = class extends MastraBase {
272
272
  }
273
273
  }
274
274
  /**
275
+ * Delete up to `limit` rows keyed by `runId` whose epoch-ms `column` is
276
+ * older than `cutoff`. Used to prune the run fencing tables.
277
+ */
278
+ async pruneRunRows({ tableName, column, cutoff, limit }) {
279
+ const table = quoteIdent(tableName, "table name");
280
+ const col = quoteIdent(column, "column name");
281
+ const runId = quoteIdent("runId", "column name");
282
+ const [rows] = await this.database.run({
283
+ sql: `SELECT ${runId} FROM ${table} WHERE ${col} IS NOT NULL AND ${col} < @cutoff ORDER BY ${col} LIMIT @limit`,
284
+ params: {
285
+ cutoff,
286
+ limit
287
+ },
288
+ types: {
289
+ cutoff: "int64",
290
+ limit: "int64"
291
+ },
292
+ json: true
293
+ });
294
+ const runIds = rows.map((row) => row.runId);
295
+ if (runIds.length === 0) return 0;
296
+ let deleted = 0;
297
+ await this.runWithAbortRetry(async () => {
298
+ await this.database.runTransactionAsync(async (tx) => {
299
+ const [count] = await tx.runUpdate({
300
+ sql: `DELETE FROM ${table} WHERE ${runId} IN UNNEST(@runIds) AND ${col} < @cutoff`,
301
+ params: {
302
+ runIds,
303
+ cutoff
304
+ },
305
+ types: {
306
+ runIds: {
307
+ type: "array",
308
+ child: "string"
309
+ },
310
+ cutoff: "int64"
311
+ }
312
+ });
313
+ await tx.commit();
314
+ deleted = count;
315
+ });
316
+ });
317
+ return deleted;
318
+ }
319
+ /**
275
320
  * Determine the primary-key column list for a table. Some core tables (like
276
321
  * `mastra_workflow_snapshot`) don't carry a single-column PK in the schema,
277
322
  * so we hardcode known composite PKs here.
@@ -6760,6 +6805,17 @@ var MemorySpanner = class MemorySpanner extends MemoryStorage {
6760
6805
  TABLE_MESSAGES,
6761
6806
  TABLE_RESOURCES
6762
6807
  ];
6808
+ /**
6809
+ * Tables `prune()` can clean up. Run fences anchor on `retiredAt` (epoch ms), which stays NULL until the
6810
+ * execution holding the fence settles, so a running execution's fence is
6811
+ * never pruned.
6812
+ */
6813
+ static retentionTables = { runFences: {
6814
+ table: TABLE_MEMORY_RUN_FENCES,
6815
+ column: "retiredAt",
6816
+ indexed: false,
6817
+ anchorType: "epoch-ms"
6818
+ } };
6763
6819
  constructor(config) {
6764
6820
  super();
6765
6821
  const { database, indexes, skipDefaultIndexes, initMode } = resolveSpannerConfig(config);
@@ -6816,6 +6872,24 @@ var MemorySpanner = class MemorySpanner extends MemoryStorage {
6816
6872
  await this.db.createIndexes(this.indexes);
6817
6873
  }
6818
6874
  /** Removes every row from threads/messages/resources and run fence tables. Intended for tests. */
6875
+ async prune(policies, options) {
6876
+ return executeRetentionPrune({
6877
+ domain: "memory",
6878
+ targets: resolveRetentionTargets({
6879
+ policies,
6880
+ descriptor: MemorySpanner.retentionTables,
6881
+ order: ["runFences"]
6882
+ }),
6883
+ options,
6884
+ cutoffFor: (target, now) => retentionCutoffMs(target.policy, now),
6885
+ deleteBatch: (target, cutoff, limit) => this.db.pruneRunRows({
6886
+ tableName: target.table,
6887
+ column: target.column,
6888
+ cutoff,
6889
+ limit
6890
+ })
6891
+ });
6892
+ }
6819
6893
  async dangerouslyClearAll() {
6820
6894
  await this.db.clearTable({ tableName: TABLE_MESSAGES });
6821
6895
  await this.db.clearTable({ tableName: TABLE_THREADS });
@@ -12478,6 +12552,17 @@ var WorkflowsSpanner = class WorkflowsSpanner extends WorkflowsStorage {
12478
12552
  skipDefaultIndexes;
12479
12553
  indexes;
12480
12554
  static MANAGED_TABLES = [TABLE_WORKFLOW_SNAPSHOT];
12555
+ /**
12556
+ * Tables `prune()` can clean up. Run ownership rows anchor on `updatedAt` (epoch ms), which every
12557
+ * claim, renewal and release refreshes, so a live execution's row is never
12558
+ * older than its renew interval.
12559
+ */
12560
+ static retentionTables = { runOwnership: {
12561
+ table: TABLE_WORKFLOW_RUN_OWNERS,
12562
+ column: "updatedAt",
12563
+ indexed: false,
12564
+ anchorType: "epoch-ms"
12565
+ } };
12481
12566
  constructor(config) {
12482
12567
  super();
12483
12568
  const { database, indexes, skipDefaultIndexes, initMode } = resolveSpannerConfig(config);
@@ -12610,6 +12695,24 @@ var WorkflowsSpanner = class WorkflowsSpanner extends WorkflowsStorage {
12610
12695
  await this.db.createIndexes(this.indexes);
12611
12696
  }
12612
12697
  /** Removes every workflow snapshot and run ownership row. Intended for tests. */
12698
+ async prune(policies, options) {
12699
+ return executeRetentionPrune({
12700
+ domain: "workflows",
12701
+ targets: resolveRetentionTargets({
12702
+ policies,
12703
+ descriptor: WorkflowsSpanner.retentionTables,
12704
+ order: ["runOwnership"]
12705
+ }),
12706
+ options,
12707
+ cutoffFor: (target, now) => retentionCutoffMs(target.policy, now),
12708
+ deleteBatch: (target, cutoff, limit) => this.db.pruneRunRows({
12709
+ tableName: target.table,
12710
+ column: target.column,
12711
+ cutoff,
12712
+ limit
12713
+ })
12714
+ });
12715
+ }
12613
12716
  async dangerouslyClearAll() {
12614
12717
  await this.db.clearTable({ tableName: TABLE_WORKFLOW_SNAPSHOT });
12615
12718
  await this.db.clearTable({ tableName: TABLE_WORKFLOW_RUN_OWNERS });