@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.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createClient } from '@libsql/client';
2
2
  import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
3
- import { createVectorErrorId, AgentsStorage, AGENTS_SCHEMA, TABLE_AGENTS, AGENT_VERSIONS_SCHEMA, TABLE_AGENT_VERSIONS, createStorageErrorId, normalizePerPage, calculatePagination, BackgroundTasksStorage, TABLE_SCHEMAS, TABLE_BACKGROUND_TASKS, BlobStore, TABLE_SKILL_BLOBS, SKILL_BLOBS_SCHEMA, ChannelsStorage, TABLE_CHANNEL_INSTALLATIONS, TABLE_CHANNEL_CONFIG, DatasetsStorage, DATASETS_SCHEMA, TABLE_DATASETS, DATASET_ITEMS_SCHEMA, TABLE_DATASET_ITEMS, DATASET_VERSIONS_SCHEMA, TABLE_DATASET_VERSIONS, ensureDate, safelyParseJSON, TABLE_EXPERIMENT_RESULTS, TABLE_EXPERIMENTS, ExperimentsStorage, EXPERIMENTS_SCHEMA, EXPERIMENT_RESULTS_SCHEMA, FavoritesStorage, FAVORITES_SCHEMA, TABLE_FAVORITES, TABLE_SKILLS, HarnessStorage, TABLE_HARNESS_SESSIONS, MCPClientsStorage, MCP_CLIENTS_SCHEMA, TABLE_MCP_CLIENTS, MCP_CLIENT_VERSIONS_SCHEMA, TABLE_MCP_CLIENT_VERSIONS, MCPServersStorage, MCP_SERVERS_SCHEMA, TABLE_MCP_SERVERS, MCP_SERVER_VERSIONS_SCHEMA, TABLE_MCP_SERVER_VERSIONS, MemoryStorage, TABLE_THREADS, TABLE_MESSAGES, TABLE_RESOURCES, OBSERVATIONAL_MEMORY_TABLE_SCHEMA, NotificationsStorage, TABLE_NOTIFICATIONS, ObservabilityStorage, SPAN_SCHEMA, TABLE_SPANS, listTracesArgsSchema, toTraceSpans, PromptBlocksStorage, PROMPT_BLOCKS_SCHEMA, TABLE_PROMPT_BLOCKS, PROMPT_BLOCK_VERSIONS_SCHEMA, TABLE_PROMPT_BLOCK_VERSIONS, SchedulesStorage, TABLE_SCHEDULES, TABLE_SCHEDULE_TRIGGERS, ScorerDefinitionsStorage, SCORER_DEFINITIONS_SCHEMA, TABLE_SCORER_DEFINITIONS, SCORER_DEFINITION_VERSIONS_SCHEMA, TABLE_SCORER_DEFINITION_VERSIONS, ScoresStorage, SCORERS_SCHEMA, TABLE_SCORERS, transformScoreRow, SkillsStorage, SKILLS_SCHEMA, SKILL_VERSIONS_SCHEMA, TABLE_SKILL_VERSIONS, ThreadStateStorage, THREAD_STATE_SCHEMA, TABLE_THREAD_STATE, ToolProviderConnectionsStorage, TOOL_PROVIDER_CONNECTIONS_SCHEMA, TABLE_TOOL_PROVIDER_CONNECTIONS, WorkflowsStorage, TABLE_WORKFLOW_SNAPSHOT, WorkspacesStorage, WORKSPACES_SCHEMA, TABLE_WORKSPACES, WORKSPACE_VERSIONS_SCHEMA, TABLE_WORKSPACE_VERSIONS, MastraCompositeStore, getSqlType, TraceStatus, mergeWorkflowStepResult } from '@mastra/core/storage';
3
+ import { createVectorErrorId, AgentsStorage, AGENTS_SCHEMA, TABLE_AGENTS, AGENT_VERSIONS_SCHEMA, TABLE_AGENT_VERSIONS, createStorageErrorId, normalizePerPage, calculatePagination, BackgroundTasksStorage, TABLE_BACKGROUND_TASKS, TABLE_SCHEMAS, BlobStore, TABLE_SKILL_BLOBS, SKILL_BLOBS_SCHEMA, ChannelsStorage, TABLE_CHANNEL_INSTALLATIONS, TABLE_CHANNEL_CONFIG, DatasetsStorage, DATASETS_SCHEMA, TABLE_DATASETS, DATASET_ITEMS_SCHEMA, TABLE_DATASET_ITEMS, DATASET_VERSIONS_SCHEMA, TABLE_DATASET_VERSIONS, ensureDate, safelyParseJSON, TABLE_EXPERIMENT_RESULTS, TABLE_EXPERIMENTS, ExperimentsStorage, EXPERIMENTS_SCHEMA, EXPERIMENT_RESULTS_SCHEMA, FavoritesStorage, FAVORITES_SCHEMA, TABLE_FAVORITES, TABLE_SKILLS, HarnessStorage, TABLE_HARNESS_SESSIONS, MCPClientsStorage, MCP_CLIENTS_SCHEMA, TABLE_MCP_CLIENTS, MCP_CLIENT_VERSIONS_SCHEMA, TABLE_MCP_CLIENT_VERSIONS, MCPServersStorage, MCP_SERVERS_SCHEMA, TABLE_MCP_SERVERS, MCP_SERVER_VERSIONS_SCHEMA, TABLE_MCP_SERVER_VERSIONS, MemoryStorage, TABLE_THREADS, TABLE_RESOURCES, TABLE_MESSAGES, OBSERVATIONAL_MEMORY_TABLE_SCHEMA, NotificationsStorage, TABLE_NOTIFICATIONS, ObservabilityStorage, TABLE_SPANS, SPAN_SCHEMA, listTracesArgsSchema, toTraceSpans, PromptBlocksStorage, PROMPT_BLOCKS_SCHEMA, TABLE_PROMPT_BLOCKS, PROMPT_BLOCK_VERSIONS_SCHEMA, TABLE_PROMPT_BLOCK_VERSIONS, SchedulesStorage, TABLE_SCHEDULE_TRIGGERS, TABLE_SCHEDULES, ScorerDefinitionsStorage, SCORER_DEFINITIONS_SCHEMA, TABLE_SCORER_DEFINITIONS, SCORER_DEFINITION_VERSIONS_SCHEMA, TABLE_SCORER_DEFINITION_VERSIONS, ScoresStorage, TABLE_SCORERS, SCORERS_SCHEMA, transformScoreRow, SkillsStorage, SKILLS_SCHEMA, SKILL_VERSIONS_SCHEMA, TABLE_SKILL_VERSIONS, ThreadStateStorage, TABLE_THREAD_STATE, THREAD_STATE_SCHEMA, ToolProviderConnectionsStorage, TOOL_PROVIDER_CONNECTIONS_SCHEMA, TABLE_TOOL_PROVIDER_CONNECTIONS, WorkflowsStorage, TABLE_WORKFLOW_SNAPSHOT, WorkspacesStorage, WORKSPACES_SCHEMA, TABLE_WORKSPACES, WORKSPACE_VERSIONS_SCHEMA, TABLE_WORKSPACE_VERSIONS, MastraCompositeStore, getSqlType, parseDuration, TraceStatus, mergeWorkflowStepResult } from '@mastra/core/storage';
4
4
  import { parseSqlIdentifier, parseFieldKey } from '@mastra/core/utils';
