From 203d82bf3bd74aa736b8c9e90bee479c2d0e15f4 Mon Sep 17 00:00:00 2001 From: Leon Zhao Date: Sat, 19 Oct 2024 17:09:03 +0800 Subject: [PATCH] feat: wasm api 1.0 (#521) * feat: new 1.0 api wasm * test: add new api test --- crates/loro-wasm/Cargo.toml | 6 +- crates/loro-wasm/src/lib.rs | 113 ++++++++++++++++++++++++++++++++++-- loro-js/tests/basic.test.ts | 36 ++++++++++++ loro-js/tests/misc.test.ts | 1 - 4 files changed, 150 insertions(+), 6 deletions(-) diff --git a/crates/loro-wasm/Cargo.toml b/crates/loro-wasm/Cargo.toml index e0cb2b93..b4729c3b 100644 --- a/crates/loro-wasm/Cargo.toml +++ b/crates/loro-wasm/Cargo.toml @@ -10,7 +10,11 @@ crate-type = ["cdylib", "rlib"] [dependencies] js-sys = "0.3.60" -loro-internal = { path = "../loro-internal", features = ["wasm", "counter"] } +loro-internal = { path = "../loro-internal", features = [ + "wasm", + "counter", + "jsonpath", +] } wasm-bindgen = "=0.2.92" serde-wasm-bindgen = { version = "^0.6.5" } wasm-bindgen-derive = "0.2.1" diff --git a/crates/loro-wasm/src/lib.rs b/crates/loro-wasm/src/lib.rs index f2dfc01f..f22d3542 100644 --- a/crates/loro-wasm/src/lib.rs +++ b/crates/loro-wasm/src/lib.rs @@ -28,7 +28,7 @@ use loro_internal::{ }; use rle::HasLength; use serde::{Deserialize, Serialize}; -use std::{cell::RefCell, cmp::Ordering, mem::ManuallyDrop, rc::Rc, sync::Arc}; +use std::{cell::RefCell, cmp::Ordering, mem::ManuallyDrop, ops::ControlFlow, rc::Rc, sync::Arc}; use wasm_bindgen::{__rt::IntoJsResult, prelude::*, throw_val}; use wasm_bindgen_derive::TryFromJsValue; @@ -556,6 +556,14 @@ impl LoroDoc { Self(Arc::new(self.0.fork())) } + /// Creates a new LoroDoc at a specified version (Frontiers) + #[wasm_bindgen(js_name = "forkAt")] + pub fn fork_at(&self, frontiers: Vec) -> JsResult { + Ok(Self(Arc::new( + self.0.fork_at(&ids_to_frontiers(frontiers)?), + ))) + } + /// Checkout the `DocState` to the latest version of `OpLog`. /// /// > The document becomes detached during a `checkout` operation. @@ -584,6 +592,47 @@ impl LoroDoc { Ok(()) } + /// + #[wasm_bindgen(js_name = "travelChangeAncestors")] + pub fn travel_change_ancestors(&self, ids: Vec, f: js_sys::Function) -> JsResult<()> { + let observer = observer::Observer::new(f); + self.0 + .travel_change_ancestors( + &ids.into_iter() + .map(|id| js_id_to_id(id).unwrap()) + .collect::>(), + &mut |meta| { + let res = observer + .call1( + &ChangeMeta { + lamport: meta.lamport, + length: meta.len as u32, + peer: meta.id.peer.to_string(), + counter: meta.id.counter, + deps: meta + .deps + .iter() + .map(|id| StringID { + peer: id.peer.to_string(), + counter: id.counter, + }) + .collect(), + timestamp: meta.timestamp as f64, + message: meta.message, + } + .to_js(), + ) + .unwrap(); + if res.as_bool().unwrap() { + ControlFlow::Continue(()) + } else { + ControlFlow::Break(()) + } + }, + ) + .map_err(|e| JsValue::from(e.to_string())) + } + /// Checkout the `DocState` to a specific version. /// /// > The document becomes detached during a `checkout` operation. @@ -894,6 +943,47 @@ impl LoroDoc { }) } + /// Set the commit message of the next commit + #[wasm_bindgen(js_name = "setNextCommitMessage")] + pub fn set_next_commit_message(&self, msg: &str) { + self.0.set_next_commit_message(msg); + } + + /// Get deep value of the document with container id + #[wasm_bindgen(js_name = "getDeepValueWithID")] + pub fn get_deep_value_with_id(&self) -> JsValue { + self.0.get_deep_value_with_id().into() + } + + /// Get the path from the root to the container + #[wasm_bindgen(js_name = "getPathToContainer")] + pub fn get_path_to_container(&self, id: JsContainerID) -> JsResult> { + let id: ContainerID = id.to_owned().try_into()?; + let ans = self + .0 + .get_path_to_container(&id) + .map(|p| convert_container_path_to_js_value(&p)); + Ok(ans) + } + + /// Evaluate JSONPath against a LoroDoc + #[wasm_bindgen(js_name = "JSONPath")] + pub fn json_path(&self, jsonpath: &str) -> JsResult { + let ans = Array::new(); + for v in self + .0 + .jsonpath(jsonpath) + .map_err(|e| JsValue::from(e.to_string()))? + .into_iter() + { + ans.push(&match v { + ValueOrHandler::Handler(h) => handler_to_js_value(h, Some(self.0.clone())), + ValueOrHandler::Value(v) => v.into(), + }); + } + Ok(ans) + } + /// Get the encoded version vector of the current document. /// /// If you checkout to a specific version, the version vector will change. @@ -1634,13 +1724,12 @@ fn container_diff_to_js_value( obj.into() } -fn convert_container_path_to_js_value(path: &[(ContainerID, Index)]) -> JsValue { +fn convert_container_path_to_js_value(path: &[(ContainerID, Index)]) -> Array { let arr = Array::new_with_length(path.len() as u32); for (i, p) in path.iter().enumerate() { arr.set(i as u32, p.1.clone().into()); } - let path: JsValue = arr.into_js_result().unwrap(); - path + arr } /// The handler of a text container. It supports rich text CRDT. @@ -1745,6 +1834,12 @@ impl LoroText { self.handler.update(text); } + /// Update the current text based on the provided text line by line. + #[wasm_bindgen(js_name = "updateByLine")] + pub fn update_by_line(&self, text: &str) { + self.handler.update_by_line(text); + } + /// Insert some string at index. /// /// @example @@ -3693,6 +3788,12 @@ impl LoroTree { pub fn disable_fractional_index(&self) { self.handler.disable_fractional_index(); } + + /// Whether the tree enables the fractional index generation. + #[wasm_bindgen(js_name = "isFractionalIndexEnabled")] + pub fn is_fractional_index_enabled(&self) -> bool { + self.handler.is_fractional_index_enabled() + } } impl Default for LoroTree { @@ -4035,6 +4136,10 @@ impl UndoManager { self.undo.set_on_pop(None); } } + + pub fn clear(&self) { + self.undo.clear(); + } } /// Use this function to throw an error after the micro task. diff --git a/loro-js/tests/basic.test.ts b/loro-js/tests/basic.test.ts index 5186c345..be80e1e4 100644 --- a/loro-js/tests/basic.test.ts +++ b/loro-js/tests/basic.test.ts @@ -16,6 +16,7 @@ import { encodeFrontiers, decodeFrontiers, } from "../src"; +import { m } from "vitest/dist/reporters-yx5ZTtEV"; it("basic example", () => { const doc = new LoroDoc(); @@ -619,3 +620,38 @@ it("can encode/decode frontiers", () => { const decoded = decodeFrontiers(encoded); expect(decoded).toStrictEqual(frontiers); }) + +it("travel changes", () => { + let doc = new LoroDoc(); + doc.setPeerId(1); + doc.getText("text").insert(0, "abc"); + doc.commit(); + let n = 0; + doc.travelChangeAncestors([{ peer: "1", counter: 0 }], (meta: any) => { + n += 1; + return true + }) + expect(n).toBe(1); +}) + +it("get path to container", () => { + const doc = new LoroDoc(); + const map = doc.getMap("map"); + const list = map.setContainer("list", new LoroList()); + const path = doc.getPathToContainer(list.id); + expect(path).toStrictEqual(["map", "list"]) +}) + +it("json path", () => { + const doc = new LoroDoc(); + const map = doc.getMap("map"); + map.set("key", "value"); + const books = map.setContainer("books", new LoroList()); + const book = books.insertContainer(0, new LoroMap()); + book.set("title", "1984"); + book.set("author", "George Orwell"); + const path = "$['map'].books[0].title"; + const result = doc.JSONPath(path); + expect(result.length).toBe(1); + expect(result).toStrictEqual(["1984"]) +}) \ No newline at end of file diff --git a/loro-js/tests/misc.test.ts b/loro-js/tests/misc.test.ts index 31c92669..c4b0f398 100644 --- a/loro-js/tests/misc.test.ts +++ b/loro-js/tests/misc.test.ts @@ -246,7 +246,6 @@ describe("type", () => { describe("list stable position", () => { it("basic tests", () => { const loro = new LoroDoc(); - loro.oplogFrontiers const list = loro.getList("list"); list.insert(0, "a"); const pos0 = list.getCursor(0);