Crate yrs

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()));

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. In snippet above 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 they were defined in. We can differentiate two transaction types:

  1. Read-only transactions, created via Transact::transact/Transact::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 Transact::transact_mut/Transact::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 the 2nd option can produce smaller binary payload than 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 yrs::{Any, Array, ArrayPrelim, Doc, GetString, Text, Transact, WriteTxn, XmlFragment, XmlTextPrelim};
use yrs::types::Attrs;

let doc = Doc::new();
let mut txn = doc.transact_mut();
let f = txn.get_or_insert_xml_fragment("article");
let xml = f.insert(&mut txn, 0, XmlTextPrelim::new(""));

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), "<b>hello</b> <i>world</i>");

// 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), "<b>hello</b> 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 a full collection of text chunks, formatting attributes or embedded items is required, use Text::diff instead.

§Cursor positioning

Another common problem of collaborative text editors is a requirement of keeping track of cursor position in the face of concurrent updates incoming from remote peers. Let’s present the problem on following 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.

This functionality requires a “weak” feature flag to be turned on:

yrs = { version = "0.17", features = ["weak"] }

Yrs document structure can be represented as a tree of elements. That means that usually a node can have only one parent and cannot be referenced by any other node. This can be changed by usage of weak links - they offer you a way to reference to values existing in other parts of the document (also other collections):

  • Map elements can be references via [Map::link] method.
  • Other collections like text and arrays, can quote entire ranges of values via [Quotable::quote] method.

Both of these methods return a [WeakPrelim] struct can be integrated as an input value in other collections and convert into [WeakRef] shared type.

use yrs::{Doc, Text, Transact, GetString, Quotable, Map};

let doc = Doc::new();
let text = doc.get_or_insert_text("text");
let map = doc.get_or_insert_map("map");
let mut txn = doc.transact_mut();
text.insert(&mut txn, 0, "hello!");
let quote = text.quote(&txn, 0..5).unwrap();
let quote = map.insert(&mut txn, "title", quote);

// retrieve quoted text fragment
assert_eq!(quote.get_string(&txn), "hello".to_string());

// quotations are actively reacting to changes happening at source within quoted range
text.insert(&mut txn, 5, " world");
// since quoted range 0..5 was right-side exclusive, index 5 itself is not included in range,
// but inserts between position 4 and 5 are
assert_eq!(quote.get_string(&txn), "hello world".to_string());

Weak refs also expose observer API that allows to subscribe to changes happening in source collections within quoted range.

Keep in mind that weak refs don’t maintain ownership over quoted elements. If a source collection removes a quoted element, it will no longer be accessible from weak ref:

use yrs::{Doc, Transact, Quotable, Map};

let doc = Doc::new();
let map = doc.get_or_insert_map("map");
let mut txn = doc.transact_mut();
map.insert(&mut txn, "origin", "value");
// establish a link 'origin' entry
let link = map.link(&txn, "origin").unwrap();
let link = map.insert(&mut txn, "link", link);
let linked_value: String = link.try_deref(&txn).unwrap();
assert_eq!(linked_value, "value".to_string());

// remove original value
map.remove(&mut txn, "origin");
let linked_value = link.try_deref_value(&txn);
assert_eq!(linked_value, None); // linked value is no longer accessible

§Undo/redo

Among very popular features of many user-facing applications is an ability to revert/reapply operations performed by user. This becomes even more complicated, once we consider multiple peers collaborating on the same document, as we may need to skip over the changes synchronized from remote peers - even thou they could have happened later - in order to only undo our own actions. UndoManager is a Yrs response for these needs, supporting wide variety of options:

use yrs::{Doc, GetString, ReadTxn, Text, Transact, UndoManager, Update};
use yrs::undo::Options;
use yrs::updates::decoder::Decode;

let local = Doc::with_client_id(123);
let text1 = local.get_or_insert_text("article");
let mut mgr = UndoManager::with_options(&local, &text1, Options::default());
mgr.include_origin(local.client_id()); // only track changes originating from local peer

let remote = Doc::with_client_id(321);
let text2 = remote.get_or_insert_text("article");

// perform changes locally
text1.push(&mut local.transact_mut_with(local.client_id()), "hello ");
mgr.reset(); // prevent previous and next operation to be treated by Undo manager as one batch
text1.push(&mut local.transact_mut_with(local.client_id()), "world");
assert_eq!(text1.get_string(&local.transact()), "hello world");

