# lorosurgeon
[](https://crates.io/crates/lorosurgeon)
[](https://docs.rs/lorosurgeon)
[](https://github.com/rlch/lorosurgeon/actions/workflows/ci.yml)
[](LICENSE)
Derive macros for bidirectional serialization between Rust types and [Loro](https://loro.dev) CRDT containers — the Loro equivalent of [autosurgeon](https://github.com/automerge/autosurgeon) for Automerge.
`#[derive(Hydrate, Reconcile)]` generates field-level mapping between Rust types and Loro containers. Only modified fields produce CRDT operations.
## Quick Start
```rust
use loro::LoroDoc;
use lorosurgeon::{Hydrate, Reconcile, DocSync};
#[derive(Debug, PartialEq, Hydrate, Reconcile)]
#[loro(root = "config")]
struct Config {
name: String,
version: i64,
position: Position,
}
#[derive(Debug, PartialEq, Hydrate, Reconcile)]
struct Position { x: f64, y: f64 }
let doc = LoroDoc::new();
let config = Config {
name: "hello".into(),
version: 1,
position: Position { x: 10.0, y: 20.0 },
};
config.to_doc(&doc).unwrap(); // Rust → Loro
doc.commit();
let loaded = Config::from_doc(&doc).unwrap(); // Loro → Rust
assert_eq!(loaded, config);
```
## Documentation
**[Full API documentation on docs.rs →](https://docs.rs/lorosurgeon)**
The crate docs include type mapping tables, attribute reference, examples for concurrent editing, custom serialization, flatten, keyed list diffing, and more.
## Features
- **Structs** → `LoroMap` (fields become keys)
- **Enums** → `LoroMap` with variant discriminator, unit variants as strings
- **`Vec<T>`** → `LoroList` with Myers LCS diffing
- **`#[loro(movable)]`** → `LoroMovableList` with identity-preserving `mov()`/`set()`
- **`HashMap<String, V>`** → `LoroMap` with stale-key cleanup
- **`#[loro(text)]`** → `LoroText` with character-level diffing (on `String` fields)
- **No-op detection** — identical values produce zero CRDT operations
- **Concurrent merge** — field-level granularity means independent edits compose
## Attributes
```rust
// Container-level
#[loro(root = "key")] // DocSync: to_doc() / from_doc()
// Field-level
#[key] // Identity key for movable list diffing
#[loro(rename = "name")] // Different key in Loro
#[loro(json)] // serde_json round-trip
#[loro(text)] // LoroText with character-level LCS
#[loro(movable)] // LoroMovableList instead of LoroList
#[loro(default)] // Default::default() when absent
#[loro(default = "fn")] // Custom default function
#[loro(flatten)] // Inline nested struct fields
#[loro(with = "module")] // Custom hydrate + reconcile
#[loro(hydrate = "fn")] // Custom hydrate only
#[loro(reconcile = "fn")] // Custom reconcile only
```
## License
MIT