/** The legacy JSON snapshot format emitted by this release's writer. */ export declare const SNAPSHOT_V3_MAGIC: Buffer; /** The packed journal snapshot format accepted by the reader before it is emitted. */ export declare const SNAPSHOT_V4_MAGIC: Buffer; export declare function resetStateStoreSnapshotRecoveryCountForTest(): void; export declare function getStateStoreSnapshotRecoveryCountForTest(): number; export declare function resetStateStoreSnapshotMaterializationCountForTest(): void; export declare function getStateStoreSnapshotMaterializationCountForTest(): number; export declare function resetStateStoreSnapshotEncodeCountForTest(): void; export declare function getStateStoreSnapshotEncodeCountForTest(): number; /** The real snapshot writer's serialisation, exposed so tests can inspect chunking. */ export declare function encodeSnapshotChunksForTest(generation: number, scopeDigest: Buffer, payload: unknown): { chunks: readonly Buffer[]; byteLength: number; }; /** The whole-buffer encoder, exposed as the byte-for-byte oracle for the chunked one. */ export declare function encodeSnapshotBytesForTest(generation: number, scopeDigest: Buffer, payload: unknown): Buffer; /** Test-only seam placed immediately before the expensive snapshot write. */ export declare function setStateStoreSnapshotWriteHookForTest(hook: (() => void) | undefined): void; /** Test-only seam after snapshot validation and before its large payload read. */ export declare function setStateStoreSnapshotReadHookForTest(hook: (() => void) | undefined): void; /** Test-only sampling seam before and after decoder work for each bounded snapshot chunk. */ export declare function setStateStoreSnapshotMemorySampleHookForTest(hook: (() => void) | undefined): void; /** Test-only seam after the WAL length is sampled during cold recovery. */ export declare function setStateStoreWalReadHookForTest(hook: (() => void) | undefined): void; /** Test-only seam for counting authenticated WAL records applied during recovery or tailing. */ export declare function setStateStoreWalRecordReadHookForTest(hook: ((record: StateStoreRecord) => void) | undefined): void; /** Test-only crash seam after durable staging and before lock-protected publish. */ export declare function setStateStoreAfterGenerationStageHookForTest(hook: (() => void) | undefined): void; /** Test-only seam for filesystems that do not support forced reflinks. */ export declare function setStateStoreReflinkCopyHookForTest(hook: (() => void) | undefined): void; /** Test-only seam for short positional snapshot writes. */ export declare function setStateStorePositionalWriteForTest(write: ((fd: number, bytes: Buffer, offset: number, length: number, position: number) => number) | undefined): void; /** Test-only seam for rotation-claim PID write failures. */ export declare function setStateStoreRotationClaimWriteHookForTest(hook: (() => void) | undefined): void; /** Test-only seam for a failure while releasing an acquired rotation claim. */ export declare function setStateStoreRotationClaimReleaseHookForTest(hook: (() => void) | undefined): void; /** Test-only seam for proving same-process async lock waiters queue cooperatively. */ export declare function setStateStoreScopeLockAcquiredHookForTest(hook: (() => void | Promise) | undefined): void; /** Test-only seam for contention after async optimistic recovery completes. */ export declare function setStateStoreAfterOptimisticRecoveryHookForTest(hook: (() => void | Promise) | undefined): void; /** Test-only seam for a failure after a batch lock file has been created. */ export declare function setStateStoreScopeLockWriteHookForTest(hook: (() => void) | undefined): void; /** Test-only seam for exercising async lock deadlines without a minute-long wait. */ export declare function setStateStoreAsyncLockTimeoutForTest(timeoutMs: number | undefined): void; /** Test-only seam for exercising synchronous lock contention without a five-second wait. */ export declare function setStateStoreSyncLockTimeoutForTest(timeoutMs: number | undefined): void; /** Test-only clock for contended fold retries. Production uses `Date.now()`. */ export declare function setStateStoreNowMsForTest(nowMs: (() => number) | undefined): void; /** Test-only sleep for contended fold backoff. Production uses `Atomics.wait`. */ export declare function setStateStoreSleepForTest(sleepMs: ((ms: number) => void) | undefined): void; /** Test-only RNG for fold-retry jitter. Production uses `Math.random()`. */ export declare function setStateStoreRandomForTest(random: (() => number) | undefined): void; export interface StateStoreRecord { sequence: number; type: number; flags: number; payload: T; } export interface StateStoreOptions { rootDir: string; scopeId: string; initialState: T; reduce: (state: T, record: StateStoreRecord) => T; /** * Optional owner-defined snapshot decoder. The store authenticates the * header and every payload byte before invoking it, then validates the * returned envelope exactly as it does for JSON.parse. */ snapshotDecoder?: StateStoreSnapshotDecoder; /** * Optional owner-defined snapshot encoder. The store owns durable framing * publication; an owner that has a packed representation may supply its * authenticated header, payload chunks, and checksum without serialising its * state through the legacy JSON encoder. */ snapshotEncoder?: StateStoreSnapshotEncoder; /** * Additional authenticated snapshot magics this reader understands. The * writer remains on HQSNAP3 until its owning domain explicitly switches. */ snapshotMagics?: readonly Buffer[]; /** * Validate a record before it becomes durable when `reduce` mutates its * state in place. Ordinary reducers remain validate-before-append through * their return value; mutable projections opt in explicitly. */ validateRecord?: (record: StateStoreRecord) => void; /** Compact after this many WAL records. Defaults to 256. */ maxRecordsBeforeCompaction?: number; /** Compact once the current WAL reaches this many bytes. Defaults to 16 MiB. */ maxWalBytesBeforeCompaction?: number; } /** Re-readable, bounded snapshot bytes supplied to a domain-specific decoder. */ export interface StateStoreSnapshotChunks extends Iterable { readonly byteLength: number; } /** State envelope passed to an owner-defined snapshot encoder. */ export interface StateStoreSnapshotPayload { readonly lastRecordSequence: number; readonly state: T; } /** Ordered bytes ready for the state store's durable writer. */ export interface StateStoreSnapshotEncoding { readonly chunks: readonly Buffer[]; readonly byteLength: number; } /** Domain-specific streaming snapshot decode behind the store's framing boundary. */ export type StateStoreSnapshotDecoder = (chunks: StateStoreSnapshotChunks, magic: Buffer) => unknown; /** Domain-specific packed snapshot write behind the store's durability boundary. */ export type StateStoreSnapshotEncoder = (generation: number, scopeDigest: Buffer, payload: StateStoreSnapshotPayload) => StateStoreSnapshotEncoding; export interface StateStoreRefreshResult { /** Records consumed from the current generation's WAL suffix. */ records: StateStoreRecord[]; /** True when a generation change or invalid tail required full recovery. */ recovered: boolean; } export interface StateStoreAppendResult extends StateStoreRecord { refresh: StateStoreRefreshResult; } /** * One durable append participating in a short, ordered multi-scope commit. * * This is deliberately an internal-storage primitive rather than a general * transaction manager: callers still own their recovery protocol. It gives * them one important invariant that a sequence of `append()` calls cannot: * every live lock is acquired in a stable order and a contender releases all * acquired locks before waiting to retry. */ export interface StateStoreBatchAppend { /** Stable caller-owned order key. `@meta` is always acquired last. */ areaId: string; store: StateStore; type: number; payload: unknown; flags?: number; /** Caller-owned recovery protocol will compact this durable WAL tail later. */ deferCompaction?: boolean; } export interface StateStoreRecoveryStats { /** Bytes in the selected generation's WAL, including its header. */ walBytes: number; /** Largest single snapshot payload or WAL frame buffered during recovery. */ maxReadBytes: number; } /** A stable identity for one complete snapshot/WAL generation. */ export interface StateStoreGenerationIdentity { generation: number; lastRecordSequence: number; walBytes: number; } /** * A durable lease on an immutable source generation. * * Rotation normally keeps only a short rollback history. Migration uses this * lease to make an older source generation part of that history until the * copy/catch-up transaction has finished (or has explicitly been abandoned). */ export interface StateStoreGenerationPin { readonly identity: StateStoreGenerationIdentity; /** Durable pin file, persisted by long-running migration control state. */ readonly path: string; release(): void; } /** One bounded value yielded from a keyed state projection. */ export interface StateStoreStateEntry { key: string; value: unknown; } export interface StateStoreRepairResult { recoveredGeneration: number; recoveredWalBytes: number; firstCompactGeneration: number; currentGeneration: number; } export interface StateStoreRecoveryProgress { /** Bytes consumed from the snapshot plus WAL selected for recovery. */ bytesRead: number; /** Total bytes in that snapshot/WAL generation. */ totalBytes: number; } export type StateStoreMaintenanceResult = { status: "missing" | "not-needed"; } | ({ status: "repaired"; } & StateStoreRepairResult); export interface StateStoreMaintenanceOptions { /** Rewrite even when the generation is already under its byte budget. */ force?: boolean; /** Synchronous recovery progress, suitable for a process heartbeat. */ onProgress?: (event: StateStoreRecoveryProgress) => void; } export declare class StateStoreCorruptionError extends Error { constructor(message: string, options?: ErrorOptions); } /** * A snapshot whose 8-byte magic this reader does not understand. * * That is not corruption. A newer writer (HQSNAP4 while this process still * reads HQSNAP3, or a later format) left a well-formed file this version * cannot parse. Labelling it corruption implies wiping `~/.hq/sync-state-v3`. */ export declare class StateStoreUnsupportedFormatError extends Error { readonly foundMagic: string; readonly acceptedMagics: readonly string[]; constructor(options: { generation: number; foundMagic: Buffer; acceptedMagics: readonly Buffer[]; cause?: unknown; }); } export type StateStoreLockErrorReason = "in-process-async-holder"; export declare class StateStoreLockError extends Error { readonly reason?: StateStoreLockErrorReason; constructor(message: string, options?: ErrorOptions & { reason?: StateStoreLockErrorReason; }); } /** A recovered v3 store. `append` is the ordinary hot path. */ export declare class StateStore { private readonly options; readonly stateDir: string; readonly scopeDigest: Buffer; readonly wasCreated: boolean; private generation; private state; private lastRecordSequence; private recordCount; private walBytes; private maxRecoveryReadBytes; private observedHighestSnapshotGeneration; private constructor(); static exists(rootDir: string, scopeId: string): boolean; static open(options: StateStoreOptions): StateStore; /** Yielding first-open variant for callback-originated projections. */ static openAsync(options: StateStoreOptions): Promise>; /** * Append one record to each independent store under lexical, all-or-none * locks. The caller's `areaId` is the stable order key; hashed on-disk * directories must never decide lock order. `@meta` intentionally follows * all data areas so a watermark can only publish after its data is durable. * * A process death between records is still possible, by design. Consumers * that need cross-area atomic visibility must use a data-first recovery * protocol (AreaLedger does this with prepare records and a meta watermark). */ static appendBatch(entries: readonly StateStoreBatchAppend[]): StateStoreAppendResult[]; /** * Repair an over-budget generation without making appenders wait for snapshot * I/O. Recovery captures a stable generation while holding the scope lock, * then materialises the replacement off-lock; publication is conditional on * that source generation still being current. */ static repairIfNeeded(options: StateStoreOptions, maintenance?: StateStoreMaintenanceOptions): StateStoreMaintenanceResult; getState(): T; getGeneration(): number; getLastRecordSequence(): number; getRecoveryStats(): StateStoreRecoveryStats; /** * Run format-preserving maintenance for this scope only. * * Callers that own several independent scopes must invoke this one scope at * a time. In particular, this does not acquire a process-wide maintenance * lock, so an area rotation cannot accidentally serialize another area. */ repairIfNeeded(maintenance?: StateStoreMaintenanceOptions): StateStoreMaintenanceResult; /** Compact only this scope when its ordinary append thresholds require it. */ compactIfNeeded(): boolean; /** Observe the caller's configured WAL threshold without publishing a snapshot. */ isCompactionDue(): boolean; /** Async counterpart used by callback-originated recovery paths. */ private compactIfNeededAsync; getGenerationIdentity(): StateStoreGenerationIdentity; /** * Check whether another process has advanced this store without taking its * append lock. The current pointer moves only when a new immutable * generation is published; ordinary appends extend the active WAL. Callers * can therefore avoid an otherwise-locking refresh when both are unchanged. * A concurrent publication or torn file is conservatively reported as * changed and is classified by the normal locked refresh path. */ hasChangedOnDisk(identity?: StateStoreGenerationIdentity): boolean; /** * Persist a generation pin while holding append.lock. The pin is a tiny * checksummed file in the store directory, so a concurrent compactor sees it * before deciding which immutable generations may be pruned. */ pinCurrentGeneration(): StateStoreGenerationPin; /** * Iterate a keyed projection without exposing its mutable backing object. * Each yielded row is cloned independently, allowing migration clients to * keep projected WAL records bounded even when the source journal is huge. */ iterateStateEntries(path: readonly string[]): IterableIterator; append(type: number, payload: unknown, flags?: number): StateStoreAppendResult; /** * Async lock acquisition for callback-originated work. The write itself * retains the ordinary synchronous durability contract once this scope owns * the lock, but a contended lock yields the Node event loop between retries. */ appendAsync(type: number, payload: unknown, flags?: number): Promise; /** Conditionally append after obtaining the async scope lock. */ appendIfAsync(type: number, payload: unknown, shouldAppend: () => boolean, flags?: number): Promise; /** * Materialize a record only after this async appender owns append.lock. * * This is for callers whose admission predicate must be checked at the * exact durable-write boundary, but whose accepted mutations share one * physical record. Returning `undefined` skips the append. */ appendGeneratedIfAsync(type: number, preparePayload: () => unknown | undefined, flags?: number): Promise; /** Refresh only the authenticated unseen WAL suffix when possible. */ refresh(): StateStoreRefreshResult; /** Async counterpart for callback-originated projections. */ refreshAsync(): Promise; private appendUnlocked; /** * Atomically replace the current state with a compact snapshot generation. * * This is the rollback-compatible write path for domains whose previous * reducer cannot understand a new WAL record type. If the recovered * generation contains records, publish two compact copies so every retained * WAL is header-only before pruning it. Later replacements need one new copy * and retain the prior compact copy. */ replaceState(nextState: T): void; /** * Atomically derive a compact replacement from the newest durable state. * * Callers that fold a projection must not calculate their replacement before * taking the append lock: another writer could otherwise be lost between the * read and publication. The transform runs under that lock and publication * still validates the source generation after the off-lock snapshot stage. */ replaceStateWith(transform: (state: T) => T): void; private generationPublishRetryBudgetMs; private replaceStateAttempt; /** Create G+1 without ever merging two generations during recovery. */ compact(): void; private refreshFromDisk; private recoverNewestGenerationOutsideLock; private recoverNewestGenerationOutsideLockAsync; private needsCompaction; private compactionPlan; /** Materialise off-lock, then publish only if no writer advanced the source. */ private publishCompactionPlan; private installGeneration; } //# sourceMappingURL=state-store.d.ts.map