Container view
Each device runs several co-operating processes. The engine is separated from the OS integration layer, and a background daemon owns all mutable engine state.
C4 Level 2 — Containers (per device)
graph TB
subgraph Device["Device (macOS or Windows)"]
direction TB
subgraph UX["Control Plane"]
Tauri["🖥️ Tauri Desktop App\n(tray, status, vault management)"]
CLI["⌨️ CLI\n(recovery, headless ops)"]
end
Daemon["⚙️ TinySync Daemon\n(per-user background service)\nOwns: SQLite · credentials · event stream"]
subgraph Providers["OS Native Providers"]
FPExt["🍎 File Provider Extension\n(macOS)"]
CFProc["🪟 CFAPI Provider Process\n(Windows)"]
end
end
subgraph Cloud["Cloud"]
Server["🔄 TinySync Server"]
PG["PostgreSQL"]
Blobs["Blob Store"]
Server --- PG
Server --- Blobs
end
Tauri & CLI -- "JSON / IPC" --> Daemon
FPExt -- "JSON over Unix socket" --> Daemon
CFProc -- "JSON over named pipe" --> Daemon
Daemon -- "HTTPS + WebSocket" --> Server
| Container | Owns | Does not own |
|---|---|---|
| TinySync Daemon | SQLite engine state, credentials, server polling, WebSocket, pending ops queue, retry, panic-resync, unified event stream | OS placeholder state, shell callbacks, Finder/Explorer presentation |
| File Provider Ext (macOS) | Placeholder registration, Finder enumeration, hydration callbacks, own-write suppression | Credentials, SQLite, server protocol, conflict policy |
| CFAPI Process (Windows) | Sync root registration, placeholder operations, Explorer callbacks, byte-range hydration | Same as above |
| Tauri App | Setup UI, tray, status display, device management, process supervision | Sync semantics — it is a control plane only |
| CLI | Register, attach, sync-once, status, devices, admin (groups/grants), panic-resync | Owns no state — reads from daemon or engine directly |
Why a separate daemon?
The macOS File Provider extension has an OS-controlled lifecycle. The Tauri app can be quit by the user. Neither is the right owner of SQLite, credentials, or WebSocket connections. A per-user background daemon (macOS LaunchAgent, Windows user-mode service) gives sync one durable, crash-recoverable owner. Quitting the UI does not stop sync.
Engine components
The daemon contains tinysync-engine — the heart of the system. It is isolated behind two traits: one facing the cloud, one facing the OS.
C4 Level 3 — Engine Component View
graph TB
subgraph Engine["tinysync-engine"]
SE["SyncEngine<A>\nOrchestrates all sync logic"]
LS["LocalStore (SQLite)\nItem state · pending ops · engine_state kv"]
CC["CloudClient trait\n(HTTP abstraction)"]
PA["PresentationAdapter trait\n(OS abstraction)"]
SE --> LS
SE --> CC
SE --> PA
end
subgraph Adapters["Adapter Implementations"]
FSA["FolderWatcherAdapter\n(prototype, eager)"]
FPA["FileProviderAdapter\n(macOS, lazy)"]
CFA["CFAPIAdapter\n(Windows, lazy)"]
MockA["MockAdapter (tests)"]
end
subgraph CloudImpls["CloudClient Implementations"]
HC["HostedCloudClient (HTTPS)"]
TestC["TestCloudClient (tests)"]
end
PA -.-> FSA & FPA & CFA & MockA
CC -.-> HC & TestC
The Two Trait Boundaries
CloudClient — server-facing
trait CloudClient {
get_snapshot(vault_id) → Snapshot
get_log(vault_id, after: Seq)
→ LogPage
upload_blob(vault_id, hash, bytes)
download_blob(vault_id, hash)→Bytes
submit_mutation(vault_id, req)
→ MutationResponse
}
The engine has no HTTP dependency. Swapping in a test double is trivial — and that's how all engine tests run.
PresentationAdapter — OS-facing
trait PresentationAdapter {
hydration_strategy()→Eager|Lazy
apply_create(item, content)
apply_update(item, content)
apply_delete(item)
apply_move_rename(from, to)
enumerate_local()→Vec<LocalEntry>
read_local_content(path, hash)→Bytes
preserve_conflict_copy(path,
op_id, device_id)→Option<VPath>
}
The engine knows nothing about notify events, File Provider callbacks, or CFAPI requests. It speaks in sync intent.
The crate map
| Crate | Layer | Contents |
|---|---|---|
tinysync-core | Shared | All protocol DTOs, ID types, content hashing, path/name validation, Unicode normalization. |
tinysync-server | Server | Axum HTTP API, Postgres queries (sqlx), blob store trait + FS impl, WebSocket wake hub (in-process + Postgres LISTEN/NOTIFY), token auth. |
tinysync-engine | Client | SyncEngine, LocalStore (SQLite), PresentationAdapter + CloudClient traits, all sync state machine logic, conflict detection, pending ops queue, panic-resync. |
tinysync-adapter-fs | Client | Prototype eager adapter: filesystem watcher (notify), file materialization, content hashing, own-write suppression, inode-based rename detection. |
tinysync-provider-macos-ffi | Client | Thin Swift-Rust FFI shim for macOS File Provider callback bridge. |
tinysync-provider-windows | Client | Windows CFAPI sync root registration, placeholder management, hydration callback handler. |
tinysync-provider-ipc | Shared | Length-prefixed JSON IPC protocol (4-byte big-endian + JSON body, max 1 MiB/frame) between native providers and daemon. |
tinysync-daemon | Client | Per-user background daemon: IPC server, engine orchestration, multi-vault routing, client event stream, wake subscription. |
tinysync-client | Client | HTTP implementation of CloudClient, device identity storage, sync loop primitives. |
tinysync-desktop | UI | Tauri shell, tray, daemon lifecycle management, settings UI. |
tinysync-cli | UI | CLI: register, attach, sync-once, status, devices, admin (groups/grants), panic-resync. |
Engine lifecycle
The engine has five states, persisted in the engine_state SQLite table. Transitions are explicit and logged.
Engine Lifecycle State Machine
stateDiagram-v2
[*] --> Bootstrapping : process start
Bootstrapping --> OnlineIdle : snapshot + log complete
OnlineIdle --> OnlineSyncing : changes_pending
OnlineSyncing --> OnlineIdle : queue_drained
OnlineIdle --> Offline : connection_lost
OnlineSyncing --> Offline : connection_lost
Offline --> OnlineSyncing : reconnect
OnlineIdle --> PanicResync : integrity_violation
OnlineSyncing --> PanicResync : integrity_violation
Offline --> PanicResync : local_state_corrupt
PanicResync --> Bootstrapping : local_index_rebuilt
| State | Description |
|---|---|
| Bootstrapping | Fetches server snapshot, enumerates local adapter state, reconciles, replays log events after snapshot seq. Also the entry state after panic-resync. |
| OnlineIdle | Fully caught up. Listening on WebSocket for wake notifications. |
| OnlineSyncing | Actively processing: replaying remote log, hashing local changes, uploading blobs, submitting mutations, satisfying hydration requests. |
| Offline | Server unreachable. Local changes queue in pending_ops. Remote changes fetched on reconnect via GET /log?after=last_seq_processed. |
| PanicResync | Local state suspected corrupt. Engine halts. Recovery: fetch fresh snapshot, hash local files, preserve divergent content as conflict copies, rebuild SQLite from scratch. Re-runnable if interrupted. |