Store documents as Automerge state

This commit is contained in:
Eric Wendland 2026-05-22 14:54:00 +02:00
commit b01da7be4b
9 changed files with 490 additions and 73 deletions

View file

@ -2537,12 +2537,15 @@ async fn document_sync_from_peer(
if let Some(state) = state {
let local = ensure_local_document(&store, name)?;
if state.updated_at.0 >= local.updated_at_ms {
let normalized = geth_document::normalize_document_state(&state.state_json)?;
let merged = geth_document::merge_automerge_states(
&local.state_json,
&state.state_json,
)?;
store.insert_document_resource(&StoredDocumentResource {
document_id: local.document_id,
resource_id: local.resource_id,
name: local.name,
state_json: normalized,
state_json: merged,
updated_at_ms: state.updated_at.0,
})?;
updated = true;
@ -5242,7 +5245,7 @@ async fn handle_iroh_control_connection(
bearer_proof.as_ref(),
)?;
let state = if explanation.allowed && document.updated_at_ms >= since_ms {
Some(document_state_from_stored(&document))
Some(document_sync_state_from_stored(&document)?)
} else {
None
};
@ -5258,7 +5261,7 @@ async fn handle_iroh_control_connection(
reason: explanation.reason,
evaluated_ops: explanation.evaluated_ops,
nonce,
note: "document sync authenticated endpoint/card binding and required document.read on the remote document resource; JSON LWW sync is a bootstrap before Automerge".to_owned(),
note: "document sync authenticated endpoint/card binding and required document.read on the remote document resource; durable Automerge state is exchanged with a JSON view for CLI output".to_owned(),
}
} else {
PeerControlResponse::Error {
@ -7430,7 +7433,7 @@ pub fn handle_request(
document_id,
resource_id,
name,
state_json: "{}".to_owned(),
state_json: geth_document::create_automerge_state("{}")?,
updated_at_ms: geth_store::now_ms(),
};
store.insert_document_resource(&stored)?;
@ -7454,11 +7457,11 @@ pub fn handle_request(
let mut stored = store
.get_document_resource_by_name(&name)?
.ok_or_else(|| NodeError::DocumentNotFound(name.clone()))?;
stored.state_json = geth_document::normalize_document_state(&state_json)?;
stored.state_json = geth_document::create_automerge_state(&state_json)?;
stored.updated_at_ms = geth_store::now_ms();
store.insert_document_resource(&stored)?;
Ok(ControlResponse::DocumentSet {
state: document_state_from_stored(&stored),
state: document_state_from_stored(&stored)?,
})
}
ControlRequest::DocumentGet { name } => {
@ -7468,7 +7471,7 @@ pub fn handle_request(
.get_document_resource_by_name(&name)?
.ok_or_else(|| NodeError::DocumentNotFound(name.clone()))?;
Ok(ControlResponse::DocumentGet {
state: document_state_from_stored(&stored),
state: document_state_from_stored(&stored)?,
})
}
ControlRequest::PubsubPub {
@ -7820,7 +7823,7 @@ fn ensure_local_document(store: &Store, name: &str) -> Result<StoredDocumentReso
document_id: format!("document:{name}"),
resource_id: format!("resource:document:{name}"),
name: name.to_owned(),
state_json: "{}".to_owned(),
state_json: geth_document::create_automerge_state("{}")?,
updated_at_ms: 0,
};
store.insert_document_resource(&stored)?;
@ -7832,17 +7835,27 @@ fn document_resource_from_stored(stored: &StoredDocumentResource) -> DocumentRes
id: stored.document_id.clone().into(),
resource: stored.resource_id.clone().into(),
name: stored.name.clone(),
sync_status: "local-only".to_owned(),
state_bytes: stored.state_json.len() as u64,
sync_status: "automerge-local".to_owned(),
state_bytes: geth_document::document_state_bytes(&stored.state_json),
}
}
fn document_state_from_stored(stored: &StoredDocumentResource) -> DocumentState {
DocumentState {
fn document_state_from_stored(stored: &StoredDocumentResource) -> Result<DocumentState, NodeError> {
Ok(DocumentState {
document: document_resource_from_stored(stored),
state_json: geth_document::document_view_json(&stored.state_json)?,
updated_at: UnixMillis(stored.updated_at_ms),
})
}
fn document_sync_state_from_stored(
stored: &StoredDocumentResource,
) -> Result<DocumentState, NodeError> {
Ok(DocumentState {
document: document_resource_from_stored(stored),
state_json: stored.state_json.clone(),
updated_at: UnixMillis(stored.updated_at_ms),
}
})
}
fn file_root_from_stored(stored: &StoredFileRoot) -> FileRoot {
@ -11528,7 +11541,7 @@ mod tests {
assert!(allowed);
assert!(updated);
assert!(reason.contains("direct grant"));
assert!(note.contains("JSON LWW"));
assert!(note.contains("durable Automerge"));
}
other => panic!("unexpected allowed document sync response: {other:?}"),
}
@ -11804,7 +11817,10 @@ mod tests {
left_store
.get_document_resource_by_name("notes")
.expect("get live-synced document")
.map(|document| document.state_json),
.map(|document| {
geth_document::document_view_json(&document.state_json)
.expect("document view json")
}),
Some(r#"{"title":"live"}"#.to_owned())
);
let live_remote_root = left_store