Skip to content

Commit 3409c05

Browse files
authored
Merge pull request #49 from n0-computer/rae/docs-update
Update the iroh-docs page after feedback from user
2 parents a941166 + 47055f1 commit 3409c05

6 files changed

Lines changed: 220 additions & 94 deletions

File tree

concepts/protocols.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ gives you endpoints and connections. From there, you choose:
3232

3333
**Use existing protocols** - Pick from protocols built by the iroh community:
3434
- [`iroh-blobs`](/protocols/blobs) - Content-addressed blob storage and transfer
35-
- [`iroh-docs`](/protocols/kv-crdts) - Collaborative key-value documents with CRDTs
35+
- [`iroh-docs`](/protocols/documents) - Collaborative key-value documents with CRDTs
3636
- [`iroh-gossip`](/connecting/gossip) - Topic-based message broadcasting in swarms
3737
- [`iroh-automerge`](https://github.com/n0-computer/iroh-experiments/tree/main/iroh-automerge) - Automerge document sync (experimental)
3838

@@ -76,7 +76,7 @@ Ready to start building with protocols?
7676

7777
**Using existing protocols:**
7878
- [Blob storage with iroh-blobs](/protocols/blobs)
79-
- [Document collaboration with iroh-docs](/protocols/kv-crdts)
79+
- [Document collaboration with iroh-docs](/protocols/documents)
8080
- [Message broadcasting with iroh-gossip](/connecting/gossip)
8181

8282
**Building your own:**

docs.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
"group": "Sending Data",
5858
"expanded": false,
5959
"pages": [
60-
"protocols/kv-crdts",
60+
"protocols/documents",
6161
"protocols/blobs",
6262
"protocols/rpc",
6363
"protocols/automerge",
@@ -178,6 +178,10 @@
178178
{
179179
"source": "/examples/examples",
180180
"destination": "/examples"
181+
},
182+
{
183+
"source": "/protocols/kv-crdts",
184+
"destination": "/protocols/documents"
181185
}
182186
]
183187
}

index.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,11 @@ composable, so you can pick and choose what you need.
7272
Store and transfer large binary files.
7373
</Card>
7474
<Card
75-
title="Key-Value CRDTs"
75+
title="Documents"
7676
icon="computer"
77-
href="/protocols/kv-crdts"
77+
href="/protocols/documents"
7878
>
79-
Collaborative key-value store with conflict-free replication.
79+
Collaborative key-value documents with conflict-free replication.
8080
</Card>
8181
<Card
8282
title="Streaming"