5
5
  import { MastraVector, validateTopK, validateUpsertInput } from '@mastra/core/vector';
6
6
  import { BaseFilterTranslator } from '@mastra/core/vector/filter';
@@ -1456,6 +1456,11 @@ function resolveClient(config) {
1456
1456
  ...isLocal ? { timeout } : {}
1457
1457
  });
1458
1458
  }
1459
+ function assertPositiveLimit(limit) {
1460
+ if (!Number.isSafeInteger(limit) || limit <= 0) {
1461
+ throw new Error(`prune limit must be a positive integer; received ${limit}`);
1462
+ }
1463
+ }
1459
1464
  var LibSQLDB = class extends MastraBase {
1460
1465
  client;
1461
1466
  maxRetries;
@@ -2276,6 +2281,102 @@ Note: This migration may take some time for large tables.
2276
2281
  this.logger?.error?.(mastraError.toString());
2277
2282
  }
2278
2283
  }
2284
+ // ---------------------------------------------------------------------------
2285
+ // Retention helpers (prune)
2286
+ //
2287
+ // Low-level SQL primitives the memory and observability domains build their
2288
+ // `prune()` on, plus a plain user-invoked `VACUUM`. Keeping the SQL here
2289
+ // reuses the existing write-lock + retry machinery and keeps identifier
2290
+ // parsing in one place.
2291
+ // ---------------------------------------------------------------------------
2292
+ /**
2293
+ * Delete up to `limit` of the oldest rows whose `column` timestamp is strictly
2294
+ * before `cutoffMs`, in a single bounded statement. Returns the number of rows
2295
+ * removed so the caller can decide whether more batches remain.
2296
+ *
2297
+ * Deletes by `rowid` so the range scan is bounded by LIMIT and the write set
2298
+ * stays small — short locks, bounded WAL growth. Requires an index on
2299
+ * `column` to be fast at scale (see ensureIndex).
2300
+ */
2301
+ async pruneBatch({
2302
+ tableName,
2303
+ column,
2304
+ cutoff,
2305
+ limit
2306
+ }) {
2307
+ assertPositiveLimit(limit);
2308
+ const parsedTable = parseSqlIdentifier(tableName, "table name");
2309
+ const parsedColumn = parseSqlIdentifier(column, "column name");
2310
+ const sql = `DELETE FROM "${parsedTable}" WHERE rowid IN (SELECT rowid FROM "${parsedTable}" WHERE "${parsedColumn}" < ? LIMIT ?)`;
2311
+ const result = await this.executeWriteOperationWithRetry(
2312
+ () => withClientWriteLock(this.client, () => this.client.execute({ sql, args: [cutoff, limit] })),
2313
+ `prune ${tableName}`
2314
+ );
2315
+ return Number(result.rowsAffected ?? 0);
2316
+ }
2317
+ /**
2318
+ * Delete up to `limit` aged parent rows *and* their child rows together, in a
2319
+ * single transaction. Used for whole-unit (parent-driven) pruning where
2320
+ * children must die with their parent (e.g. an aged experiment and its
2321
+ * experiment_results) — deleting per unit means a bound or abort between
2322
+ * batches never leaves a parent hollow (kept, but with its children gone) or
2323
+ * children orphaned.
2324
+ *
2325
+ * Both deletes target the same parent set via an identical deterministic
2326
+ * subquery (`ORDER BY rowid LIMIT ?`); the batch runs as one write
2327
+ * transaction, so the set cannot change between the two statements.
2328
+ */
2329
+ async pruneUnitsBatch({
2330
+ parentTable,
2331
+ parentKey,
2332
+ parentColumn,
2333
+ childTable,
2334
+ childForeignKey,
2335
+ cutoff,
2336
+ limit
2337
+ }) {
2338
+ assertPositiveLimit(limit);
2339
+ const parsedChild = parseSqlIdentifier(childTable, "table name");
2340
+ const parsedChildFk = parseSqlIdentifier(childForeignKey, "column name");
2341
+ const parsedParent = parseSqlIdentifier(parentTable, "table name");
2342
+ const parsedParentKey = parseSqlIdentifier(parentKey, "column name");
2343
+ const parsedParentColumn = parseSqlIdentifier(parentColumn, "column name");
2344
+ const agedParents = `SELECT "${parsedParentKey}" FROM "${parsedParent}" WHERE "${parsedParentColumn}" < ? ORDER BY rowid LIMIT ?`;
2345
+ const results = await this.executeWriteOperationWithRetry(
2346
+ () => withClientWriteLock(
2347
+ this.client,
2348
+ () => this.client.batch(
2349
+ [
2350
+ {
2351
+ sql: `DELETE FROM "${parsedChild}" WHERE "${parsedChildFk}" IN (${agedParents})`,
2352
+ args: [cutoff, limit]
2353
+ },
2354
+ {
2355
+ sql: `DELETE FROM "${parsedParent}" WHERE "${parsedParentKey}" IN (${agedParents})`,
2356
+ args: [cutoff, limit]
2357
+ }
2358
+ ],
2359
+ "write"
2360
+ )
2361
+ ),
2362
+ `prune units ${parentTable}`
2363
+ );
2364
+ return {
2365
+ children: Number(results[0]?.rowsAffected ?? 0),
2366
+ parents: Number(results[1]?.rowsAffected ?? 0)
2367
+ };
2368
+ }
2369
+ /** Create an index if it does not already exist. */
2370
+ async ensureIndex({
2371
+ indexName,
2372
+ tableName,
2373
+ column
2374
+ }) {
2375
+ const parsedTable = parseSqlIdentifier(tableName, "table name");
2376
+ const parsedColumn = parseSqlIdentifier(column, "column name");
2377
+ const parsedIndex = parseSqlIdentifier(indexName, "index name");
2378
+ await this.client.execute(`CREATE INDEX IF NOT EXISTS "${parsedIndex}" ON "${parsedTable}" ("${parsedColumn}")`);
2379
+ }
2279
2380
  };
