The document file
crdt::file: one append-only file per document, every finished step durable, Save as name and fold, crash recovery, branches, versions and blobs.
What it is and when to use it#
crdt::file keeps a document in one file. Every Branch::transact, undo
and redo is on disk before it returns, so there is no unsaved state: Save
means “name this version and fold the steps into a snapshot”. A file holds
the document’s kind and schema, its branches (forked from main), every
named step, checkpoints, snapshots, and blobs by SHA-256. Opening after a
crash drops the torn tail and keeps every finished step. The format is plain
bytes, so a browser reads it with DocFile::from_bytes.
Use it for anything a person opens as a file. Give a CrdtDoc the file’s
persistence to sync it while the steps stay in the file. Design:
Documents, sections on the file and on
versions and branches.
A minimal example#
In the default build of crdt, wasm-clean; DiskStorage is native only.
From features/crdt/crdt/tests/file.rs:
use crdt::file::{DiskStorage, DocFile, MAIN};
use crdt::DocKind;
let kind = DocKind::new("test.note", "Note").extension("note");
let file = DocFile::create(DiskStorage::open(&path)?, &kind).await?;
let mut main = file.open_branch(MAIN).await?;
main.transact("Type hello", |d| d.get_text("body").insert(0, "hello").unwrap()).await?;
main.save("Sent to Dan").await?; // a checkpoint, then a fold
let alt = main.fork("acoustic").await?;
main.merge(&alt).await?; // a step named "Merge acoustic"
let v = file.versions(MAIN).await?[1].clone();
let old = file.open_version(&v).await?; // read only
let back = file.restore(&v, "back to one").await?;
let hash = file.put_blob("fx", &bytes).await?; // sha256 hex
let replica = CrdtDoc::open_shared(id, Arc::new(file.persistence(MAIN))).await?;
Edit through the Branch, not straight on its loro(): a direct edit is
written only with the next step.
Code tour#
features/crdt/crdt/src/file/mod.rsDocFile:create,create_as,adopt,open,open_any,from_bytes,to_bytes;open_branch,versions,open_version,restore,delete_branch;put_blob,blob;recovered;persistence.features/crdt/crdt/src/file/branch.rsBranch:transact,undo,redo,flush,save,checkpoint,fork,merge,import.features/crdt/crdt/src/file/format.rsRecord,frame,parse: a 12-byte header (ARCHDOC\0and a format number), then records framed as length, payload, CRC32.features/crdt/crdt/src/file/index.rsIndex: the records folded in memory; rebuilds a branch from its snapshot and steps.features/crdt/crdt/src/file/storage.rsStorage(read,append,replace,truncate),MemoryStorage,DiskStorage.features/crdt/crdt/src/file/persistence.rsBranchPersistence: a branch as aCrdtDoc’s persistence; synced updates land as steps.
Branch::transact(name, f) runs f inside History::transact, one undo
group, then flushes: it exports the update and DocFile appends one framed
step record through Storage::append, which syncs to disk before returning.
save writes a checkpoint and folds: it rebuilds the branch from the file
(keeping another handle’s steps), adds a snapshot, and replaces the file
atomically (write to a temp file, sync, rename, sync the directory).
Reopening parses the records and cuts off the first frame that runs past the
end, fails its CRC or doesn’t decode; recovered() reports it. open_branch
upgrades an old branch to the kind’s schema as a step of its own.
Tests to read#
features/crdt/crdt/tests/file.rs: every finished step on disk when it returns; a torn write dropped; a tail cut anywhere reopening with the steps before it;kill -9mid-edit; save naming a version and folding; branches forked, opened side by side, merged and deleted; a version restored as a new branch; two handles on one branch; blobs kept once by hash; the kind checked and a branch upgraded; aCrdtDockeeping its replica in a branch.- Unit tests in
features/crdt/crdt/src/file/format.rs: every record round-trips; a corrupt tail is cut at the last whole record. features/crdt/crdt/tests/org_sync.rs: synced documents ending up in placed files.
Status#
Done in architect. Not yet: blobs kept beside the file and their
reachability; an IndexedDB Storage; undo that survives reopening; one
writer per file is assumed (two processes appending would interleave).
- Task does not use it.
- Studio still keeps sessions in its own store
(
studio:crates/daw/session-format/src/store/mod.rs); moving that format ontoDocFileis Studio’s work.
Linked from
- FeaturesOne page per major architect feature: what it is, the least code to use it, a tour of its code, the tests to read, and its status.
- DocumentsCo-edited state as Loro documents: CrdtDoc, kinds of document with schema stamps and migrations, named undo, DocRegistry, crdt entities and the sync hooks.
- Org sync driverOrgSyncs, OrgDocs and Placement: a device keeps every document of each of its orgs synced, offline-safe, in the files the person opens.