https://docs.rs/yrs/0.16.0/yrs/
Docs.rs
* yrs-0.16.0
+ yrs 0.16.0
+ Docs.rs crate page
+ MIT
+ Links
+ Homepage
+ Repository
+ Crates.io
+ Source
+ Owners
+ dmonad
+ Horusiath
+ Dependencies
+
o atomic_refcell ^0.1 normal
o lib0 ^0.16.0 normal
o rand ^0.7.0 normal
o smallstr ^0.2 normal
o smallvec ^1.10 normal
o thiserror ^1 normal
o criterion ^0.3 dev
+ Versions
+
o 0.16.0
o 0.15.0
o 0.14.1
o 0.14.0
o 0.13.0
o 0.12.2
o 0.12.1
o 0.12.0
o 0.11.2
o 0.11.1
o 0.11.0
o 0.10.6
o 0.10.5
o 0.10.4
o 0.10.3
o 0.10.2
o 0.10.1
o 0.10.0
o 0.9.3
o 0.9.2
o 0.9.1
o 0.9.0
o 0.8.1
o 0.8.0
o 0.7.1
o 0.7.0
o 0.6.2
o 0.6.1
o 0.6.0
o 0.5.0
o 0.4.0
o 0.2.1
o 0.2.0
o 0.1.0
o 0.0.1
+ 68.74% of the crate is documented
* Platform
+ x86_64-unknown-linux-gnu
+ x86_64-apple-darwin
+ i686-pc-windows-msvc
+ i686-unknown-linux-gnu
+ x86_64-pc-windows-msvc
* Feature flags
* Rust
+ About docs.rs
+ Privacy policy
+ Rust website
+ The Book
+ Standard Library API Reference
+ Rust by Example
+ The Cargo Guide
+ Clippy Documentation
[ ]
logo
logo
Crate yrs
* Version 0.16.0
* All Items
* Re-exports
* Modules
* Structs
* Enums
* Traits
* Functions
* Type Definitions
[ ]
?
Change settings
Crate yrsCopy item path
source * [-]
Expand description
Yrs (read: "wires") is a high performance CRDT implementation based
on the idea of Shared Types. It is a compatible port of the Yjs CRDT.
Shared Types work just like normal data types, but they automatically
sync with other peers.
A Document is the access point to create shared types, and to listen
to update events.
Quick start
Let's discuss basic features of Yrs. We'll introduce these concepts
starting from the following code snippet:
use yrs::{Doc, GetString, ReadTxn, StateVector, Text, Transact, Update};
use yrs::updates::decoder::Decode;
use yrs::updates::encoder::Encode;
let doc = Doc::new();
let text = doc.get_or_insert_text("article");
{
let mut txn = doc.transact_mut();
text.insert(&mut txn, 0, "hello");
text.insert(&mut txn, 5, " world");
// other rich text operations include formatting or inserting embedded elements
} // transaction is automatically committed when dropped
assert_eq!(text.get_string(&doc.transact()), "hello world".to_owned());
// synchronize state with remote replica
let remote_doc = Doc::new();
let remote_text = remote_doc.get_or_insert_text("article");
let remote_timestamp = remote_doc.transact().state_vector().encode_v1();
// get update with contents not observed by remote_doc
let update = doc.transact().encode_diff_v1(&StateVector::decode_v1(&remote_timestamp).unwrap());
// apply update on remote doc
remote_doc.transact_mut().apply_update(Update::decode_v1(&update).unwrap());
assert_eq!(text.get_string(&doc.transact()), remote_text.get_string(&remote_doc.transact()));
The Doc is a core structure of Yrs. All other structures and
operations are performed in context of their document. All documents
gets randomly generated Doc::client_id (which can be also defined
explicitly), which must be unique per active peer. It's crucial, as
potential concurrent changes made by different peers sharing the same
[ClientID] will cause a document state corruption.
Next line defines TextRef - a shared collection specialized in
providing collaborative rich text operations. Here it has been
defined by calling Doc::get_or_insert_text method. Shared types
defined at the document level are so called root types. Root level
types sharing the same name across different peers are considered to
be replicas of the same logical entity, regardless of their type.
It's highly recommended for all collaborating clients to define all
root level types they are going to use up front, during document
creation. A list of supported shared types include: TextRef, ArrayRef
, MapRef, XmlTextRef, XmlFragmentRef and XmlElementRef.
Next section of code performs an update over the defined text
replica. In Yrs all operations must be executed in a scope of
transaction created by the document there were defined in. We can
differentiate two transaction types:
1. Read-only transactions, created via Doc::transact/
Doc::try_transact. They are used only to access the contents of
an underlying document store but they never alter it. They are
useful for methods like reading the structure state or for
serialization. It's allowed to have multiple active read-only
transactions as long as no read-write transaction is in progress.
2. Read-write transactions, create via Doc::transact_mut/
Doc::try_transact_mut. These can be used to modify the internal
document state. These transactions work as intelligent batches.
They are automatically committed when dropped, performing tasks
like state cleaning, metadata compression and triggering event
callbacks. Read-write transactions require exclusive access to an
underlying document store - no other transaction (neither
read-write nor read-only one) can be active while read-write
transaction is to be created.
In order to synchronize state between the document replicas living on
a different peer processes, there are two possible cases:
1. Peer who wishes to receive an update first encodes its document's
state vector. It's a logical timestamp describing which updates
that has been observed by this document instance so far. This
StateVector can be later serialized and passed to the remote
collaborator. This collaborator can then deserialize it back and
generate an update which will contain all new changes performed
since provided state vector. Finally this update can be passed
back to the requester, deserialized and integrated into a
document store via TransactionMut::apply_update.
2. Another propagation mechanism relies on subscribing to
Doc::observe_update_v1 or Doc::observe_update_v2 events, which
will be fired whenever an referenced document will detect new
changes.
While 2nd option can produce smaller binary payload than the 1st one
at times and doesn't require request-response cycles, it cannot pass
the document state prior the observer callback registration and
expects that all changes will surely be delivered to other peer. A
practical approach (used i.e. by y-sync protocol) is usually a
combination of both variants: use 1st one on connection
initialization between two peers followed by 2nd approach to deliver
subsequent changes.
Formatting and embedding
While the quick start example covered only a simple text insertions,
structures such as TextRef/XmlTextRef are capable of including more
advanced operators, such as adding formatting attributes, inserting
embedded content (eg. image binaries or ArrayRefs that we could
interpret in example as nested tables).
use lib0::any::Any;
use yrs::{Array, ArrayPrelim, Doc, GetString, Text, Transact};
use yrs::types::Attrs;
let doc = Doc::new();
let xml = doc.get_or_insert_xml_text("article");
let mut txn = doc.transact_mut();
let bold = Attrs::from([("b".into(), true.into())]);
let italic = Attrs::from([("i".into(), true.into())]);
xml.insert(&mut txn, 0, "hello ");
xml.insert_with_attributes(&mut txn, 6, "world", italic);
xml.format(&mut txn, 0, 5, bold);
assert_eq!(xml.get_string(&txn), "hello world");
// remove formatting
let remove_italic = Attrs::from([("i".into(), Any::Null)]);
xml.format(&mut txn, 6, 5, remove_italic);
assert_eq!(xml.get_string(&txn), "hello world");
// insert binary payload eg. images
let image = b"deadbeaf".to_vec();
xml.insert_embed(&mut txn, 1, image);
// insert nested shared type eg. table as ArrayRef of ArrayRefs
let table = xml.insert_embed(&mut txn, 5, ArrayPrelim::default());
let header = table.insert(&mut txn, 0, ArrayPrelim::from(["Book title", "Author"]));
let row = table.insert(&mut txn, 1, ArrayPrelim::from(["\"Moby-Dick\"", "Herman Melville"]));
Keep in mind that this kind of special content may not be displayed
using standard methods (TextRef::get_string returns only inserted
text and ignores other content, while XmlTextRef::get_string renders
formatting attributes as XML nodes, but still ignores embedded
values). Reason behind this behavior is that as generic collaboration
library, Yrs cannot make opinionated decisions in this regard -
whenever an full collection of text chunks, formatting attributes and
embedded items is required, use Text::diff instead.
Cursor positioning
Another common problem collaborative text editors is a requirement of
keeping track of cursor position in face of concurrent updates
incoming from remote peers. Let's present the problem on an example:
use yrs::{Doc, GetString, ReadTxn, StateVector, Text, Transact, Update};
use yrs::updates::decoder::Decode;
let doc1 = Doc::with_client_id(1);
let text1 = doc1.get_or_insert_text("article");
let mut txn1 = doc1.transact_mut();
text1.insert(&mut txn1, 0, "hello");
let doc2 = Doc::with_client_id(2);
let text2 = doc2.get_or_insert_text("article");
let mut txn2 = doc2.transact_mut();
text2.insert(&mut txn2, 0, "world");
const INDEX: usize = 1;
// Doc 2: cursor at index 1 points to character 'o'
let str = text2.get_string(&txn2);
assert_eq!(str.chars().nth(INDEX), Some('o'));
// synchronize full state of doc1 -> doc2
txn2.apply_update(Update::decode_v1(&txn1.encode_diff_v1(&StateVector::default())).unwrap());
// Doc 2: cursor at index 1 no longer points to the same character
let str = text2.get_string(&txn2);
assert_ne!(str.chars().nth(INDEX), Some('o'));
Since TransactionMut::apply_update merges updates performed by remote
peer, some of these them may shift the cursor position. However in
such case the old index that we used (1 in the example above) is no
longer valid.
To address these issues, we can make use of StickyIndex struct to
save the permanent location, that will persist between concurrent
updates being made:
use yrs::{Assoc, Doc, GetString, ReadTxn, IndexedSequence, StateVector, Text, Transact, Update};
use yrs::updates::decoder::Decode;
let doc1 = Doc::with_client_id(1);
let text1 = doc1.get_or_insert_text("article");
let mut txn1 = doc1.transact_mut();
text1.insert(&mut txn1, 0, "hello");
let doc2 = Doc::with_client_id(2);
let text2 = doc2.get_or_insert_text("article");
let mut txn2 = doc2.transact_mut();
text2.insert(&mut txn2, 0, "world");
const INDEX: usize = 1;
// Doc 2: cursor at index 1 points to character 'o'
let str = text2.get_string(&txn2);
assert_eq!(str.chars().nth(INDEX), Some('o'));
// get a permanent index for cursor at index 1
let pos = text2.sticky_index(&mut txn2, INDEX as u32, Assoc::After).unwrap();
// synchronize full state of doc1 -> doc2
txn2.apply_update(Update::decode_v1(&txn1.encode_diff_v1(&StateVector::default())).unwrap());
// restore the index from position saved previously
let idx = pos.get_offset(&txn2).unwrap();
let str = text2.get_string(&txn2);
assert_eq!(str.chars().nth(idx.index as usize), Some('o'));
StickyIndex structure is serializable and can be persisted or passed
over the network as well, which may help with tracking and displaying
the cursor location of other peers.
Other shared types
So far we only discussed rich text oriented capabilities of Yrs.
However it's possible to make use of Yrs to represent any tree-like
object:
* ArrayRef can be used to represent any indexable sequence of
values. If there are multiple peers inserting values at the same
position, a [ClientID] will be used to determine a final
deterministic order once all peers get in sync.
* MapRef is a map object (with keys limited to be strings), where
values can be of any given type. If there are multiple peers
updating the same entry concurrently - creating an update
conflict in the result - Yrs will prioritize update belonging to
a peer with higher [ClientID] to make conflict resolution
algorithm deterministic.
* Yrs also provides support fo XML nodes in form of XmlElementRef,
XmlTextRef and XmlFragmentRef.
Underneath all of these types are represented by the same abstract
types::Branch type. Each branch is always capable of working as both
indexed sequence of elements and a map. In practice specialized
shared types are actually a projections over branch type and can be
used interchangeably if needed, i.e.: XmlElementRef can be also
interpreted as MapRef, in which case the collection of that XML node
attributes become key-value entries of casted map's.
Preliminary vs Integrated types
In Yrs core library, every shared type has 2 representations:
* Integrated type (eg. TextRef, ArrayRef, MapRef) represents a
reference that has already been attached to its parent Doc. As
such, its state is tracked as part of that document, it can be
modified concurrently by multiple peers and any conflicts that
occurred due to such actions will be automatically resolved
accordingly to YATA conflict resolution algorithm.
* Preliminary type (eg. TextPrelim, ArrayPrelim, MapPrelim)
represents a content that we want to eventually turn into an
integrated reference, but it has not been integrated yet.
Whenever we want to nest shared types one into another - using
methods such as Array::insert, Map::insert or Text::insert_embed - we
always must do so using preliminary types. These methods will return
an integrated representation of the preliminary content we wished to
integrate.
Keep in mind that we cannot integrate references that have been
already integrated - neither in the same document nor in any other
one. Yjs/Yrs doesn't allow to have the same object to be linked in
multiple places. Same rule concerns primitive types (they will
eventually be serialized and deserialized as unique independent
objects), integrated types or sub-documents.
Transaction event lifecycle
Yrs provides a variety of lifecycle events, which enable users to
react on various situations and changes performed. Some of these
events are used by Yrs own features (eg. UndoManager). They are
always triggered once performed update is committed by dropping or
committing a read-write transaction.
An order in which these updates are fired is as follows:
1. Observers on updated shared types: TextRef::observe,
ArrayRef::observe, MapRef::observe, XmlTextRef::observe,
XmlFragmentRef::observe and XmlElementRef::observe.
2. Deep observers (special kind of observers that are bubbled up
from nested shared types through their parent collections
hierarchy): [TextRef::observe_deep], [ArrayRef::observe_deep],
[MapRef::observe_deep], [XmlTextRef::observe_deep],
[XmlFragmentRef::observe_deep] and [XmlElementRef::observe_deep].
3. After transaction callbacks: Doc::observe_after_transaction.
4. After transaction cleanup callbacks (moment after all changes
performed by transaction have been compressed an integrated into
document store): Doc::observe_transaction_cleanup.
5. Update callbacks: Doc::observe_update_v1 and
Doc::observe_update_v2. Useful when we want to encode and
propagate incremental changes made by transaction to other peers.
6. Sub-document change callbacks: Doc::observe_subdocs.
External learning materials
* A short walkthrough over YATA - a conflict resolution algorithm
used by Yrs/Yjs.
* Deep dive into internal architecture of Yrs.
* Detailed explanation of conflict-free reordering algorithm used
by Yrs.
Re-exports
pub use crate::block::ID;
pub use crate::observer::Observer;
pub use crate::observer::Subscription;
pub use crate::observer::SubscriptionId;
pub use crate::types::array::Array;
pub use crate::types::array::ArrayPrelim;
pub use crate::types::array::ArrayRef;
pub use crate::types::map::Map;
pub use crate::types::map::MapPrelim;
pub use crate::types::map::MapRef;
pub use crate::types::text::Text;
pub use crate::types::text::TextPrelim;
pub use crate::types::text::TextRef;
pub use crate::types::xml::Xml;
pub use crate::types::xml::XmlElementPrelim;
pub use crate::types::xml::XmlElementRef;
pub use crate::types::xml::XmlFragment;
pub use crate::types::xml::XmlFragmentPrelim;
pub use crate::types::xml::XmlFragmentRef;
pub use crate::types::xml::XmlNode;
pub use crate::types::xml::XmlTextPrelim;
pub use crate::types::xml::XmlTextRef;
pub use crate::types::GetString;
pub use crate::types::Observable;
pub use crate::undo::UndoManager;
Modules
atomic
atomic module is a home for AtomicRef cell-like struct, used to
perform thread-safe operations using underlying hardware intristics.
block
observer
types
undo
updates
Structs
DeleteSet
DeleteSet contains information about all blocks (described by clock
ranges) that have been subjected to delete process.
Doc
A Yrs document type. Documents are most important units of
collaborative resources management. All shared collections live
within a scope of their corresponding documents. All updates are
generated on per document basis (rather than individual shared type).
All operations on shared collections happen via Transaction, which
lifetime is also bound to a document.
Offset
Offset is a result of mapping of StickyIndex onto document store at a
current point in time.
Options
Configuration options of Doc instance.
Origin
A binary marker that can be assigned to a read-write transaction upon
creation via Transact::try_transact_mut_with/
Transact::transact_mut_with. It can be used to classify transaction
updates within a specific context, which exists for the duration of a
transaction (it's not persisted in the document store itself), i.e.
you can use unique document client identifiers to differentiate
updates incoming from remote nodes from those performed locally.
RootRefs
Iterator struct used to traverse over all of the root level types
defined in a corresponding Doc.
Snapshot
Snapshot describes a state of a document store at a given point in
(logical) time. In practice it's a combination of StateVector (a
summary of all observed insert/update operations) and a DeleteSet (a
summary of all observed deletions).
StateVector
State vector is a compact representation of all known blocks inserted
and integrated into a given document. This descriptor can be
serialized and used to determine a difference between seen and unseen
inserts of two replicas of the same document, potentially existing in
different processes.
StickyIndex
A sticky index is based on the Yjs model and is not affected by
document changes. E.g. If you place a sticky index before a certain
character, it will always point to this character. If you place a
sticky index at the end of a type, it will always point to the end of
the type.
Store
Store is a core element of a document. It contains all of the
information, like block store map of root types, pending updates
waiting to be applied once a missing update information arrives and
all subscribed callbacks.
SubdocsEvent
Event used to communicate load requests from the underlying
subdocuments.
SubdocsEventIter
Transaction
A very lightweight read-only transaction. These transactions are
guaranteed to not modify the contents of an underlying Doc and can be
used to read it or for serialization purposes. For this reason it's
allowed to have a multiple active read-only transactions, but it's
not allowed to have any active read-write transactions at the same
time.
TransactionCleanupEvent
Holds transaction update information from a commit after state
vectors have been compressed.
TransactionMut
Read-write transaction. It can be used to modify an underlying state
of the corresponding Doc. Read-write transactions require an
exclusive access to document store - only one such transaction can be
present per Doc at the same time (read-only Transactions are not
allowed to coexists at the same time as well).
Update
Update type which contains an information about all decoded blocks
which are incoming from a remote peer. Since these blocks are not yet
integrated into current document's block store, they still may
require repairing before doing so as they don't contain full data
about their relations.
UpdateEvent
An update event passed to a callback subscribed with
Doc::observe_update_v1/Doc::observe_update_v2.
Enums
Assoc
Association type used by StickyIndex. In general StickyIndex refers
to a cursor space between two elements (eg. "ab.c" where "abc" is our
string and . is the StickyIndex placement). However in a situation
when another peer is updating a collection concurrently, a new set of
elements may be inserted into that space, expanding it in the result.
In such case Assoc tells us if the StickyIndex should stick to
location before or after referenced index.
IndexScope
Struct describing context in which StickyIndex is placed. For items
pointing inside of the shared typed sequence it's always
[StickyIndex::Relative] which refers to a block ID found under
corresponding position.
OffsetKind
Determines how string length and offsets of [Text]/[XmlText] are
being determined.
Traits
IndexedSequence
Trait used to retrieve a StickyIndex corresponding to a given
human-readable index. Unlike standard indexes StickyIndex enables to
track the location inside of a shared y-types, even in the face of
concurrent updates.
ReadTxn
Trait defining read capabilities present in a transaction.
Implemented by both lightweight read-only and read-write
transactions.
Transact
Trait implemented by Doc and shared types, used for carrying over the
responsibilities of creating new transactions, used as a unit of work
in Yrs.
WriteTxn
Functions
diff_updates_v1
Givens an input update (encoded using lib0 v1 encoding) of document A
and an encoded state_vector of document B, returns a lib0 v1 encoded
update, that contains all changes from A which have not been observed
by B (based on its state vector).
diff_updates_v2
Givens an input update (encoded using lib0 v2 encoding) of document A
and an encoded state_vector of document B, returns a lib0 v2 encoded
update, that contains all changes from A which have not been observed
by B (based on its state vector).
encode_state_vector_from_update_v1
Decodes a input update (encoded using lib0 v1 encoding) and returns
an encoded StateVector of that update.
encode_state_vector_from_update_v2
Decodes a input update (encoded using lib0 v2 encoding) and returns
an encoded StateVector of that update.
merge_updates_v1
Merges a sequence of updates (encoded using lib0 v1 encoding)
together, producing another update (also lib0 v1 encoded) in the
result. Returned binary is a combination of all input updates,
compressed.
merge_updates_v2
Merges a sequence of updates (encoded using lib0 v2 encoding)
together, producing another update (also lib0 v2 encoded) in the
result. Returned binary is a combination of all input updates,
compressed.
uuid_v4
Generate random v4 UUID. (See: https://www.rfc-editor.org/rfc/rfc4122
#section-4.4)
Type Definitions
DestroySubscription
Subscription type for callbacks registered via Doc::observe_destroy.
SubdocsSubscription
Subscription type for callbacks registered via Doc::observe_subdocs.
TransactionCleanupSubscription
Subscription type for callbacks registered via
Doc::observe_transaction_cleanup.
UpdateSubscription
Subscription type for callbacks registered via Doc::observe_update_v1
and Doc::observe_update_v2.
Uuid