fix: wasm interface

This commit is contained in:
leeeon233 2022-11-22 10:39:43 +08:00 committed by Leonzhao
parent 71fd00418e
commit e124bbbec1
13 changed files with 194 additions and 84 deletions

12
Cargo.lock generated
View file

@ -788,6 +788,7 @@ dependencies = [
"ring",
"rle",
"serde",
"serde-wasm-bindgen",
"serde_columnar",
"serde_json",
"smallvec",
@ -1483,6 +1484,17 @@ dependencies = [
"serde_derive",
]
[[package]]
name = "serde-wasm-bindgen"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3b4c031cd0d9014307d82b8abf653c0290fbdaeb4c02d00c63cf52f728628bf"
dependencies = [
"js-sys",
"serde",
"wasm-bindgen",
]
[[package]]
name = "serde_columnar"
version = "0.1.0"

View file

@ -26,6 +26,7 @@ tabled = { version = "0.10.0", optional = true }
colored = "2.0.0"
bit-vec = "0.6.3"
wasm-bindgen = { version = "0.2.83", optional = true }
serde-wasm-bindgen = { version = "0.4.5", optional = true }
js-sys = { version = "0.3.60", optional = true }
serde_json = { version = "1.0.87", optional = true }
arref = "0.1.0"
@ -52,7 +53,7 @@ debug-log = "0.1.1"
doctest = false
[features]
wasm = ["wasm-bindgen", "js-sys"]
wasm = ["wasm-bindgen", "js-sys", "serde-wasm-bindgen"]
json = ["serde_json"]
# whether to use list slice instead of raw str in text container
test_utils = ["crdt-list/fuzzing", "rand", "arbitrary", "tabled", "json"]

View file

@ -23,6 +23,7 @@ use crate::{
op::{InnerContent, Op, RemoteContent, RichOp},
value::LoroValue,
version::IdSpanVector,
LoroError,
};
use super::list_op::InnerListOp;
@ -90,6 +91,13 @@ impl ListContainer {
Some(id)
}
pub fn get(&self, pos: usize) -> Option<LoroValue> {
self.state
.get(pos)
.map(|range| self.raw_data.slice(&range.as_ref().0))
.and_then(|slice| slice.first().cloned())
}
pub fn delete<C: Context>(&mut self, ctx: &C, pos: usize, len: usize) -> Option<ID> {
if len == 0 {
return None;
@ -279,7 +287,12 @@ impl List {
}
}
pub fn insert_batch<C: Context>(&mut self, ctx: &C, pos: usize, values: Vec<LoroValue>) {
pub fn insert_batch<C: Context>(
&mut self,
ctx: &C,
pos: usize,
values: Vec<LoroValue>,
) -> Result<(), LoroError> {
self.with_container_checked(ctx, |x| x.insert_batch(ctx, pos, values))
}
@ -288,7 +301,7 @@ impl List {
ctx: &C,
pos: usize,
value: V,
) -> Option<ID> {
) -> Result<Option<ID>, LoroError> {
self.with_container_checked(ctx, |x| x.insert(ctx, pos, value))
}
@ -297,12 +310,21 @@ impl List {
ctx: &C,
pos: usize,
obj: ContainerType,
) -> ContainerID {
) -> Result<ContainerID, LoroError> {
self.with_container_checked(ctx, |x| x.insert_obj(ctx, pos, obj))
}
pub fn delete<C: Context>(&mut self, ctx: &C, pos: usize, len: usize) -> Option<ID> {
self.with_container_checked(ctx, |text| text.delete(ctx, pos, len))
pub fn delete<C: Context>(
&mut self,
ctx: &C,
pos: usize,
len: usize,
) -> Result<Option<ID>, LoroError> {
self.with_container_checked(ctx, |list| list.delete(ctx, pos, len))
}
pub fn get(&self, pos: usize) -> Option<LoroValue> {
self.with_container(|list| list.get(pos))
}
pub fn len(&self) -> usize {

View file

@ -117,6 +117,11 @@ impl MapContainer {
pub fn delete<C: Context>(&mut self, ctx: &C, key: InternalString) {
self.insert(ctx, key, LoroValue::Null);
}
#[inline]
pub fn get(&self, key: &InternalString) -> Option<LoroValue> {
self.state.get(key).map(|v| &v.value).cloned()
}
}
impl Container for MapContainer {
@ -230,7 +235,12 @@ impl Map {
}
}
pub fn insert<C: Context, V: Into<LoroValue>>(&mut self, ctx: &C, key: &str, value: V) {
pub fn insert<C: Context, V: Into<LoroValue>>(
&mut self,
ctx: &C,
key: &str,
value: V,
) -> Result<(), crate::LoroError> {
self.with_container_checked(ctx, |map| {
map.insert(ctx, key.into(), value);
})
@ -241,16 +251,20 @@ impl Map {
ctx: &C,
key: &str,
obj: ContainerType,
) -> ContainerID {
) -> Result<ContainerID, crate::LoroError> {
self.with_container_checked(ctx, |map| map.insert_obj(ctx, key.into(), obj))
}
pub fn delete<C: Context>(&mut self, ctx: &C, key: &str) {
pub fn delete<C: Context>(&mut self, ctx: &C, key: &str) -> Result<(), crate::LoroError> {
self.with_container_checked(ctx, |map| {
map.delete(ctx, key.into());
})
}
pub fn get(&self, key: &str) -> Option<LoroValue> {
self.with_container(|map| map.get(&key.into()))
}
pub fn id(&self) -> ContainerID {
self.instance.lock().unwrap().as_map().unwrap().id.clone()
}

View file

@ -14,7 +14,7 @@ use crate::{
id::{ClientID, ContainerIdx},
op::{RemoteContent, RichOp},
version::IdSpanVector,
LoroValue, VersionVector,
LoroError, LoroValue, VersionVector,
};
use super::{
@ -297,19 +297,18 @@ pub trait ContainerWrapper {
where
F: FnOnce(&mut Self::Container) -> R;
fn with_container_checked<C: Context, F, R>(&self, ctx: &C, f: F) -> R
fn with_container_checked<C: Context, F, R>(&self, ctx: &C, f: F) -> Result<R, LoroError>
where
F: FnOnce(&mut Self::Container) -> R,
{
let store_client_id = ctx.log_store().read().unwrap().this_client_id();
if store_client_id != self.client_id() {
panic!(
"Context's client_id({}) does not match Container's client_id({})",
store_client_id,
self.client_id()
);
return Err(LoroError::UnmatchedContext {
expected: self.client_id(),
found: store_client_id,
});
}
self.with_container(f)
Ok(self.with_container(f))
}
fn client_id(&self) -> ClientID;

View file

@ -302,11 +302,21 @@ impl Text {
self.instance.lock().unwrap().as_text().unwrap().id.clone()
}
pub fn insert<C: Context>(&mut self, ctx: &C, pos: usize, text: &str) -> Option<ID> {
pub fn insert<C: Context>(
&mut self,
ctx: &C,
pos: usize,
text: &str,
) -> Result<Option<ID>, crate::LoroError> {
self.with_container_checked(ctx, |x| x.insert(ctx, pos, text))
}
pub fn delete<C: Context>(&mut self, ctx: &C, pos: usize, len: usize) -> Option<ID> {
pub fn delete<C: Context>(
&mut self,
ctx: &C,
pos: usize,
len: usize,
) -> Result<Option<ID>, crate::LoroError> {
self.with_container_checked(ctx, |text| text.delete(ctx, pos, len))
}

View file

@ -1,7 +1,11 @@
use thiserror::Error;
use crate::id::ClientID;
#[derive(Error, Debug)]
pub enum LoroError {
#[error("Context's client_id({found:?}) does not match Container's client_id({expected:?})")]
UnmatchedContext { expected: ClientID, found: ClientID },
// #[error("the data for key `{0}` is not available")]
// Redaction(String),
// #[error("invalid header (expected {expected:?}, found {found:?})")]

View file

@ -350,7 +350,9 @@ impl Actionable for Vec<Actor> {
container.insert(&actor.loro, &key.to_string(), *i);
}
FuzzValue::Container(c) => {
let new = container.insert_obj(&actor.loro, &key.to_string(), *c);
let new = container
.insert_obj(&actor.loro, &key.to_string(), *c)
.unwrap();
actor.add_new_container(new);
}
}
@ -380,7 +382,9 @@ impl Actionable for Vec<Actor> {
container.insert(&actor.loro, *key as usize, *i);
}
FuzzValue::Container(c) => {
let new = container.insert_obj(&actor.loro, *key as usize, *c);
let new = container
.insert_obj(&actor.loro, *key as usize, *c)
.unwrap();
actor.add_new_container(new)
}
}

View file

@ -148,9 +148,13 @@ impl From<ContainerID> for LoroValue {
pub mod wasm {
use fxhash::FxHashMap;
use js_sys::{Array, Object};
use wasm_bindgen::prelude::*;
use wasm_bindgen::{JsCast, JsValue, __rt::IntoJsResult};
use crate::LoroValue;
use crate::{container::ContainerID as _ContainerID, LoroValue};
#[wasm_bindgen]
pub struct ContainerID(_ContainerID);
pub fn convert(value: LoroValue) -> JsValue {
match value {
@ -176,8 +180,10 @@ pub mod wasm {
map.into_js_result().unwrap()
}
LoroValue::Unresolved(_) => {
unreachable!()
LoroValue::Unresolved(container_id) => {
// FIXME:
// serde_wasm_bindgen::to_value(&container_id).unwrap()
ContainerID(*container_id).into()
}
}
}

View file

@ -10,13 +10,13 @@ fn example() {
let mut doc = LoroCore::default();
let mut list = doc.get_list("list");
list.insert(&doc, 0, 123);
let map_id = list.insert_obj(&doc, 1, ContainerType::Map);
list.insert(&doc, 0, 123).unwrap();
let map_id = list.insert_obj(&doc, 1, ContainerType::Map).unwrap();
let mut map = doc.get_map(map_id);
let text = map.insert_obj(&doc, "map_b", ContainerType::Text);
let text = map.insert_obj(&doc, "map_b", ContainerType::Text).unwrap();
let mut text = doc.get_text(text);
text.insert(&doc, 0, "world!");
text.insert(&doc, 0, "hello ");
text.insert(&doc, 0, "world!").unwrap();
text.insert(&doc, 0, "hello ").unwrap();
assert_eq!(
r#"[123,{"map_b":"hello world!"}]"#,
list.get_value_deep(&doc).to_json()
@ -30,11 +30,17 @@ fn list() {
let mut loro_b = LoroCore::default();
let mut list_a = loro_a.get_list("list");
let mut list_b = loro_b.get_list("list");
list_a.insert_batch(&loro_a, 0, vec![12.into(), "haha".into()]);
list_b.insert_batch(&loro_b, 0, vec![123.into(), "kk".into()]);
let map_id = list_b.insert_obj(&loro_b, 1, loro_core::ContainerType::Map);
list_a
.insert_batch(&loro_a, 0, vec![12.into(), "haha".into()])
.unwrap();
list_b
.insert_batch(&loro_b, 0, vec![123.into(), "kk".into()])
.unwrap();
let map_id = list_b
.insert_obj(&loro_b, 1, loro_core::ContainerType::Map)
.unwrap();
let mut map = loro_b.get_map(map_id);
map.insert(&loro_b, "map_b", 123);
map.insert(&loro_b, "map_b", 123).unwrap();
println!("{}", list_a.get_value().to_json());
println!("{}", list_b.get_value().to_json());
loro_b.import(loro_a.export(loro_b.vv()));
@ -49,7 +55,7 @@ fn list() {
fn map() {
let mut loro = LoroCore::new(Default::default(), Some(10));
let mut root = loro.get_map("root");
root.insert(&loro, "haha", 1.2);
root.insert(&loro, "haha", 1.2).unwrap();
let value = root.get_value();
assert_eq!(value.as_map().unwrap().len(), 1);
assert_eq!(
@ -62,7 +68,9 @@ fn map() {
.unwrap(),
1.2
);
let map_id = root.insert_obj(&loro, "map", loro_core::ContainerType::Map);
let map_id = root
.insert_obj(&loro, "map", loro_core::ContainerType::Map)
.unwrap();
drop(root);
let mut sub_map = loro.get_map(&map_id);
sub_map.insert(&loro, "sub", false);
@ -93,9 +101,9 @@ fn map() {
fn two_client_text_sync() {
let mut store = LoroCore::new(Default::default(), Some(10));
let mut text_container = store.get_text("haha");
text_container.insert(&store, 0, "012");
text_container.insert(&store, 1, "34");
text_container.insert(&store, 1, "56");
text_container.insert(&store, 0, "012").unwrap();
text_container.insert(&store, 1, "34").unwrap();
text_container.insert(&store, 1, "56").unwrap();
let value = text_container.get_value();
let value = value.as_string().unwrap();
assert_eq!(&**value, "0563412");
@ -110,8 +118,8 @@ fn two_client_text_sync() {
let value = value.as_string().unwrap();
assert_eq!(&**value, "0563412");
text_container.delete(&store_b, 0, 2);
text_container.insert(&store_b, 4, "789");
text_container.delete(&store_b, 0, 2).unwrap();
text_container.insert(&store_b, 4, "789").unwrap();
let value = text_container.get_value();
let value = value.as_string().unwrap();
assert_eq!(&**value, "63417892");
@ -122,8 +130,8 @@ fn two_client_text_sync() {
let value = text_container.get_value();
let value = value.as_string().unwrap();
assert_eq!(&**value, "63417892");
text_container.delete(&store, 0, 8);
text_container.insert(&store, 0, "abc");
text_container.delete(&store, 0, 8).unwrap();
text_container.insert(&store, 0, "abc").unwrap();
let value = text_container.get_value();
let value = value.as_string().unwrap();
assert_eq!(&**value, "abc");
@ -143,8 +151,8 @@ fn test_recursive_should_panic() {
let mut store_b = LoroCore::new(Default::default(), Some(2));
let mut text_a = store_a.get_text("text_a");
let mut text_b = store_b.get_text("text_b");
text_a.insert(&store_a, 0, "012");
text_b.insert(&store_a, 1, "34");
text_a.insert(&store_a, 0, "012").unwrap();
text_b.insert(&store_a, 1, "34").unwrap();
}
#[ctor]

View file

@ -12,6 +12,10 @@
"https://deno.land/std@0.105.0/path/posix.ts": "b81974c768d298f8dcd2c720229639b3803ca4a241fa9a355c762fa2bc5ef0c1",
"https://deno.land/std@0.105.0/path/separator.ts": "8fdcf289b1b76fd726a508f57d3370ca029ae6976fcde5044007f062e643ff1c",
"https://deno.land/std@0.105.0/path/win32.ts": "f4a3d4a3f2c9fe894da046d5eac48b5e789a0ebec5152b2c0985efe96a9f7ae1",
"https://deno.land/std@0.165.0/fmt/colors.ts": "9e36a716611dcd2e4865adea9c4bec916b5c60caad4cdcdc630d4974e6bb8bd4",
"https://deno.land/std@0.165.0/testing/_diff.ts": "a23e7fc2b4d8daa3e158fa06856bedf5334ce2a2831e8bf9e509717f455adb2c",
"https://deno.land/std@0.165.0/testing/_format.ts": "cd11136e1797791045e639e9f0f4640d5b4166148796cad37e6ef75f7d7f3832",
"https://deno.land/std@0.165.0/testing/asserts.ts": "1e340c589853e82e0807629ba31a43c84ebdcdeca910c4a9705715dfdb0f5ce8",
"https://deno.land/x/dirname@1.1.2/mod.ts": "4029ca6b49da58d262d65f826ba9b3a89cc0b92a94c7220d5feb7bd34e498a54",
"https://deno.land/x/dirname@1.1.2/types.ts": "c1ed1667545bc4b1d69bdb2fc26a5fa8edae3a56e3081209c16a408a322a2319",
"https://lra6z45nakk5lnu3yjchp7tftsdnwwikwr65ocha5eojfnlgu4sa.arweave.net/XEHs860CldW2m8JEd_5lnIbbWQq0fdcI4OkckrVmpyQ/_util/assert.ts": "e1f76e77c5ccb5a8e0dbbbe6cce3a56d2556c8cb5a9a8802fc9565af72462149",

View file

@ -23,7 +23,9 @@ Deno.test({
b.set(loro, "ab", 123);
console.log(b.value);
console.log(a.value);
const bText = b.getText(loro, "hh");
await t.step("getValueDeep", () => {
bText.insert(loro, 0, "hello world Text");
assertEquals(b.getValueDeep(loro), { ab: 123, hh: "hello world Text" });
@ -37,8 +39,12 @@ Deno.test({
});
await t.step("get value error", () => {
assertThrows(()=>{
const _ = bText.value;
});
// assertThrows(()=>{
const map = loro.getMap("map");
const list = map.getList(loro, "list");
list.insert(loro, 0, 123);
console.log(map.value);
// });
});
});

View file

@ -18,6 +18,8 @@ pub fn set_panic_hook() {
console_error_panic_hook::set_once();
}
type JsResult<T> = Result<T, JsError>;
#[wasm_bindgen]
pub struct Loro(LoroCore);
@ -43,13 +45,13 @@ impl Loro {
}
#[wasm_bindgen(js_name = "getText")]
pub fn get_text(&mut self, name: &str) -> Result<LoroText, JsValue> {
pub fn get_text(&mut self, name: &str) -> JsResult<LoroText> {
let text = self.0.get_text(name);
Ok(LoroText(text))
}
#[wasm_bindgen(js_name = "getMap")]
pub fn get_map(&mut self, name: &str) -> Result<LoroMap, JsValue> {
pub fn get_map(&mut self, name: &str) -> JsResult<LoroMap> {
let map = self.0.get_map(name);
Ok(LoroMap(map))
}
@ -60,16 +62,18 @@ pub struct LoroText(Text);
#[wasm_bindgen]
impl LoroText {
pub fn insert(&mut self, ctx: &Loro, index: usize, content: &str) {
self.0.insert(ctx.deref(), index, content);
pub fn insert(&mut self, ctx: &Loro, index: usize, content: &str) -> JsResult<()> {
self.0.insert(ctx.deref(), index, content)?;
Ok(())
}
pub fn delete(&mut self, ctx: &Loro, index: usize, len: usize) {
self.0.delete(ctx.deref(), index, len);
pub fn delete(&mut self, ctx: &Loro, index: usize, len: usize) -> JsResult<()> {
self.0.delete(ctx.deref(), index, len)?;
Ok(())
}
#[wasm_bindgen(js_name = "value", method, getter)]
pub fn get_value(&mut self) -> String {
pub fn get_value(&self) -> String {
self.0.get_value().as_string().unwrap().to_string()
}
}
@ -80,43 +84,54 @@ pub struct LoroMap(Map);
#[wasm_bindgen]
impl LoroMap {
#[wasm_bindgen(js_name = "set")]
pub fn insert(&mut self, ctx: &Loro, key: &str, value: JsValue) {
self.0.insert(ctx.deref(), key, value);
pub fn insert(&mut self, ctx: &Loro, key: &str, value: JsValue) -> JsResult<()> {
self.0.insert(ctx.deref(), key, value)?;
Ok(())
}
pub fn delete(&mut self, ctx: &Loro, key: &str) {
self.0.delete(ctx.deref(), key);
pub fn delete(&mut self, ctx: &Loro, key: &str) -> JsResult<()> {
self.0.delete(ctx.deref(), key)?;
Ok(())
}
pub fn get(&self, key: &str) -> JsValue {
self.0.get(key).into()
}
#[wasm_bindgen(js_name = "value", method, getter)]
pub fn get_value(&mut self) -> JsValue {
pub fn get_value(&self) -> JsValue {
// TODO: if unresolved, return a container ID
self.0.get_value().into()
}
#[wasm_bindgen(js_name = "getValueDeep")]
pub fn get_value_deep(&mut self, ctx: &Loro) -> JsValue {
pub fn get_value_deep(&self, ctx: &Loro) -> JsValue {
self.0.get_value_deep(ctx.deref()).into()
}
#[wasm_bindgen(js_name = "getText")]
pub fn get_text(&mut self, ctx: &mut Loro, key: &str) -> LoroText {
let id = self.0.insert_obj(&ctx.0, key, ContainerType::Text);
pub fn get_text(&mut self, ctx: &mut Loro, key: &str) -> JsResult<LoroText> {
let id = self.0.insert_obj(&ctx.0, key, ContainerType::Text)?;
let text = ctx.deref().get_container(&id).unwrap();
LoroText(Text::from_instance(text, ctx.deref().client_id()))
Ok(LoroText(Text::from_instance(text, ctx.deref().client_id())))
}
#[wasm_bindgen(js_name = "getMap")]
pub fn get_map(&mut self, ctx: &mut Loro, key: &str) -> LoroMap {
let id = self.0.insert_obj(ctx.deref_mut(), key, ContainerType::Map);
pub fn get_map(&mut self, ctx: &mut Loro, key: &str) -> JsResult<LoroMap> {
let id = self
.0
.insert_obj(ctx.deref_mut(), key, ContainerType::Map)?;
let map = ctx.deref().get_container(&id).unwrap();
LoroMap(Map::from_instance(map, ctx.deref().client_id()))
Ok(LoroMap(Map::from_instance(map, ctx.deref().client_id())))
}
#[wasm_bindgen(js_name = "getList")]
pub fn get_list(&mut self, ctx: &mut Loro, key: &str) -> LoroList {
let id = self.0.insert_obj(ctx.deref_mut(), key, ContainerType::List);
pub fn get_list(&mut self, ctx: &mut Loro, key: &str) -> JsResult<LoroList> {
let id = self
.0
.insert_obj(ctx.deref_mut(), key, ContainerType::List)?;
let list = ctx.deref().get_container(&id).unwrap();
LoroList(List::from_instance(list, ctx.deref().client_id()))
Ok(LoroList(List::from_instance(list, ctx.deref().client_id())))
}
}
@ -125,13 +140,18 @@ pub struct LoroList(List);
#[wasm_bindgen]
impl LoroList {
#[wasm_bindgen(js_name = "set")]
pub fn insert(&mut self, ctx: &Loro, index: usize, value: JsValue) {
self.0.insert(ctx.deref(), index, value);
pub fn insert(&mut self, ctx: &Loro, index: usize, value: JsValue) -> JsResult<()> {
self.0.insert(ctx.deref(), index, value)?;
Ok(())
}
pub fn delete(&mut self, ctx: &Loro, index: usize, len: usize) {
self.0.delete(ctx.deref(), index, len);
pub fn delete(&mut self, ctx: &Loro, index: usize, len: usize) -> JsResult<()> {
self.0.delete(ctx.deref(), index, len)?;
Ok(())
}
pub fn get(&self, index: usize) -> JsValue {
self.0.get(index).into()
}
#[wasm_bindgen(js_name = "value", method, getter)]
@ -140,35 +160,35 @@ impl LoroList {
}
#[wasm_bindgen(js_name = "getValueDeep")]
pub fn get_value_deep(&mut self, ctx: &Loro) -> JsValue {
pub fn get_value_deep(&self, ctx: &Loro) -> JsValue {
self.0.get_value_deep(ctx.deref()).into()
}
#[wasm_bindgen(js_name = "getText")]
pub fn get_text(&mut self, ctx: &mut Loro, index: usize) -> LoroText {
pub fn get_text(&mut self, ctx: &mut Loro, index: usize) -> JsResult<LoroText> {
let id = self
.0
.insert_obj(ctx.deref_mut(), index, ContainerType::Text);
.insert_obj(ctx.deref_mut(), index, ContainerType::Text)?;
let text = ctx.deref().get_container(&id).unwrap();
LoroText(Text::from_instance(text, ctx.deref().client_id()))
Ok(LoroText(Text::from_instance(text, ctx.deref().client_id())))
}
#[wasm_bindgen(js_name = "getMap")]
pub fn get_map(&mut self, ctx: &mut Loro, index: usize) -> LoroMap {
pub fn get_map(&mut self, ctx: &mut Loro, index: usize) -> JsResult<LoroMap> {
let id = self
.0
.insert_obj(ctx.deref_mut(), index, ContainerType::Map);
.insert_obj(ctx.deref_mut(), index, ContainerType::Map)?;
let map = ctx.deref().get_container(&id).unwrap();
LoroMap(Map::from_instance(map, ctx.deref().client_id()))
Ok(LoroMap(Map::from_instance(map, ctx.deref().client_id())))
}
#[wasm_bindgen(js_name = "getList")]
pub fn get_list(&mut self, ctx: &mut Loro, index: usize) -> LoroList {
pub fn get_list(&mut self, ctx: &mut Loro, index: usize) -> JsResult<LoroList> {
let id = self
.0
.insert_obj(ctx.deref_mut(), index, ContainerType::List);
.insert_obj(ctx.deref_mut(), index, ContainerType::List)?;
let list = ctx.deref().get_container(&id).unwrap();
LoroList(List::from_instance(list, ctx.deref().client_id()))
Ok(LoroList(List::from_instance(list, ctx.deref().client_id())))
}
}