| 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 | |
| 605 | mod alt; |
| 606 | pub mod block; |
| 607 | mod block_store; |
| 608 | pub mod doc; |
| 609 | mod event; |
| 610 | mod id_set; |
| 611 | mod store; |
| 612 | mod transaction; |
| 613 | pub mod types; |
| 614 | mod update; |
| 615 | pub mod updates; |
| 616 | mod utils; |
| 617 | |
| 618 | pub mod any; |
| 619 | pub mod atomic; |
| 620 | mod block_iter; |
| 621 | pub mod branch; |
| 622 | pub mod encoding; |
| 623 | pub mod error; |
| 624 | mod gc; |
| 625 | mod input; |
| 626 | pub mod iter; |
| 627 | pub mod json_path; |
| 628 | mod moving; |
| 629 | pub mod observer; |
| 630 | mod out; |
| 631 | mod slice; |
| 632 | mod state_vector; |
| 633 | pub mod sync; |
| 634 | #[cfg(test)] |
| 635 | mod test_utils; |
| 636 | #[cfg(test)] |
| 637 | mod tests; |
| 638 | mod transact; |
| 639 | pub mod undo; |
| 640 | |
| 641 | pub 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 | }; |
| 645 | pub use crate::any::Any; |
| 646 | pub use crate::block::ID; |
| 647 | pub use crate::branch::BranchID; |
| 648 | pub use crate::branch::Hook; |
| 649 | pub use crate::branch::Nested; |
| 650 | pub use crate::branch::Root; |
| 651 | pub use crate::doc::Doc; |
| 652 | pub use crate::doc::OffsetKind; |
| 653 | pub use crate::doc::Options; |
| 654 | pub use crate::event::{SubdocsEvent, SubdocsEventIter, TransactionCleanupEvent, UpdateEvent}; |
| 655 | pub use crate::id_set::DeleteSet; |
| 656 | pub use crate::input::In; |
| 657 | pub use crate::json_path::{JsonPath, JsonPathEval}; |
| 658 | pub use crate::moving::Assoc; |
| 659 | pub use crate::moving::IndexScope; |
| 660 | pub use crate::moving::IndexedSequence; |
| 661 | pub use crate::moving::Offset; |
| 662 | pub use crate::moving::StickyIndex; |
| 663 | pub use crate::observer::{Observer, Subscription}; |
| 664 | pub use crate::out::Out; |
| 665 | pub use crate::state_vector::Snapshot; |
| 666 | pub use crate::state_vector::StateVector; |
| 667 | pub use crate::store::Store; |
| 668 | pub use crate::transact::{ |
| 669 | AcquireTransaction, AcquireTransactionMut, AsyncTransact, Transact, TransactionAcqError, |
| 670 | }; |
| 671 | pub use crate::transaction::Origin; |
| 672 | pub use crate::transaction::ReadTxn; |
| 673 | pub use crate::transaction::RootRefs; |
| 674 | pub use crate::transaction::Transaction; |
| 675 | pub use crate::transaction::TransactionMut; |
| 676 | pub use crate::transaction::WriteTxn; |
| 677 | pub use crate::types::array::Array; |
| 678 | pub use crate::types::array::ArrayPrelim; |
| 679 | pub use crate::types::array::ArrayRef; |
| 680 | pub use crate::types::map::Map; |
| 681 | pub use crate::types::map::MapPrelim; |
| 682 | pub use crate::types::map::MapRef; |
| 683 | pub use crate::types::text::Text; |
| 684 | pub use crate::types::text::TextPrelim; |
| 685 | pub use crate::types::text::TextRef; |
| 686 | #[cfg(feature = "weak")] |
| 687 | pub use crate::types::weak::{Quotable, WeakPrelim, WeakRef}; |
| 688 | pub use crate::types::xml::Xml; |
| 689 | pub use crate::types::xml::XmlElementPrelim; |
| 690 | pub use crate::types::xml::XmlElementRef; |
| 691 | pub use crate::types::xml::XmlFragment; |
| 692 | pub use crate::types::xml::XmlFragmentPrelim; |
| 693 | pub use crate::types::xml::XmlFragmentRef; |
| 694 | pub use crate::types::xml::XmlOut; |
| 695 | pub use crate::types::xml::XmlTextPrelim; |
| 696 | pub use crate::types::xml::XmlTextRef; |
| 697 | pub use crate::types::DeepObservable; |
| 698 | pub use crate::types::GetString; |
| 699 | pub use crate::types::Observable; |
| 700 | pub use crate::types::RootRef; |
| 701 | pub use crate::types::SharedRef; |
| 702 | pub use crate::update::Update; |
| 703 | |
| 704 | #[deprecated(since = "0.19.0", note = "Use `yrs::Out` instead")] |
| 705 | pub type Value = Out; |
| 706 | |
| 707 | pub type UndoManager = crate::undo::UndoManager<()>; |
| 708 | pub type Uuid = std::sync::Arc<str>; |
| 709 | |
| 710 | /// Generate random v4 UUID. |
| 711 | /// (See: https://www.rfc-editor.org/rfc/rfc4122#section-4.4) |
| 712 | pub 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) |
| 719 | pub 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 | |