@mastra/libsql 1.14.3 → 1.15.0-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.
Files changed (57) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/docs/SKILL.md +2 -1
  3. package/dist/docs/assets/SOURCE_MAP.json +1 -1
  4. package/dist/docs/references/docs-agent-builder-deploying.md +2 -0
  5. package/dist/docs/references/docs-agent-builder-overview.md +2 -0
  6. package/dist/docs/references/docs-agents-agent-approval.md +2 -0
  7. package/dist/docs/references/docs-agents-networks.md +2 -0
  8. package/dist/docs/references/docs-memory-memory-processors.md +4 -0
  9. package/dist/docs/references/docs-memory-message-history.md +2 -0
  10. package/dist/docs/references/docs-memory-multi-user-threads.md +2 -0
  11. package/dist/docs/references/docs-memory-overview.md +5 -1
  12. package/dist/docs/references/docs-memory-semantic-recall.md +8 -0
  13. package/dist/docs/references/docs-memory-storage.md +6 -0
  14. package/dist/docs/references/docs-memory-working-memory.md +2 -0
  15. package/dist/docs/references/docs-rag-retrieval.md +2 -0
  16. package/dist/docs/references/docs-workflows-snapshots.md +2 -0
  17. package/dist/docs/references/guides-agent-frameworks-ai-sdk.md +2 -0
  18. package/dist/docs/references/reference-core-getMemory.md +2 -0
  19. package/dist/docs/references/reference-core-listMemory.md +2 -0
  20. package/dist/docs/references/reference-core-mastra-class.md +2 -0
  21. package/dist/docs/references/reference-memory-memory-class.md +3 -0
  22. package/dist/docs/references/reference-storage-composite.md +2 -0
  23. package/dist/docs/references/reference-storage-dynamodb.md +2 -0
  24. package/dist/docs/references/reference-storage-libsql.md +2 -0
  25. package/dist/docs/references/reference-storage-retention.md +180 -0
  26. package/dist/docs/references/reference-vectors-libsql.md +2 -0
  27. package/dist/index.cjs +430 -10
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.js +431 -11
  30. package/dist/index.js.map +1 -1
  31. package/dist/storage/db/index.d.ts +51 -0
  32. package/dist/storage/db/index.d.ts.map +1 -1
  33. package/dist/storage/domains/background-tasks/index.d.ts +9 -0
  34. package/dist/storage/domains/background-tasks/index.d.ts.map +1 -1
  35. package/dist/storage/domains/experiments/index.d.ts +20 -1
  36. package/dist/storage/domains/experiments/index.d.ts.map +1 -1
  37. package/dist/storage/domains/harness/index.d.ts +5 -1
  38. package/dist/storage/domains/harness/index.d.ts.map +1 -1
  39. package/dist/storage/domains/memory/index.d.ts +15 -1
  40. package/dist/storage/domains/memory/index.d.ts.map +1 -1
  41. package/dist/storage/domains/notifications/index.d.ts +5 -1
  42. package/dist/storage/domains/notifications/index.d.ts.map +1 -1
  43. package/dist/storage/domains/observability/index.d.ts +8 -1
  44. package/dist/storage/domains/observability/index.d.ts.map +1 -1
  45. package/dist/storage/domains/schedules/index.d.ts +9 -1
  46. package/dist/storage/domains/schedules/index.d.ts.map +1 -1
  47. package/dist/storage/domains/scores/index.d.ts +7 -1
  48. package/dist/storage/domains/scores/index.d.ts.map +1 -1
  49. package/dist/storage/domains/thread-state/index.d.ts +9 -0
  50. package/dist/storage/domains/thread-state/index.d.ts.map +1 -1
  51. package/dist/storage/domains/workflows/index.d.ts +8 -1
  52. package/dist/storage/domains/workflows/index.d.ts.map +1 -1
  53. package/dist/storage/index.d.ts +8 -1
  54. package/dist/storage/index.d.ts.map +1 -1
  55. package/dist/storage/retention.d.ts +77 -0
  56. package/dist/storage/retention.d.ts.map +1 -0
  57. package/package.json +4 -4
package/dist/index.cjs CHANGED
@@ -1458,6 +1458,11 @@ function resolveClient(config) {
1458
1458
  ...isLocal ? { timeout } : {}
1459
1459
  });
1460
1460
  }