2280
2381
  var AgentsLibSQL = class extends AgentsStorage {
2281
2382
  #db;
@@ -3113,6 +3214,113 @@ var AgentsLibSQL = class extends AgentsStorage {
3113
3214
  };
3114
3215
  }
3115
3216
  };
3217
+ var DEFAULT_BATCH_SIZE = 1e3;
3218
+ async function ensureAnchorIndex(db, target, logger) {
3219
+ if (!target.indexed) return;
3220
+ try {
3221
+ await db.ensureIndex({
3222
+ indexName: `idx_retention_${target.table}_${target.column}`,
3223
+ tableName: target.table,
3224
+ column: target.column
3225
+ });
3226
+ } catch (error) {
3227
+ logger?.warn?.(`Failed to ensure retention index on ${target.table}(${target.column}):`, error);
3228
+ }
3229
+ }
3230
+ async function sleep(ms, signal) {
3231
+ if (ms <= 0 || signal?.aborted) return;
3232
+ await new Promise((resolve) => {
3233
+ const timer = setTimeout(() => {
3234
+ signal?.removeEventListener("abort", onAbort);
3235
+ resolve();
3236
+ }, ms);
3237
+ const onAbort = () => {
3238
+ clearTimeout(timer);
3239
+ resolve();
3240
+ };
3241
+ signal?.addEventListener("abort", onAbort, { once: true });
3242
+ });
3243
+ }
3244
+ function cutoffFor(policy, anchorType, now = Date.now()) {
3245
+ const cutoffMs = now - parseDuration(policy.maxAge);
3246
+ return anchorType === "epoch-ms" ? cutoffMs : new Date(cutoffMs).toISOString();
3247
+ }
3248
+ async function runBatchedDelete({
3249
+ deleteBatch,
3250
+ batchSize,
3251
+ options
3252
+ }) {
3253
+ if (!Number.isSafeInteger(batchSize) || batchSize <= 0) {
3254
+ throw new Error(`retention batchSize must be a positive integer; received ${batchSize}`);
3255
+ }
3256
+ let deleted = 0;
3257
+ let batches = 0;
3258
+ while (true) {
3259
+ if (options?.signal?.aborted) return { deleted, done: false };
3260
+ if (options?.maxBatches !== void 0 && batches >= options.maxBatches) return { deleted, done: false };
3261
+ let limit = batchSize;
3262
+ if (options?.maxRows !== void 0) {
3263
+ const remaining = options.maxRows - deleted;
3264
+ if (remaining <= 0) return { deleted, done: false };
3265
+ limit = Math.min(limit, remaining);
3266
+ }
3267
+ const affected = await deleteBatch(limit);
3268
+ deleted += affected;
3269
+ batches += 1;
3270
+ if (affected < limit) return { deleted, done: true };
3271
+ if (options?.pauseMs) {
3272
+ await sleep(options.pauseMs, options.signal);
3273
+ }
3274
+ }
3275
+ }
3276
+ async function runPrune({
3277
+ db,
3278
+ domain,
3279
+ targets,
3280
+ options,
3281
+ logger
3282
+ }) {
3283
+ const results = [];
3284
+ const now = Date.now();
3285
+ for (const target of targets) {
3286
+ if (options?.signal?.aborted) {
3287
+ results.push({ domain, table: target.table, deleted: 0, done: false });
3288
+ continue;
3289
+ }
3290
+ await ensureAnchorIndex(db, target, logger);
3291
+ const cutoff = cutoffFor(target.policy, target.anchorType, now);
3292
+ const batchSize = target.policy.batchSize ?? DEFAULT_BATCH_SIZE;
3293
+ const { deleted, done } = await runBatchedDelete({
3294
+ deleteBatch: (limit) => db.pruneBatch({ tableName: target.table, column: target.column, cutoff, limit }),
3295
+ batchSize,
3296
+ options
3297
+ });
3298
+ results.push({ domain, table: target.table, deleted, done });
3299
+ }
3300
+ return results;
3301
+ }
3302
+ function resolveTargets({
3303
+ policies,
3304
+ descriptor,
3305
+ order
3306
+ }) {
3307
+ const targets = [];
3308
+ for (const key of order) {
3309
+ const policy = policies[key];
3310
+ const entry = descriptor[key];
3311
+ if (!policy || !entry) continue;
3312
+ targets.push({
3313
+ table: entry.table,
3314
+ column: entry.column,
3315
+ anchorType: entry.anchorType ?? "timestamp",
3316
+ indexed: entry.indexed ?? true,
3317
+ policy
3318
+ });
3319
+ }
3320
+ return targets;
3321
+ }
3322
+
3323
+ // src/storage/domains/background-tasks/index.ts
3116
3324
  function serializeJson(v) {
3117
3325
  if (typeof v === "object" && v != null) return JSON.stringify(v);
3118
3326
  return v ?? null;
@@ -3151,7 +3359,15 @@ function rowToTask(row) {
3151
3359
  completedAt: row.completedAt ? new Date(String(row.completedAt)) : void 0
3152
3360
  };
3153
3361
  }
