diff --git a/y-octo-node/index.d.ts b/y-octo-node/index.d.ts index ef8f2f4..d4e12e1 100644 --- a/y-octo-node/index.d.ts +++ b/y-octo-node/index.d.ts @@ -54,10 +54,12 @@ export class Doc { } export class YMap { get length(): number + get size(): number get isEmpty(): boolean get(key: string): T - set(key: string, value: YArray | YMap | YText | boolean | number | string | Record | null | undefined): void - remove(key: string): void + set | null | undefined>(key: string, value: T): T + delete(key: string): void + clear(): void toJson(): object entries(): YMapEntriesIterator keys(): YMapKeyIterator @@ -75,9 +77,13 @@ export class YText { get len(): number get isEmpty(): boolean insert(index: number, str: string): void - remove(index: number, len: number): void + delete(index: number, len: number): void get length(): number + applyDelta(delta: JsArray): void + toDelta(): JsArray toString(): string + observe(callback: (...args: any[]) => any): void + observeDeep(callback: (...args: any[]) => any): void } export type YStore = Store export class Store { } diff --git a/y-octo-node/src/map.rs b/y-octo-node/src/map.rs index 9314603..327a6be 100644 --- a/y-octo-node/src/map.rs +++ b/y-octo-node/src/map.rs @@ -19,6 +19,11 @@ impl YMap { self.map.len() as i64 } + #[napi(getter)] + pub fn size(&self) -> i64 { + self.length() + } + #[napi(getter)] pub fn is_empty(&self) -> bool { self.map.is_empty() @@ -41,25 +46,52 @@ impl YMap { } #[napi( - ts_args_type = "key: string, value: YArray | YMap | YText | boolean | number | string | Record | \ - null | undefined" + ts_generic_types = "T = YArray | YMap | YText | boolean | number | string | Record | null | undefined", + ts_args_type = "key: string, value: T", + ts_return_type = "T" )] - pub fn set(&mut self, key: String, value: MixedRefYType) -> Result<()> { + pub fn set(&mut self, env: Env, key: String, value: MixedRefYType) -> Result { match value { - MixedRefYType::A(array) => self.map.insert(key, array.array.clone()).map_err(anyhow::Error::from), - MixedRefYType::B(map) => self.map.insert(key, map.map.clone()).map_err(anyhow::Error::from), - MixedRefYType::C(text) => self.map.insert(key, text.text.clone()).map_err(anyhow::Error::from), + MixedRefYType::A(array) => { + self.map.insert(key, array.array.clone()).map_err(anyhow::Error::from)?; + Ok(MixedYType::A(YArray::inner_new(array.array.clone()))) + } + MixedRefYType::B(map) => { + self.map.insert(key, map.map.clone()).map_err(anyhow::Error::from)?; + Ok(MixedYType::B(YMap::inner_new(map.map.clone()))) + } + MixedRefYType::C(text) => { + self.map.insert(key, text.text.clone()).map_err(anyhow::Error::from)?; + Ok(MixedYType::C(YText::inner_new(text.text.clone()))) + } MixedRefYType::D(unknown) => match unknown.get_type() { Ok(value_type) => match value_type { ValueType::Undefined | ValueType::Null => { - self.map.insert(key, Any::Null).map_err(anyhow::Error::from) + self.map.insert(key, Any::Null).map_err(anyhow::Error::from)?; + Ok(MixedYType::D( + env.get_null().map(|v| v.into_unknown()).map_err(anyhow::Error::from)?, + )) } ValueType::Boolean => match unknown.coerce_to_bool().and_then(|v| v.get_value()) { - Ok(boolean) => self.map.insert(key, boolean).map_err(anyhow::Error::from), + Ok(boolean) => { + self.map.insert(key, boolean).map_err(anyhow::Error::from)?; + Ok(MixedYType::D( + env.get_boolean(boolean) + .map(|v| v.into_unknown()) + .map_err(anyhow::Error::from)?, + )) + } Err(e) => Err(anyhow::Error::from(e).context("Failed to coerce value to boolean")), }, ValueType::Number => match unknown.coerce_to_number().and_then(|v| v.get_double()) { - Ok(number) => self.map.insert(key, number).map_err(anyhow::Error::from), + Ok(number) => { + self.map.insert(key, number).map_err(anyhow::Error::from)?; + Ok(MixedYType::D( + env.create_double(number) + .map(|v| v.into_unknown()) + .map_err(anyhow::Error::from)?, + )) + } Err(e) => Err(anyhow::Error::from(e).context("Failed to coerce value to number")), }, ValueType::String => { @@ -68,12 +100,26 @@ impl YMap { .and_then(|v| v.into_utf8()) .and_then(|s| s.as_str().map(|s| s.to_string())) { - Ok(string) => self.map.insert(key, string).map_err(anyhow::Error::from), + Ok(string) => { + self.map.insert(key, string.clone()).map_err(anyhow::Error::from)?; + Ok(MixedYType::D( + env.create_string(&string) + .map(|v| v.into_unknown()) + .map_err(anyhow::Error::from)?, + )) + } Err(e) => Err(anyhow::Error::from(e).context("Failed to coerce value to string")), } } ValueType::Object => match unknown.coerce_to_object().and_then(get_any_from_js_object) { - Ok(any) => self.map.insert(key, Value::Any(any)).map_err(anyhow::Error::from), + Ok(any) => { + self.map + .insert(key, Value::Any(any.clone())) + .map_err(anyhow::Error::from)?; + Ok(MixedYType::D( + get_js_unknown_from_any(env, any).map_err(anyhow::Error::from)?.into(), + )) + } Err(e) => Err(anyhow::Error::from(e).context("Failed to coerce value to object")), }, ValueType::Symbol => Err(anyhow::Error::msg("Symbol values are not supported")), @@ -87,10 +133,18 @@ impl YMap { } #[napi] - pub fn remove(&mut self, key: String) { + pub fn delete(&mut self, key: String) { self.map.remove(&key); } + #[napi] + pub fn clear(&mut self) { + let keys = self.map.keys().map(ToOwned::to_owned).collect::>(); + for key in keys { + self.map.remove(&key); + } + } + #[napi] pub fn to_json(&self, env: Env) -> Result { let mut js_object = env.create_object()?; diff --git a/y-octo-node/src/text.rs b/y-octo-node/src/text.rs index ca67c49..fa60744 100644 --- a/y-octo-node/src/text.rs +++ b/y-octo-node/src/text.rs @@ -1,3 +1,4 @@ +use napi::{bindgen_prelude::Array as JsArray, Env, JsFunction}; use y_octo::Text; use super::*; @@ -35,7 +36,7 @@ impl YText { } #[napi] - pub fn remove(&mut self, index: i64, len: i64) -> Result<()> { + pub fn delete(&mut self, index: i64, len: i64) -> Result<()> { self.text.remove(index as u64, len as u64).map_err(anyhow::Error::from) } @@ -44,11 +45,33 @@ impl YText { self.text.len() as i64 } + #[napi] + pub fn apply_delta(&mut self, env: Env, _delta: JsArray) -> Result<()> { + unimplemented!() + } + + #[napi] + pub fn to_delta(&self, env: Env) -> Result { + unimplemented!() + } + #[allow(clippy::inherent_to_string)] #[napi] pub fn to_string(&self) -> String { self.text.to_string() } + + // TODO(@darkskygit): impl type based observe + #[napi] + pub fn observe(&mut self, _callback: JsFunction) -> Result<()> { + Ok(()) + } + + // TODO(@darkskygit): impl type based observe + #[napi] + pub fn observe_deep(&mut self, _callback: JsFunction) -> Result<()> { + Ok(()) + } } #[cfg(test)] @@ -70,7 +93,7 @@ mod tests { assert_eq!(text.to_string(), "hello"); text.insert(5, " world".into()).unwrap(); assert_eq!(text.to_string(), "hello world"); - text.remove(5, 6).unwrap(); + text.delete(5, 6).unwrap(); assert_eq!(text.to_string(), "hello"); } } diff --git a/y-octo-node/tests/map.spec.ts b/y-octo-node/tests/map.spec.ts index b355b22..eb33490 100644 --- a/y-octo-node/tests/map.spec.ts +++ b/y-octo-node/tests/map.spec.ts @@ -34,7 +34,7 @@ test("map editing", (t) => { t.is(map.get("c"), 1); t.is(map.get("d"), "hello world"); t.is(map.length, 4); - map.remove("b"); + map.delete("b"); t.is(map.length, 3); t.is(map.get("d"), "hello world"); }); diff --git a/y-octo-node/tests/text.spec.ts b/y-octo-node/tests/text.spec.ts index e0f8bab..e38c73e 100644 --- a/y-octo-node/tests/text.spec.ts +++ b/y-octo-node/tests/text.spec.ts @@ -27,11 +27,11 @@ test("text editing", (t) => { text.insert(1, "b"); text.insert(2, "c"); t.is(text.toString(), "abc"); - text.remove(0, 1); + text.delete(0, 1); t.is(text.toString(), "bc"); - text.remove(1, 1); + text.delete(1, 1); t.is(text.toString(), "b"); - text.remove(0, 1); + text.delete(0, 1); t.is(text.toString(), ""); });