1461
+ function assertPositiveLimit(limit) {
1462
+ if (!Number.isSafeInteger(limit) || limit <= 0) {
1463
+ throw new Error(`prune limit must be a positive integer; received ${limit}`);
1464
+ }
1465
+ }
1461
1466
  var LibSQLDB = class extends base.MastraBase {
1462
1467
  client;
1463
1468
  maxRetries;
@@ -2278,6 +2283,102 @@ Note: This migration may take some time for large tables.
2278
2283
  this.logger?.error?.(mastraError.toString());
2279
2284
  }
2280
2285
  }
2286
+ // ---------------------------------------------------------------------------
2287
+ // Retention helpers (prune)
2288
+ //
2289
+ // Low-level SQL primitives the memory and observability domains build their
2290
+ // `prune()` on, plus a plain user-invoked `VACUUM`. Keeping the SQL here
2291
+ // reuses the existing write-lock + retry machinery and keeps identifier
2292
+ // parsing in one place.
2293
+ // ---------------------------------------------------------------------------
2294
+ /**
2295
+ * Delete up to `limit` of the oldest rows whose `column` timestamp is strictly
2296
+ * before `cutoffMs`, in a single bounded statement. Returns the number of rows
2297
+ * removed so the caller can decide whether more batches remain.
2298
+ *
2299
+ * Deletes by `rowid` so the range scan is bounded by LIMIT and the write set
2300
+ * stays small — short locks, bounded WAL growth. Requires an index on
2301
+ * `column` to be fast at scale (see ensureIndex).
2302
+ */
2303
+ async pruneBatch({
2304
+ tableName,
2305
+ column,
2306
+ cutoff,
2307
+ limit
2308
+ }) {
2309
+ assertPositiveLimit(limit);
2310
+ const parsedTable = utils.parseSqlIdentifier(tableName, "table name");
2311
+ const parsedColumn = utils.parseSqlIdentifier(column, "column name");
2312
+ const sql = `DELETE FROM "${parsedTable}" WHERE rowid IN (SELECT rowid FROM "${parsedTable}" WHERE "${parsedColumn}" < ? LIMIT ?)`;
2313
+ const result = await this.executeWriteOperationWithRetry(
2314
+ () => withClientWriteLock(this.client, () => this.client.execute({ sql, args: [cutoff, limit] })),
2315
+ `prune ${tableName}`
2316
+ );
2317
+ return Number(result.rowsAffected ?? 0);
2318
+ }
2319
+ /**
2320
+ * Delete up to `limit` aged parent rows *and* their child rows together, in a
2321
+ * single transaction. Used for whole-unit (parent-driven) pruning where
2322
+ * children must die with their parent (e.g. an aged experiment and its
2323
+ * experiment_results) — deleting per unit means a bound or abort between
2324
+ * batches never leaves a parent hollow (kept, but with its children gone) or
2325
+ * children orphaned.
2326
+ *
2327
+ * Both deletes target the same parent set via an identical deterministic
2328
+ * subquery (`ORDER BY rowid LIMIT ?`); the batch runs as one write
2329
+ * transaction, so the set cannot change between the two statements.
2330
+ */
2331
+ async pruneUnitsBatch({
2332
+ parentTable,
2333
+ parentKey,
2334
+ parentColumn,
2335
+ childTable,
2336
+ childForeignKey,
2337
+ cutoff,
2338
+ limit
2339
+ }) {
2340
+ assertPositiveLimit(limit);
2341
+ const parsedChild = utils.parseSqlIdentifier(childTable, "table name");
2342
+ const parsedChildFk = utils.parseSqlIdentifier(childForeignKey, "column name");
2343
+ const parsedParent = utils.parseSqlIdentifier(parentTable, "table name");
2344
+ const parsedParentKey = utils.parseSqlIdentifier(parentKey, "column name");
2345
+ const parsedParentColumn = utils.parseSqlIdentifier(parentColumn, "column name");
2346
+ const agedParents = `SELECT "${parsedParentKey}" FROM "${parsedParent}" WHERE "${parsedParentColumn}" < ? ORDER BY rowid LIMIT ?`;
2347
+ const results = await this.executeWriteOperationWithRetry(
2348
+ () => withClientWriteLock(
2349
+ this.client,
2350
+ () => this.client.batch(
2351
+ [
2352
+ {
2353
+ sql: `DELETE FROM "${parsedChild}" WHERE "${parsedChildFk}" IN (${agedParents})`,
2354
+ args: [cutoff, limit]
2355
+ },
2356
+ {
2357
+ sql: `DELETE FROM "${parsedParent}" WHERE "${parsedParentKey}" IN (${agedParents})`,
2358
+ args: [cutoff, limit]
2359
+ }
2360
+ ],
2361
+ "write"
2362
+ )
2363
+ ),
2364
+ `prune units ${parentTable}`
2365
+ );
2366
+ return {
2367
+ children: Number(results[0]?.rowsAffected ?? 0),
2368
+ parents: Number(results[1]?.rowsAffected ?? 0)
2369
+ };
2370
+ }
2371
+ /** Create an index if it does not already exist. */
2372
+ async ensureIndex({
2373
+ indexName,
2374
+ tableName,
2375
+ column
2376
+ }) {
2377
+ const parsedTable = utils.parseSqlIdentifier(tableName, "table name");
2378
+ const parsedColumn = utils.parseSqlIdentifier(column, "column name");
2379
+ const parsedIndex = utils.parseSqlIdentifier(indexName, "index name");
2380
+ await this.client.execute(`CREATE INDEX IF NOT EXISTS "${parsedIndex}" ON "${parsedTable}" ("${parsedColumn}")`);
2381
+ }
2281
2382
  };
