From a62b75f5f672b37dc5138b7008dd0ec20192ab05 Mon Sep 17 00:00:00 2001 From: Rae McKelvey <633012+okdistribute@users.noreply.github.com> Date: Tue, 21 Apr 2026 20:31:15 -0700 Subject: [PATCH 1/2] Update the iroh-docs page after feedback from user --- concepts/protocols.mdx | 4 +- docs.json | 6 +- index.mdx | 2 +- protocols/documents.mdx | 207 ++++++++++++++++++++++++++++++++++++++++ protocols/kv-crdts.mdx | 85 ----------------- what-is-iroh.mdx | 4 +- 6 files changed, 217 insertions(+), 91 deletions(-) create mode 100644 protocols/documents.mdx delete mode 100644 protocols/kv-crdts.mdx diff --git a/concepts/protocols.mdx b/concepts/protocols.mdx index 40dec54..9d45482 100644 --- a/concepts/protocols.mdx +++ b/concepts/protocols.mdx @@ -32,7 +32,7 @@ gives you endpoints and connections. From there, you choose: **Use existing protocols** - Pick from protocols built by the iroh community: - [`iroh-blobs`](/protocols/blobs) - Content-addressed blob storage and transfer -- [`iroh-docs`](/protocols/kv-crdts) - Collaborative key-value documents with CRDTs +- [`iroh-docs`](/protocols/documents) - Collaborative key-value documents with CRDTs - [`iroh-gossip`](/connecting/gossip) - Topic-based message broadcasting in swarms - [`iroh-automerge`](https://github.com/n0-computer/iroh-experiments/tree/main/iroh-automerge) - Automerge document sync (experimental) @@ -76,7 +76,7 @@ Ready to start building with protocols? **Using existing protocols:** - [Blob storage with iroh-blobs](/protocols/blobs) -- [Document collaboration with iroh-docs](/protocols/kv-crdts) +- [Document collaboration with iroh-docs](/protocols/documents) - [Message broadcasting with iroh-gossip](/connecting/gossip) **Building your own:** diff --git a/docs.json b/docs.json index 1733978..a98e75f 100644 --- a/docs.json +++ b/docs.json @@ -57,7 +57,7 @@ "group": "Sending Data", "expanded": false, "pages": [ - "protocols/kv-crdts", + "protocols/documents", "protocols/blobs", "protocols/rpc", "protocols/automerge", @@ -178,6 +178,10 @@ { "source": "/examples/examples", "destination": "/examples" + }, + { + "source": "/protocols/kv-crdts", + "destination": "/protocols/documents" } ] } diff --git a/index.mdx b/index.mdx index bd1547b..08cc036 100644 --- a/index.mdx +++ b/index.mdx @@ -74,7 +74,7 @@ composable, so you can pick and choose what you need. Collaborative key-value store with conflict-free replication. diff --git a/protocols/documents.mdx b/protocols/documents.mdx new file mode 100644 index 0000000..4a05f92 --- /dev/null +++ b/protocols/documents.mdx @@ -0,0 +1,207 @@ +--- +title: "Key-value Store" +--- + + +A key-value store for multi-dimensional documents, built on CRDTs, with an +efficient synchronization protocol. `iroh-docs` provides a protocol handler for +the iroh networking stack, enabling storage and synchronization of documents over +peer-to-peer connections. + +## When would I use this? + +You would use `iroh-docs` when you need a distributed key-value store that can +handle concurrent updates from multiple peers, ensuring eventual consistency +without conflicts. This is particularly useful for collaborative applications, +distributed configuration management, and any scenario where data needs to be +shared and synchronized across multiple devices or users. + +## Vocabulary + +`iroh-docs` is built on a few terms that show up in the API: + +- **Document** (also called a **Replica**) — a named, shared key-value store. + Its identity is a **NamespaceId**, the public key of a keypair that gates + write access. +- **Entry** — a single row in a document, identified by `(namespace, author, key)`. + The entry's value is the BLAKE3 hash of its content, plus a size and timestamp — + the actual bytes live in the attached blobs store (see below). +- **Author** — a keypair that signs entries. An application can create any + number of authors, and their meaning is up to you. +- **Ticket** (`DocTicket`) — a shareable string that lets a peer import a + document and start syncing it. + +## A stack of three protocols + +`iroh-docs` is a "meta protocol": it depends on +[`iroh-blobs`](/protocols/blobs) and [`iroh-gossip`](/connecting/gossip). + +- **docs** stores entry metadata (keys, authors, content hashes) and runs + range-based set reconciliation between peers to converge on the same set of + entries. +- **blobs** stores the actual content bytes that those hashes point to. +- **gossip** carries live sync notifications, so peers learn about new entries + as they appear rather than only on reconnect. + +That's why the setup below spawns all three and registers them on the router. + +## Installation + +``` +cargo add iroh-docs +``` + +- [API documentation](https://docs.rs/iroh-docs/latest/iroh_docs/) +- [Setup example on GitHub](https://github.com/n0-computer/iroh-docs/tree/main/examples) + +## Setup + +This is the minimal setup: an endpoint, the three protocols, and a router. + +```rust +use iroh::{endpoint::presets, protocol::Router, Endpoint}; +use iroh_blobs::{store::mem::MemStore, BlobsProtocol, ALPN as BLOBS_ALPN}; +use iroh_docs::{protocol::Docs, ALPN as DOCS_ALPN}; +use iroh_gossip::{net::Gossip, ALPN as GOSSIP_ALPN}; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + // create an iroh endpoint that includes the standard address lookup + // mechanisms we've built at number0 + let endpoint = Endpoint::bind(presets::N0).await?; + + // build the blobs store (in-memory here; use FsStore::load for persistence) + let blobs = MemStore::default(); + + // build the gossip protocol + let gossip = Gossip::builder().spawn(endpoint.clone()); + + // build the docs protocol + // use Docs::persistent(path) for on-disk storage instead + let docs = Docs::memory() + .spawn(endpoint.clone(), (*blobs).clone(), gossip.clone()) + .await?; + + // register all three protocols on the router + let _router = Router::builder(endpoint.clone()) + .accept(BLOBS_ALPN, BlobsProtocol::new(&blobs, None)) + .accept(GOSSIP_ALPN, gossip) + .accept(DOCS_ALPN, docs.clone()) + .spawn(); + + // docs is ready — see the next sections for how to use it + Ok(()) +} +``` + + +`Docs::memory()` keeps everything in RAM and is perfect for experimenting. +For real use, pair `Docs::persistent(path)` with `FsStore::load(path)` from +`iroh-blobs` so both metadata and content survive restarts. + + +## Creating and sharing a document + +Once `docs` is spawned, you need two things before you can write: an **author** +to sign your entries, and a **document** to write into. + +```rust +// create an author (or load one you saved previously) +let author = docs.author_create().await?; + +// create a new, empty document +let doc = docs.create().await?; + +// generate a ticket that grants write access to peers +use iroh_docs::api::protocol::ShareMode; +let ticket = doc.share(ShareMode::Write, Default::default()).await?; +println!("share this ticket with a peer: {ticket}"); +``` + +On the other side, a peer imports the ticket to join the same document: + +```rust +use std::str::FromStr; +use iroh_docs::DocTicket; + +let ticket = DocTicket::from_str(&ticket_str)?; +let doc = docs.import(ticket).await?; +``` + +## Writing and reading entries + +Entries are `(key, value)` pairs signed by an author. The value is stored as a +blob, so writing an entry hashes your bytes into the blobs store and records +the hash in the document. + +```rust +// write an entry +doc.set_bytes(author, b"greeting".to_vec(), "hello, world".into()).await?; + +// read one entry back +use iroh_docs::store::Query; + +if let Some(entry) = doc.get_one(Query::single_latest_per_key().key_exact("greeting")).await? { + // the entry holds the hash; fetch the actual bytes from the blobs store + let bytes = blobs.blobs().get_bytes(entry.content_hash()).await?; + println!("{}", std::str::from_utf8(&bytes)?); +} + +// iterate all entries, latest write per key +use n0_future::StreamExt; + +let mut entries = doc.get_many(Query::single_latest_per_key()).await?; +while let Some(entry) = entries.next().await { + let entry = entry?; + let bytes = blobs.blobs().get_bytes(entry.content_hash()).await?; + println!("{:?} = {:?}", entry.key(), bytes); +} +``` + + +The document only stores the hash of each value. If you want the content, +fetch it from the blobs store. When two peers sync, docs exchanges the entry +metadata and blobs transfers any content the other side is missing. + + +## Reacting to sync + +`doc.subscribe()` returns a stream of live events — new entries from peers, +sync progress, content download completions — which is usually how UIs stay +up to date: + +```rust +use iroh_docs::engine::LiveEvent; + +let mut events = doc.subscribe().await?; +while let Some(event) = events.next().await { + match event? { + LiveEvent::InsertRemote { entry, .. } => { + println!("peer inserted {:?}", entry.key()); + } + LiveEvent::ContentReady { hash } => { + println!("content {hash} is now available locally"); + } + _ => {} + } +} +``` + +## How sync works + +Peers converge by exchanging a small number of messages using **range-based +set reconciliation** — recursively partitioning their entry sets and comparing +fingerprints of the partitions to detect where they disagree. The algorithm is +described in [Meyer 2022](https://arxiv.org/abs/2212.13567); the key property +is that fully-in-sync peers only need to exchange a single fingerprint to +confirm it. + +The crate exposes a generic storage interface with in-memory and persistent +file-based implementations. The persistent one uses +[redb](https://github.com/cberner/redb), an embedded key-value store, and +persists the whole store with all replicas to a single file. + +## Full examples + +- [iroh-docs setup example](https://github.com/n0-computer/iroh-docs/tree/main/examples) — the minimal setup shown above as a runnable file. +- [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. \ No newline at end of file diff --git a/protocols/kv-crdts.mdx b/protocols/kv-crdts.mdx deleted file mode 100644 index 656ff70..0000000 --- a/protocols/kv-crdts.mdx +++ /dev/null @@ -1,85 +0,0 @@ ---- -title: "Key-value Store" ---- - - -A key-value store for multi-dimensional documents, built on CRDTs, with an -efficient synchronization protocol. `iroh-docs` provides a protocol handler for -the iroh networking stack, enabling storage and synchronization of documents over -peer-to-peer connections. - -## When would I use this? - -You would use `iroh-docs` when you need a distributed key-value store that can -handle concurrent updates from multiple peers, ensuring eventual consistency -without conflicts. This is particularly useful for collaborative applications, -distributed configuration management, and any scenario where data needs to be -shared and synchronized across multiple devices or users. - - -## Installation - -``` -cargo add iroh-docs -``` - -- [API documentation](https://docs.rs/iroh-docs/latest/iroh_docs/) - -## Example - - -```rust -use iroh::{protocol::Router, Endpoint}; -use iroh_blobs::{BlobsProtocol, store::mem::MemStore, ALPN as BLOBS_ALPN}; -use iroh_docs::{protocol::Docs, ALPN as DOCS_ALPN}; -use iroh_gossip::{net::Gossip, ALPN as GOSSIP_ALPN}; - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - // create an iroh endpoint that includes the standard discovery mechanisms - // we've built at number0 - let endpoint = Endpoint::bind(presets::N0).await?; - - // build the gossip protocol - let gossip = Gossip::builder().spawn(endpoint.clone()); - - // build the docs protocol - // use Docs::persistent to use local storage - let docs = Docs::memory() - .spawn(endpoint.clone(), (*blobs).clone(), gossip.clone()) - .await?; - - // create a router builder, we will add the - // protocols to this builder and then spawn - // the router - let builder = Router::builder(endpoint.clone()); - - // setup router - let _router = builder - .accept(GOSSIP_ALPN, gossip) - .accept(DOCS_ALPN, docs) - .spawn(); - - // do fun stuff with docs! - Ok(()) -} - -``` - -## How it works - -The crate operates on Replicas. A replica contains an unlimited number of -Entries. Each entry is identified by a key, its author, and the replica's -namespace. Its value is the 32-byte BLAKE3 hash of the entry's content data, the -size of this content data, and a timestamp. The content data itself is not -stored or transferred through a replica. - -Range-based set reconciliation is a simple approach to efficiently compute the -union of two sets over a network, based on recursively partitioning the sets and -comparing fingerprints of the partitions to probabilistically detect whether a -partition requires further work. - -The crate exposes a generic storage interface with in-memory and persistent -file-based implementations. The latter makes use of [redb], an embedded -key-value store, and persists the whole store with all replicas to a single -file. \ No newline at end of file diff --git a/what-is-iroh.mdx b/what-is-iroh.mdx index b7910b9..8d89f97 100644 --- a/what-is-iroh.mdx +++ b/what-is-iroh.mdx @@ -25,7 +25,7 @@ integrity. - **Files & blobs**: With protocols like [iroh-blobs](/protocols/blobs), iroh enables efficient file transfer. - **Structured data**: iroh's support for flexible data protocols like [KV -CRDTs](/protocols/kv-crdts) and [Automerge](/protocols/automerge) allows +CRDTs](/protocols/documents) and [Automerge](/protocols/automerge) allows developers to build applications that support real-time collaborative editing and data synchronization. Any kind of CRDT or OT sync protocol can be integrated. - **Real-time communication**: Build [chat applications](/examples/chat), [RPC @@ -96,7 +96,7 @@ this node, routed by their ALPN. ## Getting started To get started with iroh, check out the [quickstart guide](/quickstart) or explore the -[protocols documentation](/protocols/kv-crdts) to see what protocols are available and +[protocols documentation](/protocols/documents) to see what protocols are available and how to use them in your applications. Read the [how it works documentation](/concepts/endpoints) to understand the underlying From 47055f1189db0f42acc0a8f07778aebc26ae5c25 Mon Sep 17 00:00:00 2001 From: Rae McKelvey <633012+okdistribute@users.noreply.github.com> Date: Tue, 21 Apr 2026 20:35:35 -0700 Subject: [PATCH 2/2] use Documents --- index.mdx | 4 ++-- protocols/documents.mdx | 2 +- what-is-iroh.mdx | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/index.mdx b/index.mdx index 08cc036..a5923c0 100644 --- a/index.mdx +++ b/index.mdx @@ -72,11 +72,11 @@ composable, so you can pick and choose what you need. Store and transfer large binary files. - Collaborative key-value store with conflict-free replication. + Collaborative key-value documents with conflict-free replication.