// perform remote changes - these are not being tracked by mgr
{
    let mut remote_txn = remote.transact_mut_with(remote.client_id());
    text2.push(&mut remote_txn, "everyone");
    assert_eq!(text2.get_string(&remote_txn), "everyone");
}

// sync changes from remote to local
let update = remote.transact().encode_state_as_update_v1(&local.transact().state_vector());
local.transact_mut().apply_update(Update::decode_v1(&update).unwrap());
assert_eq!(text1.get_string(&local.transact()), "hello worldeveryone"); // remote changes synced

// undo last performed change on local
mgr.undo().unwrap();
assert_eq!(text1.get_string(&local.transact()), "hello everyone");

// redo change we undone
mgr.redo().unwrap();
assert_eq!(text1.get_string(&local.transact()), "hello worldeveryone");

Keep in mind, that in order to serve its purpose, undo manager may need to implicitly create transactions over underlying Doc - for that reason make sure that no other transaction is active while calling methods like UndoManager::undo or UndoManager::redo.

It’s important to understand a context, in which UndoManager operates:

  • origins can be specific classifiers attached to read-write transactions upon creation (see: Transact::transact_mut_with). Undo manager can include any number of origins to its track scope. By default no origin is specified: in such case undo manager will track all changes without looking at transaction origin.
  • scope refers to shared collection references such as TextRef, MapRef etc. Undo manager will only track operations performed on tracked scope refs. By default at least one such reference must be specified, but they can be expanded to include multiple collections at once if necessary.

Notice, that undo/redo changes are not mapped 1-1 on the update batches committed by transactions. As an example: a frequent case includes establishing a new transaction for every user key stroke. Meanwhile we may decide to use different granularity of undo/redo actions. These are grouped together on time-based ranges (configurable in undo::Options, which is 500ms by default). You can also set them explicitly by calling UndoManager::reset method.

§Showing past revisions of the document

Another feature of Yrs is an ability to snapshot and recover versions of the document, as well as show the differences between them:

use yrs::{Doc, GetString, Options, ReadTxn, Text, Transact, Update, WriteTxn, XmlFragment, XmlTextPrelim};
use yrs::types::Attrs;
use yrs::types::text::{Diff, YChange};
use yrs::updates::decoder::Decode;
use yrs::updates::encoder::{Encoder, EncoderV1};

let doc = Doc::with_options(Options {
    skip_gc: true,  // in order to support revisions we cannot garbage collect deleted blocks
    ..Options::default()
});
let mut txn = doc.transact_mut();
let f = txn.get_or_insert_xml_fragment("article");
let text = f.insert(&mut txn, 0, XmlTextPrelim::new(""));

const INIT: &str = "hello world";
text.push(&mut txn, INIT);

// save current state of the document: "hello world"
let rev = txn.snapshot();

// change document state
let italic = Attrs::from([("i".into(), true.into())]);
text.format(&mut txn, 6, 5, italic.clone());
text.remove_range(&mut txn, 2, 3);
assert_eq!(text.get_string(&txn), "he <i>world</i>");

// encode past version of the document
let mut encoder = EncoderV1::new();
txn.encode_state_from_snapshot(&rev, &mut encoder).unwrap();
let update = encoder.to_vec();

// restore the past state
let doc = Doc::new();
let mut txn = doc.transact_mut();
let f = txn.get_or_insert_xml_fragment("article");
txn.apply_update(Update::decode_v1(&update).unwrap());
let text = f.get(&mut txn, 0).unwrap().into_xml_text().unwrap();

assert_eq!(text.get_string(&txn), INIT);

Keep in mind that an update created from past snapshot via TransactionMut::encode_state_from_snapshot doesn’t contain updates that happened after that snapshot. What does that mean? While you can continue making new updates on top of that revision, they will be no longer compatible with any changes made on the original document since the snapshot has been made, therefore TransactionMut::apply_update on updates generated between two document revisions that branched their state is no longer possible.

For the reason above main use case of this feature is rendering read-only state of the Doc or restoring document from its past state ie. when current state has been irrecoverably corrupted.

§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 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 reinterpreted 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.

§Shared collection hooks

While type like TextRef, MapRef etc. refer to addresses of objects living in memory, sometime you’d like to be able to uniquely identify the same collection across its different replicas living on other peers. This is possible via hooks:

use yrs::{Array, ArrayRef, Doc, Hook, MapPrelim, ReadTxn, RootRef, SharedRef, Transact, Update};
use yrs::types::ToJson;
use yrs::updates::decoder::Decode;

// create a logical identifier to a root type
let root = ArrayRef::root("root");

