/** * The rebind at the next pull to head (journal spec §3.5 Rule 2 D5, §1.5; * T13278). * * Undo grows exactly while a replica cannot reach the stream. At 100% of its * budget the store keeps writing undo and records `sync.undo_budget_exceeded` * (`sequencing.ts`); a forced rebind then would need the network it does not * have. So the rebind waits for the next pull that reaches the stream's head, * and {@link rebindAtHead} runs it, in one transaction with the rebind: * * 1. segments of the old replica the server already holds (at or below the * pull cursor's replicaSeq for it) are marked pushed first, so they are * never mistaken for an unsent outbox; * 2. the store rebinds to a new replica (`undo-budget`): the old outbox * (live captures, sealed transactions, unpushed segments) is inherited; * 3. the undo log is emptied; every transaction of the old replica the * stream has not sequenced is inherited first; * 4. the reconcile re-emits what the old replica changed and never sent, * under the new replica (`reconcileInPlace`, the §1.5 three-way rule); * 5. a signed `retire` transaction for the old id is queued * (`queueRetireTxn`), naming the successor and the last replicaSeq the * server holds of the old replica; a replica that never landed a segment * pins nothing and retires with no journal transaction; * 6. `sync.undo_budget_exceeded` is cleared. Only this clears it. * * The server half (attach the new replica to this device, sign the E31 * retirement, update the link) needs the network, so the transaction also * records {@link REBIND_PENDING_KEY}: the cloud layer completes it before the * store pushes or pulls again, and a crash in between resumes there. * * @task T13278 * @module store/sync/rebind */ import type { DatabaseSync } from 'node:sqlite'; import type { TableScope } from '@cleocode/contracts'; import type { StreamCursor } from './pull.js'; import { type ReconcileReport } from './reconcile.js'; import { type SyncOpenOptions } from './replica.js'; /** * `_sync_meta` key holding the server half of a rebind the store committed * and the cloud has not completed yet ({@link PendingRebind}). */ export declare const REBIND_PENDING_KEY = "sync.rebind_pending"; /** The server half of a committed rebind: attach `to`, retire `from` (E31). */ export interface PendingRebind { readonly stream: string; readonly scope: TableScope; /** The retired replica. */ readonly from: string; /** Its successor, this store's replica now. */ readonly to: string; /** The last replicaSeq of `from` the server holds, or null when it holds none. */ readonly lastReplicaSeq: number | null; /** The journal `retire` transaction, or null when `from` landed nothing. */ readonly retireTxn: string | null; /** When the store rebound. */ readonly at: string; } /** Options for {@link rebindAtHead}. */ export interface RebindAtHeadOptions extends SyncOpenOptions { /** The stream the pull reached the head of. */ readonly stream: string; /** The pull cursor at head: the server's last replicaSeq per replica. */ readonly cursor: StreamCursor; } /** What {@link rebindAtHead} did. */ export interface RebindAtHeadReport { readonly previousReplicaId: string; readonly replicaId: string; /** The server half the cloud must complete. */ readonly pending: PendingRebind; /** Old segments the server held, marked pushed before the rebind. */ readonly segmentsMarkedPushed: number; /** Undo rows dropped (`_sync_undo` plus `_sync_row_undo`). */ readonly undoDropped: number; readonly reconcile: ReconcileReport; } /** * Whether the undo budget scheduled a rebind (D5): `sync.undo_budget_exceeded` * is set. Read-only. * * @param db - The store. * @returns True when the next pull to head must rebind. */ export declare function undoBudgetRebindDue(db: DatabaseSync): boolean; /** * The server half of a rebind still to complete, or null. Read-only. * * @param db - The store. * @returns The pending rebind, or null. */ export declare function pendingRebind(db: DatabaseSync): PendingRebind | null; /** * Record that the server half of a rebind completed (the new replica is * attached and the old one retired on the server). * * @param db - The store, outside a transaction. * @param to - The replica the completed rebind bound; a newer pending rebind is kept. */ export declare function clearPendingRebind(db: DatabaseSync, to: string): void; /** * Run the scheduled rebind after a pull reached the stream's head (module * docs). Must run outside a transaction, and only after a pull that staged * and applied everything up to the server's head. * * @param db - The store. * @param o - The open options of the canonical store, the stream and the pull cursor. * @returns What was done, or null when no rebind is due. * @throws {Error} When the store has no active replica. */ export declare function rebindAtHead(db: DatabaseSync, o: RebindAtHeadOptions): RebindAtHeadReport | null; /** * Turn the retirement an open-pass rebind owes (a rollback or a * cross-filesystem move, {@link RETIRE_DUE_KEY}; T13337) into the signed * `retire` transaction and the pending server rebind, in one transaction. * `cloud sync` then completes the server half (`completeServerRebind`) * before it pushes or pulls, as after a rebind at head. * * The retire names the highest replicaSeq of the old replica persisted on * the stream, by the store or the device registry; a replica that persisted * nothing retires with no journal transaction. A store that never joined a * stream owes nothing, and the record is dropped. While another rebind's * server half is still pending, nothing is done: that one completes first. * * @param db - The store, outside a transaction. * @param opts - Clock override (tests). * @returns The pending rebind it recorded, or null. */ export declare function settleRetireDue(db: DatabaseSync, opts?: { readonly now?: () => Date; }): PendingRebind | null; //# sourceMappingURL=rebind.d.ts.map