2282
2383
  var AgentsLibSQL = class extends storage.AgentsStorage {
2283
2384
  #db;
@@ -3115,6 +3216,113 @@ var AgentsLibSQL = class extends storage.AgentsStorage {
3115
3216
  };
3116
3217
  }
3117
3218
  };
3219
+ var DEFAULT_BATCH_SIZE = 1e3;
3220
+ async function ensureAnchorIndex(db, target, logger) {
3221
+ if (!target.indexed) return;
3222
+ try {
3223
+ await db.ensureIndex({
3224
+ indexName: `idx_retention_${target.table}_${target.column}`,
3225
+ tableName: target.table,
3226
+ column: target.column
3227
+ });
3228
+ } catch (error) {
3229
+ logger?.warn?.(`Failed to ensure retention index on ${target.table}(${target.column}):`, error);
3230
+ }
3231
+ }
3232
+ async function sleep(ms, signal) {
3233
+ if (ms <= 0 || signal?.aborted) return;
3234
+ await new Promise((resolve) => {
3235
+ const timer = setTimeout(() => {
3236
+ signal?.removeEventListener("abort", onAbort);
3237
+ resolve();
3238
+ }, ms);
3239
+ const onAbort = () => {
3240
+ clearTimeout(timer);
3241
+ resolve();
3242
+ };
3243
+ signal?.addEventListener("abort", onAbort, { once: true });
3244
+ });
3245
+ }
3246
+ function cutoffFor(policy, anchorType, now = Date.now()) {
3247
+ const cutoffMs = now - storage.parseDuration(policy.maxAge);
3248
+ return anchorType === "epoch-ms" ? cutoffMs : new Date(cutoffMs).toISOString();
3249
+ }
3250
+ async function runBatchedDelete({
3251
+ deleteBatch,
3252
+ batchSize,
3253
+ options
3254
+ }) {
3255
+ if (!Number.isSafeInteger(batchSize) || batchSize <= 0) {
3256
+ throw new Error(`retention batchSize must be a positive integer; received ${batchSize}`);
3257
+ }
3258
+ let deleted = 0;
3259
+ let batches = 0;
3260
+ while (true) {
3261
+ if (options?.signal?.aborted) return { deleted, done: false };
3262
+ if (options?.maxBatches !== void 0 && batches >= options.maxBatches) return { deleted, done: false };
3263
+ let limit = batchSize;
3264
+ if (options?.maxRows !== void 0) {
3265
+ const remaining = options.maxRows - deleted;
3266
+ if (remaining <= 0) return { deleted, done: false };
3267
+ limit = Math.min(limit, remaining);
3268
+ }
3269
+ const affected = await deleteBatch(limit);
3270
+ deleted += affected;
3271
+ batches += 1;
3272
+ if (affected < limit) return { deleted, done: true };
3273
+ if (options?.pauseMs) {
3274
+ await sleep(options.pauseMs, options.signal);
3275
+ }
3276
+ }
3277
+ }
3278
+ async function runPrune({
3279
+ db,
3280
+ domain,
3281
+ targets,
3282
+ options,
3283
+ logger
3284
+ }) {
3285
+ const results = [];
3286
+ const now = Date.now();
3287
+ for (const target of targets) {
3288
+ if (options?.signal?.aborted) {
3289
+ results.push({ domain, table: target.table, deleted: 0, done: false });
3290
+ continue;
3291
+ }
3292
+ await ensureAnchorIndex(db, target, logger);
3293
+ const cutoff = cutoffFor(target.policy, target.anchorType, now);
3294
+ const batchSize = target.policy.batchSize ?? DEFAULT_BATCH_SIZE;
3295
+ const { deleted, done } = await runBatchedDelete({
3296
+ deleteBatch: (limit) => db.pruneBatch({ tableName: target.table, column: target.column, cutoff, limit }),
3297
+ batchSize,
3298
+ options
3299
+ });
3300
+ results.push({ domain, table: target.table, deleted, done });
3301
+ }
3302
+ return results;
3303
+ }
3304
+ function resolveTargets({
3305
+ policies,
3306
+ descriptor,
3307
+ order
3308
+ }) {
3309
+ const targets = [];
3310
+ for (const key of order) {
3311
+ const policy = policies[key];
3312
+ const entry = descriptor[key];
3313
+ if (!policy || !entry) continue;
3314
+ targets.push({
3315
+ table: entry.table,
3316
+ column: entry.column,
3317
+ anchorType: entry.anchorType ?? "timestamp",
3318
+ indexed: entry.indexed ?? true,
3319
+ policy
3320
+ });
3321
+ }
3322
+ return targets;
3323
+ }
3324
+
3325
+ // src/storage/domains/background-tasks/index.ts
3118
3326
  function serializeJson(v) {
3119
3327
  if (typeof v === "object" && v != null) return JSON.stringify(v);
3120
3328
  return v ?? null;
@@ -3153,7 +3361,15 @@ function rowToTask(row) {
3153
3361
  completedAt: row.completedAt ? new Date(String(row.completedAt)) : void 0
3154
3362
  };
3155
3363
  }
