Skip to contentMenu Download OpenVireo

Account, ServiceAccount and SelfHostedSync are built from smaller pieces, all exported. Use them when you need control the high-level classes don’t give, for example your own storage of sync state, or the sync loop over files that aren’t vaults.

Sync loop

import { deserializeSyncState, passwordCredentials, serializeSyncState, syncKdbx } from "@openvireo/sdk";

const result = await syncKdbx({
  blob: client.database("personal"),          // or client.vault(id)
  db,                                          // your working copy; merged in place
  credentials: passwordCredentials(password),  // or several, newest first
  state: saved ? deserializeSyncState(saved) : null,
  onProgress: (step) => console.log(step),     // checking, downloading, merging, uploading
});
await persist(serializeSyncState(result.state));

syncKdbx is the desktop app’s sync loop: a HEAD when nothing changed locally (localChanged: false), a download, the rollback checks (revision and the sync generation inside the file), the three-way merge against the base copy, the If-Match upload, and on 412 download, merge and retry (maxAttempts, default 5). Persist result.state next to the file; it holds the ETag, the revision and the base copy for the next three-way merge. The web app keeps it in IndexedDB with its cached database.

Merge

mergeDatabases(local, remote, base) is the merge on its own; see Merge behaviour. Like the desktop app, the default mode doesn’t apply permanent deletions (mode: "synchronize" does); deleting to the Recycle Bin is a move and syncs normally.

HTTP clients

  • AuthClient (account.auth) has a method for every account server route, and TokenManager refreshes access tokens and rotates refresh tokens.
  • SyncClient (account.sync, server.client) covers every sync server route: vault(id) and database(id) return blob handles with get, head, put, delete, history and getRevision, and subscribeEvents opens the events stream.

Every client takes fetch (any fetch-compatible function, default globalThis.fetch) and headers to add to every request, such as a User-Agent in Node.

KDBX helpers

The web app’s helpers work on kdbxweb databases: decryptKdbx, createNewKdbx, collectAllDbData, addEntryToDb, editEntryInDb (pushes the previous version to the entry’s history first), deleteEntryFromDb, group helpers, and exportSelectedGroupsToKdbx. For references and items: parseReference, readField, writeField, findEntry, parseItemReference, itemOf, listItems and setItemFields.

Argon2

Argon2id defaults to hash-wasm. At m = 64 MiB, t = 3 it took about 104 ms, against about 608 ms for @noble/hashes (Node 24 and Bun 1.3, M-series Mac). setArgon2Impl(nobleArgon2) switches to pure JS where WASM isn’t allowed, and kdbxweb uses the same implementation (initKdbx()).

OPAQUE module

OPAQUE runs in a small WASM module (about 118 KB, no imports) built from packages/sdk/opaque-wasm/ with opaque-ke 4.0.1. It uses the same suite as the account server and the desktop app (ristretto255, SHA-512, Argon2id at libsodium’s interactive setting), so records and logins work across all three. It loads lazily, the first time a password is used. Bundlers need nothing special: vendor/opaque/opaque-wasm.js is a plain ES module. Rebuilding it is described in packages/sdk/vendor/opaque/SOURCE.md.

The SDK docs cover OpenVireo SDK, develop (86e6cd2), last updated 26 October 2026. Found a mistake? Edit this page on GitHub.