protocols/documents.mdx

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
---
2+
title: "Documents"
3+
---
4+
5+
6+
A key-value store for multi-dimensional documents, built on CRDTs, with an
7+
efficient synchronization protocol. `iroh-docs` provides a protocol handler for
8+
the iroh networking stack, enabling storage and synchronization of documents over
9+
peer-to-peer connections.
10+
11+
## When would I use this?
12+
13+
You would use `iroh-docs` when you need a distributed key-value store that can
14+
handle concurrent updates from multiple peers, ensuring eventual consistency
15+
without conflicts. This is particularly useful for collaborative applications,
16+
distributed configuration management, and any scenario where data needs to be
17+
shared and synchronized across multiple devices or users.
18+
19+
## Vocabulary
20+
21+
`iroh-docs` is built on a few terms that show up in the API:
22+
23+
- **Document** (also called a **Replica**) — a named, shared key-value store.
24+
Its identity is a **NamespaceId**, the public key of a keypair that gates
25+
write access.
26+
- **Entry** — a single row in a document, identified by `(namespace, author, key)`.
27+
The entry's value is the BLAKE3 hash of its content, plus a size and timestamp —
28+
the actual bytes live in the attached blobs store (see below).
29+
- **Author** — a keypair that signs entries. An application can create any
30+
number of authors, and their meaning is up to you.
31+
- **Ticket** (`DocTicket`) — a shareable string that lets a peer import a
32+
document and start syncing it.
33+
34+
## A stack of three protocols
35+
36+
`iroh-docs` is a "meta protocol": it depends on
37+
[`iroh-blobs`](/protocols/blobs) and [`iroh-gossip`](/connecting/gossip).
38+
39+
- **docs** stores entry metadata (keys, authors, content hashes) and runs
40+
range-based set reconciliation between peers to converge on the same set of
41+
entries.
42+
- **blobs** stores the actual content bytes that those hashes point to.
43+
- **gossip** carries live sync notifications, so peers learn about new entries
44+
as they appear rather than only on reconnect.
45+
46+
That's why the setup below spawns all three and registers them on the router.
47+
48+
## Installation
49+
50+
```
51+
cargo add iroh-docs
52+
```
53+
54+
- [API documentation](https://docs.rs/iroh-docs/latest/iroh_docs/)
55+
- [Setup example on GitHub](https://github.com/n0-computer/iroh-docs/tree/main/examples)
56+
57+
## Setup
58+
59+
This is the minimal setup: an endpoint, the three protocols, and a router.
60+
61+
```rust
62+
use iroh::{endpoint::presets, protocol::Router, Endpoint};
63+
use iroh_blobs::{store::mem::MemStore, BlobsProtocol, ALPN as BLOBS_ALPN};
64+
use iroh_docs::{protocol::Docs, ALPN as DOCS_ALPN};
65+
use iroh_gossip::{net::Gossip, ALPN as GOSSIP_ALPN};
66+
67+
#[tokio::main]
68+
async fn main() -> anyhow::Result<()> {
69+
// create an iroh endpoint that includes the standard address lookup
70+
// mechanisms we've built at number0
71+
let endpoint = Endpoint::bind(presets::N0).await?;
72+
73+
// build the blobs store (in-memory here; use FsStore::load for persistence)
74+
let blobs = MemStore::default();
75+
76+
// build the gossip protocol
77+
let gossip = Gossip::builder().spawn(endpoint.clone());
78+
79+
// build the docs protocol
80+
// use Docs::persistent(path) for on-disk storage instead
81+
let docs = Docs::memory()
82+
.spawn(endpoint.clone(), (*blobs).clone(), gossip.clone())
83+
.await?;
84+
85+
// register all three protocols on the router
86+
let _router = Router::builder(endpoint.clone())
87+
.accept(BLOBS_ALPN, BlobsProtocol::new(&blobs, None))
88+
.accept(GOSSIP_ALPN, gossip)
89+
.accept(DOCS_ALPN, docs.clone())
90+
.spawn();
91+
92+
// docs is ready — see the next sections for how to use it
93+
Ok(())
94+
}
95+
```
96+
97+
<Note>
98+
`Docs::memory()` keeps everything in RAM and is perfect for experimenting.
99+
For real use, pair `Docs::persistent(path)` with `FsStore::load(path)` from
100+
`iroh-blobs` so both metadata and content survive restarts.
101+
</Note>
102+
103+
## Creating and sharing a document
104+
105+
Once `docs` is spawned, you need two things before you can write: an **author**
106+
to sign your entries, and a **document** to write into.
107+
108+
```rust
109+
// create an author (or load one you saved previously)
110+
let author = docs.author_create().await?;
111+
112+
// create a new, empty document
113+
let doc = docs.create().await?;
114+
115+
// generate a ticket that grants write access to peers
116+
use iroh_docs::api::protocol::ShareMode;
117+
let ticket = doc.share(ShareMode::Write, Default::default()).await?;
118+
println!("share this ticket with a peer: {ticket}");
119+
```
120+
121+
On the other side, a peer imports the ticket to join the same document:
122+
123+
```rust
124+
use std::str::FromStr;
125+
use iroh_docs::DocTicket;
126+
127+
let ticket = DocTicket::from_str(&ticket_str)?;
128+
let doc = docs.import(ticket).await?;
129+
```
130+
131+
## Writing and reading entries
132+
133+
Entries are `(key, value)` pairs signed by an author. The value is stored as a
134+
blob, so writing an entry hashes your bytes into the blobs store and records
135+
the hash in the document.
136+
137+
```rust
138+
// write an entry
139+
doc.set_bytes(author, b"greeting".to_vec(), "hello, world".into()).await?;
140+
141+
// read one entry back
142+
use iroh_docs::store::Query;
143+
144+
if let Some(entry) = doc.get_one(Query::single_latest_per_key().key_exact("greeting")).await? {
145+
// the entry holds the hash; fetch the actual bytes from the blobs store
146+
let bytes = blobs.blobs().get_bytes(entry.content_hash()).await?;
147+
println!("{}", std::str::from_utf8(&bytes)?);
148+
}
149+
150+
// iterate all entries, latest write per key
151+
use n0_future::StreamExt;
152+
153+
let mut entries = doc.get_many(Query::single_latest_per_key()).await?;
154+
while let Some(entry) = entries.next().await {
155+
let entry = entry?;
156+
let bytes = blobs.blobs().get_bytes(entry.content_hash()).await?;
157+
println!("{:?} = {:?}", entry.key(), bytes);
158+
}
159+
```
160+
161+
<Note>
162+
The document only stores the hash of each value. If you want the content,
163+
fetch it from the blobs store. When two peers sync, docs exchanges the entry
164+
metadata and blobs transfers any content the other side is missing.
165+
</Note>
166+
167+
## Reacting to sync
168+
169+
`doc.subscribe()` returns a stream of live events — new entries from peers,
170+
sync progress, content download completions — which is usually how UIs stay
171+
up to date:
172+
173+
```rust
174+
use iroh_docs::engine::LiveEvent;
175+
176+
let mut events = doc.subscribe().await?;
177+
while let Some(event) = events.next().await {
178+
match event? {
179+
LiveEvent::InsertRemote { entry, .. } => {
180+
println!("peer inserted {:?}", entry.key());
181+
}
182+
LiveEvent::ContentReady { hash } => {
183+
println!("content {hash} is now available locally");
184+
}
185+
_ => {}
186+
}
187+
}
188+
```
189+
190+
## How sync works
191+
192+
Peers converge by exchanging a small number of messages using **range-based
193+
set reconciliation** — recursively partitioning their entry sets and comparing
194+
fingerprints of the partitions to detect where they disagree. The algorithm is
195+
described in [Meyer 2022](https://arxiv.org/abs/2212.13567); the key property
196+
is that fully-in-sync peers only need to exchange a single fingerprint to
197+
confirm it.
198+
199+
The crate exposes a generic storage interface with in-memory and persistent
200+
file-based implementations. The persistent one uses
201+
[redb](https://github.com/cberner/redb), an embedded key-value store, and
202+
persists the whole store with all replicas to a single file.
203+
204+
## Full examples
205+
206+
- [iroh-docs setup example](https://github.com/n0-computer/iroh-docs/tree/main/examples) — the minimal setup shown above as a runnable file.
207+
- [tauri-todos](https://github.com/n0-computer/iroh-examples/tree/main/tauri-todos) — a desktop todo app that uses `iroh-docs` end-to-end: persistent storage, ticket-based sharing, live sync, and an application model layered over entries.

protocols/kv-crdts.mdx

Lines changed: 0 additions & 85 deletions
This file was deleted.

what-is-iroh.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ integrity.
2424
building applications that can operate without reliance on servers.
2525
- **Files & blobs**: With protocols like [iroh-blobs](/protocols/blobs), iroh enables efficient
2626
file transfer.
27-
- **Structured data**: iroh's support for flexible data protocols like [KV
28-
CRDTs](/protocols/kv-crdts) and [Automerge](/protocols/automerge) allows
27+
- **Structured data**: iroh's support for flexible data protocols like
28+
[Documents](/protocols/documents) and [Automerge](/protocols/automerge) allows
2929
developers to build applications that support real-time collaborative editing
3030
and data synchronization. Any kind of CRDT or OT sync protocol can be integrated.
3131
- **Real-time communication**: Build [chat applications](/examples/chat), [RPC
@@ -96,7 +96,7 @@ this node, routed by their ALPN.
9696

9797
## Getting started
9898
To get started with iroh, check out the [quickstart guide](/quickstart) or explore the
99-
[protocols documentation](/protocols/kv-crdts) to see what protocols are available and
99+
[protocols documentation](/protocols/documents) to see what protocols are available and
100100
how to use them in your applications.
101101

102102
Read the [how it works documentation](/concepts/endpoints) to understand the underlying

0 commit comments

Comments
 (0)