# vault sync _(no submodules)_ ## Purpose `sync.ts` exposes the `VaultSync` orchestrator that delegates `pull` / `push` / `sync` operations to a `VaultSyncLike` adapter (production wires this to `@suaveplan/vault-sync`'s `SyncEngine`). The orchestrator's responsibilities are: (1) project the in-memory item rows into the wire-shaped `SyncSnapshot`, (2) hydrate the delegate with the latest local snapshot before every operation, (3) translate the delegate's `SyncDelegationResult` into the public `SyncResult` envelope, and (4) wrap any delegate failure in a `VAULT.SYNC.FAILED` `VaultError` so consumers see a stable error code regardless of the underlying transport. This keeps the sync engine generic — it has no knowledge of the vault's encryption layer, item schema, or container format. The orchestrator handles all the impedance matching at the boundary. ## Features - `pull() → SyncResult` — pull remote changes via the delegate. - `push() → SyncResult` — push local changes via the delegate. - `sync() → SyncResult` — bidirectional sync via the delegate. - `currentSnapshot()` — read-through to the delegate's view. - Snapshot is built on-demand from `rows()` — the orchestrator never caches a stale snapshot. - All operations emit a histogram sample and an `op`-tagged outcome counter; failures map to `VAULT.SYNC.FAILED` with the underlying error chained as `cause`. ## Usage: Basic Example ```ts import { VaultSync } from "@suaveplan/vault/sync/sync"; const sync = new VaultSync({ delegate: vaultSyncEngine, rows: () => items.allRows(), clock: Date.now, }); const result = await sync.sync(); console.log(`merged ${result.merged.length} items`); ``` ## Usage: Advanced Example ```ts import { VaultSync } from "@suaveplan/vault/sync/sync"; import { VaultError, VAULT_CODES } from "@suaveplan/vault/errors"; async function syncWithBackoff(sync: VaultSync, attempts = 3) { for (let i = 0; i < attempts; i++) { try { return await sync.sync(); } catch (err) { if ( !VaultError.is(err) || err.code !== VAULT_CODES["SYNC.FAILED"] || i === attempts - 1 ) throw err; await new Promise((r) => setTimeout(r, 100 * 2 ** i)); } } throw new Error("unreachable"); } ``` ## API Reference - `class VaultSync`. - `constructor(options: SyncOptions)`. - `pull(): Promise` — throws `SYNC.FAILED`. - `push(): Promise` — throws `SYNC.FAILED`. - `sync(): Promise` — throws `SYNC.FAILED`. - `currentSnapshot(): SyncSnapshot`. - `interface SyncOptions` — `{ delegate, rows, clock }`. ## Implementation Notes - `buildSnapshot` projects only the wire-essential fields (`id`, `updatedAt`, `version`, `deleted`, `fieldsJson`, `concealedFieldsJson`, `tagsJson`). It deliberately does not decrypt concealed fields — sync sees ciphertext only, end-to-end. The remote service stores ciphertext, the merge layer compares it as opaque bytes, and concealed-field plaintext never leaves the vault layer. - The orchestrator hydrates the delegate before every operation. This keeps the snapshot correct even if the caller mutates items between successive `pull` / `push` calls. - `VAULT.SYNC.FAILED` is the canonical wrapping for any error raised from the delegate. Callers can inspect `err.cause` for the underlying reason (transport timeout, merge conflict, payload-too-large, etc.). - The OTel `outcome` counter uses a fixed allowlist (`success | failed | rejected`); the orchestrator emits `success` and `failed`. The `rejected` slot is reserved for the lifecycle reject path (e.g. `vault.sync.pull` called while locked) which is enforced one layer up in `Vault`.