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