docs: update ts docs (#529)
Some checks failed
Release WASM / Release (push) Has been cancelled
Test All / build (push) Has been cancelled

* docs: update ts docs

* docs: update rust docs

* test: add js doc tests and fix outdated js docs
This commit is contained in:
Zixuan Chen 2024-10-25 16:35:33 +08:00 committed by GitHub
parent afec0b8c2e
commit e2be56b0c2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 748 additions and 220 deletions

View file

@ -76,7 +76,7 @@ type JsResult<T> = Result<T, JsValue>;
///
/// @example
/// ```ts
/// import { LoroDoc } import "loro-crdt"
/// import { LoroDoc } from "loro-crdt"
///
/// const loro = new LoroDoc();
/// const text = loro.getText("text");
@ -84,8 +84,6 @@ type JsResult<T> = Result<T, JsValue>;
/// const map = loro.getMap("Map");
/// const tree = loro.getTree("tree");
/// ```
///
// When FinalizationRegistry is unavailable, it's the users' responsibility to free the document.
#[wasm_bindgen]
pub struct LoroDoc(Arc<LoroDocInner>);
@ -332,7 +330,7 @@ impl ChangeMeta {
impl LoroDoc {
/// Create a new loro document.
///
/// New document will have random peer id.
/// New document will have a random peer id.
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
let doc = LoroDocInner::new();
@ -372,12 +370,12 @@ impl LoroDoc {
/// Set whether to record the timestamp of each change. Default is `false`.
///
/// If enabled, the Unix timestamp will be recorded for each change automatically.
/// If enabled, the Unix timestamp (in seconds) will be recorded for each change automatically.
///
/// You can also set each timestamp manually when you commit a change.
/// The timestamp manually set will override the automatic one.
///
/// NOTE: Timestamps are forced to be in ascending order.
/// NOTE: Timestamps are forced to be in ascending order in the OpLog's history.
/// If you commit a new change with a timestamp that is less than the existing one,
/// the largest existing timestamp will be used instead.
#[wasm_bindgen(js_name = "setRecordTimestamp")]
@ -387,7 +385,7 @@ impl LoroDoc {
/// If two continuous local changes are within the interval, they will be merged into one change.
///
/// The default value is 1_000_000, the default unit is milliseconds.
/// The default value is 1_000_000, the default unit is seconds.
#[wasm_bindgen(js_name = "setChangeMergeInterval")]
pub fn set_change_merge_interval(&self, interval: f64) {
self.0.set_change_merge_interval(interval as i64);
@ -458,15 +456,17 @@ impl LoroDoc {
Ok(())
}
/// Get a loro document from the snapshot.
/// Create a loro document from the snapshot.
///
/// @see You can check out what is the snapshot [here](#).
/// @see You can learn more [here](https://loro.dev/docs/tutorial/encoding).
///
/// @example
/// ```ts
/// import { LoroDoc } import "loro-crdt"
/// import { LoroDoc } from "loro-crdt"
///
/// const bytes = /* The bytes encoded from other loro document *\/;
/// const doc = new LoroDoc();
/// // ...
/// const bytes = doc.export({ mode: "snapshot" });
/// const loro = LoroDoc.fromSnapshot(bytes);
/// ```
///
@ -484,7 +484,7 @@ impl LoroDoc {
/// > 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 method has the same effect as invoking `checkout_to_latest`.
/// This method has the same effect as invoking `checkoutToLatest`.
///
/// @example
/// ```ts
@ -494,9 +494,9 @@ impl LoroDoc {
/// const text = doc.getText("text");
/// const frontiers = doc.frontiers();
/// text.insert(0, "Hello World!");
/// loro.checkout(frontiers);
/// doc.checkout(frontiers);
/// // you need call `attach()` or `checkoutToLatest()` before changing the doc.
/// loro.attach();
/// doc.attach();
/// text.insert(0, "Hi");
/// ```
pub fn attach(&mut self) {
@ -507,11 +507,9 @@ impl LoroDoc {
///
/// > 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
/// > In a detached state, the document is not editable by default, and any `import` operations will be
/// > recorded in the `OpLog` without being applied to the `DocState`.
///
/// When `detached`, the document is not editable.
///
/// @example
/// ```ts
/// import { LoroDoc } from "loro-crdt";
@ -520,11 +518,11 @@ impl LoroDoc {
/// const text = doc.getText("text");
/// const frontiers = doc.frontiers();
/// text.insert(0, "Hello World!");
/// console.log(doc.is_detached()); // false
/// loro.checkout(frontiers);
/// console.log(doc.is_detached()); // true
/// loro.attach();
/// console.log(doc.is_detached()); // false
/// console.log(doc.isDetached()); // false
/// doc.checkout(frontiers);
/// console.log(doc.isDetached()); // true
/// doc.attach();
/// console.log(doc.isDetached()); // false
/// ```
///
#[wasm_bindgen(js_name = "isDetached")]
@ -543,7 +541,7 @@ impl LoroDoc {
///
/// const doc = new LoroDoc();
/// doc.detach();
/// console.log(doc.is_detached()); // true
/// console.log(doc.isDetached()); // true
/// ```
pub fn detach(&self) {
self.0.detach()
@ -568,7 +566,7 @@ impl LoroDoc {
///
/// > 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
/// > In a detached state, the document is not editable by default, and any `import` operations will be
/// > recorded in the `OpLog` without being applied to the `DocState`.
///
/// This has the same effect as `attach`.
@ -581,9 +579,9 @@ impl LoroDoc {
/// const text = doc.getText("text");
/// const frontiers = doc.frontiers();
/// text.insert(0, "Hello World!");
/// loro.checkout(frontiers);
/// doc.checkout(frontiers);
/// // you need call `checkoutToLatest()` or `attach()` before changing the doc.
/// loro.checkoutToLatest();
/// doc.checkoutToLatest();
/// text.insert(0, "Hi");
/// ```
#[wasm_bindgen(js_name = "checkoutToLatest")]
@ -592,7 +590,10 @@ impl LoroDoc {
Ok(())
}
/// Visit all the ancestors of the changes in causal order.
///
/// @param ids - the changes to visit
/// @param f - the callback function, return `true` to continue visiting, return `false` to stop
#[wasm_bindgen(js_name = "travelChangeAncestors")]
pub fn travel_change_ancestors(&self, ids: Vec<JsID>, f: js_sys::Function) -> JsResult<()> {
let observer = observer::Observer::new(f);
@ -652,7 +653,7 @@ impl LoroDoc {
/// const text = doc.getText("text");
/// const frontiers = doc.frontiers();
/// text.insert(0, "Hello World!");
/// loro.checkout(frontiers);
/// doc.checkout(frontiers);
/// console.log(doc.toJSON()); // {"text": ""}
/// ```
pub fn checkout(&mut self, frontiers: Vec<JsID>) -> JsResult<()> {
@ -690,7 +691,8 @@ impl LoroDoc {
///
/// You can specify the `origin`, `timestamp`, and `message` of the commit.
///
/// The `origin` is used to mark the event, and the `message` works like a git commit message.
/// - The `origin` is used to mark the event
/// - The `message` works like a git commit message, which will be recorded and synced to peers
///
/// The events will be emitted after a transaction is committed. A transaction is committed when:
///
@ -984,7 +986,7 @@ impl LoroDoc {
Ok(ans)
}
/// Get the encoded version vector of the current document.
/// Get the version vector of the current document state.
///
/// If you checkout to a specific version, the version vector will change.
#[inline(always)]
@ -1020,15 +1022,15 @@ impl LoroDoc {
frontiers_to_ids(&self.0.shallow_since_frontiers())
}
/// Get the encoded version vector of the latest version in OpLog.
/// Get the version vector of the latest known version in OpLog.
///
/// If you checkout to a specific version, the version vector will not change.
/// If you checkout to a specific version, this version vector will not change.
#[wasm_bindgen(js_name = "oplogVersion")]
pub fn oplog_version(&self) -> VersionVector {
VersionVector(self.0.oplog_vv())
}
/// Get the frontiers of the current document.
/// Get the [frontiers](https://loro.dev/docs/advanced/version_deep_dive) of the current document state.
///
/// If you checkout to a specific version, this value will change.
#[inline]
@ -1036,7 +1038,7 @@ impl LoroDoc {
frontiers_to_ids(&self.0.state_frontiers())
}
/// Get the frontiers of the latest version in OpLog.
/// Get the [frontiers](https://loro.dev/docs/advanced/version_deep_dive) of the latest version in OpLog.
///
/// If you checkout to a specific version, this value will not change.
#[inline(always)]
@ -1102,8 +1104,8 @@ impl LoroDoc {
}
}
/// Export the snapshot of current version, it's include all content of
/// operations and states
/// Export the snapshot of current version.
/// It includes all the history and the document state
///
/// @deprecated Use `export({mode: "snapshot"})` instead
#[wasm_bindgen(js_name = "exportSnapshot")]
@ -1141,7 +1143,7 @@ impl LoroDoc {
///
/// @param mode - The export mode to use. Can be one of:
/// - `{ mode: "snapshot" }`: Export a full snapshot of the document.
/// - `{ mode: "update", from: VersionVector }`: Export updates from the given version vector.
/// - `{ mode: "update", from?: VersionVector }`: Export updates from the given version vector.
/// - `{ mode: "updates-in-range", spans: { id: ID, len: number }[] }`: Export updates within the specified ID spans.
/// - `{ mode: "shallow-snapshot", frontiers: Frontiers }`: Export a garbage-collected snapshot up to the given frontiers.
///
@ -1149,34 +1151,36 @@ impl LoroDoc {
///
/// @example
/// ```ts
/// import { LoroDoc } from "loro-crdt";
/// import { LoroDoc, LoroText } from "loro-crdt";
///
/// const doc = new LoroDoc();
/// doc.setText("text", "Hello World");
/// doc.setPeerId("1");
/// doc.getText("text").update("Hello World");
///
/// // Export a full snapshot
/// const snapshotBytes = doc.export({ mode: "snapshot" });
///
/// // Export updates from a specific version
/// const vv = doc.oplogVersion();
/// doc.setText("text", "Hello Loro");
/// doc.getText("text").update("Hello Loro");
/// const updateBytes = doc.export({ mode: "update", from: vv });
///
/// // Export a garbage-collected snapshot
/// const gcBytes = doc.export({ mode: "shallow-snapshot", frontiers: doc.oplogFrontiers() });
/// // Export a shallow snapshot that only includes the history since the frontiers
/// const shallowBytes = doc.export({ mode: "shallow-snapshot", frontiers: doc.oplogFrontiers() });
///
/// // Export updates within specific ID spans
/// const spanBytes = doc.export({
/// mode: "updates-in-range",
/// spans: [{ id: "1", len: 10 }, { id: "2", len: 5 }]
/// spans: [{ id: { peer: "1", counter: 0 }, len: 10 }]
/// });
/// ```
pub fn export(&self, mode: JsExportMode) -> JsResult<Vec<u8>> {
let export_mode = js_to_export_mode(mode)?;
let export_mode = js_to_export_mode(mode)
.map_err(|e| JsValue::from_str(&format!("Invalid export mode. Error: {:?}", e)))?;
Ok(self.0.export(export_mode)?)
}
/// Export updates from the specific version to the current version with JSON format.
/// Export updates in the given range in JSON format.
#[wasm_bindgen(js_name = "exportJsonUpdates")]
pub fn export_json_updates(
&self,
@ -1218,7 +1222,7 @@ impl LoroDoc {
Ok(import_status_to_js_value(status).into())
}
/// Import a snapshot or a update to current doc.
/// Import snapshot or updates into current doc.
///
/// Note:
/// - Updates within the current version will be ignored
@ -1232,8 +1236,8 @@ impl LoroDoc {
/// const text = doc.getText("text");
/// text.insert(0, "Hello");
/// // get all updates of the doc
/// const updates = doc.exportFrom();
/// const snapshot = doc.exportSnapshot();
/// const updates = doc.export({ mode: "update" });
/// const snapshot = doc.export({ mode: "snapshot" });
/// const doc2 = new LoroDoc();
/// // import snapshot
/// doc2.import(snapshot);
@ -1256,8 +1260,8 @@ impl LoroDoc {
/// const doc = new LoroDoc();
/// const text = doc.getText("text");
/// text.insert(0, "Hello");
/// const updates = doc.exportFrom();
/// const snapshot = doc.exportSnapshot();
/// const updates = doc.export({ mode: "update" });
/// const snapshot = doc.export({ mode: "snapshot" });
/// const doc2 = new LoroDoc();
/// doc2.importUpdateBatch([snapshot, updates]);
/// ```
@ -1286,7 +1290,7 @@ impl LoroDoc {
/// const list = doc.getList("list");
/// const tree = doc.getTree("tree");
/// const map = doc.getMap("map");
/// const shallowValue = doc.toShallowJSON();
/// const shallowValue = doc.getShallowValue();
/// /*
/// {"list": ..., "tree": ..., "map": ...}
/// *\/
@ -1298,11 +1302,11 @@ impl LoroDoc {
Ok(json.into())
}
/// Get the json format of the document state.
/// Get the json format of the entire document state.
///
/// @example
/// ```ts
/// import { LoroDoc } from "loro-crdt";
/// import { LoroDoc, LoroText, LoroMap } from "loro-crdt";
///
/// const doc = new LoroDoc();
/// const list = doc.getList("list");
@ -1323,14 +1327,14 @@ impl LoroDoc {
}
/// Subscribe to the changes of the loro document. The function will be called when the
/// transaction is committed or updates from remote are imported.
/// transaction is committed and after importing updates/snapshot from remote.
///
/// Returns a subscription ID, which can be used to unsubscribe.
/// Returns a subscription callback, which 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.exportFrom(version)` is called.
/// - `doc.export(mode)` is called.
/// - `doc.import(data)` is called.
/// - `doc.checkout(version)` is called.
///
@ -1340,12 +1344,14 @@ impl LoroDoc {
///
/// const doc = new LoroDoc();
/// const text = doc.getText("text");
/// doc.subscribe((event)=>{
/// const sub = doc.subscribe((event)=>{
/// console.log(event);
/// });
/// text.insert(0, "Hello");
/// // the events will be emitted when `commit()` is called.
/// doc.commit();
/// // unsubscribe
/// sub();
/// ```
// TODO: convert event and event sub config
pub fn subscribe(&self, f: js_sys::Function) -> JsValue {
@ -1382,20 +1388,22 @@ impl LoroDoc {
console_log!("{:#?}", oplog.diagnose_size());
}
/// Get all of changes in the oplog
/// Get all of changes in the oplog.
///
/// Note: this method is expensive when the oplog is large. O(n)
///
/// @example
/// ```ts
/// import { LoroDoc } from "loro-crdt";
/// import { LoroDoc, LoroText } from "loro-crdt";
///
/// const doc = new LoroDoc();
/// const text = doc.getText("text");
/// text.insert(0, "Hello");
/// const changes = doc.getAllChanges();
///
/// for (let [peer, changes] of changes.entries()){
/// for (let [peer, c] of changes.entries()){
/// console.log("peer: ", peer);
/// for (let change in changes){
/// for (let change of c){
/// console.log("change: ", change);
/// }
/// }
@ -1438,7 +1446,7 @@ impl LoroDoc {
value.into()
}
/// Get the change of a specific ID
/// Get the change that contains the specific ID
#[wasm_bindgen(js_name = "getChangeAt")]
pub fn get_change_at(&self, id: JsID) -> JsResult<JsChange> {
let id = js_id_to_id(id)?;
@ -1500,7 +1508,7 @@ impl LoroDoc {
Ok(change.to_js().into())
}
/// Get all ops of the change of a specific ID
/// Get all ops of the change that contains the specific ID
#[wasm_bindgen(js_name = "getOpsInChange")]
pub fn get_ops_in_change(&self, id: JsID) -> JsResult<Vec<JsValue>> {
let id = js_id_to_id(id)?;
@ -1517,7 +1525,9 @@ impl LoroDoc {
Ok(ops)
}
/// Convert frontiers to a readable version vector
/// Convert frontiers to a version vector
///
/// Learn more about frontiers and version vector [here](https://loro.dev/docs/advanced/version_deep_dive)
///
/// @example
/// ```ts
@ -1730,34 +1740,6 @@ fn convert_container_path_to_js_value(path: &[(ContainerID, Index)]) -> Array {
/// The handler of a text container. It supports rich text CRDT.
///
/// ## Updating Text Content Using a Diff Algorithm
///
/// A common requirement is to update the current text to a target text.
/// You can implement this using a text diff algorithm of your choice.
/// Below is a sample you can directly copy into your code, which uses the
/// [fast-diff](https://www.npmjs.com/package/fast-diff) package.
///
/// ```ts
/// import { diff } from "fast-diff";
/// import { LoroText } from "loro-crdt";
///
/// function updateText(text: LoroText, newText: string) {
/// const src = text.toString();
/// const delta = diff(src, newText);
/// let index = 0;
/// for (const [op, text] of delta) {
/// if (op === 0) {
/// index += text.length;
/// } else if (op === 1) {
/// text.insert(index, text);
/// index += text.length;
/// } else {
/// text.delete(index, text.length);
/// }
/// }
/// ```
///
///
/// Learn more at https://loro.dev/docs/tutorial/text
#[derive(Clone)]
#[wasm_bindgen]
@ -1775,7 +1757,7 @@ struct MarkRange {
#[wasm_bindgen]
impl LoroText {
/// Create a new detached LoroText.
/// Create a new detached LoroText (not attached to any LoroDoc).
///
/// The edits on a detached container will not be persisted.
/// To attach the container to the document, please insert it into an attached container.
@ -1815,7 +1797,13 @@ impl LoroText {
})
}
/// Update the current text based on the provided text.
/// Update the current text to the target 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.
///
/// @example
/// ```ts
@ -1825,18 +1813,21 @@ impl LoroText {
/// const text = doc.getText("text");
/// text.insert(0, "Hello");
/// text.update("Hello World");
/// console.log(text.toString()); // "Hello World"
/// ```
pub fn update(&self, text: &str) {
self.handler.update(text);
}
/// Update the current text based on the provided text line by line.
/// Update the current text to the target text, the difference is calculated line by line.
///
/// It uses Myers' diff algorithm to compute the optimal difference.
#[wasm_bindgen(js_name = "updateByLine")]
pub fn update_by_line(&self, text: &str) {
self.handler.update_by_line(text);
}
/// Insert some string at index.
/// Insert the string at the given index (utf-16 index).
///
/// @example
/// ```ts
@ -1851,7 +1842,7 @@ impl LoroText {
Ok(())
}
/// Get a string slice.
/// Get a string slice (utf-16 index).
///
/// @example
/// ```ts
@ -1869,7 +1860,7 @@ impl LoroText {
}
}
/// Get the character at the given position.
/// Get the character at the given position (utf-16 index).
///
/// @example
/// ```ts
@ -1888,7 +1879,7 @@ impl LoroText {
}
}
/// Delete and return the string at the given range and insert a string at the same position.
/// Delete and return the string at the given range and insert a string at the same position (utf-16 index).
///
/// @example
/// ```ts
@ -1922,7 +1913,7 @@ impl LoroText {
Ok(())
}
/// Delete elements from index to index + len
/// Delete elements from index to index + len (utf-16 index).
///
/// @example
/// ```ts
@ -1959,7 +1950,7 @@ impl LoroText {
Ok(())
}
/// Mark a range of text with a key and a value.
/// Mark a range of text with a key and a value (utf-16 index).
///
/// > You should call `configTextStyle` before using `mark` and `unmark`.
///
@ -1984,7 +1975,7 @@ impl LoroText {
Ok(())
}
/// Unmark a range of text with a key and a value.
/// Unmark a range of text with a key and a value (utf-16 index).
///
/// > You should call `configTextStyle` before using `mark` and `unmark`.
///
@ -2008,7 +1999,7 @@ impl LoroText {
Ok(())
}
/// Convert the state to string
/// Convert the text to a string
#[allow(clippy::inherent_to_string)]
#[wasm_bindgen(js_name = "toString")]
pub fn to_string(&self) -> String {
@ -2053,7 +2044,7 @@ impl LoroText {
value.into()
}
/// Get the length of text
/// Get the length of text (utf-16 length).
#[wasm_bindgen(js_name = "length", method, getter)]
pub fn length(&self) -> usize {
self.handler.len_utf16()
@ -2064,11 +2055,11 @@ impl LoroText {
/// The events will be emitted after a transaction is committed. A transaction is committed when:
///
/// - `doc.commit()` is called.
/// - `doc.exportFrom(version)` is called.
/// - `doc.export(mode)` is called.
/// - `doc.import(data)` is called.
/// - `doc.checkout(version)` is called.
///
/// returns a subscription id, which can be used to unsubscribe.
/// returns a subscription callback, which can be used to unsubscribe.
pub fn subscribe(&self, f: js_sys::Function) -> JsResult<JsValue> {
let observer = observer::Observer::new(f);
let doc = self
@ -2100,8 +2091,6 @@ impl LoroText {
///
/// @example
/// ```ts
/// import { LoroDoc } from "loro-crdt";
///
/// const doc = new LoroDoc();
/// const text = doc.getText("text");
/// doc.configTextStyle({bold: {expand: "after"}});
@ -2121,7 +2110,7 @@ impl LoroText {
/// Get the parent container.
///
/// - The parent container of the root tree is `undefined`.
/// - The parent of the root is `undefined`.
/// - The object returned is a new js object each time because it need to cross
/// the WASM boundary.
pub fn parent(&self) -> JsContainerOrUndefined {
@ -2132,7 +2121,7 @@ impl LoroText {
}
}
/// Whether the container is attached to a document.
/// Whether the container is attached to a LoroDoc.
///
/// If it's detached, the operations on the container will not be persisted.
#[wasm_bindgen(js_name = "isAttached")]
@ -2142,7 +2131,7 @@ impl LoroText {
/// Get the attached container associated with this.
///
/// Returns an attached `Container` that equals to this or created by this, otherwise `undefined`.
/// Returns an attached `Container` that is equal to this or created by this; otherwise, it returns `undefined`.
#[wasm_bindgen(js_name = "getAttached")]
pub fn get_attached(&self) -> JsLoroTextOrUndefined {
if self.is_attached() {
@ -2157,7 +2146,10 @@ impl LoroText {
}
}
/// get the cursor at the given position.
/// Get the cursor at the given position.
///
/// - The first argument is the position (utf16-index).
/// - The second argument is the side: `-1` for left, `0` for middle, `1` for right.
#[wasm_bindgen(skip_typescript)]
pub fn getCursor(&self, pos: usize, side: JsSide) -> Option<Cursor> {
let mut side_value = Side::Middle;
@ -2189,7 +2181,7 @@ pub struct LoroMap {
#[wasm_bindgen]
impl LoroMap {
/// Create a new detached LoroMap.
/// Create a new detached LoroMap (not attached to any LoroDoc).
///
/// The edits on a detached container will not be persisted.
/// To attach the container to the document, please insert it into an attached container.
@ -2369,14 +2361,14 @@ impl LoroMap {
///
/// @example
/// ```ts
/// import { LoroDoc } from "loro-crdt";
/// import { LoroDoc, LoroText } from "loro-crdt";
///
/// const doc = new LoroDoc();
/// const map = doc.getMap("map");
/// map.set("foo", "bar");
/// const text = map.setContainer("text", new LoroText());
/// text.insert(0, "Hello");
/// console.log(map.getDeepValue()); // {"foo": "bar", "text": "Hello"}
/// console.log(map.toJSON()); // {"foo": "bar", "text": "Hello"}
/// ```
#[wasm_bindgen(js_name = "toJSON")]
pub fn to_json(&self) -> JsValue {
@ -2387,7 +2379,7 @@ impl LoroMap {
///
/// @example
/// ```ts
/// import { LoroDoc } from "loro-crdt";
/// import { LoroDoc, LoroText } from "loro-crdt";
///
/// const doc = new LoroDoc();
/// const map = doc.getMap("map");
@ -2404,12 +2396,12 @@ impl LoroMap {
/// Subscribe to the changes of the map.
///
/// Returns a subscription id, which can be used to unsubscribe.
/// Returns a subscription callback, which 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.exportFrom(version)` is called.
/// - `doc.export(mode)` is called.
/// - `doc.import(data)` is called.
/// - `doc.checkout(version)` is called.
///
@ -2522,7 +2514,7 @@ pub struct LoroList {
#[wasm_bindgen]
impl LoroList {
/// Create a new detached LoroList.
/// Create a new detached LoroList (not attached to any LoroDoc).
///
/// The edits on a detached container will not be persisted.
/// To attach the container to the document, please insert it into an attached container.
@ -2613,7 +2605,7 @@ impl LoroList {
///
/// @example
/// ```ts
/// import { LoroDoc } from "loro-crdt";
/// import { LoroDoc, LoroText } from "loro-crdt";
///
/// const doc = new LoroDoc();
/// const list = doc.getList("list");
@ -2646,14 +2638,14 @@ impl LoroList {
///
/// @example
/// ```ts
/// import { LoroDoc } from "loro-crdt";
/// import { LoroDoc, LoroText } from "loro-crdt";
///
/// const doc = new LoroDoc();
/// const list = doc.getList("list");
/// list.insert(0, 100);
/// const text = list.insertContainer(1, new LoroText());
/// text.insert(0, "Hello");
/// console.log(list.getDeepValue()); // [100, "Hello"];
/// console.log(list.toJSON()); // [100, "Hello"];
/// ```
#[wasm_bindgen(js_name = "toJSON")]
pub fn to_json(&self) -> JsValue {
@ -2665,14 +2657,14 @@ impl LoroList {
///
/// @example
/// ```ts
/// import { LoroDoc } from "loro-crdt";
/// import { LoroDoc, LoroText } from "loro-crdt";
///
/// const doc = new LoroDoc();
/// const list = doc.getList("list");
/// list.insert(0, 100);
/// const text = list.insertContainer(1, new LoroText());
/// text.insert(0, "Hello");
/// console.log(list.getDeepValue()); // [100, "Hello"];
/// console.log(list.toJSON()); // [100, "Hello"];
/// ```
#[wasm_bindgen(js_name = "insertContainer", skip_typescript)]
pub fn insert_container(&mut self, index: usize, child: JsContainer) -> JsResult<JsContainer> {
@ -2683,12 +2675,12 @@ impl LoroList {
/// Subscribe to the changes of the list.
///
/// Returns a subscription id, which can be used to unsubscribe.
/// Returns a subscription callback, which 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.exportFrom(version)` is called.
/// - `doc.export(mode)` is called.
/// - `doc.import(data)` is called.
/// - `doc.checkout(version)` is called.
///
@ -2777,6 +2769,9 @@ impl LoroList {
}
/// Get the cursor at the position.
///
/// - The first argument is the position .
/// - The second argument is the side: `-1` for left, `0` for middle, `1` for right.
#[wasm_bindgen(skip_typescript)]
pub fn getCursor(&self, pos: usize, side: JsSide) -> Option<Cursor> {
let mut side_value = Side::Middle;
@ -2839,7 +2834,7 @@ impl Default for LoroMovableList {
#[wasm_bindgen]
impl LoroMovableList {
/// Create a new detached LoroList.
/// Create a new detached LoroMovableList (not attached to any LoroDoc).
///
/// The edits on a detached container will not be persisted.
/// To attach the container to the document, please insert it into an attached container.
@ -2930,7 +2925,7 @@ impl LoroMovableList {
///
/// @example
/// ```ts
/// import { LoroDoc } from "loro-crdt";
/// import { LoroDoc, LoroText } from "loro-crdt";
///
/// const doc = new LoroDoc();
/// const list = doc.getList("list");
@ -2963,14 +2958,14 @@ impl LoroMovableList {
///
/// @example
/// ```ts
/// import { LoroDoc } from "loro-crdt";
/// import { LoroDoc, LoroText } from "loro-crdt";
///
/// const doc = new LoroDoc();
/// const list = doc.getList("list");
/// list.insert(0, 100);
/// const text = list.insertContainer(1, new LoroText());
/// text.insert(0, "Hello");
/// console.log(list.getDeepValue()); // [100, "Hello"];
/// console.log(list.toJSON()); // [100, "Hello"];
/// ```
#[wasm_bindgen(js_name = "toJSON")]
pub fn to_json(&self) -> JsValue {
@ -2982,14 +2977,14 @@ impl LoroMovableList {
///
/// @example
/// ```ts
/// import { LoroDoc } from "loro-crdt";
/// import { LoroDoc, LoroText } from "loro-crdt";
///
/// const doc = new LoroDoc();
/// const list = doc.getList("list");
/// list.insert(0, 100);
/// const text = list.insertContainer(1, new LoroText());
/// text.insert(0, "Hello");
/// console.log(list.getDeepValue()); // [100, "Hello"];
/// console.log(list.toJSON()); // [100, "Hello"];
/// ```
#[wasm_bindgen(js_name = "insertContainer", skip_typescript)]
pub fn insert_container(&mut self, index: usize, child: JsContainer) -> JsResult<JsContainer> {
@ -3000,12 +2995,12 @@ impl LoroMovableList {
/// Subscribe to the changes of the list.
///
/// Returns a subscription id, which can be used to unsubscribe.
/// Returns a subscription callback, which 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.exportFrom(version)` is called.
/// - `doc.export(mode)` is called.
/// - `doc.import(data)` is called.
/// - `doc.checkout(version)` is called.
///
@ -3271,10 +3266,10 @@ impl LoroTreeNode {
/// ```ts
/// const doc = new LoroDoc();
/// const tree = doc.getTree("tree");
/// const root = tree.createChildNode();
/// const node = root.createChildNode();
/// const node2 = node.createChildNode();
/// node2.moveTo(undefined, 0);
/// const root = tree.createNode();
/// const node = root.createNode();
/// const node2 = node.createNode();
/// node2.move(undefined, 0);
/// // node2 root
/// // |
/// // node
@ -3416,7 +3411,7 @@ impl LoroTreeNode {
#[wasm_bindgen]
impl LoroTree {
/// Create a new detached LoroTree.
/// Create a new detached LoroTree (not attached to any LoroDoc).
///
/// The edits on a detached container will not be persisted.
/// To attach the container to the document, please insert it into an attached container.
@ -3480,9 +3475,9 @@ impl LoroTree {
/// const root = tree.createNode();
/// const node = root.createNode();
/// const node2 = node.createNode();
/// tree.move(node2, root);
/// tree.move(node2.id, root.id);
/// // Error will be thrown if move operation creates a cycle
/// tree.move(root, node);
/// // tree.move(root.id, node.id);
/// ```
#[wasm_bindgen(js_name = "move")]
pub fn mov(
@ -3677,7 +3672,7 @@ impl LoroTree {
/// Subscribe to the changes of the tree.
///
/// Returns a subscription id, which can be used to unsubscribe.
/// Returns a subscription callback, which can be used to unsubscribe.
///
/// Trees have three types of events: `create`, `delete`, and `move`.
/// - `create`: Creates a new node with its `target` TreeID. If `parent` is undefined,
@ -3695,7 +3690,7 @@ impl LoroTree {
/// The events will be emitted after a transaction is committed. A transaction is committed when:
///
/// - `doc.commit()` is called.
/// - `doc.exportFrom(version)` is called.
/// - `doc.export(mode)` is called.
/// - `doc.import(data)` is called.
/// - `doc.checkout(version)` is called.
///
@ -3766,12 +3761,14 @@ impl LoroTree {
}
}
/// Set whether to generate fractional index for Tree Position.
/// Set whether to generate a fractional index for moving and creating.
///
/// 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.
/// A fractional index can be used to determine the position of tree nodes among their siblings.
///
/// Generally speaking, jitter will affect the growth rate of document size.
/// The jitter is used to avoid conflicts when multiple users are creating a node at the same position.
/// A value of 0 is the default, which means no jitter; any value larger than 0 will enable jitter.
///
/// Generally speaking, higher jitter value will increase the size of the operation
/// [Read more about it](https://www.loro.dev/blog/movable-tree#implementation-and-encoding-size)
#[wasm_bindgen(js_name = "enableFractionalIndex")]
pub fn enable_fractional_index(&self, jitter: u8) {
@ -3779,7 +3776,7 @@ impl LoroTree {
}
/// Disable the fractional index generation for Tree Position when
/// you don't need the Tree's siblings to be sorted. The fractional index will be always default.
/// you don't need the Tree's siblings to be sorted. The fractional index will always be set to default.
#[wasm_bindgen(js_name = "disableFractionalIndex")]
pub fn disable_fractional_index(&self) {
self.handler.disable_fractional_index();
@ -4385,13 +4382,12 @@ const TYPES: &'static str = r#"
* It is most commonly used to specify the type of sub-container to be created.
* @example
* ```ts
* import { LoroDoc } from "loro-crdt";
* import { LoroDoc, LoroText } from "loro-crdt";
*
* const doc = new LoroDoc();
* const list = doc.getList("list");
* list.insert(0, 100);
* const containerType = "Text";
* const text = list.insertContainer(1, containerType);
* const text = list.insertContainer(1, new LoroText());
* ```
*/
export type ContainerType = "Text" | "Map" | "List"| "Tree" | "MovableList";

View file

@ -195,7 +195,7 @@ impl LoroDoc {
self.doc.is_detached_editing_enabled()
}
/// Set the interval of mergeable changes, in milliseconds.
/// 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.
@ -229,10 +229,10 @@ impl LoroDoc {
/// 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`.
/// 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`.
#[inline]
@ -358,7 +358,7 @@ impl LoroDoc {
/// The events will be emitted after a transaction is committed. A transaction is committed when:
///
/// - `doc.commit()` is called.
/// - `doc.exportFrom(version)` is called.
/// - `doc.export(mode)` is called.
/// - `doc.import(data)` is called.
/// - `doc.checkout(version)` is called.
#[inline]
@ -485,7 +485,7 @@ impl LoroDoc {
self.doc.oplog_vv()
}
/// Get the `VersionVector` version of `OpLog`
/// Get the `VersionVector` version of `DocState`
#[inline]
pub fn state_vv(&self) -> VersionVector {
self.doc.state_vv()
@ -529,13 +529,13 @@ impl LoroDoc {
self.doc.get_value()
}
/// Get the current state of the document.
/// Get the entire state of the current DocState
#[inline]
pub fn get_deep_value(&self) -> LoroValue {
self.doc.get_deep_value()
}
/// Get the current state with container id of the doc
/// Get the entire state of the current DocState with container id
pub fn get_deep_value_with_id(&self) -> LoroValue {
self.doc
.app_state()
@ -552,7 +552,7 @@ impl LoroDoc {
/// Get the `Frontiers` version of `DocState`
///
/// [Learn more about `Frontiers`]()
/// Learn more about [`Frontiers`](https://loro.dev/docs/advanced/version_deep_dive)
#[inline]
pub fn state_frontiers(&self) -> Frontiers {
self.doc.state_frontiers()
@ -566,7 +566,7 @@ impl LoroDoc {
/// Change the PeerID
///
/// NOTE: You need ot make sure there is no chance two peer have the same 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.
#[inline]
pub fn set_peer_id(&self, peer: PeerID) -> LoroResult<()> {
@ -576,12 +576,12 @@ impl LoroDoc {
/// Subscribe the events of a container.
///
/// The callback will be invoked after a transaction that change the container.
/// Returns a subscription id that can be used to unsubscribe.
/// 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.exportFrom(version)` is called.
/// - `doc.export(mode)` is called.
/// - `doc.import(data)` is called.
/// - `doc.checkout(version)` is called.
///
@ -614,6 +614,8 @@ impl LoroDoc {
/// text.insert(0, "123").unwrap();
/// doc.commit();
/// assert!(ran.load(std::sync::atomic::Ordering::Relaxed));
/// // unsubscribe
/// sub.unsubscribe();
/// ```
#[inline]
pub fn subscribe(&self, container_id: &ContainerID, callback: Subscriber) -> Subscription {
@ -628,12 +630,12 @@ impl LoroDoc {
/// Subscribe all the events.
///
/// The callback will be invoked when any part of the [loro_internal::DocState] is changed.
/// Returns a subscription id that can be used to unsubscribe.
/// 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.exportFrom(version)` is called.
/// - `doc.export(mode)` is called.
/// - `doc.import(data)` is called.
/// - `doc.checkout(version)` is called.
#[inline]
@ -2266,7 +2268,7 @@ impl ContainerTrait for LoroUnknown {
use enum_as_inner::EnumAsInner;
/// All the CRDT containers supported by loro.
/// All the CRDT containers supported by Loro.
#[derive(Clone, Debug, EnumAsInner)]
pub enum Container {
/// [LoroList container](https://loro.dev/docs/tutorial/list)

View file

@ -3,23 +3,23 @@ export type * from "loro-wasm";
import {
Container,
ContainerID,
ContainerType,
Delta,
LoroCounter,
LoroDoc,
LoroList,
LoroMap,
LoroText,
LoroTree,
LoroCounter,
OpId,
TreeID,
Value,
ContainerType,
} from "loro-wasm";
/**
* @deprecated Please use LoroDoc
*/
export class Loro extends LoroDoc { }
export class Loro extends LoroDoc {}
export { Awareness } from "./awareness";
export type Frontiers = OpId[];
@ -97,7 +97,12 @@ export type TreeDiffItem =
index: number;
fractionalIndex: string;
}
| { target: TreeID; action: "delete"; oldParent: TreeID | undefined; oldIndex: number }
| {
target: TreeID;
action: "delete";
oldParent: TreeID | undefined;
oldIndex: number;
}
| {
target: TreeID;
action: "move";
@ -116,7 +121,7 @@ export type TreeDiff = {
export type CounterDiff = {
type: "counter";
increment: number;
}
};
export type Diff = ListDiff | TextDiff | MapDiff | TreeDiff | CounterDiff;
@ -124,7 +129,14 @@ interface Listener {
(event: LoroEventBatch): void;
}
const CONTAINER_TYPES = ["Map", "Text", "List", "Tree", "MovableList", "Counter"];
const CONTAINER_TYPES = [
"Map",
"Text",
"List",
"Tree",
"MovableList",
"Counter",
];
export function isContainerId(s: string): s is ContainerID {
return s.startsWith("cid:");
@ -145,6 +157,7 @@ export function isContainerId(s: string): s is ContainerID {
* isContainer(123); // false
* isContainer("123"); // false
* isContainer({}); // false
* ```
*/
export function isContainer(value: any): value is Container {
if (typeof value !== "object" || value == null) {
@ -178,14 +191,10 @@ export function isContainer(value: any): value is Container {
*/
export function getType<T>(
value: T,
): T extends LoroText
? "Text"
: T extends LoroMap<any>
? "Map"
: T extends LoroTree<any>
? "Tree"
: T extends LoroList<any>
? "List"
): T extends LoroText ? "Text"
: T extends LoroMap<any> ? "Map"
: T extends LoroTree<any> ? "Tree"
: T extends LoroList<any> ? "List"
: T extends LoroCounter ? "Counter"
: "Json" {
if (isContainer(value)) {
@ -293,14 +302,14 @@ declare module "loro-wasm" {
}
interface LoroList<T = unknown> {
new(): LoroList<T>;
new (): LoroList<T>;
/**
* Get elements of the list. If the value is a child container, the corresponding
* `Container` will be returned.
*
* @example
* ```ts
* import { LoroDoc } from "loro-crdt";
* import { LoroDoc, LoroText } from "loro-crdt";
*
* const doc = new LoroDoc();
* const list = doc.getList("list");
@ -369,7 +378,7 @@ declare module "loro-wasm" {
}
interface LoroMovableList<T = unknown> {
new(): LoroMovableList<T>;
new (): LoroMovableList<T>;
/**
* Get elements of the list. If the value is a child container, the corresponding
* `Container` will be returned.
@ -393,7 +402,7 @@ declare module "loro-wasm" {
*
* @example
* ```ts
* import { LoroDoc } from "loro-crdt";
* import { LoroDoc, LoroText } from "loro-crdt";
*
* const doc = new LoroDoc();
* const list = doc.getMovableList("list");
@ -458,7 +467,7 @@ declare module "loro-wasm" {
* import { LoroDoc } from "loro-crdt";
*
* const doc = new LoroDoc();
* const list = doc.getList("list");
* const list = doc.getMovableList("list");
* list.insert(0, 100);
* list.insert(1, "foo");
* list.insert(2, true);
@ -472,7 +481,7 @@ declare module "loro-wasm" {
*
* @example
* ```ts
* import { LoroDoc } from "loro-crdt";
* import { LoroDoc, LoroText } from "loro-crdt";
*
* const doc = new LoroDoc();
* const list = doc.getMovableList("list");
@ -491,7 +500,7 @@ declare module "loro-wasm" {
interface LoroMap<
T extends Record<string, unknown> = Record<string, unknown>,
> {
new(): LoroMap<T>;
new (): LoroMap<T>;
/**
* Get the value of the key. If the value is a child container, the corresponding
* `Container` will be returned.
@ -514,13 +523,13 @@ declare module "loro-wasm" {
*
* @example
* ```ts
* import { LoroDoc } from "loro-crdt";
* import { LoroDoc, LoroText, LoroList } from "loro-crdt";
*
* const doc = new LoroDoc();
* const map = doc.getMap("map");
* map.set("foo", "bar");
* const text = map.setContainer("text", new LoroText());
* const list = map.setContainer("list", new LoroText());
* const list = map.setContainer("list", new LoroList());
* ```
*/
setContainer<C extends Container, Key extends keyof T>(
@ -569,7 +578,7 @@ declare module "loro-wasm" {
}
interface LoroText {
new(): LoroText;
new (): LoroText;
insert(pos: number, text: string): void;
delete(pos: number, len: number): void;
subscribe(listener: Listener): Subscription;
@ -578,7 +587,7 @@ declare module "loro-wasm" {
interface LoroTree<
T extends Record<string, unknown> = Record<string, unknown>,
> {
new(): LoroTree<T>;
new (): LoroTree<T>;
/**
* Create a new tree node as the child of parent and return a `LoroTreeNode` instance.
* If the parent is undefined, the tree node will be a root node.
@ -617,7 +626,7 @@ declare module "loro-wasm" {
* Get the associated metadata map container of a tree node.
*/
readonly data: LoroMap<T>;
/**
/**
* Create a new node as the child of the current node and
* return an instance of `LoroTreeNode`.
*
@ -664,6 +673,9 @@ export function newContainerID(id: OpId, type: ContainerType): ContainerID {
return `cid:${id.counter}@${id.peer}:${type}`;
}
export function newRootContainerID(name: string, type: ContainerType): ContainerID {
export function newRootContainerID(
name: string,
type: ContainerType,
): ContainerID {
return `cid:root-${name}:${type}`;
}

View file

@ -2,5 +2,8 @@
"imports": {
"@std/fs": "jsr:@std/fs@^1.0.2",
"@std/toml": "jsr:@std/toml@^1.0.1"
},
"tasks": {
"doc-test": "deno run --allow-env --allow-read --allow-run run-js-doc-tests.ts ../crates/loro-wasm/src/lib.rs ../loro-js/src/index.ts"
}
}

View file

@ -1,34 +1,319 @@
{
"version": "3",
"packages": {
"specifiers": {
"jsr:@std/collections@^1.0.5": "jsr:@std/collections@1.0.5",
"jsr:@std/fs@^1.0.2": "jsr:@std/fs@1.0.2",
"jsr:@std/path@^1.0.3": "jsr:@std/path@1.0.3",
"jsr:@std/toml@^1.0.1": "jsr:@std/toml@1.0.1"
"version": "4",
"specifiers": {
"jsr:@std/collections@^1.0.5": "1.0.5",
"jsr:@std/fs@^1.0.2": "1.0.2",
"jsr:@std/path@^1.0.3": "1.0.3",
"jsr:@std/toml@^1.0.1": "1.0.1",
"npm:expect@29.7.0": "29.7.0",
"npm:loro-crdt@1.0.7": "1.0.7"
},
"jsr": {
"@std/collections@1.0.5": {
"integrity": "ab9eac23b57a0c0b89ba45134e61561f69f3d001f37235a248ed40be260c0c10"
},
"jsr": {
"@std/collections@1.0.5": {
"integrity": "ab9eac23b57a0c0b89ba45134e61561f69f3d001f37235a248ed40be260c0c10"
},
"@std/fs@1.0.2": {
"integrity": "af57555c7a224a6f147d5cced5404692974f7a628ced8eda67e0d22d92d474ec",
"dependencies": [
"jsr:@std/path@^1.0.3"
]
},
"@std/path@1.0.3": {
"integrity": "cd89d014ce7eb3742f2147b990f6753ee51d95276bfc211bc50c860c1bc7df6f"
},
"@std/toml@1.0.1": {
"integrity": "b55b407159930f338d384b1f8fd317c8e8a35e27ebb8946155f49e3a158d16c4",
"dependencies": [
"jsr:@std/collections@^1.0.5"
]
}
"@std/fs@1.0.2": {
"integrity": "af57555c7a224a6f147d5cced5404692974f7a628ced8eda67e0d22d92d474ec",
"dependencies": [
"jsr:@std/path"
]
},
"@std/path@1.0.3": {
"integrity": "cd89d014ce7eb3742f2147b990f6753ee51d95276bfc211bc50c860c1bc7df6f"
},
"@std/toml@1.0.1": {
"integrity": "b55b407159930f338d384b1f8fd317c8e8a35e27ebb8946155f49e3a158d16c4",
"dependencies": [
"jsr:@std/collections"
]
}
},
"npm": {
"@babel/code-frame@7.25.9": {
"integrity": "sha512-z88xeGxnzehn2sqZ8UdGQEvYErF1odv2CftxInpSYJt6uHuPe9YjahKZITGs3l5LeI9d2ROG+obuDAoSlqbNfQ==",
"dependencies": [
"@babel/highlight",
"picocolors"
]
},
"@babel/helper-validator-identifier@7.25.9": {
"integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="
},
"@babel/highlight@7.25.9": {
"integrity": "sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw==",
"dependencies": [
"@babel/helper-validator-identifier",
"chalk@2.4.2",
"js-tokens",
"picocolors"
]
},
"@jest/expect-utils@29.7.0": {
"integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==",
"dependencies": [
"jest-get-type"
]
},
"@jest/schemas@29.6.3": {
"integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
"dependencies": [
"@sinclair/typebox"
]
},
"@jest/types@29.6.3": {
"integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==",
"dependencies": [
"@jest/schemas",
"@types/istanbul-lib-coverage",
"@types/istanbul-reports",
"@types/node",
"@types/yargs",
"chalk@4.1.2"
]
},
"@sinclair/typebox@0.27.8": {
"integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA=="
},
"@types/istanbul-lib-coverage@2.0.6": {
"integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w=="
},
"@types/istanbul-lib-report@3.0.3": {
"integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==",
"dependencies": [
"@types/istanbul-lib-coverage"
]
},
"@types/istanbul-reports@3.0.4": {
"integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==",
"dependencies": [
"@types/istanbul-lib-report"
]
},
"@types/node@22.5.4": {
"integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==",
"dependencies": [
"undici-types"
]
},
"@types/stack-utils@2.0.3": {
"integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw=="
},
"@types/yargs-parser@21.0.3": {
"integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ=="
},
"@types/yargs@17.0.33": {
"integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==",
"dependencies": [
"@types/yargs-parser"
]
},
"ansi-styles@3.2.1": {
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dependencies": [
"color-convert@1.9.3"
]
},
"ansi-styles@4.3.0": {
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dependencies": [
"color-convert@2.0.1"
]
},
"ansi-styles@5.2.0": {
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="
},
"braces@3.0.3": {
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dependencies": [
"fill-range"
]
},
"chalk@2.4.2": {
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dependencies": [
"ansi-styles@3.2.1",
"escape-string-regexp@1.0.5",
"supports-color@5.5.0"
]
},
"chalk@4.1.2": {
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dependencies": [
"ansi-styles@4.3.0",
"supports-color@7.2.0"
]
},
"ci-info@3.9.0": {
"integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="
},
"color-convert@1.9.3": {
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dependencies": [
"color-name@1.1.3"
]
},
"color-convert@2.0.1": {
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dependencies": [
"color-name@1.1.4"
]
},
"color-name@1.1.3": {
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
},
"color-name@1.1.4": {
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"diff-sequences@29.6.3": {
"integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q=="
},
"escape-string-regexp@1.0.5": {
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="
},
"escape-string-regexp@2.0.0": {
"integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="
},
"expect@29.7.0": {
"integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==",
"dependencies": [
"@jest/expect-utils",
"jest-get-type",
"jest-matcher-utils",
"jest-message-util",
"jest-util"
]
},
"fill-range@7.1.1": {
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dependencies": [
"to-regex-range"
]
},
"graceful-fs@4.2.11": {
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
},
"has-flag@3.0.0": {
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="
},
"has-flag@4.0.0": {
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
},
"is-number@7.0.0": {
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
},
"jest-diff@29.7.0": {
"integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==",
"dependencies": [
"chalk@4.1.2",
"diff-sequences",
"jest-get-type",
"pretty-format"
]
},
"jest-get-type@29.6.3": {
"integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw=="
},
"jest-matcher-utils@29.7.0": {
"integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==",
"dependencies": [
"chalk@4.1.2",
"jest-diff",
"jest-get-type",
"pretty-format"
]
},
"jest-message-util@29.7.0": {
"integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==",
"dependencies": [
"@babel/code-frame",
"@jest/types",
"@types/stack-utils",
"chalk@4.1.2",
"graceful-fs",
"micromatch",
"pretty-format",
"slash",
"stack-utils"
]
},
"jest-util@29.7.0": {
"integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==",
"dependencies": [
"@jest/types",
"@types/node",
"chalk@4.1.2",
"ci-info",
"graceful-fs",
"picomatch"
]
},
"js-tokens@4.0.0": {
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"loro-crdt@1.0.7": {
"integrity": "sha512-BD9qx3dQdqhEegOTEYdYo2GvRoYAeZFAfwcJLDtNRKo20y/1dUsRmzAvtLzZI/cdcRg+DkpBBLr+wcL4jRYUNw==",
"dependencies": [
"loro-wasm"
]
},
"loro-wasm@1.0.7": {
"integrity": "sha512-WFIpGGzc6I7zRMDoRGxa3AHhno7gVnOgwqcrTfmpKWOtktZQ7BvhIV4kYgsdyuIBcMSrQEJTfOY/80xQSjUKTw=="
},
"micromatch@4.0.8": {
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dependencies": [
"braces",
"picomatch"
]
},
"picocolors@1.1.1": {
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
},
"picomatch@2.3.1": {
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
},
"pretty-format@29.7.0": {
"integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
"dependencies": [
"@jest/schemas",
"ansi-styles@5.2.0",
"react-is"
]
},
"react-is@18.3.1": {
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="
},
"slash@3.0.0": {
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="
},
"stack-utils@2.0.6": {
"integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==",
"dependencies": [
"escape-string-regexp@2.0.0"
]
},
"supports-color@5.5.0": {
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dependencies": [
"has-flag@3.0.0"
]
},
"supports-color@7.2.0": {
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dependencies": [
"has-flag@4.0.0"
]
},
"to-regex-range@5.0.1": {
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dependencies": [
"is-number"
]
},
"undici-types@6.19.8": {
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="
}
},
"remote": {},
"workspace": {
"dependencies": [
"jsr:@std/fs@^1.0.2",

View file

@ -0,0 +1,113 @@
/* tslint:disable */
/* eslint-disable */
/** */
export function run(): void;
/**
* @param {({ peer: PeerID, counter: number })[]} frontiers
* @returns {Uint8Array}
*/
export function encodeFrontiers(
frontiers: ({ peer: PeerID; counter: number })[],
): Uint8Array;
/**
* @param {Uint8Array} bytes
* @returns {{ peer: PeerID, counter: number }[]}
*/
export function decodeFrontiers(
bytes: Uint8Array,
): { peer: PeerID; counter: number }[];
/**
* Enable debug info of Loro
*/
export function setDebug(): void;
/**
* Decode the metadata of the import blob.
*
* This method is useful to get the following metadata of the import blob:
*
* - startVersionVector
* - endVersionVector
* - startTimestamp
* - endTimestamp
* - isSnapshot
* - changeNum
* @param {Uint8Array} blob
* @returns {ImportBlobMetadata}
*/
export function decodeImportBlobMeta(blob: Uint8Array): ImportBlobMetadata;
/**
* Container types supported by loro.
*
* It is most commonly used to specify the type of sub-container to be created.
* @example
* ```ts
* import { LoroDoc, LoroText } from "loro-crdt";
*
* const doc = new LoroDoc();
* const list = doc.getList("list");
* list.insert(0, 100);
* const text = list.insertContainer(1, new LoroText());
* ```
*/
export type ContainerType = "Text" | "Map" | "List" | "Tree" | "MovableList";
export type PeerID = `${number}`;
/**
* The unique id of each container.
*
* @example
* ```ts
* import { LoroDoc } from "loro-crdt";
*
* const doc = new LoroDoc();
* const list = doc.getList("list");
* const containerId = list.id;
* ```
*/
export type ContainerID =
| `cid:root-${string}:${ContainerType}`
| `cid:${number}@${PeerID}:${ContainerType}`;
/**
* The unique id of each tree node.
*/
export type TreeID = `${number}@${PeerID}`;
interface LoroDoc {
/**
* Export updates from the specific version to the current version
*
* @deprecated Use `export({mode: "update", from: version})` instead
*
* @example
* ```ts
* import { LoroDoc } from "loro-crdt";
*
* const doc = new LoroDoc();
* const text = doc.getText("text");
* text.insert(0, "Hello");
* // get all updates of the doc
* const updates = doc.exportFrom();
* const version = doc.oplogVersion();
* text.insert(5, " World");
* // get updates from specific version to the latest version
* const updates2 = doc.exportFrom(version);
* ```
*/
exportFrom(version?: VersionVector): Uint8Array;
///
/// Get the container corresponding to the container id
///
/// @example
/// ```ts
/// import { LoroDoc } from "loro-crdt";
///
/// const doc = new LoroDoc();
/// let text = doc.getText("text");
/// const textId = text.id;
/// text = doc.getContainerById(textId);
/// ```
///
getContainerById(id: ContainerID): Container;
}

117
scripts/run-js-doc-tests.ts Normal file
View file

@ -0,0 +1,117 @@
const LORO_VERSION = "1.0.7";
export interface CodeBlock {
filename: string;
filePath: string;
lineNumber: number;
lang: string;
content: string;
}
export function extractCodeBlocks(
fileContent: string,
codeBlocks: CodeBlock[],
name: string,
path: string,
) {
// Regular expression to detect TypeScript code blocks
const codeBlockRegex = /```(typescript|ts|js|javascript)\n([\s\S]*?)```/g;
let match;
while ((match = codeBlockRegex.exec(fileContent)) !== null) {
const startLine =
fileContent.substring(0, match.index).split("\n").length;
let content = match[2];
content = content.replace(/^\s*\*/g, "");
content = content.replace(/\n\s*\*/g, "\n");
content = content.replace(/^\s*\/\/\//g, "");
content = content.replace(/\n\s*\/\/\//g, "\n");
content = replaceImportVersion(content, LORO_VERSION);
if (!content.includes("loro-crdt")) {
content = IMPORTS + content;
}
codeBlocks.push({
filename: name,
filePath: path,
lineNumber: startLine,
content,
lang: match[1],
});
}
}
function replaceImportVersion(input: string, targetVersion: string): string {
const regex = /from "loro-crdt"/g;
const replacement = `from "npm:loro-crdt@${targetVersion}"`;
return input.replace(regex, replacement);
}
const IMPORTS =
`import { Loro, LoroDoc, LoroMap, LoroText, LoroList, Delta, UndoManager, getType, isContainer } from "npm:loro-crdt@${LORO_VERSION}";
import { expect } from "npm:expect@29.7.0";\n
`;
Deno.test("extract doc tests", async () => {
const filePath = "./doc-tests-tests/example.txt";
const fileContent = await Deno.readTextFile(filePath);
const codeBlocks: CodeBlock[] = [];
extractCodeBlocks(fileContent, codeBlocks, "example.txt", filePath);
for (const block of codeBlocks) {
console.log(block.content);
console.log("==============================");
}
await runCodeBlocks(codeBlocks);
});
export async function runDocTests(paths: string[]) {
const codeBlocks: CodeBlock[] = [];
for (const path of paths) {
const fileContent = await Deno.readTextFile(path);
extractCodeBlocks(fileContent, codeBlocks, path, path);
}
await runCodeBlocks(codeBlocks);
}
async function runCodeBlocks(codeBlocks: CodeBlock[]) {
let testCases = 0;
let passed = 0;
let failed = 0;
for (const block of codeBlocks) {
try {
const command = new Deno.Command("deno", {
args: ["eval", "--ext=ts", block.content],
stdout: "null",
stderr: "inherit",
});
const process = command.spawn();
const status = await process.status;
testCases += 1;
if (status.success) {
passed += 1;
} else {
console.log("----------------");
console.log(block.content);
console.log("-----------------");
console.error(
`\x1b[31;1mError in \x1b[4m${block.filePath}:${block.lineNumber}\x1b[0m\n\n\n\n\n`,
);
failed += 1;
}
} catch (error) {
console.error("Error:", error);
}
await Deno.stdout.write(
new TextEncoder().encode(
`\r🧪 ${testCases} tests, ✅ ${passed} passed,${
failed > 0 ? " ❌" : ""
} ${failed} failed`,
),
);
}
}
if (Deno.args.length > 0) {
await runDocTests(Deno.args);
} else {
console.log("No paths provided. Please provide paths as arguments.");
}