oficina / docs Public

Clone
git clone https://amadan.net/oficina/docs

Plain git — no account needed to clone.

Download

Download this file

1#![doc(
2 html_logo_url = "https://raw.githubusercontent.com/y-crdt/y-crdt/main/logo-yrs.svg",
3 html_favicon_url = "https://raw.githubusercontent.com/y-crdt/y-crdt/main/logo-yrs.svg"
4)]
5
6//! Yrs (read: "wires") is a high performance CRDT implementation based on the idea of **Shared Types**.
7//! It is a compatible port of the [Yjs](https://github.com/yjs/yjs) CRDT.
8//!
9//! **Shared Types** work just like normal data types, but they automatically sync with other peers.
10//!
11//! A **Document** is the access point to create shared types, and to listen to update events.
12//!
13//! # Features
14//!
15//! - `weak` this feature enables weak references and quotations (see: [crate::WeakRef]).
16//! - `sync` this feature modifies observers callback constraints to use `Send` and `Sync` traits.
17//! These are required when using yrs features in multithreaded environments.
18//!
19//! # Quick start
20//!
21//! Let's discuss basic features of Yrs. We'll introduce these concepts starting from
22//! the following code snippet:
23//!
24//! ```rust
25//! use yrs::{Doc, GetString, ReadTxn, StateVector, Text, Transact, Update};
26//! use yrs::updates::decoder::Decode;
27//! use yrs::updates::encoder::Encode;
28//!
29//! let doc = Doc::new();
30//! let text = doc.get_or_insert_text("article");
31//!
32//! {
33//! let mut txn = doc.transact_mut();
34//! text.insert(&mut txn, 0, "hello");
35//! text.insert(&mut txn, 5, " world");
36//! // other rich text operations include formatting or inserting embedded elements
37//! } // transaction is automatically committed when dropped
38//!
39//! assert_eq!(text.get_string(&doc.transact()), "hello world".to_owned());
40//!
41//! // synchronize state with remote replica
42//! let remote_doc = Doc::new();
43//! let remote_text = remote_doc.get_or_insert_text("article");
44//! let remote_timestamp = remote_doc.transact().state_vector().encode_v1();
45//!
46//! // get update with contents not observed by remote_doc
47//! let update = doc.transact().encode_diff_v1(&StateVector::decode_v1(&remote_timestamp).unwrap());
48//! // apply update on remote doc
49//! remote_doc.transact_mut().apply_update(Update::decode_v1(&update).unwrap());
50//!
51//! assert_eq!(text.get_string(&doc.transact()), remote_text.get_string(&remote_doc.transact()));
52//! ```
53//!
54//! [Doc] is a core structure of Yrs. All other structures and operations are performed in
55//! context of their document. All documents gets randomly generated [Doc::client_id] (which can be
56//! also defined explicitly), which must be unique per active peer. It's crucial, as potential
57//! concurrent changes made by different peers sharing the same [ClientID] will cause a document
58//! state corruption.
59//!
60//! Next line defines [TextRef] - a shared collection specialized in providing collaborative rich text
61//! operations. In snippet above it has been defined by calling [Doc::get_or_insert_text] method.
62//! Shared types defined at the document level are so called root types. Root level types sharing
63//! the same name across different peers are considered to be replicas of the same logical entity,
64//! regardless of their type. It's highly recommended for all collaborating clients to define all
65//! root level types they are going to use up front, during document creation. A list of supported
66//! shared types include: [TextRef], [ArrayRef], [MapRef], [XmlTextRef], [XmlFragmentRef] and [XmlElementRef].
67//!
68//! Next section of code performs an update over the defined text replica. In Yrs all operations
69//! must be executed in a scope of transaction created by the document they were defined in. We
70//! can differentiate two transaction types:
71//!
72//! 1. [Read-only transactions](Transaction), created via [Transact::transact]/[Transact::try_transact].
73//! They are used only to access the contents of an underlying document store but they never
74//! alter it. They are useful for methods like reading the structure state or for serialization.
75//! It's allowed to have multiple active read-only transactions as long as no read-write
76//! transaction is in progress.
77//! 2. [Read-write transactions](TransactionMut), create via [Transact::transact_mut]/[Transact::try_transact_mut].
78//! These can be used to modify the internal document state. These transactions work as
79//! intelligent batches. They are automatically committed when dropped, performing tasks like
80//! state cleaning, metadata compression and triggering event callbacks. Read-write transactions
81//! require exclusive access to an underlying document store - no other transaction (neither
82//! read-write nor read-only one) can be active while read-write transaction is to be created.
83//!
84//! In order to synchronize state between the document replicas living on a different peer processes,
85//! there are two possible cases:
86//!
87//! 1. Peer who wishes to receive an update first encodes its document's [state vector](ReadTxn::state_vector).
88//! It's a logical timestamp describing which updates that has been observed by this document instance
89//! so far. This [StateVector] can be later serialized and passed to the remote collaborator. This
90//! collaborator can then deserialize it back and [generate an update](ReadTxn::encode_diff) which
91//! will contain all new changes performed since provided state vector. Finally this update can
92//! be passed back to the requester, [deserialized](Update::decode) and integrated into a document
93//! store via [TransactionMut::apply_update].
94//! 2. Another propagation mechanism relies on subscribing to [Doc::observe_update_v1] or
95//! [Doc::observe_update_v2] events, which will be fired whenever an referenced document will
96//! detect new changes.
97//!
98//! While the 2nd option can produce smaller binary payload than 1st one at times and doesn't require
99//! request-response cycles, it cannot pass the document state prior the observer callback
100//! registration and expects that all changes will surely be delivered to other peer. A practical
101//! approach (used i.e. by [y-sync protocol](https://crates.io/crates/y-sync)) is usually a combination
102//! of both variants: use 1st one on connection initialization between two peers followed by
103//! 2nd approach to deliver subsequent changes.
104//!
105//! # Formatting and embedding
106//!
107//! While the quick start example covered only a simple text insertions, structures such as
108//! [TextRef]/[XmlTextRef] are capable of including more advanced operators, such as adding
109//! [formatting attributes](Text::format), [inserting embedded content](Text::insert_embed)
110//! (eg. image binaries or [ArrayRef]s that we could interpret in example as nested tables).
111//!
112//! ```rust
113//! use yrs::{Any, Array, ArrayPrelim, Doc, GetString, Text, Transact, WriteTxn, XmlFragment, XmlTextPrelim};
114//! use yrs::types::Attrs;
115//!
116//! let doc = Doc::new();
117//! let mut txn = doc.transact_mut();
118//! let f = txn.get_or_insert_xml_fragment("article");
119//! let xml = f.insert(&mut txn, 0, XmlTextPrelim::new(""));
120//!
121//! let bold = Attrs::from([("b".into(), true.into())]);
122//! let italic = Attrs::from([("i".into(), true.into())]);
123//!
124//! xml.insert(&mut txn, 0, "hello ");
125//! xml.insert_with_attributes(&mut txn, 6, "world", italic);
126//! xml.format(&mut txn, 0, 5, bold);
127//!
128//! assert_eq!(xml.get_string(&txn), "<b>hello</b> <i>world</i>");
129//!
130//! // remove formatting
131//! let remove_italic = Attrs::from([("i".into(), Any::Null)]);
132//! xml.format(&mut txn, 6, 5, remove_italic);
133//!
134//! assert_eq!(xml.get_string(&txn), "<b>hello</b> world");
135//!
136//! // insert binary payload eg. images
137//! let image = b"deadbeaf".to_vec();
138//! xml.insert_embed(&mut txn, 1, image);
139//!
140//! // insert nested shared type eg. table as ArrayRef of ArrayRefs
141//! let table = xml.insert_embed(&mut txn, 5, ArrayPrelim::default());
142//! let header = table.insert(&mut txn, 0, ArrayPrelim::from(["Book title", "Author"]));
143//! let row = table.insert(&mut txn, 1, ArrayPrelim::from(["\"Moby-Dick\"", "Herman Melville"]));
144//! ```
145//!
146//! Keep in mind that this kind of special content may not be displayed using standard methods
147//! ([TextRef::get_string] returns only inserted text and ignores other content, while
148//! [XmlTextRef::get_string] renders formatting attributes as XML nodes, but still ignores embedded
149//! values). Reason behind this behavior is that as generic collaboration library, Yrs cannot make
150//! opinionated decisions in this regard - whenever a full collection of text chunks, formatting
151//! attributes or embedded items is required, use [Text::diff] instead.
152//!
153//! # Cursor positioning
154//!
155//! Another common problem of collaborative text editors is a requirement of keeping track of cursor
156//! position in the face of concurrent updates incoming from remote peers. Let's present the problem
157//! on following example:
158//!
159//! ```rust
160//! use yrs::{Doc, GetString, ReadTxn, StateVector, Text, Transact, Update};
161//! use yrs::updates::decoder::Decode;
162//!
163//! let doc1 = Doc::with_client_id(1);
164//! let text1 = doc1.get_or_insert_text("article");
165//! let mut txn1 = doc1.transact_mut();
166//! text1.insert(&mut txn1, 0, "hello");
167//!
168//! let doc2 = Doc::with_client_id(2);
169//! let text2 = doc2.get_or_insert_text("article");
170//! let mut txn2 = doc2.transact_mut();
171//! text2.insert(&mut txn2, 0, "world");
172//!
173//! const INDEX: usize = 1;
174//!
175//! // Doc 2: cursor at index 1 points to character 'o'
176//! let str = text2.get_string(&txn2);
177//! assert_eq!(str.chars().nth(INDEX), Some('o'));
178//!
179//! // synchronize full state of doc1 -> doc2
180//! txn2.apply_update(Update::decode_v1(&txn1.encode_diff_v1(&StateVector::default())).unwrap());
181//!
182//! // Doc 2: cursor at index 1 no longer points to the same character
183//! let str = text2.get_string(&txn2);
184//! assert_ne!(str.chars().nth(INDEX), Some('o'));
185//! ```
186//!
187//! Since [TransactionMut::apply_update] merges updates performed by remote peer, some of these
188//! them may shift the cursor position. However in such case the old index that we used (`1` in the
189//! example above) is no longer valid.
190//!
191//! To address these issues, we can make use of [StickyIndex] struct to save the permanent
192//! location, that will persist between concurrent updates being made:
193//!
194//! ```rust
195//! use yrs::{Assoc, Doc, GetString, ReadTxn, IndexedSequence, StateVector, Text, Transact, Update};
196//! use yrs::updates::decoder::Decode;
197//!
198//! let doc1 = Doc::with_client_id(1);
199//! let text1 = doc1.get_or_insert_text("article");
200//! let mut txn1 = doc1.transact_mut();
201//! text1.insert(&mut txn1, 0, "hello");
202//!
203//! let doc2 = Doc::with_client_id(2);
204//! let text2 = doc2.get_or_insert_text("article");
205//! let mut txn2 = doc2.transact_mut();
206//! text2.insert(&mut txn2, 0, "world");
207//!
208//! const INDEX: usize = 1;
209//!
210//! // Doc 2: cursor at index 1 points to character 'o'
211//! let str = text2.get_string(&txn2);
212//! assert_eq!(str.chars().nth(INDEX), Some('o'));
213//!
214//! // get a permanent index for cursor at index 1
215//! let pos = text2.sticky_index(&mut txn2, INDEX as u32, Assoc::After).unwrap();
216//!
217//! // synchronize full state of doc1 -> doc2
218//! txn2.apply_update(Update::decode_v1(&txn1.encode_diff_v1(&StateVector::default())).unwrap());
219//!
220//! // restore the index from position saved previously
221//! let idx = pos.get_offset(&txn2).unwrap();
222//! let str = text2.get_string(&txn2);
223//! assert_eq!(str.chars().nth(idx.index as usize), Some('o'));
224//! ```
225//!
226//! [StickyIndex] structure is serializable and can be persisted or passed over the network as
227//! well, which may help with tracking and displaying the cursor location of other peers.
228//!
229//! # Weak links and quotations
230//!
231//! This functionality requires a "weak" feature flag to be turned on:
232//! ```toml
233//! yrs = { version = "0.17", features = ["weak"] }
234//! ```
235//!
236//! Yrs document structure can be represented as a tree of elements. That means that usually a node
237//! can have only one parent and cannot be referenced by any other node. This can be changed by
238//! usage of weak links - they offer you a way to reference to values existing in other parts of
239//! the document (also other collections):
240//!
241//! - [Map] elements can be references via [Map::link] method.
242//! - Other collections like text and arrays, can quote entire ranges of values via [Quotable::quote]
243//! method.
244//!
245//! Both of these methods return a [WeakPrelim] struct can be integrated as an input value in other
246//! collections and convert into [WeakRef] shared type.
247//!
248//! ```rust
249//! use yrs::{Doc, Text, Transact, GetString, Quotable, Map};
250//!
251//! let doc = Doc::new();
252//! let text = doc.get_or_insert_text("text");
253//! let map = doc.get_or_insert_map("map");
254//! let mut txn = doc.transact_mut();
255//! text.insert(&mut txn, 0, "hello!");
256//! let quote = text.quote(&txn, 0..5).unwrap();
257//! let quote = map.insert(&mut txn, "title", quote);
258//!
259//! // retrieve quoted text fragment
260//! assert_eq!(quote.get_string(&txn), "hello".to_string());
261//!
262//! // quotations are actively reacting to changes happening at source within quoted range
263//! text.insert(&mut txn, 5, " world");
264//! // since quoted range 0..5 was right-side exclusive, index 5 itself is not included in range,
265//! // but inserts between position 4 and 5 are
266//! assert_eq!(quote.get_string(&txn), "hello world".to_string());
267//! ```
268//!
269//! Weak refs also expose observer API that allows to subscribe to changes happening in source
270//! collections within quoted range.
271//!
272//! Keep in mind that weak refs don't maintain ownership over quoted elements. If a source
273//! collection removes a quoted element, it will no longer be accessible from weak ref:
274//!
275//! ```rust
276//! use yrs::{Doc, Transact, Quotable, Map};
277//!
278//! let doc = Doc::new();
279//! let map = doc.get_or_insert_map("map");
280//! let mut txn = doc.transact_mut();
281//! map.insert(&mut txn, "origin", "value");
282//! // establish a link 'origin' entry
283//! let link = map.link(&txn, "origin").unwrap();
284//! let link = map.insert(&mut txn, "link", link);
285//! let linked_value: String = link.try_deref(&txn).unwrap();
286//! assert_eq!(linked_value, "value".to_string());
287//!
288//! // remove original value
289//! map.remove(&mut txn, "origin");
290//! let linked_value = link.try_deref_value(&txn);
291//! assert_eq!(linked_value, None); // linked value is no longer accessible
292//! ```
293//!
294//! # Undo/redo
295//!
296//! Among very popular features of many user-facing applications is an ability to revert/reapply
297//! operations performed by user. This becomes even more complicated, once we consider multiple peers
298//! collaborating on the same document, as we may need to skip over the changes synchronized from
299//! remote peers - even thou they could have happened later - in order to only undo our own actions.
300//! [UndoManager] is a Yrs response for these needs, supporting wide variety of options:
301//!
302//! ```rust
303//! use yrs::{Doc, GetString, ReadTxn, Text, Transact, UndoManager, Update};
304//! use yrs::undo::Options;
305//! use yrs::updates::decoder::Decode;
306//!
307//! let local = Doc::with_client_id(123);
308//! let text1 = local.get_or_insert_text("article");
309//! let mut mgr = UndoManager::with_scope_and_options(&local, &text1, Options::default());
310//! mgr.include_origin(local.client_id()); // only track changes originating from local peer
311//!
312//! let remote = Doc::with_client_id(321);
313//! let text2 = remote.get_or_insert_text("article");
314//!
315//! // perform changes locally
316//! text1.push(&mut local.transact_mut_with(local.client_id()), "hello ");
317//! mgr.reset(); // prevent previous and next operation to be treated by Undo manager as one batch
318//! text1.push(&mut local.transact_mut_with(local.client_id()), "world");
319//! assert_eq!(text1.get_string(&local.transact()), "hello world");
320//!
321//! // perform remote changes - these are not being tracked by mgr
322//! {
323//! let mut remote_txn = remote.transact_mut_with(remote.client_id());
324//! text2.push(&mut remote_txn, "everyone");
325//! assert_eq!(text2.get_string(&remote_txn), "everyone");
326//! }
327//!
328//! // sync changes from remote to local
329//! let update = remote.transact().encode_state_as_update_v1(&local.transact().state_vector());
330//! local.transact_mut().apply_update(Update::decode_v1(&update).unwrap());
331//! assert_eq!(text1.get_string(&local.transact()), "hello worldeveryone"); // remote changes synced
332//!
333//! // undo last performed change on local
334//! mgr.undo_blocking();
335//! assert_eq!(text1.get_string(&local.transact()), "hello everyone");
336//!
337//! // redo change we undone
338//! mgr.redo_blocking();
339//! assert_eq!(text1.get_string(&local.transact()), "hello worldeveryone");
340//! ```
341//!
342//! > Keep in mind, that in order to serve its purpose, undo manager may need to implicitly create
343//! transactions over underlying [Doc] - for that reason make sure that no other transaction is
344//! active while calling methods like [UndoManager::undo] or [UndoManager::redo].
345//!
346//! It's important to understand a context, in which [UndoManager] operates:
347//!
348//! - **origins** can be specific classifiers attached to read-write transactions upon creation (see:
349//! [Transact::transact_mut_with]). Undo manager can [include](UndoManager::include_origin) any
350//! number of origins to its track scope. By default no origin is specified: in such case undo
351//! manager will track all changes without looking at transaction origin.
352//! - **scope** refers to shared collection references such as [TextRef], [MapRef] etc. Undo manager
353//! will only track operations performed on tracked scope refs. By default at least one such
354//! reference must be specified, but they can be [expanded](UndoManager::expand_scope) to include
355//! multiple collections at once if necessary.
356//!
357//! Notice, that undo/redo changes are **not** mapped 1-1 on the update batches committed by
358//! transactions. As an example: a frequent case includes establishing a new transaction for every
359//! user key stroke. Meanwhile we may decide to use different granularity of undo/redo actions.
360//! These are grouped together on time-based ranges (configurable in [undo::Options], which is
361//! 500ms by default). You can also set them explicitly by calling [UndoManager::reset] method.
362//!
363//! # Showing past revisions of the document
364//!
365//! Another feature of Yrs is an ability to snapshot and recover versions of the document,
366//! as well as show the differences between them:
367//!
368//! ```rust
369//! use yrs::{Doc, GetString, Options, ReadTxn, Text, Transact, Update, WriteTxn, XmlFragment, XmlTextPrelim};
370//! use yrs::types::Attrs;
371//! use yrs::types::text::{Diff, YChange};
372//! use yrs::updates::decoder::Decode;
373//! use yrs::updates::encoder::{Encoder, EncoderV1};
374//!
375//! let doc = Doc::with_options(Options {
376//! skip_gc: true, // in order to support revisions we cannot garbage collect deleted blocks
377//! ..Options::default()
378//! });
379//! let mut txn = doc.transact_mut();
380//! let f = txn.get_or_insert_xml_fragment("article");
381//! let text = f.insert(&mut txn, 0, XmlTextPrelim::new(""));
382//!
383//! const INIT: &str = "hello world";
384//! text.push(&mut txn, INIT);
385//!
386//! // save current state of the document: "hello world"
387//! let rev = txn.snapshot();
388//!
389//! // change document state
390//! let italic = Attrs::from([("i".into(), true.into())]);
391//! text.format(&mut txn, 6, 5, italic.clone());
392//! text.remove_range(&mut txn, 2, 3);
393//! assert_eq!(text.get_string(&txn), "he <i>world</i>");
394//!
395//! // encode past version of the document
396//! let mut encoder = EncoderV1::new();
397//! txn.encode_state_from_snapshot(&rev, &mut encoder).unwrap();
398//! let update = encoder.to_vec();
399//!
400//! // restore the past state
401//! let doc = Doc::new();
402//! let mut txn = doc.transact_mut();
403//! let f = txn.get_or_insert_xml_fragment("article");
404//! txn.apply_update(Update::decode_v1(&update).unwrap());
405//! let text = f.get(&mut txn, 0).unwrap().into_xml_text().unwrap();
406//!
407//! assert_eq!(text.get_string(&txn), INIT);
408//! ```
409//!
410//! Keep in mind that an update created from past snapshot via [TransactionMut::encode_state_from_snapshot]
411//! doesn't contain updates that happened after that snapshot. What does that mean? While you can
412//! continue making new updates on top of that revision, they will be no longer compatible with any
413//! changes made on the original document since the snapshot has been made, therefore [TransactionMut::apply_update]
414//! on updates generated between two document revisions that branched their state is no longer possible.
415//!
416//! For the reason above main use case of this feature is rendering read-only state of the [Doc]
417//! or restoring document from its past state ie. when current state has been irrecoverably corrupted.
418//!
419//! # Other shared types
420//!
421//! So far we only discussed rich text oriented capabilities of Yrs. However, it's possible to make
422//! use of Yrs to represent any tree-like object:
423//!
424//! - [ArrayRef] can be used to represent any indexable sequence of values. If there are multiple
425//! peers inserting values at the same position, a [ClientID] will be used to determine a final
426//! deterministic order once all peers get in sync.
427//! - [MapRef] is a map object (with keys limited to be strings), where values can be of any given
428//! type. If there are multiple peers updating the same entry concurrently - creating an update
429//! conflict in the result - Yrs will prioritize update belonging to a peer with higher [ClientID]
430//! to make conflict resolution algorithm deterministic.
431//! - Yrs also provides support fo XML nodes in form of [XmlElementRef], [XmlTextRef] and [XmlFragmentRef].
432//!
433//! Underneath all of these types are represented by the same abstract [types::Branch] type. Each
434//! branch is always capable of working as both indexed sequence of elements and a map. In practice
435//! specialized shared types are actually projections over branch type and can be used interchangeably
436//! if needed, i.e.: [XmlElementRef] can be also interpreted as [MapRef], in which case the collection
437//! of that XML node attributes become key-value entries of reinterpreted map's.
438//!
439//! # Preliminary vs Integrated types
440//!
441//! In Yrs core library, every shared type has 2 representations:
442//!
443//! - **Integrated type** (eg. [TextRef], [ArrayRef], [MapRef]) represents a reference that has already
444//! been attached to its parent [Doc]. As such, its state is tracked as part of that document, it
445//! can be modified concurrently by multiple peers and any conflicts that occurred due to such
446//! actions will be automatically resolved accordingly to [YATA conflict resolution algorithm](https://www.researchgate.net/publication/310212186_Near_Real-Time_Peer-to-Peer_Shared_Editing_on_Extensible_Data_Types).
447//! - **Preliminary type** (eg. [TextPrelim], [ArrayPrelim], [MapPrelim]) represents a content that
448//! we want to eventually turn into an integrated reference, but it has not been integrated yet.
449//!
450//! Whenever we want to nest shared types one into another - using methods such as [Array::insert],
451//! [Map::insert] or [Text::insert_embed] - we always must do so using preliminary types. These
452//! methods will return an integrated representation of the preliminary content we wished to integrate.
453//!
454//! Keep in mind that we cannot integrate references that have been already integrated - neither in
455//! the same document nor in any other one. Yjs/Yrs doesn't allow to have the same object to be linked
456//! in multiple places. Same rule concerns primitive types (they will eventually be serialized and
457//! deserialized as unique independent objects), integrated types or sub-documents.
458//!
459//! # Shared collection hooks
460//!
461//! While type like [TextRef], [MapRef] etc. refer to addresses of objects living in memory,
462//! sometime you'd like to be able to uniquely identify the same collection across its different
463//! replicas living on other peers. This is possible via hooks:
464//!
465//! ```rust
466//! use yrs::{Array, ArrayRef, Doc, Hook, MapPrelim, ReadTxn, RootRef, SharedRef, Transact, Update};
467//! use yrs::types::ToJson;
468//! use yrs::updates::decoder::Decode;
469//!
470//! // create a logical identifier to a root type
471//! let root = ArrayRef::root("root");
472//!
473//! let local = Doc::with_client_id(1);
474//! let local_array = root.get_or_create(&mut local.transact_mut());
475//! assert_eq!(local_array.hook(), Hook::from(root.clone())); // another way to get hook for existing type
476//!
477//! let local_map = local_array.push_back(&mut local.transact_mut(), MapPrelim::from([("key", "old")]));
478//! let nested = local_map.hook(); // logical identifier to a nested shared type
479//!
480//! let remote = Doc::with_client_id(2);
481//! let remote_array = root.get_or_create(&mut local.transact_mut());
482//! // we haven't synchronized yet, so nested element doesn't exist on remote
483//! assert!(nested.get(&remote.transact()).is_none());
484//!
485//! // synchronize the changes
486//! {
487//! let mut txn = remote.transact_mut();
488//! let update = local.transact().encode_state_as_update_v1(&txn.state_vector());
489//! txn.apply_update(Update::decode_v1(&update).unwrap());
490//! }
491//!
492//! // after synchronizing, we can now instantiate instance of the same logical type
493//! let remote_map = nested.get(&remote.transact()).unwrap();
494//! assert_eq!(local_map.hook(), remote_map.hook());
495//! assert_eq!(local_map.to_json(&local.transact()), remote_map.to_json(&remote.transact()));
496//! ```
497//!
498//! Logical references are serializable. They also can help to avoid segfaults in cases when we hold
499//! an unsafe reference to a nested collection that has been already deleted by concurrent operation.
500//!
501//! # Transaction event lifecycle
502//!
503//! Yrs provides a variety of lifecycle events, which enable users to react on various situations
504//! and changes performed. Some of these events are used by Yrs own features (e.g. [UndoManager]).
505//! They are always triggered once performed update is committed by dropping or
506//! [committing](TransactionMut::commit) a read-write transaction.
507//!
508//! An order in which these updates are fired is as follows:
509//!
510//! 1. Observers on updated shared types: [TextRef::observe], [ArrayRef::observe], [MapRef::observe],
511//! [XmlTextRef::observe], [XmlFragmentRef::observe] and [XmlElementRef::observe].
512//! 2. [Deep observers](DeepObservable::observe_deep) (special kind of observers that are bubbled up
513//! from nested shared types through their parent collections hierarchy).
514//! 3. After transaction callbacks: [Doc::observe_after_transaction].
515//! 4. After transaction cleanup callbacks (moment after all changes performed by transaction have
516//! been compressed an integrated into document store): [Doc::observe_transaction_cleanup].
517//! 5. Update callbacks: [Doc::observe_update_v1] and [Doc::observe_update_v2]. Useful when we want
518//! to encode and propagate incremental changes made by transaction to other peers.
519//! 6. Sub-document change callbacks: [Doc::observe_subdocs].
520//!
521//! # Update encoding v1 vs. v2
522//!
523//! Yrs ships with so called lib0 encoding, which offers two different variants, both of which are
524//! compatible with [Yjs](https://docs.yjs.dev/) and can be used to bridge between applications
525//! written in different languages using Yrs underneath. They offer a highly compact format,
526//! that aims to produce small binary payload size and high encoding/decoding speed.
527//!
528//! V1 encoding is the default one and should be preferred most of the time. V2 encoding aims to
529//! perform payload size optimizations whenever multiple updates are being encoded and passed
530//! together (e.g. when you want to serialize an entire document state), however for small updates
531//! (e.g. sending individual user keystrokes) V2 may turn out to be less optimal than its V1
532//! equivalent.
533//!
534//! # Awareness and y-sync protocol
535//!
536//! Sometimes it's not always feasible to keep all state changes related to a document within a
537//! document update history - especially if they are notifications bound to a single user or session.
538//! For this reason most of the editor bindings using Yjs supplement the document state with
539//! [sync::Awareness] - it's a separate entity that keeps user related data such as username
540//! or cursor position, but without the overhead of keeping metadata necessary for conflict
541//! resolution.
542//!
543//! [sync::Awareness] together with [Doc] updates combined create basis of y-sync [sync::Protocol].
544//! It's used by most of the network providers in Yjs/Yrs ecosystem and enables universal way of
545//! communicating between different systems.
546//!
547//! In its core y-sync protocol can operate as a simple state machine that serves exchanging and
548//! responding to different message types described by the protocol. The [sync::DefaultProtocol]
549//! provides all the message handlers necessary to make basic communication possible.
550//!
551//! y-sync protocol is extensible leaves a space for the users to define their own messages if
552//! necessary:
553//!
554//! ```rust
555//! use yrs::sync::{Awareness, Message, Protocol, Error};
556//!
557//! struct MyProtocol;
558//! impl Protocol for MyProtocol {
559//! fn missing_handle(&self, awareness: &Awareness, tag: u8, data: Vec<u8>) -> Result<Option<Message>, Error> {
560//! // you can not only override existing message handlers but also define your own
561//! Ok(Some(Message::Custom(tag, data))) // echo
562//! }
563//! }
564//! ```
565//!
566//! # Querying document state
567//!
568//! Starting from version 0.23.0 Yrs provides a way to query document state using [JsonPath] queries.
569//! These are available for transactions and [Any] types. They allow to extract specific parts of
570//! the document state in a way similar to [JSONPath](https://en.wikipedia.org/wiki/JSONPath):
571//!
572//! ```rust
573//! use yrs::{any, Array, ArrayPrelim, Doc, In, JsonPath, JsonPathEval, Map, MapPrelim, Out, Transact, WriteTxn};
574//!
575//! let doc = Doc::new();
576//! let mut txn = doc.transact_mut();
577//! let users = txn.get_or_insert_array("users");
578//!
579//! // populate the document with some data to query
580//! users.insert(&mut txn, 0, MapPrelim::from([
581//! ("name".to_string(), In::Any(any!("Alice"))),
582//! ("surname".into(), In::Any(any!("Smith"))),
583//! ("age".into(), In::Any(any!(25))),
584//! (
585//! "friends".into(),
586//! In::from(ArrayPrelim::from([
587//! any!({ "name": "Bob", "nick": "boreas" }),
588//! any!({ "nick": "crocodile91" }),
589//! ])),
590//! ),
591//! ]));
592//!
593//! let query = JsonPath::parse("$.users..friends.*.nick").unwrap();
594//! let values: Vec<Out> = txn.json_path(&query).collect();
595//! assert_eq!(values, vec![Out::Any(any!("boreas")), Out::Any(any!("crocodile91"))]);
596//! ```
597//!
598//! # External learning materials
599//!
600//! - [A short walkthrough over YATA](https://bartoszsypytkowski.com/yata/) - a conflict resolution
601//! algorithm used by Yrs/Yjs.
602//! - [Deep dive into internal architecture of Yrs](https://bartoszsypytkowski.com/yrs-architecture/).
603//! - [Detailed explanation of conflict-free reordering algorithm](https://bartoszsypytkowski.com/yata-move/) used by Yrs.
604
605mod alt;
606pub mod block;
607mod block_store;
608pub mod doc;
609mod event;
610mod id_set;
611mod store;
612mod transaction;
613pub mod types;
614mod update;
615pub mod updates;
616mod utils;
617
618pub mod any;
619pub mod atomic;
620mod block_iter;
621pub mod branch;
622pub mod encoding;
623pub mod error;
624mod gc;
625mod input;
626pub mod iter;
627pub mod json_path;
628mod moving;
629pub mod observer;
630mod out;
631mod slice;
632mod state_vector;
633pub mod sync;
634#[cfg(test)]
635mod test_utils;
636#[cfg(test)]
637mod tests;
638mod transact;
639pub mod undo;
640
641pub use crate::alt::{
642 diff_updates_v1, diff_updates_v2, encode_state_vector_from_update_v1,
643 encode_state_vector_from_update_v2, merge_updates_v1, merge_updates_v2,
644};
645pub use crate::any::Any;
646pub use crate::block::ID;
647pub use crate::branch::BranchID;
648pub use crate::branch::Hook;
649pub use crate::branch::Nested;
650pub use crate::branch::Root;
651pub use crate::doc::Doc;
652pub use crate::doc::OffsetKind;
653pub use crate::doc::Options;
654pub use crate::event::{SubdocsEvent, SubdocsEventIter, TransactionCleanupEvent, UpdateEvent};
655pub use crate::id_set::DeleteSet;
656pub use crate::input::In;
657pub use crate::json_path::{JsonPath, JsonPathEval};
658pub use crate::moving::Assoc;
659pub use crate::moving::IndexScope;
660pub use crate::moving::IndexedSequence;
661pub use crate::moving::Offset;
662pub use crate::moving::StickyIndex;
663pub use crate::observer::{Observer, Subscription};
664pub use crate::out::Out;
665pub use crate::state_vector::Snapshot;
666pub use crate::state_vector::StateVector;
667pub use crate::store::Store;
668pub use crate::transact::{
669 AcquireTransaction, AcquireTransactionMut, AsyncTransact, Transact, TransactionAcqError,
670};
671pub use crate::transaction::Origin;
672pub use crate::transaction::ReadTxn;
673pub use crate::transaction::RootRefs;
674pub use crate::transaction::Transaction;
675pub use crate::transaction::TransactionMut;
676pub use crate::transaction::WriteTxn;
677pub use crate::types::array::Array;
678pub use crate::types::array::ArrayPrelim;
679pub use crate::types::array::ArrayRef;
680pub use crate::types::map::Map;
681pub use crate::types::map::MapPrelim;
682pub use crate::types::map::MapRef;
683pub use crate::types::text::Text;
684pub use crate::types::text::TextPrelim;
685pub use crate::types::text::TextRef;
686#[cfg(feature = "weak")]
687pub use crate::types::weak::{Quotable, WeakPrelim, WeakRef};
688pub use crate::types::xml::Xml;
689pub use crate::types::xml::XmlElementPrelim;
690pub use crate::types::xml::XmlElementRef;
691pub use crate::types::xml::XmlFragment;
692pub use crate::types::xml::XmlFragmentPrelim;
693pub use crate::types::xml::XmlFragmentRef;
694pub use crate::types::xml::XmlOut;
695pub use crate::types::xml::XmlTextPrelim;
696pub use crate::types::xml::XmlTextRef;
697pub use crate::types::DeepObservable;
698pub use crate::types::GetString;
699pub use crate::types::Observable;
700pub use crate::types::RootRef;
701pub use crate::types::SharedRef;
702pub use crate::update::Update;
703
704#[deprecated(since = "0.19.0", note = "Use `yrs::Out` instead")]
705pub type Value = Out;
706
707pub type UndoManager = crate::undo::UndoManager<()>;
708pub type Uuid = std::sync::Arc<str>;
709
710/// Generate random v4 UUID.
711/// (See: https://www.rfc-editor.org/rfc/rfc4122#section-4.4)
712pub fn uuid_v4() -> Uuid {
713 let mut rand = fastrand::Rng::new();
714 uuid_v4_from(rand.u128(..))
715}
716
717/// Generate random v4 UUID.
718/// (See: https://www.rfc-editor.org/rfc/rfc4122#section-4.4)
719pub fn uuid_v4_from(value: u128) -> Uuid {
720 let uuid = value & 0xFFFFFFFFFFFF4FFFBFFFFFFFFFFFFFFF | 0x40008000000000000000;
721 let src = uuid.to_be_bytes();
722 const HEX: [u8; 16] = [
723 b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'a', b'b', b'c', b'd', b'e',
724 b'f',
725 ];
726 let mut dst = [0; 36];
727 dst[0] = HEX[(src[0] >> 4) as usize];
728 dst[1] = HEX[(src[0] & 0x0f) as usize];
729 dst[2] = HEX[(src[1] >> 4) as usize];
730 dst[3] = HEX[(src[1] & 0x0f) as usize];
731 dst[4] = HEX[(src[2] >> 4) as usize];
732 dst[5] = HEX[(src[2] & 0x0f) as usize];
733 dst[6] = HEX[(src[3] >> 4) as usize];
734 dst[7] = HEX[(src[3] & 0x0f) as usize];
735 dst[8] = '-' as u8;
736 dst[9] = HEX[(src[4] >> 4) as usize];
737 dst[10] = HEX[(src[4] & 0x0f) as usize];
738 dst[11] = HEX[(src[5] >> 4) as usize];
739 dst[12] = HEX[(src[5] & 0x0f) as usize];
740 dst[13] = '-' as u8;
741 dst[14] = HEX[(src[6] >> 4) as usize];
742 dst[15] = HEX[(src[6] & 0x0f) as usize];
743 dst[16] = HEX[(src[7] >> 4) as usize];
744 dst[17] = HEX[(src[7] & 0x0f) as usize];
745 dst[18] = '-' as u8;
746 dst[19] = HEX[(src[8] >> 4) as usize];
747 dst[20] = HEX[(src[8] & 0x0f) as usize];
748 dst[21] = HEX[(src[9] >> 4) as usize];
749 dst[22] = HEX[(src[9] & 0x0f) as usize];
750 dst[23] = '-' as u8;
751 dst[24] = HEX[(src[10] >> 4) as usize];
752 dst[25] = HEX[(src[10] & 0x0f) as usize];
753 dst[26] = HEX[(src[11] >> 4) as usize];
754 dst[27] = HEX[(src[11] & 0x0f) as usize];
755 dst[28] = HEX[(src[12] >> 4) as usize];
756 dst[29] = HEX[(src[12] & 0x0f) as usize];
757 dst[30] = HEX[(src[13] >> 4) as usize];
758 dst[31] = HEX[(src[13] & 0x0f) as usize];
759 dst[32] = HEX[(src[14] >> 4) as usize];
760 dst[33] = HEX[(src[14] & 0x0f) as usize];
761 dst[34] = HEX[(src[15] >> 4) as usize];
762 dst[35] = HEX[(src[15] & 0x0f) as usize];
763 let str: &str = unsafe { std::str::from_utf8_unchecked(&dst) };
764 str.into()
765}
766