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