let local = Doc::with_client_id(1);
let local_array = root.get_or_create(&mut local.transact_mut());
assert_eq!(local_array.hook(), Hook::from(root.clone())); // another way to get hook for existing type

let local_map = local_array.push_back(&mut local.transact_mut(), MapPrelim::from([("key", "old")]));
let nested = local_map.hook(); // logical identifier to a nested shared type

let remote = Doc::with_client_id(2);
let remote_array = root.get_or_create(&mut local.transact_mut());
// we haven't synchronized yet, so nested element doesn't exist on remote
assert!(nested.get(&remote.transact()).is_none());

// synchronize the changes
{
    let mut txn = remote.transact_mut();
    let update = local.transact().encode_state_as_update_v1(&txn.state_vector());
    txn.apply_update(Update::decode_v1(&update).unwrap());
}

// after synchronizing, we can now instantiate instance of the same logical type
let remote_map = nested.get(&remote.transact()).unwrap();
assert_eq!(local_map.hook(), remote_map.hook());
assert_eq!(local_map.to_json(&local.transact()), remote_map.to_json(&remote.transact()));

Logical references are serializable. They also can help to avoid segfaults in cases when we hold an unsafe reference to a nested collection that has been already deleted by concurrent operation.

§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 (e.g. 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).
  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.

§Update encoding v1 vs. v2

Yrs ships with so called lib0 encoding, which offers two different variants, both of which are compatible with Yjs and can be used to bridge between applications written in different languages using Yrs underneath. They offer a highly compact format, that aims to produce small binary payload size and high encoding/decoding speed.

V1 encoding is the default one and should be preferred most of the time. V2 encoding aims to perform payload size optimizations whenever multiple updates are being encoded and passed together (e.g. when you want to serialize an entire document state), however for small updates (e.g. sending individual user keystrokes) V2 may turn out to be less optimal than its V1 equivalent.

§Awareness and y-sync protocol

Sometimes it’s not always feasible to keep all state changes related to a document within a document update history - especially if they are notifications bound to a single user or session. For this reason most of the editor bindings using Yjs supplement the document state with sync::Awareness - it’s a separate entity that keeps user related data such as username or cursor position, but without the overhead of keeping metadata necessary for conflict resolution.

sync::Awareness together with Doc updates combined create basis of y-sync sync::Protocol. It’s used by most of the network providers in Yjs/Yrs ecosystem and enables universal way of communicating between different systems.

In its core y-sync protocol can operate as a simple state machine that serves exchanging and responding to different message types described by the protocol. The sync::DefaultProtocol provides all the message handlers necessary to make basic communication possible.

y-sync protocol is extensible leaves a space for the users to define their own messages if necessary:

use yrs::sync::{Awareness, Message, Protocol, Error};

struct MyProtocol;
impl Protocol for MyProtocol {
    fn missing_handle(&self, awareness: &mut Awareness, tag: u8, data: Vec<u8>) -> Result<Option<Message>, Error> {
        // you can not only override existing message handlers but also define your own
        Ok(Some(Message::Custom(tag, data))) // echo
    }
}

§External learning materials

Re-exports§

Modules§

Macros§

  • Construct a lib0 Any value literal.

Structs§

  • DeleteSet contains information about all blocks (described by clock ranges) that have been subjected to delete process.
  • Offset is a result of mapping of StickyIndex onto document store at a current point in time.
  • 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.
  • Iterator struct used to traverse over all of the root level types defined in a corresponding Doc.
  • 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).
  • 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.
  • 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 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.
  • Event used to communicate load requests from the underlying subdocuments.
  • 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.
  • Holds transaction update information from a commit after state vectors have been compressed.
  • 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 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.
  • An update event passed to a callback subscribed with Doc::observe_update_v1/Doc::observe_update_v2.

Enums§

  • 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.
  • 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.

Traits§

  • 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.
  • Trait defining read capabilities present in a transaction. Implemented by both lightweight read-only and read-write transactions.

Functions§

  • 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).
  • 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).
  • Decodes a input update (encoded using lib0 v1 encoding) and returns an encoded StateVector of that update.
  • Decodes a input update (encoded using lib0 v2 encoding) and returns an encoded StateVector of that update.
  • 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.
  • 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.
  • Generate random v4 UUID. (See: https://www.rfc-editor.org/rfc/rfc4122#section-4.4)
  • Generate random v4 UUID. (See: https://www.rfc-editor.org/rfc/rfc4122#section-4.4)

Type Aliases§