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, andTokenManagerrefreshes access tokens and rotates refresh tokens.SyncClient(account.sync,server.client) covers every sync server route:vault(id)anddatabase(id)return blob handles withget,head,put,delete,historyandgetRevision, andsubscribeEventsopens 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.