@tanstack/ai-persistence
Version:
Composable state persistence for TanStack AI messages, runs, interrupts, metadata, and locks.
153 lines (152 loc) • 5.5 kB
TypeScript
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>;