An offline-first JSON document sync library in Rust, with a C API, for the Replicant sync server.
cargo build --release
python3 scripts/build_dist.py
build_dist.py puts the headers (replicant.h, replicant.hpp) and the libraries in dist/.
The static library contains the bundled SQLite amalgamation (SQLite 3.46.0 today, through libsqlite3-sys 0.30.1) and exports its sqlite3_* C API. Code linked into the same binary must use that SQLite and not link a second copy; entonal-common's Migration.cpp and IndieKey's SQLiteCpp already do. These symbols are not part of the replicant ABI, and the SQLite version may change in any release. The shared library exports only the replicant_* functions.
#include "replicant.h" for C, or replicant.hpp for C++ (replicant::Client, a thin RAII wrapper that throws SyncException). The header's comments are the reference. The ABI is REPLICANT_ABI_VERSION_MAJOR.MINOR: a breaking change bumps the major, an addition the minor; replicant_abi_version returns the library's, packed (major << 16) | minor.
replicant_createattaches to the engine for a data dir; every handle in the process on that data dir shares it. It never waits on the network. Setstruct_sizeinReplicantConfig.ErrorNewerSchemameans a newer build migrated the database;ErrorMigrationFailedthat a v1 library could not be upgraded (either the backup failed and nothing changed, or the migration failed after a.v1-backupcopy was kept,.v1-backup-<unix seconds>when that name was taken; an existing backup is never replaced);ErrorBusythat another process kept it locked;ErrorConfigMismatchthat an open engine uses another config.ReplicantConfig.list_mergeandlist_merge_rules_jsonchoose how a list changed on both sides is merged:Append(default) merges element by element while positions line up,Atomickeeps the server's list and sets the local one aside.Fullis refused for now. Every handle on a data dir must pass the same config.ReplicantConfig.title_pointeris a JSON Pointer (e.g."/title") to the string in each document's content that is its title. Thetitlein reads, callbacks, kept copies and thetitle:search field comes only from it; without it the library assigns no titles. The shared-engine check (ErrorConfigMismatch) covers handles in one process. Every process on a data dir must pass the same pointer: a different pointer in another process is not detected, each launch with one recomputes every title and the search index, and titles end up mixed while both run.- Reads and writes are local SQLite; the engine uploads and downloads in the background. Never call from an audio thread.
- Sign-in:
replicant_enroll_requestemails a code,replicant_enroll_claimexchanges it and stores the credentials itself (it returns only the user id).replicant_clear_credentialssigns out. Both apply at once to open engines on that data dir in this process; engines in other processes pick up a sign-in within about 3 s and a sign-out within about a second. The api key and secret never cross the C API: a host tells whether it is signed in fromreplicant_get_state(halt_reasonisNotEnrolledwhen signed out or the stored credentials are damaged) and reads the user id withreplicant_get_user_id. replicant_get_stategives connection, sync phase and halt reason;replicant_reconnectretries.- Kept copies:
replicant_list_recovered,replicant_restore_document,replicant_restore_fields,replicant_dismiss_recovered. Documents the server refused to change:replicant_list_parked. - JSON results: every key, type and null case is documented on
replicant_get_document(documents),replicant_list_recoveredandreplicant_list_parked. A document's own id isid; other records name a documentdoc_id;created_atandupdated_atare RFC 3339 in UTC,recovered_atis Unix seconds. - Events: see
EVENT_CALLBACKS.md. - Plugins must never unload the library while the process runs:
replicant_destroyreturns at once and the engine stops afterwards.
Every binary that links replicant-client carries replicant-client-version=<version>. Before a release, check that all of them link the same version:
python3 scripts/check_replicant_versions.py <app bundle> <plugin bundles…>
It exits 1 when a binary has no marker, carries two versions, or the binaries disagree.
MIT