feat(sync): replay durable projections from Rust

req: ms/001

req: ms/002

req: ms/003
This commit is contained in:
slhx agent
2026-07-14 00:17:20 +02:00
parent 2a74a28d3b
commit 3bad745da9
6 changed files with 217 additions and 178 deletions
+41
View File
@@ -417,6 +417,29 @@ impl SyncEffect {
payload: patch.payload(),
}])
}
/// Apply an optimistic projection now and carry the same ordinary batch in
/// the durable patch event so the framework sync runtime can replay it
/// after reload before acknowledgement.
pub fn durable(patch: FlatPatch, projection: EffectBatch) -> Self {
patch.validate().expect("FlatPatch must remain valid");
let projection_wire = projection
.to_wire()
.iter()
.map(u8::to_string)
.collect::<Vec<_>>()
.join(",");
let payload = format!(
r#"{{"patch":{},"projection":[{projection_wire}]}}"#,
patch.payload()
);
let mut ops = projection.ops;
ops.push(Effect::Emit {
name: PATCH_EVENT.to_owned(),
payload,
});
Self(ops)
}
}
impl IntoEffect for SyncEffect {
@@ -429,6 +452,24 @@ impl IntoEffect for SyncEffect {
mod tests {
use super::*;
#[test]
fn durable_patch_carries_and_applies_ordinary_projection_batch() {
let patch =
FlatPatch::for_interaction("cardColumn", PatchValue::String("done".into())).unwrap();
let projection = Effect::Emit {
name: "projected".into(),
payload: "card:1".into(),
}
.into_batch(hemx_core::BuildFingerprint(7));
let batch =
SyncEffect::durable(patch, projection).into_batch(hemx_core::BuildFingerprint(7));
assert_eq!(batch.ops.len(), 2);
assert!(matches!(&batch.ops[0], Effect::Emit { name, .. } if name == "projected"));
assert!(
matches!(&batch.ops[1], Effect::Emit { name, payload } if name == PATCH_EVENT && payload.contains("\"projection\":[") && payload.contains("$hemx-interaction"))
);
}
#[test]
fn acknowledgement_updates_atom_and_emits_queue_signal() {
let atom = Atom::<String>::new(17);