3154
- var BackgroundTasksLibSQL = class extends BackgroundTasksStorage {
3362
+ var BackgroundTasksLibSQL = class _BackgroundTasksLibSQL extends BackgroundTasksStorage {
3363
+ /**
3364
+ * Completed/failed task records accumulate. Anchored on `completedAt`, which
3365
+ * is NULL while a task is in-flight (pending/running/suspended) — so `completedAt
3366
+ * < cutoff` never prunes a live task, no explicit status filter needed.
3367
+ */
3368
+ static retentionTables = {
3369
+ backgroundTasks: { table: TABLE_BACKGROUND_TASKS, column: "completedAt", indexed: true }
3370
+ };
3155
3371
  #db;
3156
3372
  #client;
3157
3373
  constructor(config) {
@@ -3174,6 +3390,15 @@ var BackgroundTasksLibSQL = class extends BackgroundTasksStorage {
3174
3390
  async dangerouslyClearAll() {
3175
3391
  await this.#db.deleteData({ tableName: TABLE_BACKGROUND_TASKS });
3176
3392
  }
3393
+ /** Delete completed tasks older than the `backgroundTasks` policy's `maxAge`, batched. */
3394
+ async prune(policies, options) {
3395
+ const targets = resolveTargets({
3396
+ policies,
3397
+ descriptor: _BackgroundTasksLibSQL.retentionTables,
3398
+ order: ["backgroundTasks"]
3399
+ });
3400
+ return runPrune({ db: this.#db, domain: "backgroundTasks", targets, options, logger: this.logger });
3401
+ }
3177
3402
  async createTask(task) {
3178
3403
  await this.#db.insert({
3179
3404
  tableName: TABLE_BACKGROUND_TASKS,
@@ -4624,7 +4849,18 @@ var DatasetsLibSQL = class extends DatasetsStorage {
4624
4849
  }
4625
4850
  }
4626
4851
  };
4852
+ var DEFAULT_PRUNE_BATCH_SIZE = 1e3;
4627
4853
  var ExperimentsLibSQL = class extends ExperimentsStorage {
4854
+ /**
4855
+ * An experiment is pruned as a whole unit: when `experiments.completedAt` is
4856
+ * older than the policy, the run and all its `experiment_results` rows are
4857
+ * deleted together (results cascade with their parent, matching
4858
+ * `deleteExperiment`). Results are not an independent retention key. NULL
4859
+ * `completedAt` (still running) is never pruned.
4860
+ */
4861
+ static retentionTables = {
4862
+ experiments: { table: TABLE_EXPERIMENTS, column: "completedAt", indexed: true }
4863
+ };
4628
4864
  #db;
4629
4865
  #client;
4630
4866
  constructor(config) {
@@ -4680,6 +4916,58 @@ var ExperimentsLibSQL = class extends ExperimentsStorage {
4680
4916
  await this.#db.deleteData({ tableName: TABLE_EXPERIMENT_RESULTS });
4681
4917
  await this.#db.deleteData({ tableName: TABLE_EXPERIMENTS });
4682
4918
  }
4919
+ /**
4920
+ * Prune whole experiments older than the `experiments` policy's `maxAge`.
4921
+ *
4922
+ * Each batch selects up to `batchSize` aged experiments and deletes their
4923
+ * `experiment_results` rows and the experiment rows in one transaction —
4924
+ * mirroring `deleteExperiment` — so hitting `maxBatches`/`maxRows` or the
4925
+ * abort signal between batches never leaves a run hollow (parent kept,
4926
+ * results gone). NULL `completedAt` (still running) is excluded by the
4927
+ * `< cutoff` predicate. Bounds count whole experiments, not rows.
4928
+ */
4929
+ async prune(policies, options) {
4930
+ const policy = policies["experiments"];
4931
+ if (!policy || options?.signal?.aborted) {
4932
+ return policy ? [
4933
+ { domain: "experiments", table: TABLE_EXPERIMENT_RESULTS, deleted: 0, done: false },
4934
+ { domain: "experiments", table: TABLE_EXPERIMENTS, deleted: 0, done: false }
4935
+ ] : [];
4936
+ }
4937
+ try {
4938
+ await this.#db.ensureIndex({
4939
+ indexName: `idx_retention_${TABLE_EXPERIMENTS}_completedAt`,
4940
+ tableName: TABLE_EXPERIMENTS,
4941
+ column: "completedAt"
4942
+ });
4943
+ } catch (error) {
4944
+ this.logger?.warn?.(`Failed to ensure retention index on ${TABLE_EXPERIMENTS}(completedAt):`, error);
4945
+ }
4946
+ const cutoff = cutoffFor(policy, "timestamp");
4947
+ const batchSize = policy.batchSize ?? DEFAULT_PRUNE_BATCH_SIZE;
4948
+ let childDeleted = 0;
4949
+ const parent = await runBatchedDelete({
4950
+ deleteBatch: async (limit) => {
4951
+ const { parents, children } = await this.#db.pruneUnitsBatch({
4952
+ parentTable: TABLE_EXPERIMENTS,
4953
+ parentKey: "id",
4954
+ parentColumn: "completedAt",
4955
+ childTable: TABLE_EXPERIMENT_RESULTS,
4956
+ childForeignKey: "experimentId",
4957
+ cutoff,
4958
+ limit
4959
+ });
4960
+ childDeleted += children;
4961
+ return parents;
4962
+ },
4963
+ batchSize,
4964
+ options
4965
+ });
4966
+ return [
4967
+ { domain: "experiments", table: TABLE_EXPERIMENT_RESULTS, deleted: childDeleted, done: parent.done },
4968
+ { domain: "experiments", table: TABLE_EXPERIMENTS, deleted: parent.deleted, done: parent.done }
4969
+ ];
4970
+ }
4683
4971
  // Helper to transform row to Experiment
4684
4972
  transformExperimentRow(row) {
4685
4973
  return {
@@ -5567,7 +5855,11 @@ function sessionToRecord(record) {
5567
5855
  deletedAt: record.deletedAt ?? null
5568
5856
  };
5569
5857
  }
5570
- var HarnessLibSQL = class extends HarnessStorage {
5858
+ var HarnessLibSQL = class _HarnessLibSQL extends HarnessStorage {
5859
+ /** Session records accumulate over time. Single table, anchored on `createdAt`. */
5860
+ static retentionTables = {
5861
+ sessions: { table: TABLE_HARNESS_SESSIONS, column: "createdAt", indexed: true }
5862
+ };
5571
5863
  #db;
5572
5864
  constructor(config) {
5573
5865
  super();
@@ -5583,6 +5875,15 @@ var HarnessLibSQL = class extends HarnessStorage {
5583
5875
  async dangerouslyClearAll() {
5584
5876
  await this.#db.deleteData({ tableName: TABLE_HARNESS_SESSIONS });
5585
5877
  }
5878
+ /** Delete harness sessions older than the `sessions` policy's `maxAge`, batched. */
5879
+ async prune(policies, options) {
5880
+ const targets = resolveTargets({
5881
+ policies,
5882
+ descriptor: _HarnessLibSQL.retentionTables,
5883
+ order: ["sessions"]
5884
+ });
5885
+ return runPrune({ db: this.#db, domain: "harness", targets, options, logger: this.logger });
5886
+ }
5586
5887
  async loadSession(sessionId) {
5587
5888
  const row = await this.#db.select({
5588
5889
  tableName: TABLE_HARNESS_SESSIONS,
@@ -6568,8 +6869,18 @@ var MCPServersLibSQL = class extends MCPServersStorage {
6568
6869
  }
6569
6870
  };
6570
6871
  var OM_TABLE = "mastra_observational_memory";
6571
- var MemoryLibSQL = class extends MemoryStorage {
6872
+ var MemoryLibSQL = class _MemoryLibSQL extends MemoryStorage {
6572
6873
  supportsObservationalMemory = true;
6874
+ /**
6875
+ * Retention-eligible tables. `threads`, `messages`, and `resources` all anchor
6876
+ * on `createdAt` and are indexed for fast batched deletes. Cascade order is
6877
+ * enforced in `prune()` (children before threads), not here.
6878
+ */
6879
+ static retentionTables = {
6880
+ messages: { table: TABLE_MESSAGES, column: "createdAt", indexed: true },
6881
+ resources: { table: TABLE_RESOURCES, column: "createdAt", indexed: true },
6882
+ threads: { table: TABLE_THREADS, column: "createdAt", indexed: true }
6883
+ };
6573
6884
  #client;
6574
6885
  #db;
6575
6886
  constructor(config) {
@@ -6644,6 +6955,21 @@ var MemoryLibSQL = class extends MemoryStorage {
6644
6955
  await this.#db.deleteData({ tableName: OM_TABLE });
6645
6956
  }
6646
6957
  }
6958
+ /**
6959
+ * Delete rows older than each policy's `maxAge`, batched and cancellable.
6960
+ *
6961
+ * Deletes children before parents (messages/resources before threads) so a
6962
+ * `threads` policy doesn't strand rows behind a deleted parent — there are no
6963
+ * FKs in the LibSQL schema, so cascade ordering is explicit here.
6964
+ */
6965
+ async prune(policies, options) {
6966
+ const targets = resolveTargets({
6967
+ policies,
6968
+ descriptor: _MemoryLibSQL.retentionTables,
6969
+ order: ["messages", "resources", "threads"]
6970
+ });
6971
+ return runPrune({ db: this.#db, domain: "memory", targets, options, logger: this.logger });
6972
+ }
6647
6973
  parseRow(row) {
6648
6974
  let content = row.content;
6649
6975
  try {
@@ -8710,7 +9036,11 @@ function addArrayFilter(conditions, args, column, value) {
8710
9036
  conditions.push(`"${column}" IN (${values.map(() => "?").join(", ")})`);
8711
9037
  args.push(...values);
8712
9038
  }
8713
- var NotificationsLibSQL = class extends NotificationsStorage {
9039
+ var NotificationsLibSQL = class _NotificationsLibSQL extends NotificationsStorage {
9040
+ /** The notification feed grows unbounded. Single table, anchored on `createdAt`. */
9041
+ static retentionTables = {
9042
+ notifications: { table: TABLE_NOTIFICATIONS, column: "createdAt", indexed: true }
9043
+ };
8714
9044
  #db;
8715
9045
  #client;
8716
9046
  constructor(config) {
@@ -8745,6 +9075,15 @@ var NotificationsLibSQL = class extends NotificationsStorage {
8745
9075
  async dangerouslyClearAll() {
8746
9076
  await this.#db.deleteData({ tableName: TABLE_NOTIFICATIONS });
8747
9077
  }
9078
+ /** Delete notifications older than the `notifications` policy's `maxAge`, batched. */
9079
+ async prune(policies, options) {
9080
+ const targets = resolveTargets({
9081
+ policies,
9082
+ descriptor: _NotificationsLibSQL.retentionTables,
9083
+ order: ["notifications"]
9084
+ });
9085
+ return runPrune({ db: this.#db, domain: "notifications", targets, options, logger: this.logger });
9086
+ }
8748
9087
  async createNotification(input) {
8749
9088
  const existing = await this.findCoalescable(input);
8750
9089
  if (existing) {
@@ -8934,7 +9273,14 @@ var NotificationsLibSQL = class extends NotificationsStorage {
8934
9273
  return row ? rowToNotification(row) : void 0;
8935
9274
  }
8936
9275
  };
8937
- var ObservabilityLibSQL = class extends ObservabilityStorage {
9276
+ var ObservabilityLibSQL = class _ObservabilityLibSQL extends ObservabilityStorage {
9277
+ /**
9278
+ * Spans are the only physical table; traces are derived from them. Spans
9279
+ * anchor on `startedAt` (not `createdAt`), stored as an ISO-8601 string.
9280
+ */
9281
+ static retentionTables = {
9282
+ spans: { table: TABLE_SPANS, column: "startedAt", indexed: true }
9283
+ };
8938
9284
  #db;
8939
9285
  constructor(config) {
8940
9286
  super();
@@ -8952,6 +9298,15 @@ var ObservabilityLibSQL = class extends ObservabilityStorage {
8952
9298
  async dangerouslyClearAll() {
8953
9299
  await this.#db.deleteData({ tableName: TABLE_SPANS });
8954
9300
  }
9301
+ /** Delete spans older than the `spans` policy's `maxAge`, batched. */
9302
+ async prune(policies, options) {
9303
+ const targets = resolveTargets({
9304
+ policies,
9305
+ descriptor: _ObservabilityLibSQL.retentionTables,
9306
+ order: ["spans"]
9307
+ });
9308
+ return runPrune({ db: this.#db, domain: "observability", targets, options, logger: this.logger });
9309
+ }
8955
9310
  /**
8956
9311
  * Manually run the spans migration to deduplicate and add the unique constraint.
8957
9312
  * This is intended to be called from the CLI when duplicates are detected.
@@ -9960,7 +10315,15 @@ function rowToTrigger(row) {
9960
10315
  if (metadata !== void 0) trigger.metadata = metadata;
9961
10316
  return trigger;
9962
10317
  }
9963
- var SchedulesLibSQL = class extends SchedulesStorage {
10318
+ var SchedulesLibSQL = class _SchedulesLibSQL extends SchedulesStorage {
10319
+ /**
10320
+ * The fire/run history (`schedule_triggers`, one row per fire) is the growth
10321
+ * table; schedule definitions are config and excluded. Anchored on
10322
+ * `actual_fire_at`, a bigint epoch-ms column (numeric comparison, not ISO).
10323
+ */
10324
+ static retentionTables = {
10325
+ triggers: { table: TABLE_SCHEDULE_TRIGGERS, column: "actual_fire_at", indexed: true, anchorType: "epoch-ms" }
10326
+ };
9964
10327
  #db;
9965
10328
  #client;
9966
10329
  constructor(config) {
@@ -9996,6 +10359,15 @@ var SchedulesLibSQL = class extends SchedulesStorage {
9996
10359
  await this.#db.deleteData({ tableName: TABLE_SCHEDULE_TRIGGERS });
9997
10360
  await this.#db.deleteData({ tableName: TABLE_SCHEDULES });
9998
10361
  }
10362
+ /** Delete schedule fire history older than the `triggers` policy's `maxAge`, batched. */
10363
+ async prune(policies, options) {
10364
+ const targets = resolveTargets({
10365
+ policies,
10366
+ descriptor: _SchedulesLibSQL.retentionTables,
10367
+ order: ["triggers"]
10368
+ });
10369
+ return runPrune({ db: this.#db, domain: "schedules", targets, options, logger: this.logger });
10370
+ }
9999
10371
  async createSchedule(schedule) {
10000
10372
  const existing = await this.getSchedule(schedule.id);
10001
10373
  if (existing) {
@@ -10670,7 +11042,13 @@ var ScorerDefinitionsLibSQL = class extends ScorerDefinitionsStorage {
10670
11042
  };
10671
11043
  }
10672
11044
  };
10673
- var ScoresLibSQL = class extends ScoresStorage {
11045
+ var ScoresLibSQL = class _ScoresLibSQL extends ScoresStorage {
11046
+ /**
11047
+ * Scorer results accumulate as evals run. Single table, anchored on `createdAt`.
11048
+ */
11049
+ static retentionTables = {
11050
+ scorers: { table: TABLE_SCORERS, column: "createdAt", indexed: true }
11051
+ };
10674
11052
  #db;
10675
11053
  #client;
10676
11054
  constructor(config) {
@@ -10690,6 +11068,15 @@ var ScoresLibSQL = class extends ScoresStorage {
10690
11068
  async dangerouslyClearAll() {
10691
11069
  await this.#db.deleteData({ tableName: TABLE_SCORERS });
10692
11070
  }
11071
+ /** Delete scorer results older than the `scorers` policy's `maxAge`, batched. */
11072
+ async prune(policies, options) {
11073
+ const targets = resolveTargets({
11074
+ policies,
11075
+ descriptor: _ScoresLibSQL.retentionTables,
11076
+ order: ["scorers"]
11077
+ });
11078
+ return runPrune({ db: this.#db, domain: "scores", targets, options, logger: this.logger });
11079
+ }
10693
11080
  async listScoresByRunId({
10694
11081
  runId,
10695
11082
  pagination
@@ -11573,7 +11960,15 @@ var SkillsLibSQL = class extends SkillsStorage {
11573
11960
  };
11574
11961
  }
11575
11962
  };
11576
- var ThreadStateLibSQL = class extends ThreadStateStorage {
11963
+ var ThreadStateLibSQL = class _ThreadStateLibSQL extends ThreadStateStorage {
11964
+ /**
11965
+ * `thread_state` grows as a side effect of thread activity (one row per
11966
+ * thread per state type). It anchors on `updatedAt` (last activity), so state
11967
+ * for a thread that is still being appended to is not pruned by creation age.
11968
+ */
11969
+ static retentionTables = {
11970
+ threadState: { table: TABLE_THREAD_STATE, column: "updatedAt", indexed: true }
11971
+ };
11577
11972
  #db;
11578
11973
  #client;
11579
11974
  constructor(config) {
@@ -11589,6 +11984,15 @@ var ThreadStateLibSQL = class extends ThreadStateStorage {
11589
11984
  compositePrimaryKey: ["threadId", "type"]
11590
11985
  });
11591
11986
  }
11987
+ /** Delete thread state older than the `threadState` policy's `maxAge`, batched. */
11988
+ async prune(policies, options) {
11989
+ const targets = resolveTargets({
11990
+ policies,
11991
+ descriptor: _ThreadStateLibSQL.retentionTables,
11992
+ order: ["threadState"]
11993
+ });
11994
+ return runPrune({ db: this.#db, domain: "threadState", targets, options, logger: this.logger });
11995
+ }
11592
11996
  async dangerouslyClearAll() {
11593
11997
  try {
11594
11998
  await this.#client.execute(`DELETE FROM "${TABLE_THREAD_STATE}"`);
@@ -11857,7 +12261,14 @@ var ToolProviderConnectionsLibSQL = class extends ToolProviderConnectionsStorage
11857
12261
  }
11858
12262
  }
11859
12263
  };
11860
- var WorkflowsLibSQL = class extends WorkflowsStorage {
12264
+ var WorkflowsLibSQL = class _WorkflowsLibSQL extends WorkflowsStorage {
12265
+ /**
12266
+ * Workflow run snapshots accumulate as runs execute. Anchored on `updatedAt`
12267
+ * (last activity) so suspended/long-running runs are not pruned by start age.
12268
+ */
12269
+ static retentionTables = {
12270
+ workflowSnapshot: { table: TABLE_WORKFLOW_SNAPSHOT, column: "updatedAt", indexed: true }
12271
+ };
11861
12272
  #db;
11862
12273
  #client;
11863
12274
  executeWithRetry;
@@ -11910,6 +12321,15 @@ var WorkflowsLibSQL = class extends WorkflowsStorage {
11910
12321
  async dangerouslyClearAll() {
11911
12322
  await this.#db.deleteData({ tableName: TABLE_WORKFLOW_SNAPSHOT });
11912
12323
  }
12324
+ /** Delete workflow snapshots older than the `workflowSnapshot` policy's `maxAge`, batched. */
12325
+ async prune(policies, options) {
12326
+ const targets = resolveTargets({
12327
+ policies,
12328
+ descriptor: _WorkflowsLibSQL.retentionTables,
12329
+ order: ["workflowSnapshot"]
12330
+ });
12331
+ return runPrune({ db: this.#db, domain: "workflows", targets, options, logger: this.logger });
12332
+ }
11913
12333
  async setupPragmaSettings() {
11914
12334
  try {
11915
12335
  await this.#client.execute("PRAGMA busy_timeout = 10000;");
@@ -12765,7 +13185,7 @@ var LibSQLStore = class extends MastraCompositeStore {
12765
13185
  if (!config.id || typeof config.id !== "string" || config.id.trim() === "") {
12766
13186
  throw new Error("LibSQLStore: id must be provided and cannot be empty.");
12767
13187
  }
12768
- super({ id: config.id, name: `LibSQLStore`, disableInit: config.disableInit });
13188
+ super({ id: config.id, name: `LibSQLStore`, disableInit: config.disableInit, retention: config.retention });
12769
13189
  this.maxRetries = config.maxRetries ?? 5;
12770
13190
  this.initialBackoffMs = config.initialBackoffMs ?? 100;
12771
13191
  this.connectionTimeoutMs = config.connectionTimeoutMs ?? DEFAULT_CONNECTION_TIMEOUT_MS;