UNPKG

@tanstack/ai-persistence

Version:

Composable state persistence for TanStack AI messages, runs, interrupts, metadata, and locks.

153 lines (152 loc) • 5.5 kB
import { ActivityStore, ArtifactStore, BlobStore, GenerationRunStore, InterruptStore, LogRecord, LogSnapshot, LogStore, MessageStore, MetadataStore, RunStore, SessionIndexStore } from './types.js'; /** * The ownership record of a log that a host acquired. The record stays after * a release, because the epoch never goes down. */ interface Ownership { harness: string; ownerId: string; epoch: number; /** Epoch milliseconds, from the store clock. */ expiresAt: number; released: boolean; /** Epoch milliseconds, from `release({ wakeInMs })`. */ wakeAt?: number; } /** An input that a host offered to a log. */ interface Offer { record: LogRecord; /** Epoch milliseconds, from the store clock. */ offeredAt: number; /** True after an append consumed it. The id stays, so a retry is a duplicate. */ consumed: boolean; } /** The offers of one log. */ interface Lane { /** The harness of the first offer, for `listLogsWithoutOwner`. */ harness: string; /** By input id, in offer order. */ inputs: Map<string, Offer>; } /** The records that a log keeps. A truncate drops the first ones. */ interface KeptLog { /** The position of `records[0]`. A truncate moves it up. */ firstSeq: number; records: Array<LogRecord>; } /** The listeners of one store instance, by log id. */ type Listeners = Map<string, Set<() => void>>; /** * The state of the memory log, shared by every store instance on it. Make one * with {@link memoryLogState}. Two instances on one state act as two machines * on one database: they see the same records, owners, offers, and snapshots, * and a write on one calls the listeners of both. */ declare class MemoryLogState { /** The store clock, in epoch milliseconds. */ readonly now: () => number; readonly logs: Map<string, KeptLog>; readonly snapshots: Map<string, LogSnapshot>; readonly owners: Map<string, Ownership>; readonly affinities: Map<string, string>; /** The deleted logs. Only an `acquireOwnership` lets a write or an offer in again. */ readonly deleted: Set<string>; /** When the heartbeat of each host expires, by its host id. */ readonly hosts: Map<string, number>; readonly lanes: Map<string, Lane>; /** The listeners of each store instance on this backend. */ readonly instances: Set<Listeners>; constructor(now: () => number); /** * Call the listeners of `logId` on every store instance. The write is done, * so a listener error must not fail it or stop the other listeners. */ notify(logId: string): void; } /** The type of the shared state from {@link memoryLogState}. */ export type { MemoryLogState }; /** * In-process reference {@link LogStore}, for tests and one-process hosts. The * log is lost when the process stops. Records are JSON-copied in and out. * * `memoryPersistence()` has one. Make your own to share a * {@link memoryLogState} between two stores. * * @param options.state - The log state to use. Pass one * {@link memoryLogState} to two stores, and they act as two machines on * one database. Without it, the store makes its own state. * * @example * ```ts * const state = memoryLogState() * const machineA = defineAIPersistence({ * stores: { ...memoryPersistence().stores, log: memoryLogStore({ state }) }, * }) * ``` */ export declare function memoryLogStore({ state, }?: { state?: MemoryLogState; }): LogStore; /** * A new log state for {@link memoryLogStore}. Give one state to two stores * to test two hosts in one process: an append on one store calls the * listeners on the other, and both see one owner per log. * * @example * ```ts * const state = memoryLogState() * const hostA = memoryLogStore({ state }) * const hostB = memoryLogStore({ state }) * hostB.subscribe({ logId: 'session-1', listener: () => console.log('new') }) * await hostA.append({ * logId: 'session-1', * seq: 1, * epoch: NO_OWNER_EPOCH, * records: [{ type: 'hello' }], * }) * // The listener on hostB ran. * ``` * * @param options.now - The store clock, in epoch milliseconds. Ownership * expiry, wake times, and offer times use it. Default: `Date.now()`. Give a * test clock to move time in a test. * * @example * ```ts * let clock = 0 * const state = memoryLogState({ now: () => clock }) * // A wake time of 5 s from now is due after this: * clock += 5_000 * ``` */ export declare function memoryLogState({ now }?: { now?: () => number; }): MemoryLogState; interface MemoryPersistenceStores { messages: MessageStore; activities: ActivityStore; runs: RunStore; generationRuns: GenerationRunStore; interrupts: InterruptStore; metadata: MetadataStore; artifacts: ArtifactStore; blobs: BlobStore; sessions: SessionIndexStore; log: LogStore; } /** * In-process reference backend for the full state + generation store set. * * Returns messages + activities + runs + generationRuns + interrupts + * metadata + artifacts + blobs + sessions + log. * Plain `chat()` does not use `log`. A harness host keeps each session in * `log` and does not use `messages`. Locks are not included. Use * `InMemoryLockStore` + `withLocks` from `@tanstack/ai` when a test or * single-process app needs coordination. * * @example * ```ts * const persistence = memoryPersistence() * ``` */ export declare function memoryPersistence(): import('./types.js').AIPersistence<MemoryPersistenceStores>;