MDCS SDK
A high-level Rust SDK for building collaborative, local-first applications on top of the Merkle-Delta CRDT Store.
Table of contents
- What you get
- Installation
- Quickstarts
- Mental model
- API reference
- End-to-end usage pattern
- Examples
- Best practices
- License
What you get
The SDK exposes a layered API:
- Client: process-level entry point, peer identity, session registry
- Session: collaboration scope for peers + documents + awareness
- Documents: CRDT-backed
TextDoc,RichTextDoc, andJsonDoc - Presence: cursor, selection, and user status tracking
- Network: pluggable transport via
NetworkTransport - Sync: transport message helpers + per-peer sync state
- Errors: typed
SdkErrorandResult<T>
Installation
Add to your Cargo.toml:
[]
= { = "../crates/mdcs-sdk" }
Quickstarts
Collaborative text in 60 seconds
use create_collaborative_clients;
JSON document quickstart
use create_collaborative_clients;
use JsonValue;
Presence quickstart
use create_collaborative_clients;
use UserStatus;
Network + sync quickstart
use ;
use ;
use Arc;
async
Mental model
Use this sequence in production:
- Create
Client<T: NetworkTransport> - Create/get a
Session - Open document handles from the session
- Apply local edits through write locks
- Ship updates over your transport
- Merge remote state (or apply remote deltas)
- Subscribe to session/document/awareness events for UI reactivity
API reference
1) Client API (client.rs)
Client owns local identity, transport, and local session cache.
ClientConfig
| Field | Type | Input | Output / Effect |
|---|---|---|---|
user_name |
String |
display name | exposed in session presence handshake |
auto_reconnect |
bool |
reconnect policy | currently configuration metadata |
max_reconnect_attempts |
u32 |
retry cap | currently configuration metadata |
ClientConfigBuilder
new() -> ClientConfigBuilderuser_name(name) -> ClientConfigBuilderauto_reconnect(enabled) -> ClientConfigBuildermax_reconnect_attempts(attempts) -> ClientConfigBuilderbuild() -> ClientConfig
Client<MemoryTransport>
new_with_memory_transport(config) -> Client<MemoryTransport>- Input:
ClientConfig - Output: client with generated
PeerId - Use when: tests, demos, local simulations
- Input:
Client<T: NetworkTransport> core methods
new(peer_id, transport, config) -> Client<T>peer_id() -> &PeerIduser_name() -> &strtransport() -> &Arc<T>create_session(session_id) -> Arc<Session<T>>- idempotent per
session_id(returns existing local instance)
- idempotent per
get_session(session_id) -> Option<Arc<Session<T>>>close_session(session_id)session_ids() -> Vec<String>connect_peer(peer_id) -> Result<(), SdkError>disconnect_peer(peer_id) -> Result<(), SdkError>connected_peers() -> Vec<Peer>
Quick helper
quick::create_collaborative_clients(user_names: &[&str]) -> Vec<Client<MemoryTransport>>- Input: ordered user names
- Output: fully connected in-memory clients in same order
2) Session API (session.rs)
Session groups collaboration by session_id and manages opened docs + awareness.
Lifecycle and metadata
new(session_id, local_peer_id, user_name, transport) -> Session<T>session_id() -> &strlocal_peer_id() -> &PeerIduser_name() -> &strawareness() -> &Arc<Awareness>subscribe() -> broadcast::Receiver<SessionEvent>
Connectivity
connect().await -> Result<(), SdkError>- broadcasts
Message::Hello { replica_id, user_name }
- broadcasts
disconnect().await -> Result<(), SdkError>
Document management
open_text_doc(document_id) -> Arc<RwLock<TextDoc>>open_rich_text_doc(document_id) -> Arc<RwLock<RichTextDoc>>open_json_doc(document_id) -> Arc<RwLock<JsonDoc>>close_doc(document_id)open_documents() -> Vec<String>peers().await -> Vec<Peer>
SessionEvent
PeerJoined { peer_id, user_name }PeerLeft { peer_id }DocumentOpened { document_id }DocumentClosed { document_id }ConnectedDisconnected
3) Document API (document.rs)
All document handles are returned as Arc<RwLock<_>>.
Use:
doc.write()for mutating callsdoc.read()for read-only calls
3.1 TextDoc
new(id, replica_id) -> TextDocinsert(position, text)delete(position, length)get_text() -> Stringlen() -> usizeis_empty() -> boolmerge(other: &TextDoc)clone_state() -> TextDoc
Inputs / outputs
- Insert input: zero-based
position, UTF-8 string slice - Delete input: zero-based
position,length - Merge output: in-place convergent state update
3.2 RichTextDoc
new(id, replica_id) -> RichTextDocinsert(position, text)delete(position, length)format(start, end, mark: MarkType)unformat_by_id(mark_id)get_text() -> Stringget_content() -> String(plain-text rendering)len() -> usizeis_empty() -> boolmerge(other: &RichTextDoc)clone_state() -> RichTextDoc
3.3 JsonDoc
new(id, replica_id) -> JsonDocset(path, value: JsonValue)get(path) -> Option<JsonValue>delete(path)root() -> serde_json::Valuekeys() -> Vec<String>merge(other: &JsonDoc)clone_state() -> JsonDoc
Path format
- Dot-path strings (example:
"profile.name","settings.theme")
DocEvent
Insert { position, text }Delete { position, length }RemoteUpdate
CollaborativeDoc trait
id() -> &strreplica_id() -> &strsubscribe() -> broadcast::Receiver<DocEvent>take_pending_deltas() -> Vec<Vec<u8>>apply_remote(delta: &[u8])
4) Presence API (presence.rs)
Presence is managed per session via session.awareness().
Awareness
new(local_user_id, local_user_name) -> Awarenesslocal_user_id() -> &strlocal_user_name() -> &strset_cursor(document_id, position)set_selection(document_id, start, end)set_status(status: UserStatus)get_users() -> Vec<UserPresenceInfo>get_cursors(document_id) -> Vec<CursorInfo>get_local_color() -> &strsubscribe() -> broadcast::Receiver<AwarenessEvent>cleanup_stale()
Awareness data models
CursorInfouser_id,user_name,document_id,position,selection_start,selection_end,color
UserPresenceInfouser_id,name,status,color,cursors
AwarenessEvent
UserUpdated(UserPresenceInfo)UserOffline(String)CursorMoved(CursorInfo)
5) Network API (network.rs)
NetworkTransport is the protocol boundary between the SDK and your networking layer.
NetworkTransport trait
connect(peer_id).await -> Result<(), NetworkError>disconnect(peer_id).await -> Result<(), NetworkError>send(peer_id, message).await -> Result<(), NetworkError>broadcast(message).await -> Result<(), NetworkError>connected_peers().await -> Vec<Peer>subscribe() -> mpsc::Receiver<(PeerId, Message)>
Built-in transport: MemoryTransport
new(local_id: PeerId) -> MemoryTransportlocal_id() -> &PeerIdconnect_to(other: &MemoryTransport)
Message envelope: Message
Hello { replica_id, user_name }SyncRequest { document_id, version }SyncResponse { document_id, deltas, version }Update { document_id, delta, version }Presence { user_id, document_id, cursor_pos }Ack { message_id }PingPong
Helpers
create_network(count: usize) -> Vec<MemoryTransport>- Returns a fully connected in-memory mesh
6) Sync API (sync.rs)
SyncManager helps send sync/update messages and track peer sync metadata.
SyncConfig
| Field | Type | Meaning |
|---|---|---|
sync_interval_ms |
u64 |
periodic sync frequency |
presence_interval_ms |
u64 |
periodic presence broadcast frequency |
sync_timeout_ms |
u64 |
timeout budget per sync request |
max_batch_size |
usize |
max deltas per transfer |
auto_sync |
bool |
enables background sync policy |
SyncConfigBuilder
new() -> SyncConfigBuildersync_interval(ms) -> SyncConfigBuilderpresence_interval(ms) -> SyncConfigBuildersync_timeout(ms) -> SyncConfigBuildermax_batch_size(size) -> SyncConfigBuilderauto_sync(enabled) -> SyncConfigBuilderbuild() -> SyncConfig
SyncManager<T: NetworkTransport>
new(transport, config) -> SyncManager<T>config() -> &SyncConfigbroadcast_update(document_id, delta, version).await -> Result<(), SdkError>request_sync(peer_id, document_id, version).await -> Result<(), SdkError>update_peer_state(peer_id, document_id, version)get_peer_state(peer_id) -> Option<&PeerSyncState>
PeerSyncState
document_versions: HashMap<String, u64>last_sync: Option<Instant>
7) Error API (error.rs)
SdkError
DocumentNotFound(String)PeerNotFound(String)ConnectionFailed(String)SyncError(String)NetworkError(String)SerializationError(String)Internal(String)
Alias
type Result<T> = std::result::Result<T, SdkError>
End-to-end usage pattern
use ;
use Arc;
Examples
Run from repository root:
Best practices
- Use stable
session_id/document_idnaming across peers. - Keep network transport idempotent and resilient to retries.
- Treat merge/apply operations as potentially repeated (CRDT-safe path).
- Subscribe to events for UI updates instead of polling document state.
- Use
clone_state()+merge()in demos; use delta transport in production paths.
License
MIT