/** * Append-only update log for incremental `put` writes. * * A table with the format-2 update log stores base columns exactly as before * (`.txt` / `.gz`) plus one `updates.log` file in the table directory. Every * logged update batch is a self-committing frame; readers overlay frames on the * base columns by stable record id. * * Frame layout (binary, little-endian): * * * payload = JSON: { "gen": number, "records": UpdateRecord[] } * * The payload ends with a `\n` and is included in both the length and the * checksum, so a frame is unambiguous and self-delimiting. `crc32` is computed * over the exact bytes (Node's zlib.crc32 where available, with a portable * fallback for older 22.x runtimes). * * Recovery contract (see plan §2): * - the committed boundary is the contiguous run of valid frames starting at * `baseGen + 1`; a frame is committed once its bytes are complete and its * checksum validates; * - a failing frame at end-of-file (no bytes after it) is a torn tail and is * truncated before the next append; * - a failing frame with valid-looking bytes after it is mid-log corruption and * is surfaced as {@link UpdateLogCorruptionError} — never silently skipped; * - `gen` must be strictly increasing by exactly one per frame. */ export declare const UPDATES_LOG_NAME = "updates.log"; /** Default hard cap for one encoded frame. Oversized batches are split. */ export declare const DEFAULT_MAX_FRAME_BYTES: number; export interface UpdateRecord { /** Stable record id (as stored in the base `id` column). */ id: string | number; /** Encoded column values, exactly as written by `formatData`. */ cols: Record; updatedAt: number; } export interface UpdateFrame { /** Committed generation; strictly `baseGen + 1, + 2, ...` and never reused. */ gen: number; records: UpdateRecord[]; } export interface ScanResult { /** Byte offset just past the last valid frame. */ committedBytes: number; /** True when trailing bytes did not form a complete valid frame. */ tornTail: boolean; frameCount: number; /** Last committed generation, or `firstGen - 1` when no frame is valid. */ lastGen: number; } /** Raised for mid-log corruption (a reliable frame is followed by more bytes). */ export declare class UpdateLogCorruptionError extends Error { readonly path: string; readonly offset: number; readonly reason: string; constructor(path: string, offset: number, reason: string); } /** Raised when a single record cannot fit in one bounded frame. */ export declare class UpdateLogFrameTooLargeError extends Error { readonly path: string; readonly size: number; readonly maxFrameBytes: number; constructor(path: string, size: number, maxFrameBytes: number); } export declare const encodeFrame: (frame: UpdateFrame) => Buffer; /** * Scan a fully-buffered log. `firstGen` is `baseGen + 1`. Throws * {@link UpdateLogCorruptionError} for mid-log corruption; reports a torn tail * through {@link ScanResult} instead of throwing. */ export declare const scanFrames: (buffer: Buffer, firstGen: number, maxFrameBytes?: number, path?: string) => ScanResult; /** * Split records into bounded frames starting at `firstGen`. A record larger * than the cap on its own is rejected (it cannot be logged safely). */ export declare const buildFrames: (records: UpdateRecord[], firstGen: number, maxFrameBytes?: number, path?: string) => UpdateFrame[]; /** * Append-only handle. One instance per open table; callers must hold the table * writer lock around writes and repairs. */ export declare class UpdateLog { readonly path: string; constructor(tablePath: string); /** * Append one frame and make it durable. On a non-durable error the frame may * or may not have reached disk (unknown outcome); callers must not ack. * * The handle is opened and closed per call: the append handle would * otherwise live inside the process-wide overlay cache and be collected by * GC of a long-idle table, closing the descriptor asynchronously. */ append(frame: UpdateFrame): Promise; /** Append a whole batch, splitting into bounded frames. Returns the last gen. */ appendBatch(records: UpdateRecord[], firstGen: number, maxFrameBytes?: number): Promise; /** * Stream every valid frame from `firstGen` in order, calling `onFrame` for * each. The log is never loaded into one buffer, so replay is bounded by * frame size rather than log size. Returns the committed boundary and * whether a torn tail remains. */ scan(firstGen: number, maxFrameBytes?: number, onFrame?: (frame: UpdateFrame) => void | Promise): Promise; /** * Writer-only: scan, and when a torn tail is present truncate to the last * valid boundary so future frames are never written behind unreadable bytes. * Returns the committed generation. Throws on mid-log corruption. */ repairTail(firstGen: number, maxFrameBytes?: number, onFrame?: (frame: UpdateFrame) => void | Promise): Promise; dispose(): Promise; /** Current on-disk size of the log (0 when absent); drives backpressure. */ bytes(): Promise; } /** True when a table directory contains an update log. */ export declare const updateLogExists: (tablePath: string) => Promise;