Expand description
§colla
colla provides immutable nested values, canonical recursive changes, and
Operational Transformation primitives. It is the reference implementation for
the data model and binary format shared with the JavaScript colla-ot package.
The crate supports Rust 1.81 or newer.
§Install
[dependencies]
colla = "0.1"§Values
Value is a closed immutable tree containing Null, Bool, Int, Float, String,
Text, RichText, List, and Map values.
use colla::{path, Value};
let value = Value::map([
("title", Value::text("Draft")),
("tags", Value::list([Value::string("ot"), Value::string("rust")])),
])?;
assert_eq!(
value.get(&path!["title"]).and_then(Value::as_text).unwrap().as_str(),
"Draft",
);Ordinary String values are atomic and can only be replaced as a whole. Text
uses Unicode scalar positions and supports character-level OT. A Path is a
Snapshot-relative lookup address; it is not stored in a Change and is not
stable across concurrent sequence edits.
Map constructors reject duplicate keys. Floating-point values must be finite; negative zero is normalized to positive zero.
§Typed Change construction
Rust constructs recursive changes with typed constructors and converts them
with Into<Change>. Construction is independent of a Snapshot. Constructors
normalize zero-length operations, empty inserts, adjacent compatible
operations, insert/delete ordering, and trailing retains. Length or allocation
capacity overflow returns ValueError::LengthOverflow.
use colla::{Change, MapChange, MapEntryChange, TextChange, TextOp};
let title: Change = TextChange::from_ops([
TextOp::Retain(5),
TextOp::Insert(" v2".into()),
])?
.into();
let change: Change = MapChange::from_entries([(
"title",
MapEntryChange::Modify(title),
)])?
.into();
assert!(!change.is_noop());Use:
MapChange::from_entriesfor explicit insert, delete, and recursive modify;ListChange::from_opsfor retain, insert, delete, and element modify;TextChange::from_opsfor retain, insert, and delete;RichTextChange::from_opsfor content operations and attribute formatting;IntChange::Addfor checked integer addition;Change::replacefor atomic replacement, including type changes.
An empty typed change and IntChange::Add(0) convert to Change::noop().
Unmentioned sequence tails are implicit retains.
§Apply, Compose, Invert, and Transform
use colla::{
apply, compose, invert, transform_pair, Change, TextChange, TextOp, TieBreak,
Value,
};
let base = Value::text("ab");
let first: Change = TextChange::from_ops([
TextOp::Retain(1),
TextOp::Insert("x".into()),
])?
.into();
let second: Change = TextChange::from_ops([
TextOp::Retain(2),
TextOp::Insert("y".into()),
])?
.into();
let combined = compose(&first, &second)?;
let after = apply(&base, &combined)?;
let inverse = invert(&combined, &base)?;
assert_eq!(apply(&after, &inverse)?, base);
let concurrent: Change = TextChange::from_ops([
TextOp::Retain(1),
TextOp::Insert("z".into()),
])?
.into();
let (first_prime, concurrent_prime) =
transform_pair(&first, &concurrent, TieBreak::LeftFirst)?;
assert_eq!(
apply(&apply(&base, &first)?, &concurrent_prime)?,
apply(&apply(&base, &concurrent)?, &first_prime)?,
);apply validates Snapshot type, key, and range compatibility. compose
combines sequential changes. invert requires the original Snapshot because a
Change does not carry old values. transform_pair handles two concurrent
changes from one Snapshot and requires a deterministic TieBreak for conflicts.
Colla guarantees TP1 for applicable transformed paths. It does not guarantee TP2; the caller must provide an appropriate control algorithm.
§RichText
RichText is a linear sequence of text and atomic embeds. Text lengths use Unicode scalar values; every embed has length one. Attributes participate in OT through explicit Set and Remove changes.
use colla::{
apply, AttrChange, AttrPatch, AttrValue, Attrs, Change, RichSpan, RichText,
RichTextChange, RichTextOp, Value,
};
let base = Value::rich_text(RichText::from_spans(vec![
RichSpan::text("Hi", Attrs::new()),
RichSpan::embed(Value::map([("id", Value::string("user-1"))])?, Attrs::new()),
])?);
let patch = AttrPatch::from_entries([
("bold", AttrChange::Set(AttrValue::Bool(true))),
])?;
let change: Change = RichTextChange::from_ops([
RichTextOp::Retain { len: 2, attrs: patch },
])?
.into();
let after = apply(&base, &change)?;
assert_eq!(after.as_rich_text().unwrap().len(), 3);RichText::from_spans removes empty text spans and merges adjacent text spans
with equal attributes. Embeds can be inserted, deleted, or formatted as one
unit, but cannot be recursively edited inside RichText. Use a separate Value
location and a stable reference when embed state must collaborate independently.
RichText::code_point_to_utf16 and RichText::utf16_to_code_point explicitly
convert Snapshot positions for JavaScript or editor integration. UTF-16
positions inside surrogate pairs are rejected.
§Canonical codec and input limits
use colla::{InputLimits, Value};
let value = Value::text("hello");
let bytes = value.encode();
assert_eq!(Value::decode(&bytes)?, value);
let limits = InputLimits {
max_string_bytes: 4,
..InputLimits::default()
};
assert!(Value::decode_with_limits(&bytes, &limits).is_err());Value::encode and Change::encode produce the canonical binary body format.
Decode rejects malformed, trailing, excessive, or non-canonical input.
InputLimits are a receiver policy for untrusted input; they do not define the
maximum valid in-memory Value or Change and are not applied to algebra results.
The body format does not include a protocol version, document ID, author, operation identity, compression, or checksum. Applications must provide their own envelope when those fields are required.
§Errors
Construction errors use ValueError. Algebra exposes ApplyError,
ComposeError, InvertError, and TransformError. Codec failures use
CodecError, while explicit UTF-16 conversion uses Utf16PositionError.
Errors are structured and should be matched by variant. Error messages are for humans and are not a stable machine-readable protocol.
§Examples
The crate includes executable examples:
cargo run -p colla --example basic_edit
cargo run -p colla --example collab_demo
cargo run -p colla --example binary_roundtrip§Scope
Colla provides foundational OT values, changes, algebra, and codecs. It does not provide a Document or Session abstraction, history, synchronization, transport, presence, cursors, persistence envelopes, or editor adapters.
§More documentation
Re-exports§
pub use attrs::AttrChange;pub use attrs::AttrPatch;pub use attrs::AttrValue;pub use attrs::Attrs;pub use change::Change;pub use change::ChangeKind;pub use change::IntChange;pub use change::ListChange;pub use change::ListOp;pub use change::MapChange;pub use change::MapEntryChange;pub use change::RichTextChange;pub use change::RichTextOp;pub use change::TextChange;pub use change::TextOp;pub use change::TieBreak;pub use error::ApplyError;pub use error::CodecError;pub use error::ComposeError;pub use error::InvertError;pub use error::TransformError;pub use error::Utf16PositionError;pub use error::ValueError;pub use input_limits::InputLimits;pub use op::apply;pub use op::compose;pub use op::invert;pub use op::transform_pair;pub use path::Path;pub use path::PathSeg;pub use richtext::RichContent;pub use richtext::RichSpan;pub use richtext::RichText;pub use richtext::RichTextChunk;pub use value::FiniteF64;pub use value::List;pub use value::Map;pub use value::Text;pub use value::Value;pub use value::ValueKind;pub use value::ValueType;
Modules§
- attrs
- RichText attribute values, attribute sets, and formatting patches. RichText attribute values, sets, and formatting patches.
- change
- Recursive Change types and typed sequence operations. Canonical recursive Changes and typed Change constructors.
- codec
- Canonical binary encoding and strict decoding. Canonical Value and Change binary encoding.
- error
- Errors returned by construction, algebra, codecs, and coordinate conversion. Structured errors returned by Colla’s public APIs.
- input_
limits - Resource limits applied when decoding untrusted input.
- op
- Apply, Compose, Invert, and Transform operations. Functional entry points for Colla’s OT algebra.
- path
- Snapshot-relative paths used for lookup and error reporting. Snapshot-relative navigation paths.
- richtext
- RichText spans, content, metrics, and coordinate conversion. Canonical RichText spans and coordinate conversion.
- value
- Immutable Value types and their constructors and observers. Immutable Value types and Snapshot lookup.