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