3156
- var BackgroundTasksLibSQL = class extends storage.BackgroundTasksStorage {
3364
+ var BackgroundTasksLibSQL = class _BackgroundTasksLibSQL extends storage.BackgroundTasksStorage {
3365
+ /**
3366
+ * Completed/failed task records accumulate. Anchored on `completedAt`, which
3367
+ * is NULL while a task is in-flight (pending/running/suspended) — so `completedAt
3368
+ * < cutoff` never prunes a live task, no explicit status filter needed.
3369
+ */
3370
+ static retentionTables = {
3371
+ backgroundTasks: { table: storage.TABLE_BACKGROUND_TASKS, column: "completedAt", indexed: true }
3372
+ };
3157
3373
  #db;
3158
3374
  #client;
3159
3375
  constructor(config) {
@@ -3176,6 +3392,15 @@ var BackgroundTasksLibSQL = class extends storage.BackgroundTasksStorage {
3176
3392
  async dangerouslyClearAll() {
3177
3393
  await this.#db.deleteData({ tableName: storage.TABLE_BACKGROUND_TASKS });
3178
3394
  }
3395
+ /** Delete completed tasks older than the `backgroundTasks` policy's `maxAge`, batched. */
3396
+ async prune(policies, options) {
3397
+ const targets = resolveTargets({
3398
+ policies,
3399
+ descriptor: _BackgroundTasksLibSQL.retentionTables,
3400
+ order: ["backgroundTasks"]
3401
+ });
3402
+ return runPrune({ db: this.#db, domain: "backgroundTasks", targets, options, logger: this.logger });
3403
+ }
3179
3404
  async createTask(task) {
3180
3405
  await this.#db.insert({
3181
3406
  tableName: storage.TABLE_BACKGROUND_TASKS,
@@ -4626,7 +4851,18 @@ var DatasetsLibSQL = class extends storage.DatasetsStorage {
4626
4851
  }
4627
4852
  }
4628
4853
  };
4854
+ var DEFAULT_PRUNE_BATCH_SIZE = 1e3;
4629
4855
  var ExperimentsLibSQL = class extends storage.ExperimentsStorage {
4856
+ /**
4857
+ * An experiment is pruned as a whole unit: when `experiments.completedAt` is
4858
+ * older than the policy, the run and all its `experiment_results` rows are
4859
+ * deleted together (results cascade with their parent, matching
4860
+ * `deleteExperiment`). Results are not an independent retention key. NULL
4861
+ * `completedAt` (still running) is never pruned.
4862
+ */
4863
+ static retentionTables = {
4864
+ experiments: { table: storage.TABLE_EXPERIMENTS, column: "completedAt", indexed: true }
4865
+ };
4630
4866
  #db;
4631
4867
  #client;
4632
4868
  constructor(config) {
@@ -4682,6 +4918,58 @@ var ExperimentsLibSQL = class extends storage.ExperimentsStorage {
4682
4918
  await this.#db.deleteData({ tableName: storage.TABLE_EXPERIMENT_RESULTS });
4683
4919
  await this.#db.deleteData({ tableName: storage.TABLE_EXPERIMENTS });
4684
4920
  }
4921
+ /**
4922
+ * Prune whole experiments older than the `experiments` policy's `maxAge`.
4923
+ *
4924
+ * Each batch selects up to `batchSize` aged experiments and deletes their
4925
+ * `experiment_results` rows and the experiment rows in one transaction —
4926
+ * mirroring `deleteExperiment` — so hitting `maxBatches`/`maxRows` or the
4927
+ * abort signal between batches never leaves a run hollow (parent kept,
4928
+ * results gone). NULL `completedAt` (still running) is excluded by the
4929
+ * `< cutoff` predicate. Bounds count whole experiments, not rows.
4930
+ */
4931
+ async prune(policies, options) {
4932
+ const policy = policies["experiments"];
4933
+ if (!policy || options?.signal?.aborted) {
4934
+ return policy ? [
4935
+ { domain: "experiments", table: storage.TABLE_EXPERIMENT_RESULTS, deleted: 0, done: false },
4936
+ { domain: "experiments", table: storage.TABLE_EXPERIMENTS, deleted: 0, done: false }
4937
+ ] : [];
4938
+ }
4939
+ try {
4940
+ await this.#db.ensureIndex({
4941
+ indexName: `idx_retention_${storage.TABLE_EXPERIMENTS}_completedAt`,
4942
+ tableName: storage.TABLE_EXPERIMENTS,
4943
+ column: "completedAt"
4944
+ });
4945
+ } catch (error) {
4946
+ this.logger?.warn?.(`Failed to ensure retention index on ${storage.TABLE_EXPERIMENTS}(completedAt):`, error);
4947
+ }
4948
+ const cutoff = cutoffFor(policy, "timestamp");
4949
+ const batchSize = policy.batchSize ?? DEFAULT_PRUNE_BATCH_SIZE;
4950
+ let childDeleted = 0;
4951
+ const parent = await runBatchedDelete({
4952
+ deleteBatch: async (limit) => {
4953
+ const { parents, children } = await this.#db.pruneUnitsBatch({
4954
+ parentTable: storage.TABLE_EXPERIMENTS,
4955
+ parentKey: "id",
4956
+ parentColumn: "completedAt",
4957
+ childTable: storage.TABLE_EXPERIMENT_RESULTS,
4958
+ childForeignKey: "experimentId",
4959
+ cutoff,
4960
+ limit
4961
+ });
4962
+ childDeleted += children;
4963
+ return parents;
4964
+ },
4965
+ batchSize,
4966
+ options
4967
+ });
4968
+ return [
4969
+ { domain: "experiments", table: storage.TABLE_EXPERIMENT_RESULTS, deleted: childDeleted, done: parent.done },
4970
+ { domain: "experiments", table: storage.TABLE_EXPERIMENTS, deleted: parent.deleted, done: parent.done }
4971
+ ];
4972
+ }
4685
4973
  // Helper to transform row to Experiment
4686
4974
  transformExperimentRow(row) {
4687
4975
  return {
@@ -5569,7 +5857,11 @@ function sessionToRecord(record) {
5569
5857
  deletedAt: record.deletedAt ?? null
5570
5858
  };
5571
5859
  }
5572
- var HarnessLibSQL = class extends storage.HarnessStorage {
5860
+ var HarnessLibSQL = class _HarnessLibSQL extends storage.HarnessStorage {
5861
+ /** Session records accumulate over time. Single table, anchored on `createdAt`. */
5862
+ static retentionTables = {
5863
+ sessions: { table: storage.TABLE_HARNESS_SESSIONS, column: "createdAt", indexed: true }
5864
+ };
5573
5865
  #db;
5574
5866
  constructor(config) {
5575
5867
  super();
@@ -5585,6 +5877,15 @@ var HarnessLibSQL = class extends storage.HarnessStorage {
5585
5877
  async dangerouslyClearAll() {
5586
5878
  await this.#db.deleteData({ tableName: storage.TABLE_HARNESS_SESSIONS });
5587
5879
  }
5880
+ /** Delete harness sessions older than the `sessions` policy's `maxAge`, batched. */
5881
+ async prune(policies, options) {
5882
+ const targets = resolveTargets({
5883
+ policies,
5884
+ descriptor: _HarnessLibSQL.retentionTables,
5885
+ order: ["sessions"]
5886
+ });
5887
+ return runPrune({ db: this.#db, domain: "harness", targets, options, logger: this.logger });
5888
+ }
5588
5889
  async loadSession(sessionId) {
5589
5890
  const row = await this.#db.select({
5590
5891
  tableName: storage.TABLE_HARNESS_SESSIONS,
@@ -6570,8 +6871,18 @@ var MCPServersLibSQL = class extends storage.MCPServersStorage {
6570
6871
  }
6571
6872
  };
6572
6873
  var OM_TABLE = "mastra_observational_memory";
6573
- var MemoryLibSQL = class extends storage.MemoryStorage {
6874
+ var MemoryLibSQL = class _MemoryLibSQL extends storage.MemoryStorage {
6574
6875
  supportsObservationalMemory = true;
6876
+ /**
6877
+ * Retention-eligible tables. `threads`, `messages`, and `resources` all anchor
6878
+ * on `createdAt` and are indexed for fast batched deletes. Cascade order is
6879
+ * enforced in `prune()` (children before threads), not here.
6880
+ */
6881
+ static retentionTables = {
6882
+ messages: { table: storage.TABLE_MESSAGES, column: "createdAt", indexed: true },
6883
+ resources: { table: storage.TABLE_RESOURCES, column: "createdAt", indexed: true },
6884
+ threads: { table: storage.TABLE_THREADS, column: "createdAt", indexed: true }
6885
+ };
6575
6886
  #client;
6576
6887
  #db;
6577
6888
  constructor(config) {
@@ -6646,6 +6957,21 @@ var MemoryLibSQL = class extends storage.MemoryStorage {
6646
6957
  await this.#db.deleteData({ tableName: OM_TABLE });
6647
6958
  }
6648
6959
  }
6960
+ /**
6961
+ * Delete rows older than each policy's `maxAge`, batched and cancellable.
6962
+ *
6963
+ * Deletes children before parents (messages/resources before threads) so a
6964
+ * `threads` policy doesn't strand rows behind a deleted parent — there are no
6965
+ * FKs in the LibSQL schema, so cascade ordering is explicit here.
6966
+ */
6967
+ async prune(policies, options) {
6968
+ const targets = resolveTargets({
6969
+ policies,
6970
+ descriptor: _MemoryLibSQL.retentionTables,
6971
+ order: ["messages", "resources", "threads"]
6972
+ });
6973
+ return runPrune({ db: this.#db, domain: "memory", targets, options, logger: this.logger });
6974
+ }
6649
6975
  parseRow(row) {
6650
6976
  let content = row.content;
6651
6977
  try {
@@ -8712,7 +9038,11 @@ function addArrayFilter(conditions, args, column, value) {
8712
9038
  conditions.push(`"${column}" IN (${values.map(() => "?").join(", ")})`);
8713
9039
  args.push(...values);
8714
9040
  }
8715
- var NotificationsLibSQL = class extends storage.NotificationsStorage {
9041
+ var NotificationsLibSQL = class _NotificationsLibSQL extends storage.NotificationsStorage {
9042
+ /** The notification feed grows unbounded. Single table, anchored on `createdAt`. */
9043
+ static retentionTables = {
9044
+ notifications: { table: storage.TABLE_NOTIFICATIONS, column: "createdAt", indexed: true }
9045
+ };
8716
9046
  #db;
8717
9047
  #client;
8718
9048
  constructor(config) {
@@ -8747,6 +9077,15 @@ var NotificationsLibSQL = class extends storage.NotificationsStorage {
8747
9077
  async dangerouslyClearAll() {
8748
9078
  await this.#db.deleteData({ tableName: storage.TABLE_NOTIFICATIONS });
8749
9079
  }
9080
+ /** Delete notifications older than the `notifications` policy's `maxAge`, batched. */
9081
+ async prune(policies, options) {
9082
+ const targets = resolveTargets({
9083
+ policies,
9084
+ descriptor: _NotificationsLibSQL.retentionTables,
9085
+ order: ["notifications"]
9086
+ });
9087
+ return runPrune({ db: this.#db, domain: "notifications", targets, options, logger: this.logger });
9088
+ }
8750
9089
  async createNotification(input) {
8751
9090
  const existing = await this.findCoalescable(input);
8752
9091
  if (existing) {
@@ -8936,7 +9275,14 @@ var NotificationsLibSQL = class extends storage.NotificationsStorage {
8936
9275
  return row ? rowToNotification(row) : void 0;
8937
9276
  }
8938
9277
  };
8939
- var ObservabilityLibSQL = class extends storage.ObservabilityStorage {
9278
+ var ObservabilityLibSQL = class _ObservabilityLibSQL extends storage.ObservabilityStorage {
9279
+ /**
9280
+ * Spans are the only physical table; traces are derived from them. Spans
9281
+ * anchor on `startedAt` (not `createdAt`), stored as an ISO-8601 string.
9282
+ */
9283
+ static retentionTables = {
9284
+ spans: { table: storage.TABLE_SPANS, column: "startedAt", indexed: true }
9285
+ };
8940
9286
  #db;
8941
9287
  constructor(config) {
8942
9288
  super();
@@ -8954,6 +9300,15 @@ var ObservabilityLibSQL = class extends storage.ObservabilityStorage {
8954
9300
  async dangerouslyClearAll() {
8955
9301
  await this.#db.deleteData({ tableName: storage.TABLE_SPANS });
8956
9302
  }
9303
+ /** Delete spans older than the `spans` policy's `maxAge`, batched. */
9304
+ async prune(policies, options) {
9305
+ const targets = resolveTargets({
9306
+ policies,
9307
+ descriptor: _ObservabilityLibSQL.retentionTables,
9308
+ order: ["spans"]
9309
+ });
9310
+ return runPrune({ db: this.#db, domain: "observability", targets, options, logger: this.logger });
9311
+ }
8957
9312
  /**
8958
9313
  * Manually run the spans migration to deduplicate and add the unique constraint.
8959
9314
  * This is intended to be called from the CLI when duplicates are detected.
@@ -9962,7 +10317,15 @@ function rowToTrigger(row) {
9962
10317
  if (metadata !== void 0) trigger.metadata = metadata;
9963
10318
  return trigger;
9964
10319
  }
9965
- var SchedulesLibSQL = class extends storage.SchedulesStorage {
10320
+ var SchedulesLibSQL = class _SchedulesLibSQL extends storage.SchedulesStorage {
10321
+ /**
10322
+ * The fire/run history (`schedule_triggers`, one row per fire) is the growth
10323
+ * table; schedule definitions are config and excluded. Anchored on
10324
+ * `actual_fire_at`, a bigint epoch-ms column (numeric comparison, not ISO).
10325
+ */
10326
+ static retentionTables = {
10327
+ triggers: { table: storage.TABLE_SCHEDULE_TRIGGERS, column: "actual_fire_at", indexed: true, anchorType: "epoch-ms" }
10328
+ };
9966
10329
  #db;
9967
10330
  #client;
9968
10331
  constructor(config) {
@@ -9998,6 +10361,15 @@ var SchedulesLibSQL = class extends storage.SchedulesStorage {
9998
10361
  await this.#db.deleteData({ tableName: storage.TABLE_SCHEDULE_TRIGGERS });
9999
10362
  await this.#db.deleteData({ tableName: storage.TABLE_SCHEDULES });
10000
10363
  }
10364
+ /** Delete schedule fire history older than the `triggers` policy's `maxAge`, batched. */
10365
+ async prune(policies, options) {
10366
+ const targets = resolveTargets({
10367
+ policies,
10368
+ descriptor: _SchedulesLibSQL.retentionTables,
10369
+ order: ["triggers"]
10370
+ });
10371
+ return runPrune({ db: this.#db, domain: "schedules", targets, options, logger: this.logger });
10372
+ }
10001
10373
  async createSchedule(schedule) {
10002
10374
  const existing = await this.getSchedule(schedule.id);
10003
10375
  if (existing) {
@@ -10672,7 +11044,13 @@ var ScorerDefinitionsLibSQL = class extends storage.ScorerDefinitionsStorage {
10672
11044
  };
10673
11045
  }
10674
11046
  };
10675
- var ScoresLibSQL = class extends storage.ScoresStorage {
11047
+ var ScoresLibSQL = class _ScoresLibSQL extends storage.ScoresStorage {
11048
+ /**
11049
+ * Scorer results accumulate as evals run. Single table, anchored on `createdAt`.
11050
+ */
11051
+ static retentionTables = {
11052
+ scorers: { table: storage.TABLE_SCORERS, column: "createdAt", indexed: true }
11053
+ };
10676
11054
  #db;
10677
11055
  #client;
10678
11056
  constructor(config) {
@@ -10692,6 +11070,15 @@ var ScoresLibSQL = class extends storage.ScoresStorage {
10692
11070
  async dangerouslyClearAll() {
10693
11071
  await this.#db.deleteData({ tableName: storage.TABLE_SCORERS });
10694
11072
  }
11073
+ /** Delete scorer results older than the `scorers` policy's `maxAge`, batched. */
11074
+ async prune(policies, options) {
11075
+ const targets = resolveTargets({
11076
+ policies,
11077
+ descriptor: _ScoresLibSQL.retentionTables,
11078
+ order: ["scorers"]
11079
+ });
11080
+ return runPrune({ db: this.#db, domain: "scores", targets, options, logger: this.logger });
11081
+ }
10695
11082
  async listScoresByRunId({
10696
11083
  runId,
10697
11084
  pagination
@@ -11575,7 +11962,15 @@ var SkillsLibSQL = class extends storage.SkillsStorage {
11575
11962
  };
11576
11963
  }
11577
11964
  };
11578
- var ThreadStateLibSQL = class extends storage.ThreadStateStorage {
11965
+ var ThreadStateLibSQL = class _ThreadStateLibSQL extends storage.ThreadStateStorage {
11966
+ /**
11967
+ * `thread_state` grows as a side effect of thread activity (one row per
11968
+ * thread per state type). It anchors on `updatedAt` (last activity), so state
11969
+ * for a thread that is still being appended to is not pruned by creation age.
11970
+ */
11971
+ static retentionTables = {
11972
+ threadState: { table: storage.TABLE_THREAD_STATE, column: "updatedAt", indexed: true }
11973
+ };
11579
11974
  #db;
11580
11975
  #client;
11581
11976
  constructor(config) {
@@ -11591,6 +11986,15 @@ var ThreadStateLibSQL = class extends storage.ThreadStateStorage {
11591
11986
  compositePrimaryKey: ["threadId", "type"]
11592
11987
  });
11593
11988
  }
11989
+ /** Delete thread state older than the `threadState` policy's `maxAge`, batched. */
11990
+ async prune(policies, options) {
11991
+ const targets = resolveTargets({
11992
+ policies,
11993
+ descriptor: _ThreadStateLibSQL.retentionTables,
11994
+ order: ["threadState"]
11995
+ });
11996
+ return runPrune({ db: this.#db, domain: "threadState", targets, options, logger: this.logger });
11997
+ }
11594
11998
  async dangerouslyClearAll() {
11595
11999
  try {
11596
12000
  await this.#client.execute(`DELETE FROM "${storage.TABLE_THREAD_STATE}"`);
@@ -11859,7 +12263,14 @@ var ToolProviderConnectionsLibSQL = class extends storage.ToolProviderConnection
11859
12263
  }
11860
12264
  }
11861
12265
  };
11862
- var WorkflowsLibSQL = class extends storage.WorkflowsStorage {
12266
+ var WorkflowsLibSQL = class _WorkflowsLibSQL extends storage.WorkflowsStorage {
12267
+ /**
12268
+ * Workflow run snapshots accumulate as runs execute. Anchored on `updatedAt`
12269
+ * (last activity) so suspended/long-running runs are not pruned by start age.
12270
+ */
12271
+ static retentionTables = {
12272
+ workflowSnapshot: { table: storage.TABLE_WORKFLOW_SNAPSHOT, column: "updatedAt", indexed: true }
12273
+ };
11863
12274
  #db;
11864
12275
  #client;
11865
12276
  executeWithRetry;
@@ -11912,6 +12323,15 @@ var WorkflowsLibSQL = class extends storage.WorkflowsStorage {
11912
12323
  async dangerouslyClearAll() {
11913
12324
  await this.#db.deleteData({ tableName: storage.TABLE_WORKFLOW_SNAPSHOT });
11914
12325
  }
12326
+ /** Delete workflow snapshots older than the `workflowSnapshot` policy's `maxAge`, batched. */
12327
+ async prune(policies, options) {
12328
+ const targets = resolveTargets({
12329
+ policies,
12330
+ descriptor: _WorkflowsLibSQL.retentionTables,
12331
+ order: ["workflowSnapshot"]
12332
+ });
12333
+ return runPrune({ db: this.#db, domain: "workflows", targets, options, logger: this.logger });
12334
+ }
11915
12335
  async setupPragmaSettings() {
11916
12336
  try {
11917
12337
  await this.#client.execute("PRAGMA busy_timeout = 10000;");
@@ -12767,7 +13187,7 @@ var LibSQLStore = class extends storage.MastraCompositeStore {
12767
13187
  if (!config.id || typeof config.id !== "string" || config.id.trim() === "") {
12768
13188
  throw new Error("LibSQLStore: id must be provided and cannot be empty.");
12769
13189
  }
12770
- super({ id: config.id, name: `LibSQLStore`, disableInit: config.disableInit });
13190
+ super({ id: config.id, name: `LibSQLStore`, disableInit: config.disableInit, retention: config.retention });
12771
13191
  this.maxRetries = config.maxRetries ?? 5;
12772
13192
  this.initialBackoffMs = config.initialBackoffMs ?? 100;
12773
13193
  this.connectionTimeoutMs = config.connectionTimeoutMs ?? DEFAULT_CONNECTION_TIMEOUT_MS;