diff --git a/crates/loro-common/src/error.rs b/crates/loro-common/src/error.rs index a44cded0..98318b88 100644 --- a/crates/loro-common/src/error.rs +++ b/crates/loro-common/src/error.rs @@ -38,6 +38,8 @@ pub enum LoroError { ArgErr(Box), #[error("Auto commit has not started. The doc is readonly when detached. You should ensure autocommit is on and the doc and the state is attached.")] AutoCommitNotStarted, + #[error("The doc is already dropped")] + DocDropError, // #[error("the data for key `{0}` is not available")] // Redaction(String), // #[error("invalid header (expected {expected:?}, found {found:?})")] diff --git a/crates/loro-internal/src/handler.rs b/crates/loro-internal/src/handler.rs index f0dba4fa..c440984d 100644 --- a/crates/loro-internal/src/handler.rs +++ b/crates/loro-internal/src/handler.rs @@ -124,18 +124,24 @@ impl Handler { impl Handler { fn new( txn: Weak>>, - value: ContainerIdx, + idx: ContainerIdx, state: Weak>, ) -> Self { - match value.get_type() { - ContainerType::Map => Self::Map(MapHandler::new(txn, value, state)), - ContainerType::List => Self::List(ListHandler::new(txn, value, state)), - ContainerType::Tree => Self::Tree(TreeHandler::new(txn, value, state)), - ContainerType::Text => Self::Text(TextHandler::new(txn, value, state)), + match idx.get_type() { + ContainerType::Map => Self::Map(MapHandler::new(txn, idx, state)), + ContainerType::List => Self::List(ListHandler::new(txn, idx, state)), + ContainerType::Tree => Self::Tree(TreeHandler::new(txn, idx, state)), + ContainerType::Text => Self::Text(TextHandler::new(txn, idx, state)), } } } +#[derive(Clone, EnumAsInner, Debug)] +pub enum ValueOrContainer { + Value(LoroValue), + Container(Handler), +} + impl TextHandler { pub fn new( txn: Weak>>, @@ -758,6 +764,30 @@ impl ListHandler { }) } + /// Get value at given index, if it's a container, return a handler to the container + pub fn get_(&self, index: usize) -> Option { + let mutex = &self.state.upgrade().unwrap(); + let doc_state = &mutex.lock().unwrap(); + doc_state.with_state(self.container_idx, |state| { + let a = state.as_list_state().unwrap(); + match a.get(index) { + Some(v) => { + if let LoroValue::Container(id) = v { + let idx = doc_state.arena.register_container(id); + Some(ValueOrContainer::Container(Handler::new( + self.txn.clone(), + idx, + self.state.clone(), + ))) + } else { + Some(ValueOrContainer::Value(v.clone())) + } + } + None => None, + } + }) + } + pub fn for_each(&self, mut f: I) where I: FnMut(&LoroValue), @@ -946,6 +976,28 @@ impl MapHandler { }) } + /// Get the value at given key, if value is a container, return a handler to the container + pub fn get_(&self, key: &str) -> Option { + let mutex = &self.state.upgrade().unwrap(); + let doc_state = mutex.lock().unwrap(); + doc_state.with_state(self.container_idx, |state| { + let a = state.as_map_state().unwrap(); + let value = a.get(key); + match value { + Some(LoroValue::Container(container_id)) => { + let idx = doc_state.arena.register_container(container_id); + Some(ValueOrContainer::Container(Handler::new( + self.txn.clone(), + idx, + self.state.clone(), + ))) + } + Some(value) => Some(ValueOrContainer::Value(value.clone())), + None => None, + } + }) + } + pub fn id(&self) -> ContainerID { self.state .upgrade() diff --git a/crates/loro-wasm/src/lib.rs b/crates/loro-wasm/src/lib.rs index 166110d5..83c80368 100644 --- a/crates/loro-wasm/src/lib.rs +++ b/crates/loro-wasm/src/lib.rs @@ -6,7 +6,9 @@ use loro_internal::{ ContainerID, }, event::{Diff, Index}, - handler::{ListHandler, MapHandler, TextDelta, TextHandler, TreeHandler}, + handler::{ + Handler, ListHandler, MapHandler, TextDelta, TextHandler, TreeHandler, ValueOrContainer, + }, id::{Counter, PeerID, TreeID, ID}, obs::SubID, version::Frontiers, @@ -1231,7 +1233,7 @@ impl LoroMap { Ok(()) } - /// Get the value of the key. + /// Get the value of the key. If the value is a container, the corresponding handler will be returned. /// /// @example /// ```ts @@ -1243,7 +1245,12 @@ impl LoroMap { /// const bar = map.get("foo"); /// ``` pub fn get(&self, key: &str) -> JsValue { - self.0.get(key).into() + let v = self.0.get_(key); + match v { + Some(ValueOrContainer::Container(c)) => handler_to_js_value(c), + Some(ValueOrContainer::Value(v)) => v.into(), + None => JsValue::UNDEFINED, + } } /// Get the keys of the map. @@ -1461,6 +1468,15 @@ impl LoroMap { } } +fn handler_to_js_value(handler: Handler) -> JsValue { + match handler { + Handler::Text(t) => LoroText(t).into(), + Handler::Map(m) => LoroMap(m).into(), + Handler::List(l) => LoroList(l).into(), + Handler::Tree(t) => LoroTree(t).into(), + } +} + /// The handler of a list container. #[wasm_bindgen] pub struct LoroList(ListHandler); @@ -1502,7 +1518,7 @@ impl LoroList { Ok(()) } - /// Get the value at the index. + /// Get the value at the index. If the value is a container, the corresponding handler will be returned. /// /// @example /// ```ts @@ -1515,11 +1531,14 @@ impl LoroList { /// console.log(list.get(1)); // undefined /// ``` pub fn get(&self, index: usize) -> JsValue { - let Some(v) = self.0.get(index) else { + let Some(v) = self.0.get_(index) else { return JsValue::UNDEFINED; }; - JsValue::from(v) + match v { + ValueOrContainer::Value(v) => v.into(), + ValueOrContainer::Container(h) => handler_to_js_value(h), + } } /// Get the id of this container. diff --git a/loro-js/src/index.ts b/loro-js/src/index.ts index c76c1bc0..5b3dd517 100644 --- a/loro-js/src/index.ts +++ b/loro-js/src/index.ts @@ -53,6 +53,7 @@ export type Value = | Uint8Array | Value[]; +export type Container = LoroList | LoroMap | LoroText | LoroTree; export type Prelim = PrelimList | PrelimMap | PrelimText; /** @@ -109,8 +110,8 @@ export type MapDiff = { export type TreeDiff = { type: "tree"; diff: - | { target: TreeID; action: "create" | "delete" } - | { target: TreeID; action: "move"; parent: TreeID }; + | { target: TreeID; action: "create" | "delete" } + | { target: TreeID; action: "move"; parent: TreeID }; }; export type Diff = ListDiff | TextDiff | MapDiff | TreeDiff; @@ -148,7 +149,7 @@ declare module "loro-wasm" { insertContainer(pos: number, container: "Tree"): LoroTree; insertContainer(pos: number, container: string): never; - get(index: number): Value; + get(index: number): undefined | Value | Container; getTyped(loro: Loro, index: Key): T[Key]; insertTyped(pos: Key, value: T[Key]): void; insert(pos: number, value: Value | Prelim): void; @@ -163,7 +164,7 @@ declare module "loro-wasm" { setContainer(key: string, container_type: "Tree"): LoroTree; setContainer(key: string, container_type: string): never; - get(key: string): Value; + get(key: string): undefined | Value | Container; getTyped(txn: Loro, key: Key): T[Key]; set(key: string, value: Value | Prelim): void; setTyped(key: Key, value: T[Key]): void; diff --git a/loro-js/tests/basic.test.ts b/loro-js/tests/basic.test.ts index 07660c3e..3c7d9c26 100644 --- a/loro-js/tests/basic.test.ts +++ b/loro-js/tests/basic.test.ts @@ -84,14 +84,24 @@ describe("list", () => { map.set("key", "value"); const v = list.get(0); console.log(v); - expect(typeof v).toBe("string"); - const m = doc.getMap(v as ContainerID); - expect(m.getDeepValue()).toStrictEqual({ key: "value" }); + expect(v instanceof LoroMap).toBeTruthy(); + expect(v.getDeepValue()).toStrictEqual({ key: "value" }); }); it.todo("iterate"); }); +describe("map", () => { + it("get child container", () => { + const doc = new Loro(); + const map = doc.getMap("map"); + const list = map.setContainer("key", "List"); + list.insert(0, 1); + expect(map.get("key") instanceof LoroList).toBeTruthy(); + expect(map.get("key").getDeepValue()).toStrictEqual([1]); + }); +}); + describe("import", () => { it("pending", () => { const a = new Loro();