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