import type { Json, ProjectionAction, SyncStore, WasSyncPort, WireDoc } from './types.js'; /** * Maps one pulled wire document to the projection action for the decrypted * read-model. A tombstone deletes the projected row; a live document decrypts * its `data` body to the payload to upsert. A live document with no body (should * not occur on an encrypted collection) is a no-op. * * Decryption runs here, outside the store transaction, so a slow/failing decrypt * never holds the store's write lock. A document whose body cannot be decrypted * (legacy plaintext row, corrupt/foreign envelope, key mismatch) is skipped with * a `none` projection rather than throwing: the body is still stored and the * checkpoint advances past it, so one poison document can never permanently * wedge the feed for the whole replica. A document that decrypts but fails the * collection's `validatePayload` guard (written by the other replica -- * possibly a buggy or schema-incompatible writer) is skipped the same way: * stored, checkpoint advanced, never projected. * * The decrypt is addressed: the feed row's own `id` goes to `decryptDoc`, and * the cipher refuses an envelope sealed for some other resource with was- * client's `IntegrityError`. That refusal has two causes and the reader * cannot tell them apart: a host that moved one resource's stored envelope * under another resource's id, and a row minted by a writer that predates * addressed sealing (the legacy contacts rows * `docs/cross-replica-sync-compatibility.md` records as a stated loss, which * every fresh replica bootstrap re-reads). It is logged apart from the * ordinary undecryptable skip so a caller can count it, but the benign cause * is the common one and an alert on it needs a rate rather than a single * event. The projection outcome is the same `none`: the row is not applied, * the body is still stored, and the checkpoint advances past it. * * @param doc {WireDoc} * @param decryptDoc {(options: { id: string, envelope: Json }) => * Promise} * @param [validatePayload] {(payload: Json) => boolean} * @returns {Promise} */ export declare function projectionForDoc(doc: WireDoc, decryptDoc: (options: { id: string; envelope: Json; }) => Promise, validatePayload?: (payload: Json) => boolean): Promise; /** * Runs the pull loop to exhaustion for one feed. Fetches a page from the current * checkpoint, decrypts it to projections, and applies it (upserts + projection + * checkpoint advance) in one exclusive store transaction. Terminates on an empty * page (caught up / no change -- the prior checkpoint is kept, never overwritten * with `null`), or on a checkpoint that did not advance. Page size is never used * as the caught-up signal: the server may clamp `limit` below the requested * `batchSize`. Honors `signal` between pages so a lock drops * the loop promptly; a mid-loop abort leaves each already-applied page intact * and the feed resumable. * * @param options {object} * @param options.port {WasSyncPort} * @param options.store {SyncStore} * @param options.batchSize {number} pull `limit` (server clamps at 1000) * @param options.decryptDoc {(options: { id: string, envelope: Json }) => * Promise} * @param [options.validatePayload] {(payload: Json) => boolean} collection * payload guard; a decrypted document failing it is stored but not projected * @param [options.signal] {AbortSignal} * @returns {Promise<{ applied: number }>} documents applied across all pages */ export declare function runPull({ port, store, batchSize, decryptDoc, validatePayload, signal }: { port: WasSyncPort; store: SyncStore; batchSize: number; decryptDoc: (options: { id: string; envelope: Json; }) => Promise; validatePayload?: (payload: Json) => boolean; signal?: AbortSignal; }): Promise<{ applied: number; }>; //# sourceMappingURL=pull.d.ts.map