/** * Durable reads, index maintenance, and the atomic state-plus-event commit for * the application command mailbox (WFT-84). * * Every mutation goes through {@link commitMailboxTransition}, the mailbox's binding * of the shared `commitApplicationTransition`. When the mailbox * was built with an event sink, the caller's state operations and the sink's own * event write land in one `conditionalBatch`, so no restart can expose the state * transition without its event or the other way round. Without a sink the same * operations commit through `storageConditionalBatch` directly, which keeps the * atomicity guarantee and makes a lost compare-and-swap exactly detectable. * * @module core/mailbox-storage */ import { type BatchOperation, type ConditionalBatchCondition, type Storage } from '../storage/interface.ts'; import { type ApplicationCommitPlan, type ApplicationEventSink } from './application-primitive-commit.ts'; import type { LoadedCommandRecord } from './mailbox-contract.ts'; import { type ApplicationCommandIdempotencyRecord, type ApplicationCommandRecord, type MailboxRecord } from './mailbox-types.ts'; /** Every key builder for one `(namespace, resourceId)` mailbox, bound once. */ export type MailboxKeys = Readonly<{ header: string; sinkProbe: (nonce: string) => string; commandPrefix: string; command: (commandId: string) => string; readyPrefix: string; ready: (sequence: number) => string; bySequencePrefix: string; bySequence: (sequence: number) => string; idempotency: (key: string) => string; terminalPrefix: string; terminal: (terminalAt: number, commandId: string) => string; }>; /** * Bind every mailbox storage key to one namespace and resource. */ export declare function createMailboxKeys(namespace: string, resourceId: string): MailboxKeys; /** The empty header a mailbox starts from, so a first admission has something to compare against. */ export declare function emptyMailboxRecord(namespace: string, resourceId: string): MailboxRecord; /** A header read together with the exact bytes it decoded from, for compare-and-swap. */ export type LoadedMailboxRecord = { readonly record: MailboxRecord; /** `null` when the mailbox has never been written — the condition for a first admission. */ readonly bytes: Uint8Array | null; }; /** * Read the per-mailbox header, treating an absent key as a fresh mailbox. * * @throws {PersistedDataCorruptError} When the stored header is malformed. */ export declare function loadMailboxHeader(storage: Storage, keys: MailboxKeys, namespace: string, resourceId: string): Promise; /** * Read one command record with the exact bytes it decoded from. * * @throws {PersistedDataCorruptError} When the stored record is malformed. */ export declare function loadCommand(storage: Storage, keys: MailboxKeys, commandId: string): Promise; /** * Read the idempotency binding for a retry key. * * @throws {PersistedDataCorruptError} When the stored binding is malformed. */ export declare function loadIdempotencyBinding(storage: Storage, keys: MailboxKeys, idempotencyKey: string): Promise<{ readonly record: ApplicationCommandIdempotencyRecord; readonly bytes: Uint8Array; } | null>; /** * Read the FIFO head: the lowest-sequence entry still in the delivery index. * * Strict FIFO is the mailbox's ordering contract, so the head is the only entry * a claim may consider. A later command never overtakes a delayed head. * * @throws {PersistedDataCorruptError} When an index entry is malformed. */ export declare function loadDeliveryHead(storage: Storage, keys: MailboxKeys): Promise<{ readonly key: string; readonly bytes: Uint8Array; readonly commandId: string; } | null>; /** * Put/delete operations that keep the delivery and terminal indexes consistent * with a record's new state. * * The delivery entry is keyed by the command's original admission sequence, so * a redelivered command re-enters the queue at the position it was first * admitted to rather than at the back. */ export declare function indexOperationsFor(keys: MailboxKeys, previous: ApplicationCommandRecord | null, next: ApplicationCommandRecord): BatchOperation[]; /** Whether a record is in the delivery index: admitted or released, not yet claimed or settled. */ export declare function isWaitingState(record: ApplicationCommandRecord): boolean; /** * Commit one mailbox transition, atomically with its fleet event when a sink is * configured. The mailbox's binding of the shared commit: see * `application-primitive-commit.ts` for the compare-and-swap classification and * the sink probe. */ export declare function commitMailboxTransition(storage: Storage, events: ApplicationEventSink | undefined, plan: ApplicationCommitPlan): Promise; /** * Persist a command record plus its index maintenance as one plan. * * `expectedBytes` must be the exact bytes the record was read as — not a * re-encoding of the decoded value, which `conditionalBatch`'s whole-value byte * comparison would reject. */ export declare function planCommandTransition(keys: MailboxKeys, options: { readonly previous: ApplicationCommandRecord | null; readonly expectedBytes: Uint8Array | null; readonly next: ApplicationCommandRecord; readonly event: { readonly kind: string; readonly payload: unknown; } | null; readonly now: number; readonly extraConditions?: readonly ConditionalBatchCondition[] | undefined; readonly extraOperations?: readonly BatchOperation[] | undefined; }): ApplicationCommitPlan; /** The put operation that advances the mailbox header. */ export declare function headerOperation(keys: MailboxKeys, record: MailboxRecord): BatchOperation;