Skip to main content

max / synckit

4.1 KB · 107 lines History Blame Raw
1 //! SyncKit Client SDK, end-to-end encrypted cloud sync.
2 //!
3 //! All row data is encrypted client-side before leaving the device.
4 //! The server only ever sees ciphertext.
5 //!
6 //! # Design
7 //!
8 //! The `SyncStore` engine, the wire/crypto model, and the per-app manifests are
9 //! in `docs/architecture.md`. The strategy, competitive positioning, and HLC
10 //! conflict-resolution design live in the maintainer wiki.
11 //! <!-- wiki: synckit-overview -->
12 //!
13 //! # Consumer requirement: `serde_json` insertion order must stay OFF
14 //!
15 //! Conflict resolution breaks an exact-HLC tie on the canonical bytes of the row
16 //! payload (see [`conflict::resolve_field_merge`]). Those bytes are identical on
17 //! every device **only** while `serde_json` serializes map keys in sorted order,
18 //! i.e. while its `preserve_order` feature is NOT enabled anywhere in your final
19 //! dependency tree. Cargo features are additive and global, so a *different* crate
20 //! in your build turning on `serde_json/preserve_order` would silently make two
21 //! devices compute different tiebreak bytes for the same value and diverge, with
22 //! no error. There is no compile-time guard the SDK can enforce for you: if you
23 //! depend on `serde_json` with `preserve_order`, do not use SyncKit conflict
24 //! resolution. The `canonical_payload_sorts_map_keys` unit test pins this
25 //! invariant for this crate's own build.
26 //!
27 //! # Quick start
28 //!
29 //! ```no_run
30 //! use synckit_client::{SyncKitClient, SyncKitConfig, ChangeEntry, ChangeOp};
31 //! use chrono::Utc;
32 //!
33 //! # async fn example() -> synckit_client::Result<()> {
34 //! let client = SyncKitClient::new(SyncKitConfig {
35 //! server_url: "https://makenot.work".into(),
36 //! api_key: "your-api-key".into(),
37 //! });
38 //!
39 //! // Authenticate (the third arg is the developer-defined SDK key for
40 //! // billing attribution, typically one per workspace/org/end-user).
41 //! let (user_id, app_id) = client.authenticate("user@example.com", "password", "workspace-42").await?;
42 //!
43 //! // Set up encryption (first device)
44 //! client.setup_encryption_new("password").await?;
45 //!
46 //! // Register this device
47 //! let device = client.register_device("MacBook Pro", "macos").await?;
48 //!
49 //! // Push encrypted data
50 //! let cursor = client.push(device.id, vec![
51 //! ChangeEntry {
52 //! table: "tasks".into(),
53 //! op: ChangeOp::Insert,
54 //! row_id: uuid::Uuid::new_v4().to_string(),
55 //! timestamp: Utc::now(),
56 //! // Minted from the app's persistent clock in real code (see `Hlc::tick`);
57 //! // the nil-node floor stands in here so the example stays self-contained.
58 //! hlc: synckit_client::Hlc::zero(device.id),
59 //! data: Some(serde_json::json!({"title": "Buy milk"})),
60 //! extra: serde_json::Map::default(),
61 //! },
62 //! ]).await?;
63 //!
64 //! // Pull and auto-decrypt
65 //! let (changes, cursor, has_more) = client.pull(device.id, 0).await?;
66 //! # Ok(())
67 //! # }
68 //! ```
69
70 pub mod client;
71 pub mod conflict;
72 pub mod crypto;
73 pub mod error;
74 pub mod identity;
75 pub mod ids;
76 pub mod keystore;
77 pub mod oauth;
78 #[cfg(feature = "store")]
79 pub mod store;
80 pub mod types;
81
82 // Re-exports for convenience
83 pub use client::subscription::{
84 AccountInfo, AppPricing, BillingInterval, Cents, CheckoutResponse, PriceQuote,
85 SubscriptionStatus,
86 };
87 pub use client::{BlobUploadOutcome, OtaArtifactUpload, OtaManifest, OtaRelease};
88 pub use client::{
89 SecretToken, SessionInfo, SyncKitClient, SyncKitConfig, SyncNotifyStream, validate_api_key,
90 };
91 pub use conflict::{
92 CleanChanges, ConflictPair, ConflictResolver, Resolution, detect_conflicts,
93 resolve_field_merge, resolve_lww,
94 };
95 pub use error::{Result, SyncKitError};
96 pub use identity::{
97 IdentityKeypair, IdentityPublicKey, generate_group_key, open_gck_grant, seal_gck_to_member,
98 };
99 pub use ids::{AppId, DeviceId, GroupId, UserId};
100 pub use oauth::{Pkce, generate_oauth_state, generate_pkce, states_match};
101 #[cfg(feature = "store")]
102 pub use store::{
103 BlobPolicy, BlobRef, ConflictStrategy, DbSource, DeleteMode, RowIdScheme, SyncConfig, SyncMode,
104 SyncObserver, SyncOutcome, SyncSchema, SyncState, SyncStore, SyncTable,
105 };
106 pub use types::{ChangeEntry, ChangeOp, Device, Hlc, PullFilter, PulledChange, SyncStatus};
107