@mastra/libsql 1.14.3 → 1.15.0-alpha.1

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 (61) hide show
  1. package/CHANGELOG.md +165 -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 +578 -61
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.js +579 -62
  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/datasets/index.d.ts +7 -7
  36. package/dist/storage/domains/datasets/index.d.ts.map +1 -1
  37. package/dist/storage/domains/experiments/index.d.ts +24 -1
  38. package/dist/storage/domains/experiments/index.d.ts.map +1 -1
  39. package/dist/storage/domains/harness/index.d.ts +5 -1
  40. package/dist/storage/domains/harness/index.d.ts.map +1 -1
  41. package/dist/storage/domains/memory/index.d.ts +15 -1
  42. package/dist/storage/domains/memory/index.d.ts.map +1 -1
  43. package/dist/storage/domains/notifications/index.d.ts +5 -1
  44. package/dist/storage/domains/notifications/index.d.ts.map +1 -1
  45. package/dist/storage/domains/observability/index.d.ts +8 -1
  46. package/dist/storage/domains/observability/index.d.ts.map +1 -1
  47. package/dist/storage/domains/schedules/index.d.ts +9 -1
  48. package/dist/storage/domains/schedules/index.d.ts.map +1 -1
  49. package/dist/storage/domains/scores/index.d.ts +15 -5
  50. package/dist/storage/domains/scores/index.d.ts.map +1 -1
  51. package/dist/storage/domains/thread-state/index.d.ts +9 -0
  52. package/dist/storage/domains/thread-state/index.d.ts.map +1 -1
  53. package/dist/storage/domains/utils.d.ts +34 -0
  54. package/dist/storage/domains/utils.d.ts.map +1 -0
  55. package/dist/storage/domains/workflows/index.d.ts +8 -1
  56. package/dist/storage/domains/workflows/index.d.ts.map +1 -1
  57. package/dist/storage/index.d.ts +8 -1
  58. package/dist/storage/index.d.ts.map +1 -1
  59. package/dist/storage/retention.d.ts +77 -0
  60. package/dist/storage/retention.d.ts.map +1 -0
  61. 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, TABLE_EXPERIMENTS, TABLE_EXPERIMENT_RESULTS, ensureDate, safelyParseJSON, 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,
@@ -3599,6 +3824,30 @@ var ChannelsLibSQL = class extends ChannelsStorage {
3599
3824
  };
3600
3825
  }
3601
3826
  };
3827
+
3828
+ // src/storage/domains/utils.ts
3829
+ function tenancyWhere(filters) {
3830
+ const conditions = [];
3831
+ const params = [];
3832
+ if (filters?.organizationId !== void 0) {
3833
+ conditions.push("organizationId = ?");
3834
+ params.push(filters.organizationId);
3835
+ }
3836
+ if (filters?.projectId !== void 0) {
3837
+ conditions.push("projectId = ?");
3838
+ params.push(filters.projectId);
3839
+ }
3840
+ return { conditions, params };
3841
+ }
3842
+ function buildScopedWhere(idColumn, idValue, filters) {
3843
+ const { conditions, params } = tenancyWhere(filters);
3844
+ return {
3845
+ sql: [`${idColumn} = ?`, ...conditions].join(" AND "),
3846
+ args: [idValue, ...params]
3847
+ };
3848
+ }
3849
+
3850
+ // src/storage/domains/datasets/index.ts
3602
3851
  function jsonbArg(value) {
3603
3852
  return value === void 0 || value === null ? null : JSON.stringify(value);
3604
3853
  }
@@ -3679,6 +3928,18 @@ var DatasetsLibSQL = class extends DatasetsStorage {
3679
3928
  await this.#db.deleteData({ tableName: TABLE_DATASET_ITEMS });
3680
3929
  await this.#db.deleteData({ tableName: TABLE_DATASETS });
3681
3930
  }
3931
+ async experimentTablesExist() {
3932
+ try {
3933
+ const result = await this.#client.execute({
3934
+ sql: `SELECT COUNT(*) AS c FROM sqlite_master WHERE type = 'table' AND name IN (?, ?)`,
3935
+ args: [TABLE_EXPERIMENTS, TABLE_EXPERIMENT_RESULTS]
3936
+ });
3937
+ const row = result.rows?.[0];
3938
+ return Number(row?.c ?? 0) === 2;
3939
+ } catch {
3940
+ return false;
3941
+ }
3942
+ }
3682
3943
  // --- Row transformers ---
