UNPKG

@mastra/core

Version:
198 lines 7.56 kB
import type { StorageDomains } from './base.js'; /** * A human-friendly duration for retention policies. * * Either a raw number of **milliseconds**, or a string with a unit suffix: * - `ms` milliseconds * - `s` seconds * - `m` minutes * - `h` hours * - `d` days * - `w` weeks * * @example * '30d' // 30 days * '12h' // 12 hours * 604800000 // 7 days in ms */ export type Duration = number | `${number}${'ms' | 's' | 'm' | 'h' | 'd' | 'w'}`; /** * Age-based retention policy for a single table. * * Rows whose anchor timestamp column is strictly older than `maxAge` * (i.e. `anchor < Date.now() - maxAge`) are eligible for deletion. */ export interface TableRetentionPolicy { /** * Maximum age to keep rows for. Rows older than this are pruned. */ maxAge: Duration; /** * Rows deleted per batch (each batch is its own transaction). * Bounds lock duration and WAL growth on large tables. * * @default 1000 */ batchSize?: number; } /** * Options controlling a single `prune()` invocation. * * These bound the work performed so a prune can never run unbounded, and let * the caller cooperatively cancel and pace deletions against live traffic. * * `prune()` only deletes rows — it never reclaims disk. On SQLite/LibSQL freed * pages are reused by future writes so the file stops growing. Handing disk * back to the OS (e.g. `VACUUM`) is left to the underlying database and the * operator to manage. */ export interface PruneOptions { /** * Maximum number of delete batches per table per call. * When reached, the table's result is returned with `done: false`. */ maxBatches?: number; /** * Maximum number of rows deleted per table per call. * When reached, the table's result is returned with `done: false`. */ maxRows?: number; /** * Delay in milliseconds between batches, to avoid starving live traffic. */ pauseMs?: number; /** * Cooperative cancellation. The batch loop checks this between batches and * stops cleanly, returning partial results with `done: false`. */ signal?: AbortSignal; } /** * Result of pruning a single table. */ export interface PruneResult { /** Domain key the pruned table belongs to (e.g. `memory`). */ domain: string; /** Physical table name that was pruned (e.g. `mastra_messages`). */ table: string; /** Number of rows deleted during this call. */ deleted: number; /** * `false` means eligible rows remain (a cap or the abort signal stopped the * loop early). Call `prune()` again to continue. */ done: boolean; } /** * Per-domain map of the stable retention table keys each domain exposes. * * This is the source of truth for which table keys are valid under each * domain in `RetentionConfig`. It intentionally mirrors each domain's * `retentionTables` descriptor and is validated against it at runtime by the * reference implementations. * * Only **growth tables** are listed — tables that accumulate rows unbounded as * a side effect of normal operation (conversation history, telemetry, job/run * records, schedule fire history, event feeds, per-thread state). User-authored * artifacts and config (agents, skills, workspaces, prompt blocks, scorer * definitions, mcp configs, favorites, tool connections, datasets, channel * config, channel installations, schedule definitions) are deliberately * excluded: they grow with user intent and are edited/deleted explicitly, so * age-based retention does not apply. * * Note: for the `schedules` domain the growth table is the fire/run history * (`schedule_triggers`, one row appended per fire), not the schedule * definitions (`schedules`, one stable row per schedule). Hence the single * `'triggers'` key. * * Note: for the `observability` domain, `spans` is supported by every adapter * that implements retention, while `metrics` / `logs` / `scores` / `feedback` * only exist on insert-only v-next adapters (e.g. Postgres v-next), which * expire them by dropping whole day partitions / chunks rather than deleting * rows. Adapters skip table keys they don't manage. * * Note: for the `experiments` domain, an experiment is pruned as a whole unit — * the run and all of its `experiment_results` rows are deleted together (results * cascade with their parent, matching `deleteExperiment`). Results have no * independent lifespan, so they are not a separate retention key. The anchor is * `experiments.completedAt`, so in-flight runs (NULL `completedAt`) are never * pruned. Hence the single `'experiments'` key. * * Domains not listed here fall back to `never`, so no table policies can be * set on them until they declare their retention tables. */ export interface DomainRetentionTables { memory: 'threads' | 'messages' | 'resources'; threadState: 'threadState'; observability: 'spans' | 'metrics' | 'logs' | 'scores' | 'feedback'; scores: 'scorers'; workflows: 'workflowSnapshot'; backgroundTasks: 'backgroundTasks'; experiments: 'experiments'; notifications: 'notifications'; harness: 'sessions'; schedules: 'triggers'; } /** * The valid retention table keys for a given storage domain `D`. * * Resolves to the domain's declared table-key union when known, otherwise * `never` (no table policies allowed). */ export type RetentionTableKey<D extends keyof StorageDomains> = D extends keyof DomainRetentionTables ? DomainRetentionTables[D] : never; /** * Fully-typed retention configuration. * * Keys are real domain keys from `StorageDomains`; values map real per-table * keys (from that domain's `retentionTables`) to their policies. Unknown * domains or unknown table keys are compile errors. * * Anything left unset is kept forever. * * @example * ```typescript * const retention: RetentionConfig = { * memory: { * messages: { maxAge: '30d' }, * threads: { maxAge: '90d', batchSize: 500 }, * }, * observability: { * spans: { maxAge: '7d' }, * }, * }; * ``` */ export type RetentionConfig = { [D in keyof StorageDomains]?: Partial<Record<RetentionTableKey<D>, TableRetentionPolicy>>; }; /** * Descriptor entry for a single retention-eligible table. * * Each domain exposes a `retentionTables` record mapping a stable table key * to this descriptor. It is the single source of truth for the physical table * name, the timestamp anchor column, and whether that column is indexed * (batched timestamp deletes are only fast with an index on the anchor). */ export interface RetentionTableDescriptor { /** Physical table name (e.g. `mastra_messages`). */ table: string; /** Anchor column used for the age comparison (e.g. `createdAt`). */ column: string; /** Whether `column` is indexed. Unindexed anchors make batched deletes slow. */ indexed: boolean; /** * Storage type of the anchor column, which determines how the age cutoff is * bound in the delete query: * - `timestamp` (default): compared as an ISO-8601 string / `Date`. * - `epoch-ms`: compared as a raw number of milliseconds since the Unix epoch * (e.g. `schedules.created_at`, stored as `bigint`). * * @default 'timestamp' */ anchorType?: 'timestamp' | 'epoch-ms'; } /** * A domain's `retentionTables` descriptor: stable table key → descriptor. */ export type RetentionTablesDescriptor = Record<string, RetentionTableDescriptor>; //# sourceMappingURL=retention.d.ts.map