Skip to main content

loro_ffi/
doc.rs

1use std::{
2    borrow::Cow,
3    cmp::Ordering,
4    collections::HashMap,
5    ops::{ControlFlow, Deref},
6    sync::{Arc, Mutex},
7};
8
9use loro::{
10    cursor::CannotFindRelativePosition, ChangeTravelError, CounterSpan, DocAnalysis,
11    FrontiersNotIncluded, IdSpan, JsonSchema, Lamport, LoroDoc as InnerLoroDoc, LoroEncodeError,
12    LoroError, LoroResult, PeerID, StyleConfig, Timestamp, ID,
13};
14
15use crate::{
16    event::{DiffBatch, DiffEvent, Subscriber},
17    AbsolutePosition, Configure, ContainerID, ContainerIdLike, Cursor, Frontiers, Index,
18    JsonPathError, LoroCounter, LoroList, LoroMap, LoroMovableList, LoroText, LoroTree, LoroValue,
19    StyleConfigMap, SubscribeJsonPathCallback, ValueOrContainer, VersionRange, VersionVector,
20    VersionVectorDiff,
21};
22
23/// Decodes the metadata for an imported blob from the provided bytes.
24#[inline]
25pub fn decode_import_blob_meta(
26    bytes: &[u8],
27    check_checksum: bool,
28) -> LoroResult<ImportBlobMetadata> {
29    let s = InnerLoroDoc::decode_import_blob_meta(bytes, check_checksum)?;
30    Ok(s.into())
31}
32
33pub struct LoroDoc {
34    pub(crate) doc: InnerLoroDoc,
35}
36
37impl LoroDoc {
38    pub fn new() -> Self {
39        Self {
40            doc: InnerLoroDoc::new(),
41        }
42    }
43
44    pub fn fork(&self) -> Arc<Self> {
45        let doc = self.doc.fork();
46        Arc::new(LoroDoc { doc })
47    }
48
49    pub fn fork_at(&self, frontiers: &Frontiers) -> LoroResult<Arc<Self>> {
50        let doc = self.doc.fork_at(&frontiers.into())?;
51        Ok(Arc::new(LoroDoc { doc }))
52    }
53
54    /// Get the configurations of the document.
55    #[inline]
56    pub fn config(&self) -> Arc<Configure> {
57        Arc::new(self.doc.config().clone().into())
58    }
59
60    /// Get `Change` at the given id.
61    ///
62    /// `Change` is a grouped continuous operations that share the same id, timestamp, commit message.
63    ///
64    /// - The id of the `Change` is the id of its first op.
65    /// - The second op's id is `{ peer: change.id.peer, counter: change.id.counter + 1 }`
66    ///
67    /// The same applies on `Lamport`:
68    ///
69    /// - The lamport of the `Change` is the lamport of its first op.
70    /// - The second op's lamport is `change.lamport + 1`
71    ///
72    /// The length of the `Change` is how many operations it contains
73    #[inline]
74    pub fn get_change(&self, id: ID) -> Option<ChangeMeta> {
75        self.doc.get_change(id).map(|x| x.into())
76    }
77
78    /// Set whether to record the timestamp of each change. Default is `false`.
79    ///
80    /// If enabled, the Unix timestamp will be recorded for each change automatically.
81    ///
82    /// You can set each timestamp manually when committing a change.
83    ///
84    /// NOTE: Timestamps are forced to be in ascending order.
85    /// If you commit a new change with a timestamp that is less than the existing one,
86    /// the largest existing timestamp will be used instead.
87    #[inline]
88    pub fn set_record_timestamp(&self, record: bool) {
89        self.doc.set_record_timestamp(record);
90    }
91
92    /// Set the interval of mergeable changes, **in seconds**.
93    ///
94    /// If two continuous local changes are within the interval, they will be merged into one change.
95    /// The default value is 1000 seconds.
96    ///
97    /// By default, we record timestamps in seconds for each change. So if the merge interval is 1, and changes A and B
98    /// have timestamps of 3 and 4 respectively, then they will be merged into one change
99    #[inline]
100    pub fn set_change_merge_interval(&self, interval: i64) {
101        self.doc.set_change_merge_interval(interval);
102    }
103
104    /// Set the rich text format configuration of the document.
105    ///
106    /// You need to config it if you use rich text `mark` method.
107    /// Specifically, you need to config the `expand` property of each style.
108    ///
109    /// Expand is used to specify the behavior of expanding when new text is inserted at the
110    /// beginning or end of the style.
111    #[inline]
112    pub fn config_text_style(&self, text_style: Arc<StyleConfigMap>) {
113        self.doc.config_text_style(text_style.as_ref().to_loro())
114    }
115
116    /// Configures the default text style for the document.
117    ///
118    /// This method sets the default text style configuration for the document when using LoroText.
119    /// If `None` is provided, the default style is reset.
120    ///
121    /// # Parameters
122    ///
123    /// - `text_style`: The style configuration to set as the default. `None` to reset.
124    pub fn config_default_text_style(&self, text_style: Option<StyleConfig>) {
125        self.doc.config_default_text_style(text_style);
126    }
127
128    /// Attach the document state to the latest known version.
129    ///
130    /// > The document becomes detached during a `checkout` operation.
131    /// > Being `detached` implies that the `DocState` is not synchronized with the latest version of the `OpLog`.
132    /// > In a detached state, the document is not editable, and any `import` operations will be
133    /// > recorded in the `OpLog` without being applied to the `DocState`.
134    #[inline]
135    pub fn attach(&self) {
136        self.doc.attach()
137    }
138
139    /// Checkout the `DocState` to a specific version.
140    ///
141    /// > The document becomes detached during a `checkout` operation.
142    /// > Being `detached` implies that the `DocState` is not synchronized with the latest version of the `OpLog`.
143    /// > In a detached state, the document is not editable, and any `import` operations will be
144    /// > recorded in the `OpLog` without being applied to the `DocState`.
145    ///
146    /// You should call `attach` to attach the `DocState` to the latest version of `OpLog`.
147    #[inline]
148    pub fn checkout(&self, frontiers: &Frontiers) -> LoroResult<()> {
149        self.doc.checkout(&frontiers.into())
150    }
151
152    /// Checkout the `DocState` to the latest version.
153    ///
154    /// > The document becomes detached during a `checkout` operation.
155    /// > Being `detached` implies that the `DocState` is not synchronized with the latest version of the `OpLog`.
156    /// > In a detached state, the document is not editable, and any `import` operations will be
157    /// > recorded in the `OpLog` without being applied to the `DocState`.
158    ///
159    /// This has the same effect as `attach`.
160    #[inline]
161    pub fn checkout_to_latest(&self) {
162        self.doc.checkout_to_latest()
163    }
164
165    /// Compare the frontiers with the current OpLog's version.
166    ///
167    /// If `other` contains any version that's not contained in the current OpLog, return [Ordering::Less].
168    #[inline]
169    pub fn cmp_with_frontiers(&self, other: &Frontiers) -> Ordering {
170        self.doc.cmp_with_frontiers(&other.into())
171    }
172
173    // TODO:
174    pub fn cmp_frontiers(
175        &self,
176        a: &Frontiers,
177        b: &Frontiers,
178    ) -> Result<Option<Ordering>, FrontiersNotIncluded> {
179        self.doc.cmp_frontiers(&a.into(), &b.into())
180    }
181
182    /// Force the document enter the detached mode.
183    ///
184    /// In this mode, when you importing new updates, the [loro_internal::DocState] will not be changed.
185    ///
186    /// Learn more at https://loro.dev/docs/advanced/doc_state_and_oplog#attacheddetached-status
187    #[inline]
188    pub fn detach(&self) {
189        self.doc.detach()
190    }
191
192    /// Import a batch of updates/snapshot.
193    ///
194    /// The data can be in arbitrary order. The import result will be the same.
195    #[inline]
196    pub fn import_batch(&self, bytes: &[Vec<u8>]) -> Result<ImportStatus, LoroError> {
197        let status = self.doc.import_batch(bytes)?;
198        Ok(status.into())
199    }
200
201    pub fn get_movable_list(&self, id: Arc<dyn ContainerIdLike>) -> Arc<LoroMovableList> {
202        Arc::new(LoroMovableList {
203            inner: self.doc.get_movable_list(loro::ContainerID::from(
204                id.as_container_id(crate::ContainerType::MovableList),
205            )),
206        })
207    }
208
209    pub fn try_get_movable_list(
210        &self,
211        id: Arc<dyn ContainerIdLike>,
212    ) -> Option<Arc<LoroMovableList>> {
213        self.doc
214            .try_get_movable_list(loro::ContainerID::from(
215                id.as_container_id(crate::ContainerType::MovableList),
216            ))
217            .map(|inner| Arc::new(LoroMovableList { inner }))
218    }
219
220    pub fn get_list(&self, id: Arc<dyn ContainerIdLike>) -> Arc<LoroList> {
221        Arc::new(LoroList {
222            inner: self.doc.get_list(loro::ContainerID::from(
223                id.as_container_id(crate::ContainerType::List),
224            )),
225        })
226    }
227
228    pub fn try_get_list(&self, id: Arc<dyn ContainerIdLike>) -> Option<Arc<LoroList>> {
229        self.doc
230            .try_get_list(loro::ContainerID::from(
231                id.as_container_id(crate::ContainerType::List),
232            ))
233            .map(|inner| Arc::new(LoroList { inner }))
234    }
235
236    pub fn get_map(&self, id: Arc<dyn ContainerIdLike>) -> Arc<LoroMap> {
237        Arc::new(LoroMap {
238            inner: self.doc.get_map(loro::ContainerID::from(
239                id.as_container_id(crate::ContainerType::Map),
240            )),
241        })
242    }
243
244    pub fn try_get_map(&self, id: Arc<dyn ContainerIdLike>) -> Option<Arc<LoroMap>> {
245        self.doc
246            .try_get_map(loro::ContainerID::from(
247                id.as_container_id(crate::ContainerType::Map),
248            ))
249            .map(|inner| Arc::new(LoroMap { inner }))
250    }
251
252    pub fn get_text(&self, id: Arc<dyn ContainerIdLike>) -> Arc<LoroText> {
253        Arc::new(LoroText {
254            inner: self.doc.get_text(loro::ContainerID::from(
255                id.as_container_id(crate::ContainerType::Text),
256            )),
257        })
258    }
259
260    pub fn try_get_text(&self, id: Arc<dyn ContainerIdLike>) -> Option<Arc<LoroText>> {
261        self.doc
262            .try_get_text(loro::ContainerID::from(
263                id.as_container_id(crate::ContainerType::Text),
264            ))
265            .map(|inner| Arc::new(LoroText { inner }))
266    }
267
268    pub fn get_tree(&self, id: Arc<dyn ContainerIdLike>) -> Arc<LoroTree> {
269        Arc::new(LoroTree {
270            inner: self.doc.get_tree(loro::ContainerID::from(
271                id.as_container_id(crate::ContainerType::Tree),
272            )),
273        })
274    }
275
276    pub fn try_get_tree(&self, id: Arc<dyn ContainerIdLike>) -> Option<Arc<LoroTree>> {
277        self.doc
278            .try_get_tree(loro::ContainerID::from(
279                id.as_container_id(crate::ContainerType::Tree),
280            ))
281            .map(|inner| Arc::new(LoroTree { inner }))
282    }
283
284    pub fn get_counter(&self, id: Arc<dyn ContainerIdLike>) -> Arc<LoroCounter> {
285        Arc::new(LoroCounter {
286            inner: self.doc.get_counter(loro::ContainerID::from(
287                id.as_container_id(crate::ContainerType::Counter),
288            )),
289        })
290    }
291
292    pub fn try_get_counter(&self, id: Arc<dyn ContainerIdLike>) -> Option<Arc<LoroCounter>> {
293        self.doc
294            .try_get_counter(loro::ContainerID::from(
295                id.as_container_id(crate::ContainerType::Counter),
296            ))
297            .map(|inner| Arc::new(LoroCounter { inner }))
298    }
299
300    pub fn get_container(&self, id: &ContainerID) -> Option<Arc<dyn ValueOrContainer>> {
301        self.doc
302            .get_container(id.clone().into())
303            .map(|c| Arc::new(loro::ValueOrContainer::Container(c)) as Arc<dyn ValueOrContainer>)
304    }
305
306    /// Commit the cumulative auto commit transaction.
307    ///
308    /// There is a transaction behind every operation.
309    /// The events will be emitted after a transaction is committed. A transaction is committed when:
310    ///
311    /// - `doc.commit()` is called.
312    /// - `doc.exportFrom(version)` is called.
313    /// - `doc.import(data)` is called.
314    /// - `doc.checkout(version)` is called.
315    #[inline]
316    pub fn commit(&self) {
317        self.doc.commit()
318    }
319
320    pub fn commit_with(&self, options: CommitOptions) {
321        self.doc.commit_with(options.into())
322    }
323
324    /// Set commit message for the current uncommitted changes
325    ///
326    /// It will be persisted.
327    pub fn set_next_commit_message(&self, msg: &str) {
328        self.doc.set_next_commit_message(msg)
329    }
330
331    /// Set `origin` for the current uncommitted changes, it can be used to track the source of changes in an event.
332    ///
333    /// It will NOT be persisted.
334    pub fn set_next_commit_origin(&self, origin: &str) {
335        self.doc.set_next_commit_origin(origin)
336    }
337
338    /// Set the timestamp of the next commit.
339    ///
340    /// It will be persisted and stored in the `OpLog`.
341    /// You can get the timestamp from the [`Change`] type.
342    pub fn set_next_commit_timestamp(&self, timestamp: i64) {
343        self.doc.set_next_commit_timestamp(timestamp)
344    }
345
346    /// Set the options of the next commit.
347    ///
348    /// It will be used when the next commit is performed.
349    pub fn set_next_commit_options(&self, options: CommitOptions) {
350        self.doc.set_next_commit_options(options.into())
351    }
352
353    /// Clear the options of the next commit.
354    pub fn clear_next_commit_options(&self) {
355        self.doc.clear_next_commit_options()
356    }
357
358    /// Whether the document is in detached mode, where the [loro_internal::DocState] is not
359    /// synchronized with the latest version of the [loro_internal::OpLog].
360    #[inline]
361    pub fn is_detached(&self) -> bool {
362        self.doc.is_detached()
363    }
364
365    /// Import updates/snapshot exported by [`LoroDoc::export_snapshot`] or [`LoroDoc::export_from`].
366    #[inline]
367    pub fn import(&self, bytes: &[u8]) -> Result<ImportStatus, LoroError> {
368        let status = self.doc.import_with(bytes, "")?;
369        Ok(status.into())
370    }
371
372    /// Import updates/snapshot exported by [`LoroDoc::export_snapshot`] or [`LoroDoc::export_from`].
373    ///
374    /// It marks the import with a custom `origin` string. It can be used to track the import source
375    /// in the generated events.
376    #[inline]
377    pub fn import_with(&self, bytes: &[u8], origin: &str) -> Result<ImportStatus, LoroError> {
378        let status = self.doc.import_with(bytes, origin)?;
379        Ok(status.into())
380    }
381
382    pub fn import_json_updates(&self, json: &str) -> Result<ImportStatus, LoroError> {
383        let status = self.doc.import_json_updates(json)?;
384        Ok(status.into())
385    }
386
387    /// Export the current state with json-string format of the document.
388    #[inline]
389    pub fn export_json_updates(&self, start_vv: &VersionVector, end_vv: &VersionVector) -> String {
390        let json = self
391            .doc
392            .export_json_updates(&start_vv.into(), &end_vv.into());
393        serde_json::to_string(&json).unwrap()
394    }
395
396    /// Export the current state with json-string format of the document, without peer compression.
397    ///
398    /// Compared to [`export_json_updates`], this method does not compress the peer IDs in the updates.
399    /// So the operations are easier to be processed by application code.
400    #[inline]
401    pub fn export_json_updates_without_peer_compression(
402        &self,
403        start_vv: &VersionVector,
404        end_vv: &VersionVector,
405    ) -> String {
406        serde_json::to_string(
407            &self
408                .doc
409                .export_json_updates_without_peer_compression(&start_vv.into(), &end_vv.into()),
410        )
411        .unwrap()
412    }
413
414    /// Redacts sensitive content in JSON updates within the specified version range.
415    ///
416    /// This function allows you to share document history while removing potentially sensitive content.
417    /// It preserves the document structure and collaboration capabilities while replacing content with
418    /// placeholders according to these redaction rules:
419    ///
420    /// - Preserves delete and move operations
421    /// - Replaces text insertion content with the Unicode replacement character
422    /// - Substitutes list and map insert values with null
423    /// - Maintains structure of child containers
424    /// - Replaces text mark values with null
425    /// - Preserves map keys and text annotation keys
426    pub fn redact_json_updates(
427        &self,
428        json: &str,
429        version_range: &VersionRange,
430    ) -> Result<String, LoroError> {
431        let mut schema: JsonSchema =
432            serde_json::from_str(json).map_err(|_e| LoroError::InvalidJsonSchema)?;
433        loro::json::redact(&mut schema, version_range.into())
434            .map_err(|e| LoroError::Unknown(e.to_string().into_boxed_str()))?;
435        Ok(serde_json::to_string(&schema).unwrap())
436    }
437
438    /// Export the readable [`Change`]s in the given [`IdSpan`]
439    // TODO: swift type
440    pub fn export_json_in_id_span(&self, id_span: IdSpan) -> Vec<String> {
441        self.doc
442            .export_json_in_id_span(id_span)
443            .into_iter()
444            .map(|x| serde_json::to_string(&x).unwrap())
445            .collect()
446    }
447
448    /// Export all the ops not included in the given `VersionVector`
449    #[inline]
450    pub fn export_updates(&self, vv: &VersionVector) -> Result<Vec<u8>, LoroEncodeError> {
451        self.doc.export(loro::ExportMode::Updates {
452            from: Cow::Owned(vv.into()),
453        })
454    }
455
456    /// Export the current state and history of the document.
457    #[inline]
458    pub fn export_snapshot(&self) -> Result<Vec<u8>, LoroEncodeError> {
459        self.doc.export(loro::ExportMode::Snapshot)
460    }
461
462    pub fn export_snapshot_at(&self, frontiers: &Frontiers) -> Result<Vec<u8>, LoroEncodeError> {
463        self.doc.export(loro::ExportMode::SnapshotAt {
464            version: Cow::Owned(frontiers.into()),
465        })
466    }
467
468    pub fn frontiers_to_vv(&self, frontiers: &Frontiers) -> Option<Arc<VersionVector>> {
469        self.doc
470            .frontiers_to_vv(&frontiers.into())
471            .map(|v| Arc::new(v.into()))
472    }
473
474    pub fn minimize_frontiers(&self, frontiers: &Frontiers) -> FrontiersOrID {
475        match self.doc.minimize_frontiers(&frontiers.into()) {
476            Ok(f) => FrontiersOrID {
477                frontiers: Some(Arc::new(f.into())),
478                id: None,
479            },
480            Err(id) => FrontiersOrID {
481                frontiers: None,
482                id: Some(id),
483            },
484        }
485    }
486
487    pub fn vv_to_frontiers(&self, vv: &VersionVector) -> Arc<Frontiers> {
488        Arc::new(self.doc.vv_to_frontiers(&vv.into()).into())
489    }
490
491    // TODO: with oplog
492    // TODO: with state
493
494    pub fn oplog_vv(&self) -> Arc<VersionVector> {
495        Arc::new(self.doc.oplog_vv().into())
496    }
497
498    pub fn state_vv(&self) -> Arc<VersionVector> {
499        Arc::new(self.doc.state_vv().into())
500    }
501
502    /// Get the `VersionVector` of the start of the shallow history
503    ///
504    /// The ops included by the shallow history are not in the doc.
505    #[inline]
506    pub fn shallow_since_vv(&self) -> Arc<VersionVector> {
507        Arc::new(loro::VersionVector::from_im_vv(&self.doc.shallow_since_vv()).into())
508    }
509
510    /// Get the total number of operations in the `OpLog`
511    #[inline]
512    pub fn len_ops(&self) -> u64 {
513        self.doc.len_ops() as u64
514    }
515
516    /// Get the total number of changes in the `OpLog`
517    #[inline]
518    pub fn len_changes(&self) -> u64 {
519        self.doc.len_changes() as u64
520    }
521
522    /// Get the shallow value of the document.
523    #[inline]
524    pub fn get_value(&self) -> LoroValue {
525        self.doc.get_value().into()
526    }
527
528    pub fn get_deep_value(&self) -> LoroValue {
529        self.doc.get_deep_value().into()
530    }
531
532    /// Get the current state with container id of the doc
533    pub fn get_deep_value_with_id(&self) -> LoroValue {
534        self.doc.get_deep_value_with_id().into()
535    }
536
537    pub fn oplog_frontiers(&self) -> Arc<Frontiers> {
538        Arc::new(self.doc.oplog_frontiers().into())
539    }
540
541    pub fn state_frontiers(&self) -> Arc<Frontiers> {
542        Arc::new(self.doc.state_frontiers().into())
543    }
544
545    /// Get the PeerID
546    #[inline]
547    pub fn peer_id(&self) -> PeerID {
548        self.doc.peer_id()
549    }
550
551    /// Change the PeerID
552    ///
553    /// NOTE: You need ot make sure there is no chance two peer have the same PeerID.
554    /// If it happens, the document will be corrupted.
555    #[inline]
556    pub fn set_peer_id(&self, peer: PeerID) -> LoroResult<()> {
557        self.doc.set_peer_id(peer)
558    }
559
560    pub fn subscribe(
561        &self,
562        container_id: &ContainerID,
563        subscriber: Arc<dyn Subscriber>,
564    ) -> Arc<Subscription> {
565        Arc::new(
566            self.doc
567                .subscribe(
568                    &(container_id.into()),
569                    Arc::new(move |e| {
570                        subscriber.on_diff(DiffEvent::from(e));
571                    }),
572                )
573                .into(),
574        )
575    }
576
577    pub fn subscribe_root(&self, subscriber: Arc<dyn Subscriber>) -> Arc<Subscription> {
578        // self.doc.subscribe_root(callback)
579        Arc::new(
580            self.doc
581                .subscribe_root(Arc::new(move |e| {
582                    subscriber.on_diff(DiffEvent::from(e));
583                }))
584                .into(),
585        )
586    }
587
588    /// Subscribe the local update of the document.
589    pub fn subscribe_local_update(
590        &self,
591        callback: Arc<dyn LocalUpdateCallback>,
592    ) -> Arc<Subscription> {
593        let s = self.doc.subscribe_local_update(Box::new(move |update| {
594            // TODO: should it be cloned?
595            callback.on_local_update(update.to_vec());
596            true
597        }));
598        Arc::new(Subscription(Mutex::new(Some(s))))
599    }
600
601    /// Check the correctness of the document state by comparing it with the state
602    /// calculated by applying all the history.
603    #[inline]
604    pub fn check_state_correctness_slow(&self) {
605        self.doc.check_state_correctness_slow()
606    }
607
608    pub fn get_by_path(&self, path: &[Index]) -> Option<Arc<dyn ValueOrContainer>> {
609        self.doc
610            .get_by_path(&path.iter().map(|v| v.clone().into()).collect::<Vec<_>>())
611            .map(|x| Arc::new(x) as Arc<dyn ValueOrContainer>)
612    }
613
614    ///
615    /// The path can be specified in different ways depending on the container type:
616    ///
617    /// For Tree:
618    /// 1. Using node IDs: `tree/{node_id}/property`
619    /// 2. Using indices: `tree/0/1/property`
620    ///
621    /// For List and MovableList:
622    /// - Using indices: `list/0` or `list/1/property`
623    ///
624    /// For Map:
625    /// - Using keys: `map/key` or `map/nested/property`
626    ///
627    /// For tree structures, index-based paths follow depth-first traversal order.
628    /// The indices start from 0 and represent the position of a node among its siblings.
629    ///
630    /// # Examples
631    /// ```
632    /// # use loro::{LoroDoc, LoroValue};
633    /// let doc = LoroDoc::new();
634    ///
635    /// // Tree example
636    /// let tree = doc.get_tree("tree");
637    /// let root = tree.create(None).unwrap();
638    /// tree.get_meta(root).unwrap().insert("name", "root").unwrap();
639    /// // Access tree by ID or index
640    /// let name1 = doc.get_by_str_path(&format!("tree/{}/name", root)).unwrap().into_value().unwrap();
641    /// let name2 = doc.get_by_str_path("tree/0/name").unwrap().into_value().unwrap();
642    /// assert_eq!(name1, name2);
643    ///
644    /// // List example
645    /// let list = doc.get_list("list");
646    /// list.insert(0, "first").unwrap();
647    /// list.insert(1, "second").unwrap();
648    /// // Access list by index
649    /// let item = doc.get_by_str_path("list/0");
650    /// assert_eq!(item.unwrap().into_value().unwrap().into_string().unwrap(), "first".into());
651    ///
652    /// // Map example
653    /// let map = doc.get_map("map");
654    /// map.insert("key", "value").unwrap();
655    /// // Access map by key
656    /// let value = doc.get_by_str_path("map/key");
657    /// assert_eq!(value.unwrap().into_value().unwrap().into_string().unwrap(), "value".into());
658    ///
659    /// // MovableList example
660    /// let mlist = doc.get_movable_list("mlist");
661    /// mlist.insert(0, "item").unwrap();
662    /// // Access movable list by index
663    /// let item = doc.get_by_str_path("mlist/0");
664    /// assert_eq!(item.unwrap().into_value().unwrap().into_string().unwrap(), "item".into());
665    /// ```
666    pub fn get_by_str_path(&self, path: &str) -> Option<Arc<dyn ValueOrContainer>> {
667        self.doc
668            .get_by_str_path(path)
669            .map(|v| Arc::new(v) as Arc<dyn ValueOrContainer>)
670    }
671
672    pub fn get_cursor_pos(
673        &self,
674        cursor: &Cursor,
675    ) -> Result<PosQueryResult, CannotFindRelativePosition> {
676        let loro::cursor::PosQueryResult { update, current } = self.doc.get_cursor_pos(cursor)?;
677        Ok(PosQueryResult {
678            current: AbsolutePosition {
679                pos: current.pos as u32,
680                side: current.side,
681            },
682            update: update.map(|x| Arc::new(x.into())),
683        })
684    }
685
686    /// Whether the history cache is built.
687    #[inline]
688    pub fn has_history_cache(&self) -> bool {
689        self.doc.has_history_cache()
690    }
691
692    /// Free the history cache that is used for making checkout faster.
693    ///
694    /// If you use checkout that switching to an old/concurrent version, the history cache will be built.
695    /// You can free it by calling this method.
696    #[inline]
697    pub fn free_history_cache(&self) {
698        self.doc.free_history_cache()
699    }
700
701    /// Free the cached diff calculator that is used for checkout.
702    #[inline]
703    pub fn free_diff_calculator(&self) {
704        self.doc.free_diff_calculator()
705    }
706
707    /// Encoded all ops and history cache to bytes and store them in the kv store.
708    ///
709    /// The parsed ops will be dropped
710    #[inline]
711    pub fn compact_change_store(&self) {
712        self.doc.compact_change_store()
713    }
714
715    /// Export the document in the given mode.
716    pub fn export(&self, mode: ExportMode) -> Result<Vec<u8>, LoroEncodeError> {
717        self.doc.export(mode.into())
718    }
719
720    pub fn export_updates_in_range(&self, spans: &[IdSpan]) -> Result<Vec<u8>, LoroEncodeError> {
721        self.doc.export(loro::ExportMode::UpdatesInRange {
722            spans: Cow::Borrowed(spans),
723        })
724    }
725
726    pub fn export_shallow_snapshot(
727        &self,
728        frontiers: &Frontiers,
729    ) -> Result<Vec<u8>, LoroEncodeError> {
730        self.doc
731            .export(loro::ExportMode::ShallowSnapshot(Cow::Owned(
732                frontiers.into(),
733            )))
734    }
735
736    pub fn export_state_only(
737        &self,
738        frontiers: Option<Arc<Frontiers>>,
739    ) -> Result<Vec<u8>, LoroEncodeError> {
740        self.doc
741            .export(loro::ExportMode::StateOnly(frontiers.map(|x| {
742                let a = x.as_ref();
743                Cow::Owned(loro::Frontiers::from(a))
744            })))
745    }
746
747    // TODO: impl
748    /// Analyze the container info of the doc
749    ///
750    /// This is used for development and debugging. It can be slow.
751    pub fn analyze(&self) -> DocAnalysis {
752        self.doc.analyze()
753    }
754
755    /// Get the path from the root to the container
756    pub fn get_path_to_container(&self, id: &ContainerID) -> Option<Vec<ContainerPath>> {
757        self.doc.get_path_to_container(&id.into()).map(|x| {
758            x.into_iter()
759                .map(|(id, idx)| ContainerPath {
760                    id: id.into(),
761                    path: (&idx).into(),
762                })
763                .collect()
764        })
765    }
766
767    /// Evaluate a JSONPath expression on the document and return matching values or handlers.
768    ///
769    /// This method allows querying the document structure using JSONPath syntax.
770    /// It returns a vector of `ValueOrHandler` which can represent either primitive values
771    /// or container handlers, depending on what the JSONPath expression matches.
772    ///
773    /// # Arguments
774    ///
775    /// * `path` - A string slice containing the JSONPath expression to evaluate.
776    ///
777    /// # Returns
778    ///
779    /// A `Result` containing either:
780    /// - `Ok(Vec<ValueOrHandler>)`: A vector of matching values or handlers.
781    /// - `Err(String)`: An error message if the JSONPath expression is invalid or evaluation fails.
782    #[inline]
783    pub fn jsonpath(&self, path: &str) -> Result<Vec<Arc<dyn ValueOrContainer>>, JsonPathError> {
784        self.doc.jsonpath(path).map(|vec| {
785            vec.into_iter()
786                .map(|v| Arc::new(v) as Arc<dyn ValueOrContainer>)
787                .collect()
788        })
789    }
790
791    /// Subscribe to updates that may affect the given JSONPath query.
792    ///
793    /// The callback may fire false positives; it is intended as a lightweight signal so callers
794    /// can debounce or throttle before running an expensive JSONPath query themselves.
795    #[inline]
796    pub fn subscribe_jsonpath(
797        &self,
798        path: &str,
799        callback: Arc<dyn JsonPathSubscriber>,
800    ) -> LoroResult<Arc<Subscription>> {
801        let callback: SubscribeJsonPathCallback = Arc::new(move || {
802            callback.on_jsonpath_changed();
803        });
804
805        self.doc
806            .subscribe_jsonpath(path, callback)
807            .map(|subscription| Arc::new(subscription.into()))
808    }
809
810    pub fn travel_change_ancestors(
811        &self,
812        ids: &[ID],
813        f: Arc<dyn ChangeAncestorsTraveler>,
814    ) -> Result<(), ChangeTravelError> {
815        self.doc
816            .travel_change_ancestors(ids, &mut |change| match f.travel(change.into()) {
817                true => ControlFlow::Continue(()),
818                false => ControlFlow::Break(()),
819            })
820    }
821
822    pub fn get_changed_containers_in(&self, id: ID, len: u32) -> Vec<ContainerID> {
823        self.doc
824            .get_changed_containers_in(id, len as usize)
825            .into_iter()
826            .map(|x| x.into())
827            .collect()
828    }
829
830    pub fn is_shallow(&self) -> bool {
831        self.doc.is_shallow()
832    }
833
834    pub fn get_pending_txn_len(&self) -> u32 {
835        self.doc.get_pending_txn_len() as u32
836    }
837
838    /// Find the operation id spans that between the `from` version and the `to` version.
839    #[inline]
840    pub fn find_id_spans_between(&self, from: &Frontiers, to: &Frontiers) -> VersionVectorDiff {
841        self.doc
842            .find_id_spans_between(&from.into(), &to.into())
843            .into()
844    }
845
846    /// Revert the current document state back to the target version
847    ///
848    /// Internally, it will generate a series of local operations that can revert the
849    /// current doc to the target version. It will calculate the diff between the current
850    /// state and the target state, and apply the diff to the current state.
851    #[inline]
852    pub fn revert_to(&self, version: &Frontiers) -> LoroResult<()> {
853        self.doc.revert_to(&version.into())
854    }
855
856    /// Apply a diff to the current document state.
857    ///
858    /// Internally, it will apply the diff to the current state.
859    #[inline]
860    pub fn apply_diff(&self, diff: &DiffBatch) -> LoroResult<()> {
861        self.doc.apply_diff(diff.clone().into())
862    }
863
864    /// Calculate the diff between two versions
865    #[inline]
866    pub fn diff(&self, a: &Frontiers, b: &Frontiers) -> LoroResult<Arc<DiffBatch>> {
867        self.doc
868            .diff(&a.into(), &b.into())
869            .map(|x| Arc::new(x.into()))
870    }
871
872    /// Check if the doc contains the target container.
873    ///
874    /// A root container always exists, while a normal container exists
875    /// if it has ever been created on the doc.
876    pub fn has_container(&self, id: &ContainerID) -> bool {
877        self.doc.has_container(&id.into())
878    }
879
880    /// Subscribe to the first commit from a peer. Operations performed on the `LoroDoc` within this callback
881    /// will be merged into the current commit.
882    ///
883    /// This is useful for managing the relationship between `PeerID` and user information.
884    /// For example, you could store user names in a `LoroMap` using `PeerID` as the key and the `UserID` as the value.
885    pub fn subscribe_first_commit_from_peer(
886        &self,
887        subscriber: Arc<dyn FirstCommitFromPeerCallback>,
888    ) -> Arc<Subscription> {
889        let subscriber: loro::FirstCommitFromPeerCallback = Box::new(move |e| {
890            subscriber.on_first_commit_from_peer(FirstCommitFromPeerPayload { peer: e.peer });
891            true
892        });
893        Arc::new(self.doc.subscribe_first_commit_from_peer(subscriber).into())
894    }
895
896    /// Subscribe to the pre-commit event.
897    ///
898    /// The callback will be called when the changes are committed but not yet applied to the OpLog.
899    /// You can modify the commit message and timestamp in the callback by [`ChangeModifier`].
900    pub fn subscribe_pre_commit(&self, callback: Arc<dyn PreCommitCallback>) -> Arc<Subscription> {
901        let subscriber: loro::PreCommitCallback = Box::new(move |e| {
902            callback.on_pre_commit(PreCommitCallbackPayload {
903                change_meta: e.change_meta.clone().into(),
904                origin: e.origin.clone(),
905                modifier: Arc::new(ChangeModifier(e.modifier.clone())),
906            });
907            true
908        });
909        Arc::new(self.doc.subscribe_pre_commit(subscriber).into())
910    }
911
912    /// Set whether to hide empty root containers.
913    pub fn set_hide_empty_root_containers(&self, hide: bool) {
914        self.doc.set_hide_empty_root_containers(hide);
915    }
916
917    /// Delete all content from a root container and hide it from the document.
918    ///
919    /// When a root container is empty and hidden:
920    /// - It won't show up in `get_deep_value()` results
921    /// - It won't be included in document snapshots
922    ///
923    /// Only works on root containers (containers without parents).
924    pub fn delete_root_container(&self, cid: ContainerID) {
925        self.doc.delete_root_container(cid.into());
926    }
927}
928
929#[uniffi::trait_interface]
930pub trait ChangeAncestorsTraveler: Sync + Send {
931    fn travel(&self, change: ChangeMeta) -> bool;
932}
933
934impl Default for LoroDoc {
935    fn default() -> Self {
936        Self::new()
937    }
938}
939
940impl Deref for LoroDoc {
941    type Target = InnerLoroDoc;
942    fn deref(&self) -> &Self::Target {
943        &self.doc
944    }
945}
946
947pub struct ChangeMeta {
948    /// Lamport timestamp of the Change
949    pub lamport: Lamport,
950    /// The first Op id of the Change
951    pub id: ID,
952    /// [Unix time](https://en.wikipedia.org/wiki/Unix_time)
953    /// It is the number of seconds that have elapsed since 00:00:00 UTC on 1 January 1970.
954    pub timestamp: Timestamp,
955    /// The commit message of the change
956    pub message: Option<String>,
957    /// The dependencies of the first op of the change
958    pub deps: Arc<Frontiers>,
959    /// The total op num inside this change
960    pub len: u32,
961}
962
963impl From<loro::ChangeMeta> for ChangeMeta {
964    fn from(value: loro::ChangeMeta) -> Self {
965        Self {
966            lamport: value.lamport,
967            id: value.id,
968            timestamp: value.timestamp,
969            message: value.message.map(|x| (*x).to_string()),
970            deps: Arc::new(value.deps.into()),
971            len: value.len as u32,
972        }
973    }
974}
975
976pub struct ImportBlobMetadata {
977    /// The partial start version vector.
978    ///
979    /// Import blob includes all the ops from `partial_start_vv` to `partial_end_vv`.
980    /// However, it does not constitute a complete version vector, as it only contains counters
981    /// from peers included within the import blob.
982    pub partial_start_vv: Arc<VersionVector>,
983    /// The partial end version vector.
984    ///
985    /// Import blob includes all the ops from `partial_start_vv` to `partial_end_vv`.
986    /// However, it does not constitute a complete version vector, as it only contains counters
987    /// from peers included within the import blob.
988    pub partial_end_vv: Arc<VersionVector>,
989    pub start_timestamp: i64,
990    pub start_frontiers: Arc<Frontiers>,
991    pub end_timestamp: i64,
992    pub change_num: u32,
993    pub mode: String,
994}
995
996impl From<loro::ImportBlobMetadata> for ImportBlobMetadata {
997    fn from(value: loro::ImportBlobMetadata) -> Self {
998        Self {
999            partial_start_vv: Arc::new(value.partial_start_vv.into()),
1000            partial_end_vv: Arc::new(value.partial_end_vv.into()),
1001            start_timestamp: value.start_timestamp,
1002            start_frontiers: Arc::new(value.start_frontiers.into()),
1003            end_timestamp: value.end_timestamp,
1004            change_num: value.change_num,
1005            mode: value.mode.to_string(),
1006        }
1007    }
1008}
1009
1010pub struct CommitOptions {
1011    pub origin: Option<String>,
1012    pub immediate_renew: bool,
1013    pub timestamp: Option<Timestamp>,
1014    pub commit_msg: Option<String>,
1015}
1016
1017impl From<CommitOptions> for loro::CommitOptions {
1018    fn from(value: CommitOptions) -> Self {
1019        loro::CommitOptions {
1020            origin: value.origin.map(|x| x.into()),
1021            immediate_renew: value.immediate_renew,
1022            timestamp: value.timestamp,
1023            commit_msg: value.commit_msg.map(|x| x.into()),
1024        }
1025    }
1026}
1027
1028pub trait JsonSchemaLike {
1029    fn to_json_schema(&self) -> LoroResult<JsonSchema>;
1030}
1031
1032impl<T: TryInto<JsonSchema> + Clone> JsonSchemaLike for T {
1033    fn to_json_schema(&self) -> LoroResult<JsonSchema> {
1034        self.clone()
1035            .try_into()
1036            .map_err(|_| LoroError::InvalidJsonSchema)
1037    }
1038}
1039
1040#[uniffi::trait_interface]
1041pub trait JsonPathSubscriber: Sync + Send {
1042    fn on_jsonpath_changed(&self);
1043}
1044
1045#[uniffi::trait_interface]
1046pub trait LocalUpdateCallback: Sync + Send {
1047    fn on_local_update(&self, update: Vec<u8>);
1048}
1049
1050#[uniffi::trait_interface]
1051pub trait FirstCommitFromPeerCallback: Sync + Send {
1052    fn on_first_commit_from_peer(&self, e: FirstCommitFromPeerPayload);
1053}
1054
1055pub struct FirstCommitFromPeerPayload {
1056    pub peer: PeerID,
1057}
1058
1059#[uniffi::trait_interface]
1060pub trait PreCommitCallback: Sync + Send {
1061    fn on_pre_commit(&self, e: PreCommitCallbackPayload);
1062}
1063
1064pub struct PreCommitCallbackPayload {
1065    pub change_meta: ChangeMeta,
1066    pub origin: String,
1067    pub modifier: Arc<ChangeModifier>,
1068}
1069
1070pub struct ChangeModifier(loro::ChangeModifier);
1071
1072impl ChangeModifier {
1073    pub fn set_message(&self, msg: &str) {
1074        self.0.set_message(msg);
1075    }
1076    pub fn set_timestamp(&self, timestamp: Timestamp) {
1077        self.0.set_timestamp(timestamp);
1078    }
1079}
1080
1081#[uniffi::trait_interface]
1082pub trait Unsubscriber: Sync + Send {
1083    fn on_unsubscribe(&self);
1084}
1085
1086/// A handle to a subscription created by GPUI. When dropped, the subscription
1087/// is cancelled and the callback will no longer be invoked.
1088pub struct Subscription(pub(crate) Mutex<Option<loro::Subscription>>);
1089
1090impl Subscription {
1091    /// Detaches the subscription from this handle. The callback will
1092    /// continue to be invoked until the doc has been subscribed to
1093    /// are dropped
1094    pub fn detach(self: Arc<Self>) {
1095        let s = self.0.lock().unwrap().take().unwrap();
1096        s.detach();
1097    }
1098
1099    /// Unsubscribes the subscription.
1100    pub fn unsubscribe(self: Arc<Self>) {
1101        let s = self.0.lock().unwrap().take().unwrap();
1102        s.unsubscribe();
1103    }
1104}
1105
1106impl std::fmt::Debug for Subscription {
1107    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1108        f.write_str("Subscription")
1109    }
1110}
1111
1112impl From<loro::Subscription> for Subscription {
1113    fn from(value: loro::Subscription) -> Self {
1114        Self(Mutex::new(Some(value)))
1115    }
1116}
1117
1118pub struct PosQueryResult {
1119    pub update: Option<Arc<Cursor>>,
1120    pub current: AbsolutePosition,
1121}
1122
1123pub enum ExportMode {
1124    Snapshot,
1125    Updates { from: Arc<VersionVector> },
1126    UpdatesInRange { spans: Vec<IdSpan> },
1127    ShallowSnapshot { frontiers: Arc<Frontiers> },
1128    StateOnly { frontiers: Option<Arc<Frontiers>> },
1129    SnapshotAt { frontiers: Arc<Frontiers> },
1130}
1131
1132impl From<ExportMode> for loro::ExportMode<'static> {
1133    fn from(value: ExportMode) -> Self {
1134        match value {
1135            ExportMode::Snapshot => loro::ExportMode::Snapshot,
1136            ExportMode::Updates { from } => loro::ExportMode::Updates {
1137                from: Cow::Owned(from.as_ref().into()),
1138            },
1139            ExportMode::UpdatesInRange { spans } => loro::ExportMode::UpdatesInRange {
1140                spans: Cow::Owned(spans),
1141            },
1142            ExportMode::ShallowSnapshot { frontiers } => {
1143                loro::ExportMode::ShallowSnapshot(Cow::Owned(frontiers.as_ref().into()))
1144            }
1145            ExportMode::StateOnly { frontiers } => {
1146                loro::ExportMode::StateOnly(frontiers.map(|x| Cow::Owned(x.as_ref().into())))
1147            }
1148            ExportMode::SnapshotAt { frontiers } => loro::ExportMode::SnapshotAt {
1149                version: Cow::Owned(frontiers.as_ref().into()),
1150            },
1151        }
1152    }
1153}
1154
1155pub struct ContainerPath {
1156    pub id: ContainerID,
1157    pub path: Index,
1158}
1159
1160pub struct ImportStatus {
1161    pub success: HashMap<u64, CounterSpan>,
1162    pub pending: Option<HashMap<u64, CounterSpan>>,
1163}
1164
1165impl From<loro::ImportStatus> for ImportStatus {
1166    fn from(value: loro::ImportStatus) -> Self {
1167        let a = &value.success;
1168        Self {
1169            success: vr_to_map(a),
1170            pending: value.pending.as_ref().map(vr_to_map),
1171        }
1172    }
1173}
1174
1175fn vr_to_map(a: &loro::VersionRange) -> HashMap<u64, CounterSpan> {
1176    a.iter()
1177        .map(|x| {
1178            (
1179                *x.0,
1180                CounterSpan {
1181                    start: x.1 .0,
1182                    end: x.1 .1,
1183                },
1184            )
1185        })
1186        .collect()
1187}
1188
1189pub struct FrontiersOrID {
1190    pub frontiers: Option<Arc<Frontiers>>,
1191    pub id: Option<ID>,
1192}