3683
3944
  transformDatasetRow(row) {
3684
3945
  return {
@@ -3765,8 +4026,8 @@ var DatasetsLibSQL = class extends DatasetsStorage {
3765
4026
  groundTruthSchema: input.groundTruthSchema ?? null,
3766
4027
  requestContextSchema: input.requestContextSchema ?? null,
3767
4028
  targetType: input.targetType ?? null,
3768
- targetIds: input.targetIds ? JSON.stringify(input.targetIds) : null,
3769
- scorerIds: input.scorerIds ? JSON.stringify(input.scorerIds) : null,
4029
+ targetIds: input.targetIds ?? null,
4030
+ scorerIds: input.scorerIds ?? null,
3770
4031
  version: 0,
3771
4032
  organizationId: input.organizationId ?? null,
3772
4033
  projectId: input.projectId ?? null,
@@ -3806,11 +4067,16 @@ var DatasetsLibSQL = class extends DatasetsStorage {
3806
4067
  );
3807
4068
  }
3808
4069
  }
3809
- async getDatasetById({ id }) {
4070
+ async getDatasetById({
4071
+ id,
4072
+ filters
4073
+ }) {
3810
4074
  try {
4075
+ const { conditions, params } = tenancyWhere(filters);
4076
+ const whereSql = ["id = ?", ...conditions].join(" AND ");
3811
4077
  const result = await this.#client.execute({
3812
- sql: `SELECT ${buildSelectColumns(TABLE_DATASETS)} FROM ${TABLE_DATASETS} WHERE id = ?`,
3813
- args: [id]
4078
+ sql: `SELECT ${buildSelectColumns(TABLE_DATASETS)} FROM ${TABLE_DATASETS} WHERE ${whereSql}`,
4079
+ args: [id, ...params]
3814
4080
  });
3815
4081
  return result.rows?.[0] ? this.transformDatasetRow(result.rows[0]) : null;
3816
4082
  } catch (error) {
@@ -3826,7 +4092,7 @@ var DatasetsLibSQL = class extends DatasetsStorage {
3826
4092
  }
3827
4093
  async _doUpdateDataset(args) {
3828
4094
  try {
3829
- const existing = await this.getDatasetById({ id: args.id });
4095
+ const existing = await this.getDatasetById({ id: args.id, filters: args.filters });
3830
4096
  if (!existing) {
3831
4097
  throw new MastraError({
3832
4098
  id: createStorageErrorId("LIBSQL", "UPDATE_DATASET", "NOT_FOUND"),
@@ -3909,30 +4175,31 @@ var DatasetsLibSQL = class extends DatasetsStorage {
3909
4175
  );
3910
4176
  }
3911
4177
  }
3912
- async deleteDataset({ id }) {
4178
+ async deleteDataset({ id, filters }) {
3913
4179
  try {
3914
- try {
3915
- await this.#client.execute({
4180
+ const { conditions, params } = tenancyWhere(filters);
4181
+ const scopedWhere = ["id = ?", ...conditions].join(" AND ");
4182
+ const exists = await this.#client.execute({
4183
+ sql: `SELECT id FROM ${TABLE_DATASETS} WHERE ${scopedWhere}`,
4184
+ args: [id, ...params]
4185
+ });
4186
+ if (!exists.rows?.[0]) return;
4187
+ const experimentTablesExist = await this.experimentTablesExist();
4188
+ const statements = [];
4189
+ if (experimentTablesExist) {
4190
+ statements.push({
3916
4191
  sql: `DELETE FROM ${TABLE_EXPERIMENT_RESULTS} WHERE experimentId IN (SELECT id FROM ${TABLE_EXPERIMENTS} WHERE datasetId = ?)`,
3917
4192
  args: [id]
3918
4193
  });
3919
- } catch {
3920
- }
3921
- try {
3922
- await this.#client.execute({
4194
+ statements.push({
3923
4195
  sql: `UPDATE ${TABLE_EXPERIMENTS} SET datasetId = NULL, datasetVersion = NULL WHERE datasetId = ?`,
3924
4196
  args: [id]
3925
4197
  });
3926
- } catch {
3927
4198
  }
3928
- await this.#client.batch(
3929
- [
3930
- { sql: `DELETE FROM ${TABLE_DATASET_VERSIONS} WHERE datasetId = ?`, args: [id] },
3931
- { sql: `DELETE FROM ${TABLE_DATASET_ITEMS} WHERE datasetId = ?`, args: [id] },
3932
- { sql: `DELETE FROM ${TABLE_DATASETS} WHERE id = ?`, args: [id] }
3933
- ],
3934
- "write"
3935
- );
4199
+ statements.push({ sql: `DELETE FROM ${TABLE_DATASET_VERSIONS} WHERE datasetId = ?`, args: [id] });
4200
+ statements.push({ sql: `DELETE FROM ${TABLE_DATASET_ITEMS} WHERE datasetId = ?`, args: [id] });
4201
+ statements.push({ sql: `DELETE FROM ${TABLE_DATASETS} WHERE ${scopedWhere}`, args: [id, ...params] });
4202
+ await this.#client.batch(statements, "write");
3936
4203
  } catch (error) {
3937
4204
  throw new MastraError(
3938
4205
  {
@@ -3965,6 +4232,21 @@ var DatasetsLibSQL = class extends DatasetsStorage {
3965
4232
  filterConditions.push("candidateId = ?");
3966
4233
  filterParams.push(args.filters.candidateId);
3967
4234
  }
4235
+ if (args.filters?.targetType !== void 0) {
4236
+ filterConditions.push("targetType = ?");
4237
+ filterParams.push(args.filters.targetType);
4238
+ }
4239
+ if (args.filters?.targetIds !== void 0 && args.filters.targetIds.length > 0) {
4240
+ const placeholders = args.filters.targetIds.map(() => "?").join(",");
4241
+ filterConditions.push(
4242
+ `EXISTS (SELECT 1 FROM json_each(${TABLE_DATASETS}.targetIds) WHERE value IN (${placeholders}))`
4243
+ );
4244
+ for (const id of args.filters.targetIds) filterParams.push(id);
4245
+ }
4246
+ if (args.filters?.name !== void 0 && args.filters.name.length > 0) {
4247
+ filterConditions.push("LOWER(name) LIKE ?");
4248
+ filterParams.push(`%${args.filters.name.toLowerCase()}%`);
4249
+ }
3968
4250
  const whereClause = filterConditions.length > 0 ? `WHERE ${filterConditions.join(" AND ")}` : "";
3969
4251
  const countResult = await this.#client.execute({
3970
4252
  sql: `SELECT COUNT(*) as count FROM ${TABLE_DATASETS} ${whereClause}`,
@@ -4624,7 +4906,18 @@ var DatasetsLibSQL = class extends DatasetsStorage {
4624
4906
  }
4625
4907
  }
4626
4908
  };
4909
+ var DEFAULT_PRUNE_BATCH_SIZE = 1e3;
4627
4910
  var ExperimentsLibSQL = class extends ExperimentsStorage {
4911
+ /**
4912
+ * An experiment is pruned as a whole unit: when `experiments.completedAt` is
4913
+ * older than the policy, the run and all its `experiment_results` rows are
4914
+ * deleted together (results cascade with their parent, matching
4915
+ * `deleteExperiment`). Results are not an independent retention key. NULL
4916
+ * `completedAt` (still running) is never pruned.
4917
+ */
4918
+ static retentionTables = {
4919
+ experiments: { table: TABLE_EXPERIMENTS, column: "completedAt", indexed: true }
4920
+ };
4628
4921
  #db;
4629
4922
  #client;
4630
4923
  constructor(config) {
@@ -4680,6 +4973,58 @@ var ExperimentsLibSQL = class extends ExperimentsStorage {
4680
4973
  await this.#db.deleteData({ tableName: TABLE_EXPERIMENT_RESULTS });
4681
4974
  await this.#db.deleteData({ tableName: TABLE_EXPERIMENTS });
4682
4975
  }
4976
+ /**
4977
+ * Prune whole experiments older than the `experiments` policy's `maxAge`.
4978
+ *
4979
+ * Each batch selects up to `batchSize` aged experiments and deletes their
4980
+ * `experiment_results` rows and the experiment rows in one transaction —
4981
+ * mirroring `deleteExperiment` — so hitting `maxBatches`/`maxRows` or the
4982
+ * abort signal between batches never leaves a run hollow (parent kept,
4983
+ * results gone). NULL `completedAt` (still running) is excluded by the
4984
+ * `< cutoff` predicate. Bounds count whole experiments, not rows.
4985
+ */
4986
+ async prune(policies, options) {
4987
+ const policy = policies["experiments"];
4988
+ if (!policy || options?.signal?.aborted) {
4989
+ return policy ? [
4990
+ { domain: "experiments", table: TABLE_EXPERIMENT_RESULTS, deleted: 0, done: false },
4991
+ { domain: "experiments", table: TABLE_EXPERIMENTS, deleted: 0, done: false }
4992
+ ] : [];
4993
+ }
4994
+ try {
4995
+ await this.#db.ensureIndex({
4996
+ indexName: `idx_retention_${TABLE_EXPERIMENTS}_completedAt`,
4997
+ tableName: TABLE_EXPERIMENTS,
4998
+ column: "completedAt"
4999
+ });
5000
+ } catch (error) {
5001
+ this.logger?.warn?.(`Failed to ensure retention index on ${TABLE_EXPERIMENTS}(completedAt):`, error);
5002
+ }
5003
+ const cutoff = cutoffFor(policy, "timestamp");
5004
+ const batchSize = policy.batchSize ?? DEFAULT_PRUNE_BATCH_SIZE;
5005
+ let childDeleted = 0;
5006
+ const parent = await runBatchedDelete({
5007
+ deleteBatch: async (limit) => {
5008
+ const { parents, children } = await this.#db.pruneUnitsBatch({
5009
+ parentTable: TABLE_EXPERIMENTS,
5010
+ parentKey: "id",
5011
+ parentColumn: "completedAt",
5012
+ childTable: TABLE_EXPERIMENT_RESULTS,
5013
+ childForeignKey: "experimentId",
5014
+ cutoff,
5015
+ limit
5016
+ });
5017
+ childDeleted += children;
5018
+ return parents;
5019
+ },
5020
+ batchSize,
5021
+ options
5022
+ });
5023
+ return [
5024
+ { domain: "experiments", table: TABLE_EXPERIMENT_RESULTS, deleted: childDeleted, done: parent.done },
5025
+ { domain: "experiments", table: TABLE_EXPERIMENTS, deleted: parent.deleted, done: parent.done }
5026
+ ];
5027
+ }
4683
5028
  // Helper to transform row to Experiment
4684
5029
  transformExperimentRow(row) {
4685
5030
  return {
@@ -4867,9 +5212,10 @@ var ExperimentsLibSQL = class extends ExperimentsStorage {
4867
5212
  }
4868
5213
  async getExperimentById(args) {
4869
5214
  try {
5215
+ const scoped = buildScopedWhere("id", args.id, args.filters);
4870
5216
  const result = await this.#client.execute({
4871
- sql: `SELECT ${buildSelectColumns(TABLE_EXPERIMENTS)} FROM ${TABLE_EXPERIMENTS} WHERE id = ?`,
4872
- args: [args.id]
5217
+ sql: `SELECT ${buildSelectColumns(TABLE_EXPERIMENTS)} FROM ${TABLE_EXPERIMENTS} WHERE ${scoped.sql}`,
5218
+ args: scoped.args
4873
5219
  });
4874
5220
  return result.rows?.[0] ? this.transformExperimentRow(result.rows[0]) : null;
4875
5221
  } catch (error) {
@@ -4961,14 +5307,17 @@ var ExperimentsLibSQL = class extends ExperimentsStorage {
4961
5307
  }
4962
5308
  async deleteExperiment(args) {
4963
5309
  try {
4964
- await this.#client.execute({
4965
- sql: `DELETE FROM ${TABLE_EXPERIMENT_RESULTS} WHERE experimentId = ?`,
4966
- args: [args.id]
4967
- });
4968
- await this.#client.execute({
4969
- sql: `DELETE FROM ${TABLE_EXPERIMENTS} WHERE id = ?`,
4970
- args: [args.id]
4971
- });
5310
+ const parentScoped = buildScopedWhere("id", args.id, args.filters);
5311
+ const { conditions, params } = tenancyWhere(args.filters);
5312
+ const cascadeWhere = conditions.length ? `experimentId IN (SELECT id FROM ${TABLE_EXPERIMENTS} WHERE ${["id = ?", ...conditions].join(" AND ")})` : `experimentId = ?`;
5313
+ const cascadeArgs = conditions.length ? [args.id, ...params] : [args.id];
5314
+ await this.#client.batch(
5315
+ [
5316
+ { sql: `DELETE FROM ${TABLE_EXPERIMENT_RESULTS} WHERE ${cascadeWhere}`, args: cascadeArgs },
5317
+ { sql: `DELETE FROM ${TABLE_EXPERIMENTS} WHERE ${parentScoped.sql}`, args: parentScoped.args }
5318
+ ],
5319
+ "write"
5320
+ );
4972
5321
  } catch (error) {
4973
5322
  throw new MastraError(
4974
5323
  {
@@ -5106,9 +5455,10 @@ var ExperimentsLibSQL = class extends ExperimentsStorage {
5106
5455
  }
5107
5456
  async getExperimentResultById(args) {
5108
5457
  try {
5458
+ const scoped = buildScopedWhere("id", args.id, args.filters);
5109
5459
  const result = await this.#client.execute({
5110
- sql: `SELECT ${buildSelectColumns(TABLE_EXPERIMENT_RESULTS)} FROM ${TABLE_EXPERIMENT_RESULTS} WHERE id = ?`,
5111
- args: [args.id]
5460
+ sql: `SELECT ${buildSelectColumns(TABLE_EXPERIMENT_RESULTS)} FROM ${TABLE_EXPERIMENT_RESULTS} WHERE ${scoped.sql}`,
5461
+ args: scoped.args
5112
5462
  });
5113
5463
  return result.rows?.[0] ? this.transformExperimentResultRow(result.rows[0]) : null;
5114
5464
  } catch (error) {
@@ -5188,6 +5538,14 @@ var ExperimentsLibSQL = class extends ExperimentsStorage {
5188
5538
  }
5189
5539
  async deleteExperimentResults(args) {
5190
5540
  try {
5541
+ const { conditions, params } = tenancyWhere(args.filters);
5542
+ if (conditions.length) {
5543
+ await this.#client.execute({
5544
+ sql: `DELETE FROM ${TABLE_EXPERIMENT_RESULTS} WHERE experimentId IN (SELECT id FROM ${TABLE_EXPERIMENTS} WHERE ${["id = ?", ...conditions].join(" AND ")})`,
5545
+ args: [args.experimentId, ...params]
5546
+ });
5547
+ return;
5548
+ }
5191
5549
  await this.#client.execute({
5192
5550
  sql: `DELETE FROM ${TABLE_EXPERIMENT_RESULTS} WHERE experimentId = ?`,
5193
5551
  args: [args.experimentId]
@@ -5567,7 +5925,11 @@ function sessionToRecord(record) {
5567
5925
  deletedAt: record.deletedAt ?? null
5568
5926
  };
5569
5927
  }
5570
- var HarnessLibSQL = class extends HarnessStorage {
5928
+ var HarnessLibSQL = class _HarnessLibSQL extends HarnessStorage {
5929
+ /** Session records accumulate over time. Single table, anchored on `createdAt`. */
5930
+ static retentionTables = {
5931
+ sessions: { table: TABLE_HARNESS_SESSIONS, column: "createdAt", indexed: true }
5932
+ };
5571
5933
  #db;
5572
5934
  constructor(config) {
5573
5935
  super();
@@ -5583,6 +5945,15 @@ var HarnessLibSQL = class extends HarnessStorage {
5583
5945
  async dangerouslyClearAll() {
5584
5946
  await this.#db.deleteData({ tableName: TABLE_HARNESS_SESSIONS });
5585
5947
  }
5948
+ /** Delete harness sessions older than the `sessions` policy's `maxAge`, batched. */
5949
+ async prune(policies, options) {
5950
+ const targets = resolveTargets({
5951
+ policies,
5952
+ descriptor: _HarnessLibSQL.retentionTables,
5953
+ order: ["sessions"]
5954
+ });
5955
+ return runPrune({ db: this.#db, domain: "harness", targets, options, logger: this.logger });
5956
+ }
5586
5957
  async loadSession(sessionId) {
5587
5958
  const row = await this.#db.select({
5588
5959
  tableName: TABLE_HARNESS_SESSIONS,
@@ -6568,8 +6939,18 @@ var MCPServersLibSQL = class extends MCPServersStorage {
6568
6939
  }
6569
6940
  };
6570
6941
  var OM_TABLE = "mastra_observational_memory";
6571
- var MemoryLibSQL = class extends MemoryStorage {
6942
+ var MemoryLibSQL = class _MemoryLibSQL extends MemoryStorage {
6572
6943
  supportsObservationalMemory = true;
6944
+ /**
6945
+ * Retention-eligible tables. `threads`, `messages`, and `resources` all anchor
6946
+ * on `createdAt` and are indexed for fast batched deletes. Cascade order is
6947
+ * enforced in `prune()` (children before threads), not here.
6948
+ */
6949
+ static retentionTables = {
6950
+ messages: { table: TABLE_MESSAGES, column: "createdAt", indexed: true },
6951
+ resources: { table: TABLE_RESOURCES, column: "createdAt", indexed: true },
6952
+ threads: { table: TABLE_THREADS, column: "createdAt", indexed: true }
6953
+ };
6573
6954
  #client;
6574
6955
  #db;
6575
6956
  constructor(config) {
@@ -6644,6 +7025,21 @@ var MemoryLibSQL = class extends MemoryStorage {
6644
7025
  await this.#db.deleteData({ tableName: OM_TABLE });
6645
7026
  }
6646
7027
  }
7028
+ /**
7029
+ * Delete rows older than each policy's `maxAge`, batched and cancellable.
7030
+ *
7031
+ * Deletes children before parents (messages/resources before threads) so a
7032
+ * `threads` policy doesn't strand rows behind a deleted parent — there are no
7033
+ * FKs in the LibSQL schema, so cascade ordering is explicit here.
7034
+ */
7035
+ async prune(policies, options) {
7036
+ const targets = resolveTargets({
7037
+ policies,
7038
+ descriptor: _MemoryLibSQL.retentionTables,
7039
+ order: ["messages", "resources", "threads"]
7040
+ });
7041
+ return runPrune({ db: this.#db, domain: "memory", targets, options, logger: this.logger });
7042
+ }
6647
7043
  parseRow(row) {
6648
7044
  let content = row.content;
6649
7045
  try {
@@ -8710,7 +9106,11 @@ function addArrayFilter(conditions, args, column, value) {
8710
9106
  conditions.push(`"${column}" IN (${values.map(() => "?").join(", ")})`);
8711
9107
  args.push(...values);
8712
9108
  }
8713
- var NotificationsLibSQL = class extends NotificationsStorage {
9109
+ var NotificationsLibSQL = class _NotificationsLibSQL extends NotificationsStorage {
9110
+ /** The notification feed grows unbounded. Single table, anchored on `createdAt`. */
9111
+ static retentionTables = {
9112
+ notifications: { table: TABLE_NOTIFICATIONS, column: "createdAt", indexed: true }
9113
+ };
8714
9114
  #db;
8715
9115
  #client;
8716
9116
  constructor(config) {
@@ -8745,6 +9145,15 @@ var NotificationsLibSQL = class extends NotificationsStorage {
8745
9145
  async dangerouslyClearAll() {
8746
9146
  await this.#db.deleteData({ tableName: TABLE_NOTIFICATIONS });
8747
9147
  }
9148
+ /** Delete notifications older than the `notifications` policy's `maxAge`, batched. */
9149
+ async prune(policies, options) {
9150
+ const targets = resolveTargets({
9151
+ policies,
9152
+ descriptor: _NotificationsLibSQL.retentionTables,
9153
+ order: ["notifications"]
9154
+ });
9155
+ return runPrune({ db: this.#db, domain: "notifications", targets, options, logger: this.logger });
9156
+ }
8748
9157
  async createNotification(input) {
8749
9158
  const existing = await this.findCoalescable(input);
8750
9159
  if (existing) {
@@ -8934,7 +9343,14 @@ var NotificationsLibSQL = class extends NotificationsStorage {
8934
9343
  return row ? rowToNotification(row) : void 0;
8935
9344
  }
8936
9345
  };
8937
- var ObservabilityLibSQL = class extends ObservabilityStorage {
9346
+ var ObservabilityLibSQL = class _ObservabilityLibSQL extends ObservabilityStorage {
9347
+ /**
9348
+ * Spans are the only physical table; traces are derived from them. Spans
9349
+ * anchor on `startedAt` (not `createdAt`), stored as an ISO-8601 string.
9350
+ */
9351
+ static retentionTables = {
9352
+ spans: { table: TABLE_SPANS, column: "startedAt", indexed: true }
9353
+ };
8938
9354
  #db;
8939
9355
  constructor(config) {
8940
9356
  super();
@@ -8952,6 +9368,15 @@ var ObservabilityLibSQL = class extends ObservabilityStorage {
8952
9368
  async dangerouslyClearAll() {
8953
9369
  await this.#db.deleteData({ tableName: TABLE_SPANS });
8954
9370
  }
9371
+ /** Delete spans older than the `spans` policy's `maxAge`, batched. */
9372
+ async prune(policies, options) {
9373
+ const targets = resolveTargets({
9374
+ policies,
9375
+ descriptor: _ObservabilityLibSQL.retentionTables,
9376
+ order: ["spans"]
9377
+ });
9378
+ return runPrune({ db: this.#db, domain: "observability", targets, options, logger: this.logger });
9379
+ }
8955
9380
  /**
8956
9381
  * Manually run the spans migration to deduplicate and add the unique constraint.
8957
9382
  * This is intended to be called from the CLI when duplicates are detected.
@@ -9960,7 +10385,15 @@ function rowToTrigger(row) {
9960
10385
  if (metadata !== void 0) trigger.metadata = metadata;
9961
10386
  return trigger;
9962
10387
  }
9963
- var SchedulesLibSQL = class extends SchedulesStorage {
10388
+ var SchedulesLibSQL = class _SchedulesLibSQL extends SchedulesStorage {
10389
+ /**
10390
+ * The fire/run history (`schedule_triggers`, one row per fire) is the growth
10391
+ * table; schedule definitions are config and excluded. Anchored on
10392
+ * `actual_fire_at`, a bigint epoch-ms column (numeric comparison, not ISO).
10393
+ */
10394
+ static retentionTables = {
10395
+ triggers: { table: TABLE_SCHEDULE_TRIGGERS, column: "actual_fire_at", indexed: true, anchorType: "epoch-ms" }
10396
+ };
9964
10397
  #db;
9965
10398
  #client;
9966
10399
  constructor(config) {
@@ -9996,6 +10429,15 @@ var SchedulesLibSQL = class extends SchedulesStorage {
9996
10429
  await this.#db.deleteData({ tableName: TABLE_SCHEDULE_TRIGGERS });
9997
10430
  await this.#db.deleteData({ tableName: TABLE_SCHEDULES });
9998
10431
  }
10432
+ /** Delete schedule fire history older than the `triggers` policy's `maxAge`, batched. */
10433
+ async prune(policies, options) {
10434
+ const targets = resolveTargets({
10435
+ policies,
10436
+ descriptor: _SchedulesLibSQL.retentionTables,
10437
+ order: ["triggers"]
10438
+ });
10439
+ return runPrune({ db: this.#db, domain: "schedules", targets, options, logger: this.logger });
10440
+ }
9999
10441
  async createSchedule(schedule) {
10000
10442
  const existing = await this.getSchedule(schedule.id);
10001
10443
  if (existing) {
@@ -10670,7 +11112,23 @@ var ScorerDefinitionsLibSQL = class extends ScorerDefinitionsStorage {
10670
11112
  };
10671
11113
  }
10672
11114
  };
10673
- var ScoresLibSQL = class extends ScoresStorage {
11115
+ function appendTenancyConditions(conditions, args, filters) {
11116
+ if (filters?.organizationId !== void 0) {
11117
+ conditions.push(`organizationId = ?`);
11118
+ args.push(filters.organizationId);
11119
+ }
11120
+ if (filters?.projectId !== void 0) {
11121
+ conditions.push(`projectId = ?`);
11122
+ args.push(filters.projectId);
11123
+ }
11124
+ }
11125
+ var ScoresLibSQL = class _ScoresLibSQL extends ScoresStorage {
11126
+ /**
11127
+ * Scorer results accumulate as evals run. Single table, anchored on `createdAt`.
11128
+ */
11129
+ static retentionTables = {
11130
+ scorers: { table: TABLE_SCORERS, column: "createdAt", indexed: true }
11131
+ };
10674
11132
  #db;
10675
11133
  #client;
10676
11134
  constructor(config) {
@@ -10684,21 +11142,35 @@ var ScoresLibSQL = class extends ScoresStorage {
10684
11142
  await this.#db.alterTable({
10685
11143
  tableName: TABLE_SCORERS,
10686
11144
  schema: SCORERS_SCHEMA,
10687
- ifNotExists: ["spanId", "requestContext"]
11145
+ ifNotExists: ["spanId", "requestContext", "organizationId", "projectId", "batchId", "datasetId", "datasetItemId"]
10688
11146
  });
10689
11147
  }
10690
11148
  async dangerouslyClearAll() {
10691
11149
  await this.#db.deleteData({ tableName: TABLE_SCORERS });
10692
11150
  }
11151
+ /** Delete scorer results older than the `scorers` policy's `maxAge`, batched. */
11152
+ async prune(policies, options) {
11153
+ const targets = resolveTargets({
11154
+ policies,
11155
+ descriptor: _ScoresLibSQL.retentionTables,
11156
+ order: ["scorers"]
11157
+ });
11158
+ return runPrune({ db: this.#db, domain: "scores", targets, options, logger: this.logger });
11159
+ }
10693
11160
  async listScoresByRunId({
10694
11161
  runId,
10695
- pagination
11162
+ pagination,
11163
+ filters
10696
11164
  }) {
10697
11165
  try {
10698
11166
  const { page, perPage: perPageInput } = pagination;
11167
+ const conditions = [`runId = ?`];
11168
+ const queryParams = [runId];
11169
+ appendTenancyConditions(conditions, queryParams, filters);
11170
+ const whereClause = `WHERE ${conditions.join(" AND ")}`;
10699
11171
  const countResult = await this.#client.execute({
10700
- sql: `SELECT COUNT(*) as count FROM ${TABLE_SCORERS} WHERE runId = ?`,
10701
- args: [runId]
11172
+ sql: `SELECT COUNT(*) as count FROM ${TABLE_SCORERS} ${whereClause}`,
11173
+ args: queryParams
10702
11174
  });
10703
11175
  const total = Number(countResult.rows?.[0]?.count ?? 0);
10704
11176
  if (total === 0) {
@@ -10717,8 +11189,8 @@ var ScoresLibSQL = class extends ScoresStorage {
10717
11189
  const limitValue = perPageInput === false ? total : perPage;
10718
11190
  const end = perPageInput === false ? total : start + perPage;
10719
11191
  const result = await this.#client.execute({
10720
- sql: `SELECT ${buildSelectColumns(TABLE_SCORERS)} FROM ${TABLE_SCORERS} WHERE runId = ? ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
10721
- args: [runId, limitValue, start]
11192
+ sql: `SELECT ${buildSelectColumns(TABLE_SCORERS)} FROM ${TABLE_SCORERS} ${whereClause} ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
11193
+ args: [...queryParams, limitValue, start]
10722
11194
  });
10723
11195
  const scores = result.rows?.map((row) => this.transformScoreRow(row)) ?? [];
10724
11196
  return {
@@ -10746,7 +11218,8 @@ var ScoresLibSQL = class extends ScoresStorage {
10746
11218
  entityId,
10747
11219
  entityType,
10748
11220
  source,
10749
- pagination
11221
+ pagination,
11222
+ filters
10750
11223
  }) {
10751
11224
  try {
10752
11225
  const { page, perPage: perPageInput } = pagination;
@@ -10768,6 +11241,7 @@ var ScoresLibSQL = class extends ScoresStorage {
10768
11241
  conditions.push(`source = ?`);
10769
11242
  queryParams.push(source);
10770
11243
  }
11244
+ appendTenancyConditions(conditions, queryParams, filters);
10771
11245
  const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
10772
11246
  const countResult = await this.#client.execute({
10773
11247
  sql: `SELECT COUNT(*) as count FROM ${TABLE_SCORERS} ${whereClause}`,
@@ -10875,13 +11349,18 @@ var ScoresLibSQL = class extends ScoresStorage {
10875
11349
  async listScoresByEntityId({
10876
11350
  entityId,
10877
11351
  entityType,
10878
- pagination
11352
+ pagination,
11353
+ filters
10879
11354
  }) {
10880
11355
  try {
10881
11356
  const { page, perPage: perPageInput } = pagination;
11357
+ const conditions = [`entityId = ?`, `entityType = ?`];
11358
+ const queryParams = [entityId, entityType];
11359
+ appendTenancyConditions(conditions, queryParams, filters);
11360
+ const whereClause = `WHERE ${conditions.join(" AND ")}`;
10882
11361
  const countResult = await this.#client.execute({
10883
- sql: `SELECT COUNT(*) as count FROM ${TABLE_SCORERS} WHERE entityId = ? AND entityType = ?`,
10884
- args: [entityId, entityType]
11362
+ sql: `SELECT COUNT(*) as count FROM ${TABLE_SCORERS} ${whereClause}`,
11363
+ args: queryParams
10885
11364
  });
10886
11365
  const total = Number(countResult.rows?.[0]?.count ?? 0);
10887
11366
  if (total === 0) {
@@ -10900,8 +11379,8 @@ var ScoresLibSQL = class extends ScoresStorage {
10900
11379
  const limitValue = perPageInput === false ? total : perPage;
10901
11380
  const end = perPageInput === false ? total : start + perPage;
10902
11381
  const result = await this.#client.execute({
10903
- sql: `SELECT ${buildSelectColumns(TABLE_SCORERS)} FROM ${TABLE_SCORERS} WHERE entityId = ? AND entityType = ? ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
10904
- args: [entityId, entityType, limitValue, start]
11382
+ sql: `SELECT ${buildSelectColumns(TABLE_SCORERS)} FROM ${TABLE_SCORERS} ${whereClause} ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
11383
+ args: [...queryParams, limitValue, start]
10905
11384
  });
10906
11385
  const scores = result.rows?.map((row) => this.transformScoreRow(row)) ?? [];
10907
11386
  return {
@@ -10927,22 +11406,27 @@ var ScoresLibSQL = class extends ScoresStorage {
10927
11406
  async listScoresBySpan({
10928
11407
  traceId,
10929
11408
  spanId,
10930
- pagination
11409
+ pagination,
11410
+ filters
10931
11411
  }) {
10932
11412
  try {
10933
11413
  const { page, perPage: perPageInput } = pagination;
10934
11414
  const perPage = normalizePerPage(perPageInput, 100);
10935
11415
  const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
11416
+ const conditions = [`traceId = ?`, `spanId = ?`];
11417
+ const queryParams = [traceId, spanId];
11418
+ appendTenancyConditions(conditions, queryParams, filters);
11419
+ const whereClause = `WHERE ${conditions.join(" AND ")}`;
10936
11420
  const countSQLResult = await this.#client.execute({
10937
- sql: `SELECT COUNT(*) as count FROM ${TABLE_SCORERS} WHERE traceId = ? AND spanId = ?`,
10938
- args: [traceId, spanId]
11421
+ sql: `SELECT COUNT(*) as count FROM ${TABLE_SCORERS} ${whereClause}`,
11422
+ args: queryParams
10939
11423
  });
10940
11424
  const total = Number(countSQLResult.rows?.[0]?.count ?? 0);
10941
11425
  const limitValue = perPageInput === false ? total : perPage;
10942
11426
  const end = perPageInput === false ? total : start + perPage;
10943
11427
  const result = await this.#client.execute({
10944
- sql: `SELECT ${buildSelectColumns(TABLE_SCORERS)} FROM ${TABLE_SCORERS} WHERE traceId = ? AND spanId = ? ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
10945
- args: [traceId, spanId, limitValue, start]
11428
+ sql: `SELECT ${buildSelectColumns(TABLE_SCORERS)} FROM ${TABLE_SCORERS} ${whereClause} ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
11429
+ args: [...queryParams, limitValue, start]
10946
11430
  });
10947
11431
  const scores = result.rows?.map((row) => this.transformScoreRow(row)) ?? [];
10948
11432
  return {
@@ -11573,7 +12057,15 @@ var SkillsLibSQL = class extends SkillsStorage {
11573
12057
  };
11574
12058
  }
11575
12059
  };
11576
- var ThreadStateLibSQL = class extends ThreadStateStorage {
12060
+ var ThreadStateLibSQL = class _ThreadStateLibSQL extends ThreadStateStorage {
12061
+ /**
12062
+ * `thread_state` grows as a side effect of thread activity (one row per
12063
+ * thread per state type). It anchors on `updatedAt` (last activity), so state
12064
+ * for a thread that is still being appended to is not pruned by creation age.
12065
+ */
12066
+ static retentionTables = {
12067
+ threadState: { table: TABLE_THREAD_STATE, column: "updatedAt", indexed: true }
12068
+ };
11577
12069
  #db;
11578
12070
  #client;
11579
12071
  constructor(config) {
@@ -11589,6 +12081,15 @@ var ThreadStateLibSQL = class extends ThreadStateStorage {
11589
12081
  compositePrimaryKey: ["threadId", "type"]
11590
12082
  });
11591
12083
  }
12084
+ /** Delete thread state older than the `threadState` policy's `maxAge`, batched. */
12085
+ async prune(policies, options) {
12086
+ const targets = resolveTargets({
12087
+ policies,
12088
+ descriptor: _ThreadStateLibSQL.retentionTables,
12089
+ order: ["threadState"]
12090
+ });
12091
+ return runPrune({ db: this.#db, domain: "threadState", targets, options, logger: this.logger });
12092
+ }
11592
12093
  async dangerouslyClearAll() {
11593
12094
  try {
11594
12095
  await this.#client.execute(`DELETE FROM "${TABLE_THREAD_STATE}"`);
@@ -11857,7 +12358,14 @@ var ToolProviderConnectionsLibSQL = class extends ToolProviderConnectionsStorage
11857
12358
  }
11858
12359
  }
11859
12360
  };
11860
- var WorkflowsLibSQL = class extends WorkflowsStorage {
12361
+ var WorkflowsLibSQL = class _WorkflowsLibSQL extends WorkflowsStorage {
12362
+ /**
12363
+ * Workflow run snapshots accumulate as runs execute. Anchored on `updatedAt`
12364
+ * (last activity) so suspended/long-running runs are not pruned by start age.
12365
+ */
12366
+ static retentionTables = {
12367
+ workflowSnapshot: { table: TABLE_WORKFLOW_SNAPSHOT, column: "updatedAt", indexed: true }
12368
+ };
11861
12369
  #db;
11862
12370
  #client;
11863
12371
  executeWithRetry;
@@ -11910,6 +12418,15 @@ var WorkflowsLibSQL = class extends WorkflowsStorage {
11910
12418
  async dangerouslyClearAll() {
11911
12419
  await this.#db.deleteData({ tableName: TABLE_WORKFLOW_SNAPSHOT });
11912
12420
  }
12421
+ /** Delete workflow snapshots older than the `workflowSnapshot` policy's `maxAge`, batched. */
12422
+ async prune(policies, options) {
12423
+ const targets = resolveTargets({
12424
+ policies,
12425
+ descriptor: _WorkflowsLibSQL.retentionTables,
12426
+ order: ["workflowSnapshot"]
12427
+ });
12428
+ return runPrune({ db: this.#db, domain: "workflows", targets, options, logger: this.logger });
12429
+ }
11913
12430
  async setupPragmaSettings() {
11914
12431
  try {
11915
12432
  await this.#client.execute("PRAGMA busy_timeout = 10000;");
@@ -12765,7 +13282,7 @@ var LibSQLStore = class extends MastraCompositeStore {
12765
13282
  if (!config.id || typeof config.id !== "string" || config.id.trim() === "") {
12766
13283
  throw new Error("LibSQLStore: id must be provided and cannot be empty.");
12767
13284
  }
12768
- super({ id: config.id, name: `LibSQLStore`, disableInit: config.disableInit });
13285
+ super({ id: config.id, name: `LibSQLStore`, disableInit: config.disableInit, retention: config.retention });
12769
13286
  this.maxRetries = config.maxRetries ?? 5;
12770
13287
  this.initialBackoffMs = config.initialBackoffMs ?? 100;
12771
13288
  this.connectionTimeoutMs = config.connectionTimeoutMs ?? DEFAULT_CONNECTION_TIMEOUT_MS;