namespace loro{
/// Decodes the metadata for an imported blob from the provided bytes.
[Throws=LoroError]
ImportBlobMetadata decode_import_blob_meta([ByRef] bytes bytes, boolean check_checksum);
string get_version();
};
// ============= Traits =============
[Trait]
interface ValueOrContainer{
boolean is_value();
boolean is_container();
ContainerType? container_type();
LoroValue? as_value();
ContainerID? as_container();
LoroText? as_loro_text();
LoroList? as_loro_list();
LoroMap? as_loro_map();
LoroTree? as_loro_tree();
LoroCounter? as_loro_counter();
LoroMovableList? as_loro_movable_list();
LoroUnknown? as_loro_unknown();
};
[Trait, WithForeign]
interface LoroValueLike{
LoroValue as_loro_value();
};
[Trait, WithForeign]
interface ContainerIdLike{
ContainerID as_container_id(ContainerType ty);
};
[Trait, WithForeign]
interface Subscriber{
void on_diff(DiffEvent diff);
};
[Trait, WithForeign]
interface LocalUpdateCallback{
void on_local_update(bytes update);
};
[Trait, WithForeign]
interface JsonPathSubscriber{
/// Called when a change may affect the subscribed JSONPath query.
void on_jsonpath_changed();
};
[Trait, WithForeign]
interface Unsubscriber{
void on_unsubscribe();
};
[Trait, WithForeign]
interface OnPush{
UndoItemMeta on_push(UndoOrRedo undo_or_redo, CounterSpan span, DiffEvent? diff_event);
};
[Trait, WithForeign]
interface OnPop{
void on_pop(UndoOrRedo undo_or_redo, CounterSpan span, UndoItemMeta undo_meta);
};
// [Trait, WithForeign]
// interface JsonSchemaLike{
// [Throws=LoroError]
// JsonSchema into_json_schema();
// };
[Trait, WithForeign]
interface ChangeAncestorsTraveler{
boolean travel(ChangeMeta change);
};
[Trait, WithForeign]
interface FirstCommitFromPeerCallback{
void on_first_commit_from_peer(FirstCommitFromPeerPayload payload);
};
[Trait, WithForeign]
interface PreCommitCallback{
void on_pre_commit(PreCommitCallbackPayload payload);
};
[Trait, WithForeign]
interface LocalEphemeralListener{
void on_ephemeral_update(bytes update);
};
[Trait, WithForeign]
interface EphemeralSubscriber{
void on_ephemeral_event(EphemeralStoreEvent event);
};
// ============= LORO DOC =============
/// `LoroDoc` is the entry for the whole document.
/// When it's dropped, all the associated [`Handler`]s will be invalidated.
///
/// **Important:** Loro is a pure library and does not handle network protocols.
/// It is the responsibility of the user to manage the storage, loading, and synchronization
/// of the bytes exported by Loro in a manner suitable for their specific environment.
interface LoroDoc{
/// Create a new `LoroDoc` instance.
constructor();
/// Duplicate the document with a different PeerID
///
/// The time complexity and space complexity of this operation are both O(n),
///
/// When called in detached mode, it will fork at the current state frontiers.
/// It will have the same effect as `fork_at(&self.state_frontiers())`.
LoroDoc fork();
/// Fork the document at the given frontiers.
///
/// The created doc will only contain the history before the specified frontiers.
[Throws=LoroError]
LoroDoc fork_at([ByRef] Frontiers frontiers);
/// Get the configurations of the document.
Configure config();
/// Get `Change` at the given id.
///
/// `Change` is a grouped continuous operations that share the same id, timestamp, commit message.
///
/// - The id of the `Change` is the id of its first op.
/// - The second op's id is `{ peer: change.id.peer, counter: change.id.counter + 1 }`
///
/// The same applies on `Lamport`:
///
/// - The lamport of the `Change` is the lamport of its first op.
/// - The second op's lamport is `change.lamport + 1`
///
/// The length of the `Change` is how many operations it contains
ChangeMeta? get_change(ID id);
/// Set whether to record the timestamp of each change. Default is `false`.
///
/// If enabled, the Unix timestamp will be recorded for each change automatically.
///
/// You can set each timestamp manually when committing a change.
///
/// NOTE: Timestamps are forced to be in ascending order.
/// If you commit a new change with a timestamp that is less than the existing one,
/// the largest existing timestamp will be used instead.
void set_record_timestamp(boolean record);
/// Set the interval of mergeable changes, **in seconds**.
///
/// If two continuous local changes are within the interval, they will be merged into one change.
/// The default value is 1000 seconds.
///
/// By default, we record timestamps in seconds for each change. So if the merge interval is 1, and changes A and B
/// have timestamps of 3 and 4 respectively, then they will be merged into one change
void set_change_merge_interval(i64 interval);
/// Set the rich text format configuration of the document.
///
/// You need to config it if you use rich text `mark` method.
/// Specifically, you need to config the `expand` property of each style.
///
/// Expand is used to specify the behavior of expanding when new text is inserted at the
/// beginning or end of the style.
void config_text_style(StyleConfigMap text_style);
/// Configures the default text style for the document.
///
/// This method sets the default text style configuration for the document when using LoroText.
/// If `None` is provided, the default style is reset.
///
/// # Parameters
///
/// - `text_style`: The style configuration to set as the default. `None` to reset.
void config_default_text_style(StyleConfig? text_style);
/// Attach the document state to the latest known version.
///
/// > The document becomes detached during a `checkout` operation.
/// > Being `detached` implies that the `DocState` is not synchronized with the latest version of the `OpLog`.
/// > In a detached state, the document is not editable, and any `import` operations will be
/// > recorded in the `OpLog` without being applied to the `DocState`.
void attach();
/// Checkout the `DocState` to a specific version.
///
/// The document becomes detached during a `checkout` operation.
/// Being `detached` implies that the `DocState` is not synchronized with the latest version of the `OpLog`.
/// In a detached state, the document is not editable, and any `import` operations will be
/// recorded in the `OpLog` without being applied to the `DocState`.
///
/// You should call `attach` to attach the `DocState` to the latest version of `OpLog`.
[Throws=LoroError]
void checkout([ByRef] Frontiers frontiers);
/// Checkout the `DocState` to the latest version.
///
/// > The document becomes detached during a `checkout` operation.
/// > Being `detached` implies that the `DocState` is not synchronized with the latest version of the `OpLog`.
/// > In a detached state, the document is not editable, and any `import` operations will be
/// > recorded in the `OpLog` without being applied to the `DocState`.
///
/// This has the same effect as `attach`.
void checkout_to_latest();
/// Compare the frontiers with the current OpLog's version.
///
/// If `other` contains any version that's not contained in the current OpLog, return [Ordering::Less].
Ordering cmp_with_frontiers([ByRef] Frontiers other);
// cmp_frontiers();
/// Force the document enter the detached mode.
///
/// In this mode, when you importing new updates, the [loro_internal::DocState] will not be changed.
///
/// Learn more at https://loro.dev/docs/advanced/doc_state_and_oplog#attacheddetached-status
void detach();
/// Import a batch of updates/snapshot.
///
/// The data can be in arbitrary order. The import result will be the same.
[Throws=LoroError]
ImportStatus import_batch([ByRef] sequence<bytes> bytes);
/// Get a [LoroMovableList] by container id.
///
/// If the provided id is string, it will be converted into a root container id with the name of the string.
LoroMovableList get_movable_list(ContainerIdLike id);
/// Try to get a [LoroMovableList] by container id.
///
/// Returns null if the container does not exist.
LoroMovableList? try_get_movable_list(ContainerIdLike id);
/// Get a [LoroList] by container id.
///
/// If the provided id is string, it will be converted into a root container id with the name of the string.
LoroList get_list(ContainerIdLike id);
/// Try to get a [LoroList] by container id.
///
/// Returns null if the container does not exist.
LoroList? try_get_list(ContainerIdLike id);
/// Get a [LoroMap] by container id.
///
/// If the provided id is string, it will be converted into a root container id with the name of the string.
LoroMap get_map(ContainerIdLike id);
/// Try to get a [LoroMap] by container id.
///
/// Returns null if the container does not exist.
LoroMap? try_get_map(ContainerIdLike id);
/// Get a [LoroText] by container id.
///
/// If the provided id is string, it will be converted into a root container id with the name of the string.
LoroText get_text(ContainerIdLike id);
/// Try to get a [LoroText] by container id.
///
/// Returns null if the container does not exist.
LoroText? try_get_text(ContainerIdLike id);
/// Get a [LoroTree] by container id.
///
/// If the provided id is string, it will be converted into a root container id with the name of the string.
LoroTree get_tree(ContainerIdLike id);
/// Try to get a [LoroTree] by container id.
///
/// Returns null if the container does not exist.
LoroTree? try_get_tree(ContainerIdLike id);
/// Get a [LoroCounter] by container id.
///
/// If the provided id is string, it will be converted into a root container id with the name of the string.
LoroCounter get_counter(ContainerIdLike id);
/// Try to get a [LoroCounter] by container id.
///
/// Returns null if the container does not exist.
LoroCounter? try_get_counter(ContainerIdLike id);
/// Get a container by container id.
ValueOrContainer? get_container([ByRef] ContainerID id);
/// Commit the cumulative auto commit transaction.
///
/// There is a transaction behind every operation.
/// The events will be emitted after a transaction is committed. A transaction is committed when:
///
/// - `doc.commit()` is called.
/// - `doc.export(mode)` is called.
/// - `doc.import(data)` is called.
/// - `doc.checkout(version)` is called.
void commit();
void commit_with(CommitOptions options);
/// Set commit message for the current uncommitted changes
///
/// It will be persisted.
void set_next_commit_message([ByRef] string msg);
/// Set `origin` for the current uncommitted changes, it can be used to track the source of changes in an event.
///
/// It will NOT be persisted.
void set_next_commit_origin([ByRef] string origin);
/// Set the timestamp of the next commit.
///
/// It will be persisted and stored in the `OpLog`.
/// You can get the timestamp from the [`Change`] type.
void set_next_commit_timestamp(i64 timestamp);
/// Set the options of the next commit.
///
/// It will be used when the next commit is performed.
void set_next_commit_options(CommitOptions options);
/// Clear the options of the next commit.
void clear_next_commit_options();
/// Whether the document is in detached mode, where the [loro_internal::DocState] is not
/// synchronized with the latest version of the [loro_internal::OpLog].
boolean is_detached();
/// Import updates/snapshot exported by [`LoroDoc::export_snapshot`] or [`LoroDoc::export_from`].
[Throws=LoroError]
ImportStatus import([ByRef]bytes bytes);
/// Import updates/snapshot exported by [`LoroDoc::export_snapshot`] or [`LoroDoc::export_from`].
///
/// It marks the import with a custom `origin` string. It can be used to track the import source
/// in the generated events.
[Throws=LoroError]
ImportStatus import_with([ByRef] bytes bytes, [ByRef] string origin);
[Throws=LoroError]
ImportStatus import_json_updates([ByRef]string json);
/// Export the current state with json-string format of the document.
string export_json_updates([ByRef]VersionVector start_vv, [ByRef]VersionVector end_vv);
/// Export all the ops not included in the given `VersionVector`
[Throws=LoroEncodeError]
bytes export_updates([ByRef] VersionVector vv);
/// Export the current state and history of the document.
[Throws=LoroEncodeError]
bytes export_snapshot();
/// Convert `Frontiers` into `VersionVector`
VersionVector? frontiers_to_vv([ByRef] Frontiers frontiers);
/// Minimize the frontiers by removing the unnecessary entries.
FrontiersOrID minimize_frontiers([ByRef] Frontiers frontiers);
/// Convert `VersionVector` into `Frontiers`
Frontiers vv_to_frontiers([ByRef] VersionVector vv);
// with_oplog
/// Get the `VersionVector` version of `OpLog`
VersionVector oplog_vv();
/// Get the `VersionVector` version of `DocState`
VersionVector state_vv();
/// Get the `VersionVector` of trimmed history
///
/// The ops included by the trimmed history are not in the doc.
VersionVector shallow_since_vv();
/// Get the total number of operations in the `OpLog`
u64 len_ops();
/// Get the total number of changes in the `OpLog`
u64 len_changes();
/// Get the shallow value of the document.
LoroValue get_value();
/// Get the entire state of the current DocState
LoroValue get_deep_value();
/// Get the entire state of the current DocState with container id
LoroValue get_deep_value_with_id();
/// Get the `Frontiers` version of `OpLog`
Frontiers oplog_frontiers();
/// Get the `Frontiers` version of `DocState`
///
/// Learn more about [`Frontiers`](https://loro.dev/docs/advanced/version_deep_dive)
Frontiers state_frontiers();
/// Get the PeerID
u64 peer_id();
/// Change the PeerID
///
/// NOTE: You need to make sure there is no chance two peer have the same PeerID.
/// If it happens, the document will be corrupted.
[Throws=LoroError]
void set_peer_id(u64 peer);
/// Subscribe the events of a container.
///
/// The callback will be invoked when the container is changed.
/// Returns a subscription that can be used to unsubscribe.
///
/// The events will be emitted after a transaction is committed. A transaction is committed when:
///
/// - `doc.commit()` is called.
/// - `doc.export(mode)` is called.
/// - `doc.import(data)` is called.
/// - `doc.checkout(version)` is called.
Subscription subscribe([ByRef] ContainerID container_id, Subscriber subscriber);
/// Subscribe all the events.
///
/// The callback will be invoked when any part of the [loro_internal::DocState] is changed.
/// Returns a subscription that can be used to unsubscribe.
Subscription subscribe_root(Subscriber subscriber);
/// Subscribe the local update of the document.
Subscription subscribe_local_update(LocalUpdateCallback callback);
/// Check the correctness of the document state by comparing it with the state
/// calculated by applying all the history.
void check_state_correctness_slow();
/// Get the handler by the path.
ValueOrContainer? get_by_path([ByRef] sequence<Index> path);
/// The path can be specified in different ways depending on the container type:
///
/// For Tree:
/// 1. Using node IDs: `tree/{node_id}/property`
/// 2. Using indices: `tree/0/1/property`
///
/// For List and MovableList:
/// - Using indices: `list/0` or `list/1/property`
///
/// For Map:
/// - Using keys: `map/key` or `map/nested/property`
///
/// For tree structures, index-based paths follow depth-first traversal order.
/// The indices start from 0 and represent the position of a node among its siblings.
///
/// # Examples
/// ```
/// # use loro::{LoroDoc, LoroValue};
/// let doc = LoroDoc::new();
///
/// // Tree example
/// let tree = doc.get_tree("tree");
/// let root = tree.create(None).unwrap();
/// tree.get_meta(root).unwrap().insert("name", "root").unwrap();
/// // Access tree by ID or index
/// let name1 = doc.get_by_str_path(&format!("tree/{}/name", root)).unwrap().into_value().unwrap();
/// let name2 = doc.get_by_str_path("tree/0/name").unwrap().into_value().unwrap();
/// assert_eq!(name1, name2);
///
/// // List example
/// let list = doc.get_list("list");
/// list.insert(0, "first").unwrap();
/// list.insert(1, "second").unwrap();
/// // Access list by index
/// let item = doc.get_by_str_path("list/0");
/// assert_eq!(item.unwrap().into_value().unwrap().into_string().unwrap(), "first".into());
///
/// // Map example
/// let map = doc.get_map("map");
/// map.insert("key", "value").unwrap();
/// // Access map by key
/// let value = doc.get_by_str_path("map/key");
/// assert_eq!(value.unwrap().into_value().unwrap().into_string().unwrap(), "value".into());
///
/// // MovableList example
/// let mlist = doc.get_movable_list("mlist");
/// mlist.insert(0, "item").unwrap();
/// // Access movable list by index
/// let item = doc.get_by_str_path("mlist/0");
/// assert_eq!(item.unwrap().into_value().unwrap().into_string().unwrap(), "item".into());
/// ```
ValueOrContainer? get_by_str_path([ByRef] string path);
[Throws=CannotFindRelativePosition]
PosQueryResult get_cursor_pos([ByRef]Cursor cursor);
boolean has_history_cache();
/// Free the history cache that is used for making checkout faster.
///
/// If you use checkout that switching to an old/concurrent version, the history cache will be built.
/// You can free it by calling this method.
void free_history_cache();
/// Free the cached diff calculator that is used for checkout.
void free_diff_calculator();
/// Encoded all ops and history cache to bytes and store them in the kv store.
///
/// The parsed ops will be dropped
void compact_change_store();
/// Export the document in the given mode.
[Throws=LoroEncodeError]
bytes export(ExportMode mode);
[Throws=LoroEncodeError]
bytes export_updates_in_range([ByRef]sequence<IdSpan> spans);
[Throws=LoroEncodeError]
bytes export_shallow_snapshot([ByRef]Frontiers frontiers);
[Throws=LoroEncodeError]
bytes export_snapshot_at([ByRef]Frontiers frontiers);
[Throws=LoroEncodeError]
bytes export_state_only(Frontiers? frontiers);
// /// Analyze the container info of the doc
// ///
// /// This is used for development and debugging. It can be slow.
// DocAnalysis analyze();
/// Get the path from the root to the container
sequence<ContainerPath>? get_path_to_container([ByRef] ContainerID id);
/// Evaluate a JSONPath expression on the document and return matching values or handlers.
///
/// This method allows querying the document structure using JSONPath syntax.
/// It returns a vector of `ValueOrHandler` which can represent either primitive values
/// or container handlers, depending on what the JSONPath expression matches.
///
/// # Arguments
///
/// * `path` - A string slice containing the JSONPath expression to evaluate.
///
/// # Returns
///
/// A `Result` containing either:
/// - `Ok(Vec<ValueOrHandler>)`: A vector of matching values or handlers.
/// - `Err(String)`: An error message if the JSONPath expression is invalid or evaluation fails.
///
/// # Example
///
/// ```
/// # use loro::LoroDoc;
/// let doc = LoroDoc::new();
/// let map = doc.get_map("users");
/// map.insert("alice", 30).unwrap();
/// map.insert("bob", 25).unwrap();
///
/// let result = doc.jsonpath("$.users.alice").unwrap();
/// assert_eq!(result.len(), 1);
/// assert_eq!(result[0].to_json_value(), serde_json::json!(30));
/// ```
[Throws=JsonPathError]
sequence<ValueOrContainer> jsonpath([ByRef] string path);
/// Subscribe to updates that might affect the given JSONPath query.
///
/// The callback may fire false positives; it is intended as a lightweight notification so
/// callers can debounce or throttle before re-running JSONPath themselves.
[Throws=LoroError]
Subscription subscribe_jsonpath([ByRef] string path, JsonPathSubscriber callback);
/// Traverses the ancestors of the Change containing the given ID, including itself.
///
/// This method visits all ancestors in causal order, from the latest to the oldest,
/// based on their Lamport timestamps.
///
/// # Arguments
///
/// * `ids` - The IDs of the Change to start the traversal from.
/// * `f` - A mutable function that is called for each ancestor. It can return `ControlFlow::Break(())` to stop the traversal.
[Throws=ChangeTravelError]
void travel_change_ancestors([ByRef] sequence<ID> ids, ChangeAncestorsTraveler f);
/// Gets container IDs modified in the given ID range.
///
/// **NOTE:** This method will implicitly commit.
///
/// This method can be used in conjunction with `doc.travel_change_ancestors()` to traverse
/// the history and identify all changes that affected specific containers.
///
/// # Arguments
///
/// * `id` - The starting ID of the change range
/// * `len` - The length of the change range to check
sequence<ContainerID> get_changed_containers_in(ID id, u32 len);
/// Check if the doc contains the full history.
boolean is_shallow();
/// Get the number of operations in the pending transaction.
///
/// The pending transaction is the one that is not committed yet. It will be committed
/// after calling `doc.commit()`, `doc.export(mode)` or `doc.checkout(version)`.
u32 get_pending_txn_len();
/// Export the current state with json-string format of the document, without peer compression.
///
/// Compared to [`export_json_updates`], this method does not compress the peer IDs in the updates.
/// So the operations are easier to be processed by application code.
string export_json_updates_without_peer_compression([ByRef] VersionVector start_vv, [ByRef] VersionVector end_vv);
/// Exports changes within the specified ID span to JSON schema format.
///
/// The JSON schema format produced by this method is identical to the one generated by `export_json_updates`.
/// It ensures deterministic output, making it ideal for hash calculations and integrity checks.
///
/// This method can also export pending changes from the uncommitted transaction that have not yet been applied to the OpLog.
///
/// This method will NOT trigger a new commit implicitly.
sequence<string> export_json_in_id_span(IdSpan id_span);
/// Find the operation id spans that between the `from` version and the `to` version.
VersionVectorDiff find_id_spans_between([ByRef] Frontiers from, [ByRef] Frontiers to);
/// Revert the current document state back to the target version
///
/// Internally, it will generate a series of local operations that can revert the
/// current doc to the target version. It will calculate the diff between the current
/// state and the target state, and apply the diff to the current state.
[Throws=LoroError]
void revert_to([ByRef] Frontiers version);
/// Apply a diff to the current document state.
///
/// Internally, it will apply the diff to the current state.
[Throws=LoroError]
void apply_diff([ByRef] DiffBatch diff);
/// Calculate the diff between two versions
[Throws=LoroError]
DiffBatch diff([ByRef] Frontiers a, [ByRef] Frontiers b);
/// Check if the doc contains the target container.
///
/// A root container always exists, while a normal container exists
/// if it has ever been created on the doc.
boolean has_container([ByRef] ContainerID id);
/// Subscribe to the first commit from a peer. Operations performed on the `LoroDoc` within this callback
/// will be merged into the current commit.
///
/// This is useful for managing the relationship between `PeerID` and user information.
/// For example, you could store user names in a `LoroMap` using `PeerID` as the key and the `UserID` as the value.
Subscription subscribe_first_commit_from_peer(FirstCommitFromPeerCallback callback);
/// Subscribe to the pre-commit event.
///
/// The callback will be called when the changes are committed but not yet applied to the OpLog.
/// You can modify the commit message and timestamp in the callback by [`ChangeModifier`].
Subscription subscribe_pre_commit(PreCommitCallback callback);
/// Redacts sensitive content in JSON updates within the specified version range.
///
/// This function allows you to share document history while removing potentially sensitive content.
/// It preserves the document structure and collaboration capabilities while replacing content with
/// placeholders according to these redaction rules:
///
/// - Preserves delete and move operations
/// - Replaces text insertion content with the Unicode replacement character
/// - Substitutes list and map insert values with null
/// - Maintains structure of child containers
/// - Replaces text mark values with null
/// - Preserves map keys and text annotation keys
[Throws=LoroError]
string redact_json_updates([ByRef] string json, [ByRef] VersionRange version_range);
/// Set whether to hide empty root containers.
void set_hide_empty_root_containers(boolean hide);
/// Delete all content from a root container and hide it from the document.
///
/// When a root container is empty and hidden:
/// - It won't show up in `get_deep_value()` results
/// - It won't be included in document snapshots
///
/// Only works on root containers (containers without parents).
void delete_root_container(ContainerID cid);
};
[Remote]
dictionary ContainerPath{
ContainerID id;
Index path;
};
// ============= CONTAINERS =============
[Traits=(Display)]
interface LoroText{
/// Create a new container that is detached from the document.
///
/// The edits on a detached container will not be persisted.
/// To attach the container to the document, please insert it into an attached container.
constructor();
/// Whether the container is attached to a document
///
/// The edits on a detached container will not be persisted.
/// To attach the container to the document, please insert it into an attached container.
boolean is_attached();
/// If a detached container is attached, this method will return its corresponding attached handler.
LoroText? get_attached();
/// Get the [ContainerID] of the text container.
ContainerID id();
/// Insert a string at the given unicode position.
[Throws=LoroError]
void insert(u32 pos, [ByRef] string s);
/// Insert a string at the given utf-8 position.
[Throws=LoroError]
void insert_utf8(u32 pos, [ByRef] string s);
/// Insert a string at the given utf-16 position.
[Throws=LoroError]
void insert_utf16(u32 pos, [ByRef] string s);
/// Delete a range of text at the given unicode position with unicode length.
[Throws=LoroError]
void delete(u32 pos, u32 len);
/// Delete a range of text at the given utf-8 position with utf-8 length.
[Throws=LoroError]
void delete_utf8(u32 pos, u32 len);
/// Delete a range of text at the given utf-16 position with utf-16 length.
[Throws=LoroError]
void delete_utf16(u32 pos, u32 len);
/// Get a string slice at the given Unicode range
[Throws=LoroError]
string slice(u32 start_index, u32 end_index);
/// Get a string slice at the given UTF-16 range
[Throws=LoroError]
string slice_utf16(u32 start_index, u32 end_index);
/// Get the rich-text delta within a range.
[Throws=LoroError]
sequence<TextDelta> slice_delta(u32 start_index, u32 end_index, PosType pos_type);
/// Delete specified character and insert string at the same position at given unicode position.
[Throws=LoroError]
string splice(u32 pos, u32 len, [ByRef] string s);
/// Delete specified range and insert a string at the same UTF-16 position.
[Throws=LoroError]
void splice_utf16(u32 pos, u32 len, [ByRef] string s);
/// Get the characters at given unicode position.
[Throws=LoroError]
string char_at(u32 pos);
/// Whether the text container is empty.
boolean is_empty();
/// Get the length of the text container in UTF-8.
u32 len_utf8();
/// Get the length of the text container in Unicode.
u32 len_unicode();
/// Get the length of the text container in UTF-16.
u32 len_utf16();
/// Update the current text based on the provided text.
///
/// It will calculate the minimal difference and apply it to the current text.
/// It uses Myers' diff algorithm to compute the optimal difference.
///
/// This could take a long time for large texts (e.g. > 50_000 characters).
/// In that case, you should use `updateByLine` instead.
[Throws=UpdateTimeoutError]
void update([ByRef] string s, UpdateOptions options);
/// Apply a [delta](https://quilljs.com/docs/delta/) to the text container.
[Throws=LoroError]
void apply_delta(sequence<TextDelta> delta);
/// Update the current text based on the provided text.
///
/// This update calculation is line-based, which will be more efficient but less precise.
[Throws=UpdateTimeoutError]
void update_by_line([ByRef] string s, UpdateOptions options);
/// Mark a range of text with a key-value pair.
///
/// You can use it to create a highlight, make a range of text bold, or add a link to a range of text.
///
/// You can specify the `expand` option to set the behavior when inserting text at the boundary of the range.
///
/// - `after`(default): when inserting text right after the given range, the mark will be expanded to include the inserted text
/// - `before`: when inserting text right before the given range, the mark will be expanded to include the inserted text
/// - `none`: the mark will not be expanded to include the inserted text at the boundaries
/// - `both`: when inserting text either right before or right after the given range, the mark will be expanded to include the inserted text
///
/// *You should make sure that a key is always associated with the same expand type.*
///
/// Note: this is not suitable for unmergeable annotations like comments.
[Throws=LoroError]
void mark(u32 from, u32 to, [ByRef] string key, LoroValueLike value);
/// Mark a range of text with UTF-8 offsets.
[Throws=LoroError]
void mark_utf8(u32 from, u32 to, [ByRef] string key, LoroValueLike value);
/// Mark a range of text with UTF-16 offsets.
[Throws=LoroError]
void mark_utf16(u32 from, u32 to, [ByRef] string key, LoroValueLike value);
/// Unmark a range of text with a key and a value.
///
/// You can use it to remove highlights, bolds or links
///
/// You can specify the `expand` option to set the behavior when inserting text at the boundary of the range.
///
/// **Note: You should specify the same expand type as when you mark the text.**
///
/// - `after`(default): when inserting text right after the given range, the mark will be expanded to include the inserted text
/// - `before`: when inserting text right before the given range, the mark will be expanded to include the inserted text
/// - `none`: the mark will not be expanded to include the inserted text at the boundaries
/// - `both`: when inserting text either right before or right after the given range, the mark will be expanded to include the inserted text
///
/// *You should make sure that a key is always associated with the same expand type.*
///
/// Note: you cannot delete unmergeable annotations like comments by this method.
[Throws=LoroError]
void unmark(u32 from, u32 to, [ByRef] string key);
/// Unmark a UTF-16 range of text with a key.
[Throws=LoroError]
void unmark_utf16(u32 from, u32 to, [ByRef] string key);
/// Get the text in [Delta](https://quilljs.com/docs/delta/) format.
sequence<TextDelta> to_delta();
/// Get the text in [Delta](https://quilljs.com/docs/delta/) format.
LoroValue get_richtext_value();
/// Get the cursor at the given position in the given Unicode position..
///
/// Using "index" to denote cursor positions can be unstable, as positions may
/// shift with document edits. To reliably represent a position or range within
/// a document, it is more effective to leverage the unique ID of each item/character
/// in a List CRDT or Text CRDT.
///
/// Loro optimizes State metadata by not storing the IDs of deleted elements. This
/// approach complicates tracking cursors since they rely on these IDs. The solution
/// recalculates position by replaying relevant history to update stable positions
/// accurately. To minimize the performance impact of history replay, the system
/// updates cursor info to reference only the IDs of currently present elements,
/// thereby reducing the need for replay.
Cursor? get_cursor(u32 pos, Side side);
/// Whether the container is deleted.
boolean is_deleted();
/// Convert a position between coordinate systems (Unicode, UTF-16, UTF-8 bytes, Event).
u32? convert_pos(u32 index, PosType from, PosType to);
/// Push a string to the end of the text container.
[Throws=LoroError]
void push_str([ByRef] string s);
/// Get the editor of the text at the given position.
u64? get_editor_at_unicode_pos(u32 pos);
/// Get the LoroDoc from this container
LoroDoc? doc();
/// Subscribe the events of a container.
///
/// The callback will be invoked when the container is changed.
/// Returns a subscription that can be used to unsubscribe.
///
/// The events will be emitted after a transaction is committed. A transaction is committed when:
///
/// - `doc.commit()` is called.
/// - `doc.export(mode)` is called.
/// - `doc.import(data)` is called.
/// - `doc.checkout(version)` is called.
Subscription? subscribe(Subscriber subscriber);
};
interface LoroList{
/// Create a new container that is detached from the document.
///
/// The edits on a detached container will not be persisted.
/// To attach the container to the document, please insert it into an attached container.
constructor();
/// Whether the container is attached to a document
///
/// The edits on a detached container will not be persisted.
/// To attach the container to the document, please insert it into an attached container.
boolean is_attached();
/// If a detached container is attached, this method will return its corresponding attached handler.
LoroList? get_attached();
/// Insert a value at the given position.
[Throws=LoroError]
void insert(u32 pos, LoroValueLike v);
/// Delete values at the given position.
[Throws=LoroError]
void delete(u32 pos, u32 len);
/// Get the value at the given position.
ValueOrContainer? get(u32 index);
/// Get the deep value of the container.
LoroValue get_deep_value();
/// Get the shallow value of the container.
///
/// This does not convert the state of sub-containers; instead, it represents them as [LoroValue::Container].
LoroValue get_value();
/// Get the ID of the container.
ContainerID id();
u32 len();
boolean is_empty();
/// Pop the last element of the list.
[Throws=LoroError]
LoroValue? pop();
[Throws=LoroError]
void push(LoroValueLike v);
// TODO: for_each
[Throws=LoroError]
LoroList insert_list_container(u32 pos, LoroList child);
[Throws=LoroError]
LoroMap insert_map_container(u32 pos, LoroMap child);
[Throws=LoroError]
LoroTree insert_tree_container(u32 pos, LoroTree child);
[Throws=LoroError]
LoroMovableList insert_movable_list_container(u32 pos, LoroMovableList child);
[Throws=LoroError]
LoroText insert_text_container(u32 pos, LoroText child);
[Throws=LoroError]
LoroCounter insert_counter_container(u32 pos, LoroCounter child);
Cursor? get_cursor(u32 pos, Side side);
/// Converts the LoroList to a Vec of LoroValue.
///
/// This method unwraps the internal Arc and clones the data if necessary,
/// returning a Vec containing all the elements of the LoroList as LoroValue.
sequence<LoroValue> to_vec();
/// Delete all elements in the list.
[Throws=LoroError]
void clear();
/// Get the ID of the list item at the given position.
ID? get_id_at(u32 pos);
/// Whether the container is deleted.
boolean is_deleted();
/// Get the LoroDoc from this container
LoroDoc? doc();
/// Subscribe the events of a container.
///
/// The callback will be invoked when the container is changed.
/// Returns a subscription that can be used to unsubscribe.
///
/// The events will be emitted after a transaction is committed. A transaction is committed when:
///
/// - `doc.commit()` is called.
/// - `doc.export(mode)` is called.
/// - `doc.import(data)` is called.
/// - `doc.checkout(version)` is called.
Subscription? subscribe(Subscriber subscriber);
};
interface LoroMap{
/// Create a new container that is detached from the document.
///
/// The edits on a detached container will not be persisted.
/// To attach the container to the document, please insert it into an attached container.
constructor();
/// Whether the container is attached to a document.
boolean is_attached();
/// If a detached container is attached, this method will return its corresponding attached handler.
LoroMap? get_attached();
/// Delete a key-value pair from the map.
[Throws=LoroError]
void delete([ByRef] string key);
/// Insert a key-value pair into the map.
///
/// > **Note**: When calling `map.set(key, value)` on a LoroMap, if `map.get(key)` already returns `value`,
/// > the operation will be a no-op (no operation recorded) to avoid unnecessary updates.
[Throws=LoroError]
void insert([ByRef] string key, LoroValueLike v);
/// Get the length of the map.
u32 len();
/// Get the ID of the map.
ContainerID id();
/// Whether the map is empty.
boolean is_empty();
/// Get the value of the map with the given key.
ValueOrContainer? get([ByRef] string key);
[Throws=LoroError]
LoroList get_or_create_list_container([ByRef] string key, LoroList child);
[Throws=LoroError]
LoroMap get_or_create_map_container([ByRef] string key, LoroMap child);
[Throws=LoroError]
LoroTree get_or_create_tree_container([ByRef] string key, LoroTree child);
[Throws=LoroError]
LoroMovableList get_or_create_movable_list_container([ByRef] string key, LoroMovableList child);
[Throws=LoroError]
LoroText get_or_create_text_container([ByRef] string key, LoroText child);
[Throws=LoroError]
LoroCounter get_or_create_counter_container([ByRef] string key, LoroCounter child);
[Throws=LoroError]
LoroList ensure_mergeable_list([ByRef] string key);
[Throws=LoroError]
LoroMap ensure_mergeable_map([ByRef] string key);
[Throws=LoroError]
LoroTree ensure_mergeable_tree([ByRef] string key);
[Throws=LoroError]
LoroMovableList ensure_mergeable_movable_list([ByRef] string key);
[Throws=LoroError]
LoroText ensure_mergeable_text([ByRef] string key);
[Throws=LoroError]
LoroCounter ensure_mergeable_counter([ByRef] string key);
[Throws=LoroError]
LoroList insert_list_container([ByRef] string key, LoroList child);
[Throws=LoroError]
LoroMap insert_map_container([ByRef] string key, LoroMap child);
[Throws=LoroError]
LoroTree insert_tree_container([ByRef] string key, LoroTree child);
[Throws=LoroError]
LoroMovableList insert_movable_list_container([ByRef] string key, LoroMovableList child);
[Throws=LoroError]
LoroText insert_text_container([ByRef] string key, LoroText child);
[Throws=LoroError]
LoroCounter insert_counter_container([ByRef] string key, LoroCounter child);
/// Get the shallow value of the map.
///
/// It will not convert the state of sub-containers, but represent them as [LoroValue::Container].
LoroValue get_value();
/// Get the deep value of the map.
///
/// It will convert the state of sub-containers into a nested JSON value.
LoroValue get_deep_value();
/// Whether the container is deleted.
boolean is_deleted();
/// Get the peer id of the last editor on the given entry
u64? get_last_editor([ByRef] string key);
/// Delete all key-value pairs in the map.
[Throws=LoroError]
void clear();
// TODO: iter?
/// Get the keys of the map.
sequence<string> keys();
/// Get the values of the map.
sequence<ValueOrContainer> values();
/// Get the LoroDoc from this container
LoroDoc? doc();
/// Subscribe the events of a container.
///
/// The callback will be invoked when the container is changed.
/// Returns a subscription that can be used to unsubscribe.
///
/// The events will be emitted after a transaction is committed. A transaction is committed when:
///
/// - `doc.commit()` is called.
/// - `doc.export(mode)` is called.
/// - `doc.import(data)` is called.
/// - `doc.checkout(version)` is called.
Subscription? subscribe(Subscriber subscriber);
};
interface LoroTree{
/// Create a new container that is detached from the document.
///
/// The edits on a detached container will not be persisted.
/// To attach the container to the document, please insert it into an attached container.
constructor();
/// Whether the container is attached to a document
///
/// The edits on a detached container will not be persisted.
/// To attach the container to the document, please insert it into an attached container.
boolean is_attached();
/// If a detached container is attached, this method will return its corresponding attached handler.
LoroTree? get_attached();
/// Create a new tree node and return the [`TreeID`].
///
/// If the `parent` is `None`, the created node is the root of a tree.
/// Otherwise, the created node is a child of the parent tree node.
[Throws=LoroError]
TreeID create(TreeParentId parent);
/// Create a new tree node at the given index and return the [`TreeID`].
///
/// If the `parent` is `None`, the created node is the root of a tree.
/// If the `index` is greater than the number of children of the parent, error will be returned.
[Throws=LoroError]
TreeID create_at(TreeParentId parent, u32 index);
/// Move the `target` node to be a child of the `parent` node.
///
/// If the `parent` is `None`, the `target` node will be a root.
[Throws=LoroError]
void mov(TreeID target, TreeParentId parent);
/// Move the `target` node to be a child of the `parent` node at the given index.
/// If the `parent` is `None`, the `target` node will be a root.
[Throws=LoroError]
void mov_to(TreeID target, TreeParentId parent, u32 to);
/// Move the `target` node to be a child after the `after` node with the same parent.
[Throws=LoroError]
void mov_after(TreeID target, TreeID after);
/// Move the `target` node to be a child before the `before` node with the same parent.
[Throws=LoroError]
void mov_before(TreeID target, TreeID before);
/// Delete a tree node.
///
/// Note: If the deleted node has children, the children do not appear in the state
/// rather than actually being deleted.
[Throws=LoroError]
void delete(TreeID target);
/// Get the associated metadata map handler of a tree node.
[Throws=LoroError]
LoroMap get_meta(TreeID target);
/// Return the parent of target node.
///
/// - If the target node does not exist, throws Error.
/// - If the target node is a root node, return nil.
[Throws=LoroError]
TreeParentId parent(TreeID target);
/// Return whether target node exists.
boolean contains(TreeID target);
/// Return whether target node is deleted.
///
/// # Errors
/// - If the target node does not exist, return `LoroTreeError::TreeNodeNotExist`.
[Throws=LoroError]
boolean is_node_deleted(TreeID target);
/// Return all nodes, including deleted nodes
sequence<TreeID> nodes();
/// Get the root nodes of the forest.
sequence<TreeID> roots();
/// Return all children of the target node.
///
/// If the parent node does not exist, return `None`.
sequence<TreeID>? children(TreeParentId parent);
/// Return the number of children of the target node.
u32? children_num(TreeParentId parent);
/// Return container id of the tree.
ContainerID id();
/// Return the fractional index of the target node with hex format.
string? fractional_index(TreeID target);
/// Return the flat array of the forest.
///
/// Note: the metadata will be not resolved. So if you don't only care about hierarchy
/// but also the metadata, you should use `get_value_with_meta()`.
LoroValue get_value();
/// Return the flat array of the forest, each node is with metadata.
LoroValue get_value_with_meta();
/// Whether the fractional index is enabled.
boolean is_fractional_index_enabled();
/// Enable fractional index for Tree Position.
///
/// The jitter is used to avoid conflicts when multiple users are creating the node at the same position.
/// value 0 is default, which means no jitter, any value larger than 0 will enable jitter.
///
/// Generally speaking, jitter will affect the growth rate of document size.
/// [Read more about it](https://www.loro.dev/blog/movable-tree#implementation-and-encoding-size)
void enable_fractional_index(u8 jitter);
/// Disable the fractional index generation when you don't need the Tree's siblings to be sorted.
/// The fractional index will always be set to the same default value 0.
///
/// After calling this, you cannot use `tree.moveTo()`, `tree.moveBefore()`, `tree.moveAfter()`,
/// and `tree.createAt()`.
void disable_fractional_index();
/// Get the last move id of the target node.
ID? get_last_move_id([ByRef] TreeID target);
/// Whether the container is deleted.
boolean is_deleted();
/// Get the LoroDoc from this container
LoroDoc? doc();
/// Subscribe the events of a container.
///
/// The callback will be invoked when the container is changed.
/// Returns a subscription that can be used to unsubscribe.
///
/// The events will be emitted after a transaction is committed. A transaction is committed when:
///
/// - `doc.commit()` is called.
/// - `doc.export(mode)` is called.
/// - `doc.import(data)` is called.
/// - `doc.checkout(version)` is called.
Subscription? subscribe(Subscriber subscriber);
};
interface LoroMovableList{
/// Create a new container that is detached from the document.
///
/// The edits on a detached container will not be persisted.
/// To attach the container to the document, please insert it into an attached container.
constructor();
/// Whether the container is attached to a document
///
/// The edits on a detached container will not be persisted.
/// To attach the container to the document, please insert it into an attached container.
boolean is_attached();
/// If a detached container is attached, this method will return its corresponding attached handler.
LoroMovableList? get_attached();
/// Get the container id.
ContainerID id();
/// Insert a value at the given position.
[Throws=LoroError]
void insert(u32 pos, LoroValueLike v);
/// Delete values at the given position.
[Throws=LoroError]
void delete(u32 pos, u32 len);
/// Get the value at the given position.
ValueOrContainer? get(u32 index);
u32 len();
boolean is_empty();
/// Get the deep value of the container.
LoroValue get_deep_value();
/// Get the shallow value of the container.
///
/// This does not convert the state of sub-containers; instead, it represents them as [LoroValue::Container].
LoroValue get_value();
/// Pop the last element of the list.
[Throws=LoroError]
ValueOrContainer? pop();
[Throws=LoroError]
void push(LoroValueLike v);
[Throws=LoroError]
LoroList insert_list_container(u32 pos, LoroList child);
[Throws=LoroError]
LoroMap insert_map_container(u32 pos, LoroMap child);
[Throws=LoroError]
LoroTree insert_tree_container(u32 pos, LoroTree child);
[Throws=LoroError]
LoroMovableList insert_movable_list_container(u32 pos, LoroMovableList child);
[Throws=LoroError]
LoroText insert_text_container(u32 pos, LoroText child);
[Throws=LoroError]
LoroCounter insert_counter_container(u32 pos, LoroCounter child);
[Throws=LoroError]
LoroList set_list_container(u32 pos, LoroList child);
[Throws=LoroError]
LoroMap set_map_container(u32 pos, LoroMap child);
[Throws=LoroError]
LoroTree set_tree_container(u32 pos, LoroTree child);
[Throws=LoroError]
LoroMovableList set_movable_list_container(u32 pos, LoroMovableList child);
[Throws=LoroError]
LoroText set_text_container(u32 pos, LoroText child);
[Throws=LoroError]
LoroCounter set_counter_container(u32 pos, LoroCounter child);
/// Get the cursor at the given position.
///
/// Using "index" to denote cursor positions can be unstable, as positions may
/// shift with document edits. To reliably represent a position or range within
/// a document, it is more effective to leverage the unique ID of each item/character
/// in a List CRDT or Text CRDT.
///
/// Loro optimizes State metadata by not storing the IDs of deleted elements. This
/// approach complicates tracking cursors since they rely on these IDs. The solution
/// recalculates position by replaying relevant history to update stable positions
/// accurately. To minimize the performance impact of history replay, the system
/// updates cursor info to reference only the IDs of currently present elements,
/// thereby reducing the need for replay.
Cursor? get_cursor(u32 pos, Side side);
/// Set the value at the given position.
[Throws=LoroError]
void set(u32 pos, LoroValueLike value);
/// Move the value at the given position to the given position.
[Throws=LoroError]
void mov(u32 from, u32 to);
/// Get the elements of the list as a vector of LoroValues.
///
/// This method returns a vector containing all the elements in the list as LoroValues.
/// It provides a convenient way to access the entire contents of the LoroMovableList
/// as a standard Rust vector.
sequence<LoroValue> to_vec();
/// Delete all elements in the list.
[Throws=LoroError]
void clear();
/// Whether the container is deleted.
boolean is_deleted();
// TODO: for each
u64? get_creator_at(u32 pos);
/// Get the last mover of the list item at the given position.
u64? get_last_mover_at(u32 pos);
/// Get the last editor of the list item at the given position.
u64? get_last_editor_at(u32 pos);
/// Get the LoroDoc from this container
LoroDoc? doc();
/// Subscribe the events of a container.
///
/// The callback will be invoked when the container is changed.
/// Returns a subscription that can be used to unsubscribe.
///
/// The events will be emitted after a transaction is committed. A transaction is committed when:
///
/// - `doc.commit()` is called.
/// - `doc.export(mode)` is called.
/// - `doc.import(data)` is called.
/// - `doc.checkout(version)` is called.
Subscription? subscribe(Subscriber subscriber);
};
interface LoroCounter{
/// Create a new Counter.
constructor();
/// Return container id of the Counter.
ContainerID id();
/// Whether the container is attached to a document
///
/// The edits on a detached container will not be persisted.
/// To attach the container to the document, please insert it into an attached container.
boolean is_attached();
/// If a detached container is attached, this method will return its corresponding attached handler.
LoroCounter? get_attached();
/// Increment the counter by the given value.
[Throws=LoroError]
void increment(double value);
/// Decrement the counter by the given value.
[Throws=LoroError]
void decrement(double value);
/// Get the current value of the counter.
f64 get_value();
/// Whether the container is deleted.
boolean is_deleted();
/// Get the LoroDoc from this container
LoroDoc? doc();
/// Subscribe the events of a container.
///
/// The callback will be invoked when the container is changed.
/// Returns a subscription that can be used to unsubscribe.
///
/// The events will be emitted after a transaction is committed. A transaction is committed when:
///
/// - `doc.commit()` is called.
/// - `doc.export(mode)` is called.
/// - `doc.import(data)` is called.
/// - `doc.checkout(version)` is called.
Subscription? subscribe(Subscriber subscriber);
};
interface LoroUnknown{
/// Get the container id.
ContainerID id();
};
// ============= TYPES =============
[Enum]
interface ExportMode{
Snapshot();
Updates(VersionVector from);
UpdatesInRange(sequence<IdSpan> spans);
ShallowSnapshot(Frontiers frontiers);
StateOnly(Frontiers? frontiers);
SnapshotAt(Frontiers frontiers);
};
[Remote]
dictionary ChangeMeta{
/// Lamport timestamp of the Change
u32 lamport;
/// The first Op id of the Change
ID id;
/// [Unix time](https://en.wikipedia.org/wiki/Unix_time)
/// It is the number of seconds that have elapsed since 00:00:00 UTC on 1 January 1970.
i64 timestamp;
/// The commit message of the change
string? message;
/// The dependencies of the first op of the change
Frontiers deps;
/// The total op num inside this change
u32 len;
};
[Remote]
dictionary ImportBlobMetadata{
/// The partial start version vector.
///
/// Import blob includes all the ops from `partial_start_vv` to `partial_end_vv`.
/// However, it does not constitute a complete version vector, as it only contains counters
/// from peers included within the import blob.
VersionVector partial_start_vv;
/// The partial end version vector.
///
/// Import blob includes all the ops from `partial_start_vv` to `partial_end_vv`.
/// However, it does not constitute a complete version vector, as it only contains counters
/// from peers included within the import blob.
VersionVector partial_end_vv;
i64 start_timestamp;
Frontiers start_frontiers;
i64 end_timestamp;
u32 change_num;
string mode;
};
[Remote]
dictionary ImportStatus{
record<u64, CounterSpan> success;
record<u64, CounterSpan>? pending;
};
[Remote]
enum Ordering{
"Less", "Equal", "Greater"
};
[Remote]
dictionary PreCommitCallbackPayload{
ChangeMeta change_meta;
string origin;
ChangeModifier modifier;
};
[Remote]
dictionary FirstCommitFromPeerPayload{
u64 peer;
};
interface ChangeModifier{
void set_message([ByRef] string msg);
void set_timestamp(i64 timestamp);
};
// ============= CONFIG =============
interface Configure{
Configure fork();
boolean record_timestamp();
void set_record_timestamp(boolean record);
i64 merge_interval();
void set_merge_interval(i64 interval);
StyleConfigMap text_style_config();
};
interface StyleConfigMap{
constructor();
[Name=default_rich_text_config]
constructor();
void insert([ByRef] string key, StyleConfig value);
StyleConfig? get([ByRef] string key);
};
[Remote]
dictionary StyleConfig{
ExpandType expand;
};
[Remote]
enum ExpandType{
"Before",
"After",
"Both",
"None",
};
[Remote]
dictionary CommitOptions{
string? origin;
boolean immediate_renew;
i64? timestamp;
string? commit_msg;
};
// ============= CURSOR =============
[Remote]
enum Side{
"Left",
"Middle",
"Right",
};
[Remote]
enum PosType{
"Bytes",
"Unicode",
"Utf16",
"Event",
"Entity",
};
interface Cursor{
constructor(ID? id, ContainerID container, Side side, u32 origin_pos);
bytes encode();
[Name=decode, Throws=LoroError]
constructor([ByRef]bytes data);
};
[Remote]
dictionary PosQueryResult{
Cursor? update;
AbsolutePosition current;
};
[Remote]
dictionary AbsolutePosition{
u32 pos;
Side side;
};
/// Deprecated, use `EphemeralStore` instead.
interface Awareness{
constructor(u64 peer, i64 timeout);
bytes encode([ByRef]sequence<u64> peers);
bytes encode_all();
AwarenessPeerUpdate apply([ByRef]bytes encoded_peers_info);
void set_local_state(LoroValueLike value);
LoroValue? get_local_state();
sequence<u64> remove_outdated();
record<u64, PeerInfo> get_all_states();
u64 peer();
};
[Remote]
dictionary AwarenessPeerUpdate{
sequence<u64> updated;
sequence<u64> added;
};
[Remote]
dictionary PeerInfo{
LoroValue state;
i32 counter;
i64 timestamp;
};
interface EphemeralStore{
constructor(i64 timeout);
bytes encode([ByRef] string key);
bytes encode_all();
[Throws=LoroError]
void apply([ByRef] bytes data);
void set([ByRef] string key, LoroValueLike value);
void delete([ByRef] string key);
LoroValue? get([ByRef] string key);
void remove_outdated();
sequence<string> keys();
record<string, LoroValue> get_all_states();
Subscription subscribe_local_update(LocalEphemeralListener listener);
Subscription subscribe(EphemeralSubscriber listener);
};
[Remote]
dictionary EphemeralStoreEvent{
EphemeralEventTrigger by;
sequence<string> added;
sequence<string> removed;
sequence<string> updated;
};
[Remote]
enum EphemeralEventTrigger{
"Local",
"Import",
"Timeout",
};
// ============= VERSIONS =============
interface VersionVector{
constructor();
void set_last(ID id);
i32? get_last(u64 peer);
void set_end(ID id);
/// Update the end counter of the given client if the end is greater. Return whether updated
boolean try_update_last(ID id);
sequence<IdSpan> get_missing_span([ByRef] VersionVector target);
void merge([ByRef] VersionVector other);
boolean includes_vv([ByRef] VersionVector other);
boolean includes_id(ID id);
CounterSpan? intersect_span(IdSpan target);
void extend_to_include_vv([ByRef]VersionVector other);
VersionVectorDiff diff([ByRef]VersionVector rhs);
bytes encode();
[Name=decode, Throws=LoroError]
constructor([ByRef]bytes bytes);
Ordering? partial_cmp([ByRef]VersionVector other);
boolean eq([ByRef]VersionVector other);
record<u64, i32> to_hashmap();
};
interface Frontiers{
constructor();
[Name=from_id]
constructor(ID id);
[Name=from_ids]
constructor(sequence<ID> ids);
bytes encode();
[Name=decode, Throws=LoroError]
constructor([ByRef]bytes bytes);
boolean eq([ByRef]Frontiers other);
boolean is_empty();
sequence<ID> to_vec();
};
[Remote]
dictionary VersionVectorDiff{
/// need to add these spans to move from right to left
record<u64, CounterSpan> retreat;
/// need to add these spans to move from left to right
record<u64, CounterSpan> forward;
};
interface VersionRange{
constructor();
/// Create a VersionRange from a VersionVector
[Name=from_vv]
constructor([ByRef] VersionVector vv);
/// Clear all ranges in the VersionRange
void clear();
/// Get the counter range for a specific peer
/// Returns the counter range if the peer exists, null otherwise
CounterSpan? get(u64 peer);
/// Insert a counter range for a specific peer
void insert(u64 peer, i32 start, i32 end);
/// Check if this VersionRange contains operations between two VersionVectors
boolean contains_ops_between([ByRef] VersionVector vv_a, [ByRef] VersionVector vv_b);
/// Check if this VersionRange has overlap with the given ID span
boolean has_overlap_with(IdSpan span);
/// Check if this VersionRange contains a specific ID
boolean contains_id(ID id);
/// Check if this VersionRange contains a specific ID span
boolean contains_id_span(IdSpan span);
/// Extend this VersionRange to include the given ID span
void extends_to_include_id_span(IdSpan span);
/// Check if the VersionRange is empty
boolean is_empty();
/// Get all peer IDs in this VersionRange
sequence<u64> get_peers();
/// Get all ranges as a list of (peer, start, end) tuples
sequence<VersionRangeItem> get_all_ranges();
};
[Remote]
dictionary VersionRangeItem{
u64 peer;
i32 start;
i32 end;
};
// ============= UNDO MANAGER =============
interface UndoManager{
/// Create a new UndoManager.
constructor([ByRef] LoroDoc doc);
/// Undo the last change made by the peer.
[Throws=LoroError]
boolean undo();
/// Redo the last change made by the peer.
[Throws=LoroError]
boolean redo();
/// Record a new checkpoint.
[Throws=LoroError]
void record_new_checkpoint();
/// Whether the undo manager can undo.
boolean can_undo();
/// Whether the undo manager can redo.
boolean can_redo();
/// How many times the undo manager can undo.
u32 undo_count();
/// How many times the undo manager can redo.
u32 redo_count();
/// If a local event's origin matches the given prefix, it will not be recorded in the
/// undo stack.
void add_exclude_origin_prefix([ByRef] string prefix);
/// Set the maximum number of undo steps. The default value is 100.
void set_max_undo_steps(u32 size);
/// Set the merge interval in ms. The default value is 0, which means no merge.
void set_merge_interval(i64 interval);
/// Set the listener for push events.
/// The listener will be called when a new undo/redo item is pushed into the stack.
void set_on_push(OnPush? on_push);
/// Set the listener for pop events.
/// The listener will be called when an undo/redo item is popped from the stack.
void set_on_pop(OnPop? on_pop);
/// Will start a new group of changes, all subsequent changes will be merged
/// into a new item on the undo stack. If we receive remote changes, we determine
/// wether or not they are conflicting. If the remote changes are conflicting
/// we split the undo item and close the group. If there are no conflict
/// in changed container ids we continue the group merge.
[Throws=LoroError]
void group_start();
/// Ends the current group, calling UndoManager::undo() after this will
/// undo all changes that occurred during the group.
void group_end();
/// Get the peer id of the undo manager
u64 peer();
/// Get the metadata of the top undo stack item, if any.
UndoItemMeta? top_undo_meta();
/// Get the metadata of the top redo stack item, if any.
UndoItemMeta? top_redo_meta();
/// Get the value associated with the top undo stack item, if any.
LoroValue? top_undo_value();
/// Get the value associated with the top redo stack item, if any.
LoroValue? top_redo_value();
};
[Remote]
enum UndoOrRedo{
"Undo", "Redo",
};
[Remote]
dictionary CounterSpan{
i32 start;
i32 end;
};
[Remote]
dictionary UndoItemMeta{
LoroValue value;
sequence<CursorWithPos> cursors;
};
[Remote]
dictionary CursorWithPos{
Cursor cursor;
AbsolutePosition pos;
};
[Remote]
dictionary AbsolutePosition{
u32 pos;
Side side;
};
// ============= EVENTS =============
[Remote]
dictionary DiffEvent{
/// How the event is triggered.
EventTriggerKind triggered_by;
/// The origin of the event.
string origin;
/// The current receiver of the event.
ContainerID? current_target;
/// The diffs of the event.
sequence<ContainerDiff> events;
};
/// A diff of a container.
[Remote]
dictionary ContainerDiff{
/// The target container id of the diff.
ContainerID target;
/// The path of the diff.
sequence<PathItem> path;
/// Whether the diff is from unknown container.
boolean is_unknown;
/// The diff
Diff diff;
};
[Enum]
interface Diff{
List(sequence<ListDiffItem> diff);
Text(sequence<TextDelta> diff);
Map(MapDelta diff);
Tree(TreeDiff diff);
Counter(double diff);
Unknown();
};
[Enum]
interface TextDelta{
Retain(u32 retain, record<string, LoroValue>? attributes);
Insert(string insert, record<string, LoroValue>? attributes);
Delete(u32 delete);
};
[Enum]
interface ListDiffItem{
/// Insert a new element into the list.
Insert(sequence<ValueOrContainer> insert, boolean is_move);
/// Delete n elements from the list at the current index.
Delete(u32 delete);
/// Retain n elements in the list.
///
/// This is used to keep the current index unchanged.
Retain(u32 retain);
};
[Remote]
dictionary MapDelta{
record<string, ValueOrContainer?> updated;
};
[Remote]
dictionary TreeDiff{
sequence<TreeDiffItem> diff;
};
[Remote]
dictionary TreeDiffItem{
TreeID target;
TreeExternalDiff action;
};
[Enum]
interface TreeExternalDiff{
Create(TreeParentId parent, u32 index, string fractional_index);
Move(TreeParentId parent, u32 index, string fractional_index, TreeParentId old_parent, u32 old_index);
Delete(TreeParentId old_parent, u32 old_index);
};
[Remote]
dictionary PathItem{
ContainerID container;
Index index;
};
/// The kind of the event trigger.
[Remote]
enum EventTriggerKind{
/// The event is triggered by a local transaction.
"Local",
/// The event is triggered by importing
"Import",
/// The event is triggered by checkout
"Checkout",
};
[Enum]
interface Index{
Key(string key);
Seq(u32 index);
Node(TreeID target);
};
/// A handle to a subscription created by GPUI. When dropped, the subscription
/// is cancelled and the callback will no longer be invoked.
interface Subscription{
/// Detaches the subscription from this handle. The callback will
/// continue to be invoked until the views or models it has been
/// subscribed to are dropped
[Self=ByArc]
void detach();
/// Unsubscribes the subscription.
[Self=ByArc]
void unsubscribe();
};
interface DiffBatch{
constructor();
/// Push a new event to the batch.
///
/// If the cid already exists in the batch, return Err
Diff? push(ContainerID cid, Diff diff);
/// Returns an iterator over the diffs in this batch, in the order they were added.
///
/// The iterator yields tuples of `(&ContainerID, &Diff)` where:
/// - `ContainerID` is the ID of the container that was modified
/// - `Diff` contains the actual changes made to that container
///
/// The order of the diffs is preserved from when they were originally added to the batch.
sequence<ContainerIDAndDiff> get_diff();
};
[Remote]
dictionary ContainerIDAndDiff{
ContainerID cid;
Diff diff;
};
// ============= TYPES =============
[Remote]
dictionary TreeID{
u64 peer;
i32 counter;
};
[Enum]
interface TreeParentId{
Node(TreeID id);
Root();
Deleted();
Unexist();
};
[Remote]
dictionary UpdateOptions{
f64? timeout_ms;
boolean use_refined_diff;
};
[Traits=(Display)]
interface FractionalIndex{
[Name=from_bytes]
constructor(bytes bytes);
[Name=from_hex_string]
constructor([ByRef] string str);
};
[Remote]
dictionary ID{
u64 peer;
i32 counter;
};
[Remote]
dictionary IdLp{
u32 lamport;
u64 peer;
};
[Remote]
dictionary IdSpan{
u64 peer;
CounterSpan counter;
};
[Remote]
dictionary CounterSpan{
i32 start;
i32 end;
};
[Enum]
interface ContainerType{
Text();
Map();
List();
MovableList();
Tree();
Counter();
Unknown(u8 kind);
};
[Enum]
interface ContainerID{
Root(string name, ContainerType container_type);
Normal(u64 peer,i32 counter, ContainerType container_type);
};
[Remote]
dictionary FrontiersOrID{
Frontiers? frontiers;
ID? id;
};
[Enum]
interface LoroValue{
Null();
Bool(boolean value);
Double(f64 value);
I64(i64 value);
Binary(bytes value);
String(string value);
List(sequence<LoroValue> value);
Map(record<string, LoroValue> value);
Container(ContainerID value);
};
[Error, Remote]
enum LoroError {
"UnmatchedContext",
"DecodeVersionVectorError",
"DecodeError",
"DecodeDataCorruptionError",
"DecodeChecksumMismatchError",
"IncompatibleFutureEncodingError",
"JsError",
"LockError",
"DuplicatedTransactionError",
"NotFoundError",
"TransactionError",
"OutOfBound",
"UsedOpID",
"TreeError",
"ArgErr",
"AutoCommitNotStarted",
"StyleConfigMissing",
"Unknown",
"FrontiersNotFound",
"ImportWhenInTxn",
"MisuseDetachedContainer" ,
"NotImplemented",
"ReattachAttachedContainer",
"EditWhenDetached",
"UndoInvalidIdSpan",
"UndoWithDifferentPeerId",
"InvalidJsonSchema",
"UTF8InUnicodeCodePoint",
"UTF16InUnicodeCodePoint",
"EndIndexLessThanStartIndex",
"InvalidRootContainerName",
"ImportUpdatesThatDependsOnOutdatedVersion",
"ImportUnsupportedEncodingMode",
"SwitchToVersionBeforeShallowRoot",
"ContainerDeleted",
"ConcurrentOpsWithSamePeerID",
"InvalidPeerID",
"ContainersNotFound",
"UndoGroupAlreadyStarted",
};
[Error, Remote]
enum CannotFindRelativePosition{
"ContainerDeleted",
"HistoryCleared",
"IdNotFound"
};
[Error, Remote]
enum JsonPathError{
"InvalidJsonPath",
"EvaluationError"
};
[Error, NonExhaustive, Remote]
enum LoroEncodeError{
"FrontiersNotFound",
"ShallowSnapshotIncompatibleWithOldFormat",
"UnknownContainer",
};
[Error, Remote]
enum ChangeTravelError{
"TargetIdNotFound",
"TargetVersionNotIncluded"
};
[Error, Remote]
enum UpdateTimeoutError{
"Timeout"
};