/** * The per-session storage handle: one open channel onto a stored session's * append-only event log, returned by `SessionPersistence.create`/`open`. * @module @deepseek-ai/dsh-session-persistence/handle */ import type { SessionEvent, SessionHeader, SessionId, SessionLogOffset, SessionSeedEventState } from '@deepseek-ai/dsh-session' /** * Log access granted by an open. `write` is read-write: the session's single * mutator, which also reads its own log. `read` only observes — it never * takes ownership and works while another handle or process holds `write`. */ export type SessionAccess = 'read' | 'write' /** Options for {@link SessionHandle.read}. */ export interface SessionHandleReadOptions { /** Optional cancellation for backend read work. */ readonly signal?: AbortSignal } /** One persistence event slice returned by {@link SessionHandle.read}. */ export interface SessionHandleReadResult { /** * Whether event values are exclusively owned or shared only after deep * freezing. Slicing preserves the producer's state even when no events remain. */ readonly eventState: SessionSeedEventState /** Event values in a caller-owned outer array. */ readonly events: readonly SessionEvent[] } /** Options for {@link SessionHandle.append}. */ export interface SessionHandleAppendOptions { /** Optional cancellation observed before the write starts. */ readonly signal?: AbortSignal } /** Options for {@link SessionHandle.flush}. */ export interface SessionHandleFlushOptions { /** Optional cancellation observed before the barrier starts. */ readonly signal?: AbortSignal } /** * One open channel onto a stored session. A handle is single-owner state, not * a shared service: `read` never backtracks below what this handle already * observed, a `write` handle reads its own successful appends, and `close()` * is the one teardown (idempotent, uncancellable; `Symbol.asyncDispose` * delegates to it). Every operation on a closed handle rejects with * `SessionHandleClosedError`. * * Freshness across handles: once an `append` or `flush` resolves on a write * handle, every read STARTED afterwards on the same backend instance — on any * handle, or through `stat`/`list` — observes at least that prefix. * Reads concurrent with a mutation carry no ordering promise beyond the valid * contiguous prefix. */ export interface SessionHandle extends AsyncDisposable { /** The stored session this handle addresses. */ readonly id: SessionId /** The immutable stored header, fixed at `create`/`open`. */ readonly header: SessionHeader /** * Exact fork-inherited prefix length stored with the log; `0` when * `header.isSeeded` is false. Storage metadata paired with the header for * every body read, never part of the replayable event log. */ readonly inheritedEventCount: SessionLogOffset /** Whether this handle may mutate the log. */ readonly access: SessionAccess /** * Read a slice of the valid contiguous logical log. The slice is a legal log * prefix segment: a torn physical tail is never returned, and repeated reads * on this handle never observe an older state than a prior read. * @param offset - first logical event seq to include; defaults to `0`. * @param length - maximum number of events to return; defaults to the rest * of the log. An offset at or past the end returns an empty list. * @param options - optional cancellation. * @returns the caller-owned outer slice plus the ownership state of its event values. */ read(offset?: number, length?: number, options?: SessionHandleReadOptions): Promise /** * Append a contiguous batch continuing the current logical end. The first * event's `seq` MUST equal the stored next-seq; committed events are never * rewritten. Persistence is best-effort: on resolution the batch is * accepted, ordered, and visible to reads on this backend instance, but * only a resolved {@link flush} promises it survives a crash — a backend * may buffer or batch physical writes behind append. Rejects with * `SessionReadOnlyError` on a read handle and `SessionOwnershipLostError` * when write ownership is gone. * @param events - the contiguous batch, in seq order. * @param options - optional cancellation observed before the write starts. */ append(events: readonly SessionEvent[], options?: SessionHandleAppendOptions): Promise /** * The durability barrier — the one operation that promises storage: on * resolution every acknowledged append is durable and the session is * materialized for other processes; an empty created session becomes * durably listable here. Callers that must survive a crash flush; a backend * whose `append` already persists on resolution treats this as * materialize-if-needed. Rejects with `SessionReadOnlyError` on a read * handle. * @param options - optional cancellation observed before the barrier starts. */ flush(options?: SessionHandleFlushOptions): Promise /** * Release the handle: a read handle frees local resources; a write handle * completes pending durability and releases write ownership. Idempotent, * asynchronous, and deliberately not cancellable. */ close(): Promise }