//! SyncKit Client SDK, end-to-end encrypted cloud sync. //! //! All row data is encrypted client-side before leaving the device. //! The server only ever sees ciphertext. //! //! # Design //! //! The `SyncStore` engine, the wire/crypto model, and the per-app manifests are //! in `docs/architecture.md`. The strategy, competitive positioning, and HLC //! conflict-resolution design live in the maintainer wiki. //! //! //! # Consumer requirement: `serde_json` insertion order must stay OFF //! //! Conflict resolution breaks an exact-HLC tie on the canonical bytes of the row //! payload (see [`conflict::resolve_field_merge`]). Those bytes are identical on //! every device **only** while `serde_json` serializes map keys in sorted order, //! i.e. while its `preserve_order` feature is NOT enabled anywhere in your final //! dependency tree. Cargo features are additive and global, so a *different* crate //! in your build turning on `serde_json/preserve_order` would silently make two //! devices compute different tiebreak bytes for the same value and diverge, with //! no error. There is no compile-time guard the SDK can enforce for you: if you //! depend on `serde_json` with `preserve_order`, do not use SyncKit conflict //! resolution. The `canonical_payload_sorts_map_keys` unit test pins this //! invariant for this crate's own build. //! //! # Quick start //! //! ```no_run //! use synckit_client::{SyncKitClient, SyncKitConfig, ChangeEntry, ChangeOp}; //! use chrono::Utc; //! //! # async fn example() -> synckit_client::Result<()> { //! let client = SyncKitClient::new(SyncKitConfig { //! server_url: "https://makenot.work".into(), //! api_key: "your-api-key".into(), //! }); //! //! // Authenticate (the third arg is the developer-defined SDK key for //! // billing attribution, typically one per workspace/org/end-user). //! let (user_id, app_id) = client.authenticate("user@example.com", "password", "workspace-42").await?; //! //! // Set up encryption (first device) //! client.setup_encryption_new("password").await?; //! //! // Register this device //! let device = client.register_device("MacBook Pro", "macos").await?; //! //! // Push encrypted data //! let cursor = client.push(device.id, vec![ //! ChangeEntry { //! table: "tasks".into(), //! op: ChangeOp::Insert, //! row_id: uuid::Uuid::new_v4().to_string(), //! timestamp: Utc::now(), //! // Minted from the app's persistent clock in real code (see `Hlc::tick`); //! // the nil-node floor stands in here so the example stays self-contained. //! hlc: synckit_client::Hlc::zero(device.id), //! data: Some(serde_json::json!({"title": "Buy milk"})), //! extra: serde_json::Map::default(), //! }, //! ]).await?; //! //! // Pull and auto-decrypt //! let (changes, cursor, has_more) = client.pull(device.id, 0).await?; //! # Ok(()) //! # } //! ``` #![warn(missing_docs)] pub mod client; pub mod conflict; pub mod crypto; pub mod error; pub mod identity; pub mod ids; pub mod keystore; pub mod oauth; #[cfg(feature = "store")] pub mod store; pub mod types; // Re-exports for convenience pub use client::subscription::{ AccountInfo, AppPricing, BillingInterval, Cents, CheckoutResponse, PriceQuote, SubscriptionStatus, }; pub use client::{BlobUploadOutcome, OtaArtifactUpload, OtaManifest, OtaRelease}; pub use client::{ SecretToken, SessionInfo, SyncKitClient, SyncKitConfig, SyncNotifyStream, validate_api_key, }; pub use conflict::{ CleanChanges, ConflictPair, ConflictResolver, Resolution, detect_conflicts, resolve_field_merge, resolve_lww, }; pub use error::{Result, SyncKitError}; pub use identity::{ IdentityKeypair, IdentityPublicKey, generate_group_key, open_gck_grant, seal_gck_to_member, }; pub use ids::{AppId, DeviceId, GroupId, UserId}; pub use oauth::{Pkce, generate_oauth_state, generate_pkce, states_match}; #[cfg(feature = "store")] pub use store::{ BlobPolicy, BlobRef, ConflictStrategy, DbSource, DeleteMode, RowIdScheme, SyncConfig, SyncMode, SyncObserver, SyncOutcome, SyncSchema, SyncState, SyncStore, SyncTable, }; pub use types::{ChangeEntry, ChangeOp, Device, Hlc, PullFilter, PulledChange, SyncStatus};