Skip to main content

max / synckit

synckit-client: complete rustdoc coverage, seal with warn(missing_docs) Document the 52 public items rustdoc flagged as undocumented (OTA/pricing/ blob/error/sync-outcome struct fields, ChangeOp/BillingInterval variants, the ConflictResolver::resolve and BlobTransport methods) and add #![warn(missing_docs)] so the crate's public surface stays documented. No API or behavior change. Build, clippy, and the full test suite (513 tests) stay green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-25 00:40 UTC
Commit: 43defd612a8ff78e830977cd6523a97d18baa3b6
Parent: 2711c9d
11 files changed, +77 insertions, -6 deletions
@@ -28,7 +28,9 @@ use crate::error::Result;
28 28 pub struct OtaRelease {
29 29 /// Release id, used when registering artifacts.
30 30 pub id: Uuid,
31 + /// Release version string (semver).
31 32 pub version: String,
33 + /// Release notes / changelog text.
32 34 pub notes: String,
33 35 }
34 36
@@ -47,10 +49,15 @@ pub struct OtaArtifactUpload {
47 49 /// updater plugin reads these five and nothing else.
48 50 #[derive(Debug, Clone, Deserialize)]
49 51 pub struct OtaManifest {
52 + /// Version offered by the update.
50 53 pub version: String,
54 + /// Download URL for the update artifact.
51 55 pub url: String,
56 + /// Detached signature Tauri verifies against the pinned public key.
52 57 pub signature: String,
58 + /// Release notes shown in the update prompt.
53 59 pub notes: String,
60 + /// Publish timestamp (RFC 3339).
54 61 pub pub_date: String,
55 62 }
56 63
@@ -87,6 +87,7 @@ pub struct SubscriptionStatus {
87 87 #[derive(Debug, Clone, Serialize, Deserialize)]
88 88 #[non_exhaustive]
89 89 pub struct AppPricing {
90 + /// SyncKit app this pricing applies to.
90 91 pub app_name: String,
91 92 /// Minimum charge (applies to both monthly and annual).
92 93 pub min_charge_cents: Cents,
@@ -95,7 +96,9 @@ pub struct AppPricing {
95 96 pub per_gb_tenths_of_cent_per_month: i64,
96 97 /// Annual price is monthly × this multiplier.
97 98 pub annual_multiplier: i64,
99 + /// Smallest storage cap the slider allows, in bytes.
98 100 pub min_cap_bytes: i64,
101 + /// Largest storage cap the slider allows, in bytes.
99 102 pub max_cap_bytes: i64,
100 103 }
101 104
@@ -140,7 +143,9 @@ fn cap_bytes_to_gib_ceil(cap_bytes: i64) -> i64 {
140 143 /// break an older client's status parse.
141 144 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
142 145 pub enum BillingInterval {
146 + /// Billed once per month.
143 147 Monthly,
148 + /// Billed once per year.
144 149 Annual,
145 150 }
146 151
@@ -158,6 +163,7 @@ impl<'de> Deserialize<'de> for BillingInterval {
158 163 }
159 164
160 165 impl BillingInterval {
166 + /// The server wire string for this interval (`"monthly"` / `"annual"`).
161 167 pub fn as_str(self) -> &'static str {
162 168 match self {
163 169 Self::Monthly => "monthly",
@@ -200,11 +206,15 @@ pub struct AccountInfo {
200 206 pub username: String,
201 207 }
202 208
209 + /// A single computed price point for a cap/interval pair.
203 210 #[derive(Debug, Serialize, Deserialize)]
204 211 #[non_exhaustive]
205 212 pub struct PriceQuote {
213 + /// Storage cap this quote is for, in bytes.
206 214 pub cap_bytes: i64,
215 + /// Billing interval this quote is for.
207 216 pub interval: BillingInterval,
217 + /// Quoted price, in cents.
208 218 pub price_cents: Cents,
209 219 }
210 220
@@ -142,6 +142,9 @@ pub enum Resolution {
142 142 /// Implement this to plug in app-specific merge logic. The `base` parameter
143 143 /// is `Some` only if the app provides a base-store adapter.
144 144 pub trait ConflictResolver: Send + Sync {
145 + /// Resolve a conflict between a `local` change and a `remote` change for the
146 + /// same row, optionally given the last-synced `base` value. Returns which
147 + /// side wins (or a merged result).
145 148 fn resolve(
146 149 &self,
147 150 local: &ChangeEntry,
@@ -300,7 +300,12 @@ const WIRE_V2_TAG_BYTES: &[u8] = b"sk2:";
300 300 #[derive(Clone, Copy, Debug)]
301 301 pub enum AeadContext<'a> {
302 302 /// A personal sync change entry, bound to its `(table, row_id)` address.
303 - Entry { table: &'a str, row_id: &'a str },
303 + Entry {
304 + /// Table the row belongs to.
305 + table: &'a str,
306 + /// Row id within the table.
307 + row_id: &'a str,
308 + },
304 309 /// A group sync change entry, bound to its `(group_id, table, row_id)`
305 310 /// address. Sealed under the group's GCK (see [`crate::identity`]), not the
306 311 /// personal `master_key`. Folding `group_id` into the AAD makes a ciphertext
@@ -308,12 +313,18 @@ pub enum AeadContext<'a> {
308 313 /// that moves a group-entry ciphertext into a different group's changelog
309 314 /// changes its AAD, so it fails to open.
310 315 GroupEntry {
316 + /// Group whose GCK sealed the entry.
311 317 group_id: &'a str,
318 + /// Table the row belongs to.
312 319 table: &'a str,
320 + /// Row id within the table.
313 321 row_id: &'a str,
314 322 },
315 323 /// A content-addressed blob, bound to its content hash.
316 - Blob { hash: &'a str },
324 + Blob {
325 + /// Content hash the blob is addressed by.
326 + hash: &'a str,
327 + },
317 328 }
318 329
319 330 impl<'a> AeadContext<'a> {
@@ -18,7 +18,9 @@ pub enum SyncKitError {
18 18 /// extracted from response.
19 19 #[error("Server returned {status}: {message}")]
20 20 Server {
21 + /// HTTP status code the server returned.
21 22 status: u16,
23 + /// Error message extracted from the response.
22 24 message: String,
23 25 /// Parsed `Retry-After` header value in seconds (429 responses only).
24 26 /// Hidden from public API, used internally by the retry loop.
@@ -44,7 +46,12 @@ pub enum SyncKitError {
44 46 /// content address, the server served a different-but-authentic or
45 47 /// rolled-back ciphertext under this hash. The bytes are discarded.
46 48 #[error("Blob integrity check failed: expected {expected}, got {actual}")]
47 - IntegrityFailed { expected: String, actual: String },
49 + IntegrityFailed {
50 + /// Content hash that was requested.
51 + expected: String,
52 + /// Content hash the downloaded bytes actually produced.
53 + actual: String,
54 + },
48 55
49 56 /// Key envelope JSON has an unrecognized version or missing fields.
50 57 #[error("Invalid key envelope: {0}")]
@@ -66,6 +66,7 @@
66 66 //! # Ok(())
67 67 //! # }
68 68 //! ```
69 + #![warn(missing_docs)]
69 70
70 71 pub mod client;
71 72 pub mod conflict;
@@ -26,8 +26,11 @@ use crate::error::{Result, SyncKitError};
26 26 /// plaintext size in bytes.
27 27 #[derive(Debug, Clone, PartialEq, Eq)]
28 28 pub struct BlobRef {
29 + /// SHA-256 content address (the blob's identity).
29 30 pub hash: String,
31 + /// File extension, without the leading dot.
30 32 pub ext: String,
33 + /// Plaintext size in bytes.
31 34 pub size: u64,
32 35 }
33 36
@@ -44,8 +47,11 @@ pub trait BlobTransport: Sync {
44 47 hash: &str,
45 48 path: &Path,
46 49 ) -> impl Future<Output = Result<BlobUploadOutcome>> + Send;
50 + /// Confirm an uploaded blob so the server commits it at `hash`.
47 51 fn confirm(&self, hash: &str, size: u64) -> impl Future<Output = Result<()>> + Send;
52 + /// Presigned download URL for the blob at `hash`.
48 53 fn download_url(&self, hash: &str) -> impl Future<Output = Result<String>> + Send;
54 + /// Download the blob at `hash` from `url`, returning its ciphertext bytes.
49 55 fn download(&self, hash: &str, url: &str) -> impl Future<Output = Result<Vec<u8>>> + Send;
50 56 }
51 57
@@ -96,7 +102,9 @@ pub trait BlobPolicy: Send + Sync {
96 102 /// Result of a blob sync pass.
97 103 #[derive(Debug, Default, PartialEq, Eq)]
98 104 pub struct BlobOutcome {
105 + /// Number of blobs uploaded this pass.
99 106 pub uploaded: u64,
107 + /// Number of blobs downloaded this pass.
100 108 pub downloaded: u64,
101 109 }
102 110
@@ -52,9 +52,13 @@ impl Default for SyncConfig {
52 52 /// Outcome of one [`SyncStore::sync_now`] cycle.
53 53 #[derive(Debug, Default, PartialEq, Eq)]
54 54 pub struct SyncOutcome {
55 + /// Number of local changes pushed to the server.
55 56 pub pushed: u64,
57 + /// Number of remote changes pulled and applied.
56 58 pub pulled: u64,
59 + /// Tables whose rows changed as a result of the pull.
57 60 pub changed_tables: HashSet<String>,
61 + /// Blob upload/download tallies for this cycle.
58 62 pub blobs: BlobOutcome,
59 63 }
60 64
@@ -33,7 +33,10 @@ pub enum SyncMode {
33 33 /// never replace. The export side emits a single UPDATE trigger, gated on one
34 34 /// of the `set` columns actually changing. (Balanced Breakfast's `feed_items`:
35 35 /// sync just `is_read`/`is_starred`, never the article body.)
36 - PartialUpdate { set: &'static [&'static str] },
36 + PartialUpdate {
37 + /// Columns this table syncs; a remote change only ever `UPDATE`s these.
38 + set: &'static [&'static str],
39 + },
37 40 }
38 41
39 42 /// How a table handles a remote delete.
@@ -50,7 +53,11 @@ pub enum DeleteMode {
50 53 /// state the remote delete never intended to touch (audiofiles' `samples`).
51 54 /// The export DELETE trigger is still generated normally, a *local* delete
52 55 /// must still propagate; only the *apply* of a remote delete tombstones.
53 - Tombstone { column: &'static str },
56 + Tombstone {
57 + /// Column set to `now()` in place of a hard delete when applying a
58 + /// remote delete.
59 + column: &'static str,
60 + },
54 61 }
55 62
56 63 /// How a row's wire identifier (`sync_changelog.row_id`) is derived.
@@ -39,7 +39,12 @@ pub enum SyncScope {
39 39 /// The user's personal data, encrypted under the master key.
40 40 Personal,
41 41 /// A group's shared data, encrypted under the group's GCK at `gck_version`.
42 - Group { id: GroupId, gck_version: i32 },
42 + Group {
43 + /// Group whose shared data this scope covers.
44 + id: GroupId,
45 + /// GCK version the group's data is encrypted under.
46 + gck_version: i32,
47 + },
43 48 }
44 49
45 50 impl SyncScope {
@@ -243,7 +248,9 @@ pub async fn ensure_device_registered<T: SyncTransport>(
243 248 /// Outcome of a pull pass.
244 249 #[derive(Debug, Default, PartialEq, Eq)]
245 250 pub struct PullOutcome {
251 + /// Number of remote changes applied.
246 252 pub applied: u64,
253 + /// Tables whose rows changed as a result of the pull.
247 254 pub changed_tables: HashSet<String>,
248 255 }
249 256
@@ -16,8 +16,11 @@ use uuid::Uuid;
16 16 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
17 17 #[serde(rename_all = "UPPERCASE")]
18 18 pub enum ChangeOp {
19 + /// A row was inserted.
19 20 Insert,
21 + /// A row was updated.
20 22 Update,
23 + /// A row was deleted.
21 24 Delete,
22 25 }
23 26
@@ -223,6 +226,7 @@ pub(crate) struct RegisterDeviceRequest {
223 226 pub platform: String,
224 227 }
225 228
229 + /// A registered device belonging to a user, as returned by the server.
226 230 #[derive(Debug, Clone, Deserialize, Serialize)]
227 231 #[non_exhaustive]
228 232 pub struct Device {
@@ -572,6 +576,7 @@ pub(crate) struct OAuthTokenResponse {
572 576
573 577 // ── Status ──
574 578
579 + /// Server-side sync status for the authenticated app/user.
575 580 #[derive(Debug, Deserialize)]
576 581 #[non_exhaustive]
577 582 pub struct SyncStatus {
@@ -589,6 +594,7 @@ pub(crate) struct BlobUploadUrlRequest {
589 594 pub size_bytes: i64,
590 595 }
591 596
597 + /// Server response to a blob upload-URL request.
592 598 #[derive(Deserialize)]
593 599 #[non_exhaustive]
594 600 pub struct BlobUploadUrlResponse {