import { type ActivationKey, type SessionCheckpoint } from "./adapter.ts"; import { type AppendedSessionEvent, type SessionEvent } from "./events.ts"; /** * The authoritative-log port the {@link SessionBackend} adapter writes through. * Two backends implement it: {@link InMemorySessionLog} (the reference/stub) and * {@link SqliteSessionLog} (durable, over the app DataLayer). All writes are * fenced by `incarnation`; a stale writer throws {@link StaleIncarnationError}. */ export interface SessionLog { /** * Take (or renew) the lease for `key` at `incarnation`, advancing the fence * high-water mark. Throws {@link StaleIncarnationError} if a newer incarnation * already owns the activation. Called once when an adapter is constructed so a * re-lease fences prior incarnations immediately, before any write. */ lease(key: ActivationKey, incarnation: number): void; /** The current (highest leased) incarnation for `key`, or `undefined`. */ currentIncarnation(key: ActivationKey): number | undefined; /** The offset the next appended event will occupy (also the event count). */ nextOffset(key: ActivationKey): number; /** * Append `event` at `offset` under `incarnation`, returning it stamped as an * {@link AppendedSessionEvent}. Fenced. `offset` must be `<= nextOffset`: at * `nextOffset` it extends the log; below it (a resume writing back into the log * after `restore`) it first drops the now-superseded uncommitted tail * `[offset, nextOffset)` — **and every checkpoint pinned above `offset`**, which * would otherwise dangle past the rewritten head and mis-seed a later `restore` * (a gap `RangeError` on the next `emit`) — and then writes: an idempotent * re-key that keeps the authoritative log gap-free. An `offset > nextOffset` is * a gap and throws. */ append(key: ActivationKey, incarnation: number, offset: number, event: SessionEvent): AppendedSessionEvent; /** Persist a checkpoint (fenced by `incarnation`). Returns it unchanged. */ putCheckpoint(key: ActivationKey, incarnation: number, checkpoint: SessionCheckpoint): SessionCheckpoint; /** The checkpoint with the highest offset (newest), or `undefined`. */ latestCheckpoint(key: ActivationKey): SessionCheckpoint | undefined; /** A specific checkpoint by id, or `undefined`. */ getCheckpoint(key: ActivationKey, id: string): SessionCheckpoint | undefined; /** The events with `from <= offset < to`, in offset order. `to` defaults to the head. */ replay(key: ActivationKey, from: number, to?: number): AppendedSessionEvent[]; } /** * The in-memory reference backend (the stub slices 2–5 code against and the tests * exercise). Reuses the relay {@link IncarnationFence} verbatim and keeps each * activation's full event array — the authoritative, non-evicting analogue of the * relay ring's resume window. */ export declare class InMemorySessionLog implements SessionLog { #private; lease(key: ActivationKey, incarnation: number): void; currentIncarnation(key: ActivationKey): number | undefined; nextOffset(key: ActivationKey): number; append(key: ActivationKey, incarnation: number, offset: number, event: SessionEvent): AppendedSessionEvent; putCheckpoint(key: ActivationKey, incarnation: number, checkpoint: SessionCheckpoint): SessionCheckpoint; latestCheckpoint(key: ActivationKey): SessionCheckpoint | undefined; getCheckpoint(key: ActivationKey, id: string): SessionCheckpoint | undefined; replay(key: ActivationKey, from: number, to?: number): AppendedSessionEvent[]; } /** * The minimal synchronous SQLite handle the durable log needs — structurally the * same surface the Urban runtime's DataLayer exposes (`host.openSqlite`), and * identical to the presence/transcript stores' `SqliteDb`. Kept local so the log * depends on a shape, not on the runtime package. */ export interface SqliteDb { exec(sql: string): void; run(sql: string, params?: unknown[]): { changes: number; lastInsertRowid: number | bigint; }; all>(sql: string, params?: unknown[]): T[]; } /** A monotonic wall clock, injectable for deterministic tests. */ export interface Clock { now(): number; } /** The default clock: `Date.now()`. */ export declare const systemClock: Clock; /** * The durable authoritative log over the app DataLayer/SQLite. The fence * high-water lives in the activation row's `incarnation` column, so fencing * survives a process restart — the durable counterpart of the in-memory * {@link IncarnationFence}. */ export declare class SqliteSessionLog implements SessionLog { #private; constructor(db: SqliteDb, options?: { clock?: Clock; }); /** Apply the canonical DDL (idempotent). Identical to the boot migration (drift-guarded). */ ensureSchema(): void; lease(key: ActivationKey, incarnation: number): void; currentIncarnation(key: ActivationKey): number | undefined; nextOffset(key: ActivationKey): number; append(key: ActivationKey, incarnation: number, offset: number, event: SessionEvent): AppendedSessionEvent; putCheckpoint(key: ActivationKey, incarnation: number, checkpoint: SessionCheckpoint): SessionCheckpoint; latestCheckpoint(key: ActivationKey): SessionCheckpoint | undefined; getCheckpoint(key: ActivationKey, id: string): SessionCheckpoint | undefined; replay(key: ActivationKey, from: number, to?: number): AppendedSessionEvent[]; } /** * Raised when a row read back from the durable session log holds a value outside * its domain (e.g. a corrupt effect-ledger JSON). Fail fast rather than coercing. */ export declare class SessionLogCorruptionError extends Error { constructor(message: string); }