diff options
| author | Josh Kingsley <josh@joshkingsley.me> | 2025-11-29 22:16:44 +0200 |
|---|---|---|
| committer | Josh Kingsley <josh@joshkingsley.me> | 2025-11-29 22:16:44 +0200 |
| commit | 1696d665f01c6e8bff04946c69ad0258bf72b5eb (patch) | |
| tree | a37d8efcd8eacd90d0cbea08fb2e09a0b3cbaa90 | |
| parent | d724cc0bf6ff6d351319e6fb00f5184a04e16ac0 (diff) | |
| -rw-r--r-- | Cargo.lock | 92 | ||||
| -rw-r--r-- | apps/web/src/components/app/index.ts | 11 | ||||
| -rw-r--r-- | apps/web/src/index.ts | 5 | ||||
| -rw-r--r-- | packages/doc/Cargo.toml | 1 | ||||
| -rw-r--r-- | packages/doc/package.json | 9 | ||||
| -rw-r--r-- | packages/doc/src/doc.rs | 67 | ||||
| -rw-r--r-- | packages/doc/src/lib.rs | 5 | ||||
| -rw-r--r-- | packages/doc/src/vector_clock.rs | 47 |
8 files changed, 160 insertions, 77 deletions
@@ -33,6 +33,25 @@ dependencies = [ ] [[package]] +name = "gloo-utils" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] name = "js-sys" version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -49,6 +68,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] name = "notive-doc" version = "0.1.0" dependencies = [ @@ -56,6 +81,7 @@ dependencies = [ "serde", "serde-wasm-bindgen", "thiserror", + "tsify", "uuid", "wasm-bindgen", ] @@ -136,6 +162,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] name = "serde" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -177,6 +209,30 @@ dependencies = [ ] [[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] name = "syn" version = "2.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -208,6 +264,32 @@ dependencies = [ ] [[package]] +name = "tsify" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ec5505497c87f1c050b4392d3f11b49a04537fcb9dc0da57bc0af168a6331f2" +dependencies = [ + "gloo-utils", + "serde", + "serde-wasm-bindgen", + "serde_json", + "tsify-macros", + "wasm-bindgen", +] + +[[package]] +name = "tsify-macros" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc2c44dc9fe4baf55b88e032621b7a11b215a1f0a7de8d0aa04367207d915bc" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn", +] + +[[package]] name = "unicode-ident" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -279,6 +361,16 @@ dependencies = [ ] [[package]] +name = "web-sys" +version = "0.3.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] name = "wit-bindgen" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/apps/web/src/components/app/index.ts b/apps/web/src/components/app/index.ts index a2c0c9d..63fde86 100644 --- a/apps/web/src/components/app/index.ts +++ b/apps/web/src/components/app/index.ts @@ -5,16 +5,23 @@ import { changeSelectedSubdivisions, getSelectedSubdivisionsCount, } from "../../grid"; -import { Doc } from "../../types"; +import { Doc as LocalDoc } from "../../types"; import ntvGrid, { NotiveGridElement } from "../grid"; import renderGrid from "../grid/renderGrid"; import { GridSelection } from "../grid/selection"; import ntvToolbar from "../toolbar"; import "./index.css"; +import { State } from "@notive/doc"; + +const state = new State(); + +state.create_grid(); + +console.log(state.to_json()); @customElement("ntv-app") export class NotiveAppElement extends NotiveElement { - doc: Doc = defaultDoc(); + doc: LocalDoc = defaultDoc(); #selectedGridId?: string; #selection?: GridSelection; diff --git a/apps/web/src/index.ts b/apps/web/src/index.ts index 857e76a..7842326 100644 --- a/apps/web/src/index.ts +++ b/apps/web/src/index.ts @@ -1,8 +1,3 @@ import ntvApp from "./components/app"; -import { State } from "@notive/doc"; - -const state = new State(); -state.create_grid(); -console.log(state.to_json()); document.body.append(ntvApp()); diff --git a/packages/doc/Cargo.toml b/packages/doc/Cargo.toml index 5e5458d..fa73380 100644 --- a/packages/doc/Cargo.toml +++ b/packages/doc/Cargo.toml @@ -11,5 +11,6 @@ num-rational = "0.4.2" serde = { version = "1.0.228", features = ["derive"] } serde-wasm-bindgen = "0.6.5" thiserror = "2.0.17" +tsify = { version = "0.5.6", features = ["js"] } uuid = { version = "1.18.1", features = ["js", "v7"] } wasm-bindgen = "0.2.105" diff --git a/packages/doc/package.json b/packages/doc/package.json index e251e0d..d5fc437 100644 --- a/packages/doc/package.json +++ b/packages/doc/package.json @@ -2,11 +2,14 @@ "name": "@notive/doc", "private": true, "exports": { - ".": "dist/notive_doc.js" + ".": { + "types": "./dist/notive_doc.d.ts", + "default": "./dist/notive_doc.js" + } }, "scripts": { - "build": "wasm-pack build --target bundler --release --out-dir dist", - "build:dev": "wasm-pack build --target bundler --dev --out-dir dist", + "build": "wasm-pack build --target bundler --release --out-dir dist --no-pack", + "build:dev": "wasm-pack build --target bundler --dev --out-dir dist --no-pack", "clean": "rm -rf dist", "dev": "cargo watch -i dist -s 'pnpm build:dev'" }, diff --git a/packages/doc/src/doc.rs b/packages/doc/src/doc.rs index fcca1d8..22ef703 100644 --- a/packages/doc/src/doc.rs +++ b/packages/doc/src/doc.rs @@ -1,14 +1,17 @@ use serde::{Deserialize, Serialize}; use thiserror::Error; +use tsify::Tsify; use uuid::Uuid; use crate::op::{ChangeSubdivisions, CreateGrid, Op, OpKind}; /// An deterministically derived ID, e.g. a grid ID derived from the /// op ID which creates it. +#[derive(Tsify)] +#[tsify(type = "string")] pub struct DerivedId { base: String, - tag: &'static str, + tag: String, index: usize, } @@ -19,24 +22,24 @@ impl ToString for DerivedId { } trait DerivableId { - fn derive_id(&self, tag: &'static str, index: usize) -> DerivedId; + fn derive_id(&self, tag: &str, index: usize) -> DerivedId; } impl DerivableId for Uuid { - fn derive_id(&self, tag: &'static str, index: usize) -> DerivedId { + fn derive_id(&self, tag: &str, index: usize) -> DerivedId { DerivedId { base: self.to_string(), - tag, + tag: tag.to_string(), index, } } } impl DerivableId for DerivedId { - fn derive_id(&self, tag: &'static str, index: usize) -> DerivedId { + fn derive_id(&self, tag: &str, index: usize) -> DerivedId { DerivedId { base: self.to_string(), - tag, + tag: tag.to_string(), index, } } @@ -51,26 +54,56 @@ impl Serialize for DerivedId { } } -#[derive(Default, Serialize)] +impl<'de> Deserialize<'de> for DerivedId { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: serde::Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + let parts: Vec<&str> = s.split(':').collect(); + if parts.len() != 2 { + return Err(serde::de::Error::custom("Invalid DerivedId format")); + } + + let base = parts[0].to_string(); + let tag_index: Vec<&str> = parts[1].split('=').collect(); + if tag_index.len() != 2 { + return Err(serde::de::Error::custom("Invalid DerivedId format")); + } + + let tag = tag_index[0].to_string(); + let index = tag_index[1] + .parse() + .map_err(|_| serde::de::Error::custom("Invalid index"))?; + + Ok(DerivedId { base, tag, index }) + } +} + +#[derive(Default, Serialize, Deserialize, Tsify)] +#[tsify(into_wasm_abi)] pub struct Doc { - pub(crate) grids: Vec<Grid>, + pub grids: Vec<Grid>, } -#[derive(Serialize)] +#[derive(Serialize, Deserialize, Tsify)] +#[tsify(into_wasm_abi)] pub struct Grid { - pub(crate) id: DerivedId, - pub(crate) rows: Vec<Row>, + pub id: DerivedId, + pub rows: Vec<Row>, } -#[derive(Serialize)] +#[derive(Serialize, Deserialize, Tsify)] +#[tsify(into_wasm_abi)] pub struct Row { - pub(crate) id: DerivedId, - pub(crate) cells: Vec<Cell>, + pub id: DerivedId, + pub cells: Vec<Cell>, } -#[derive(Serialize)] +#[derive(Serialize, Deserialize, Tsify)] +#[tsify(into_wasm_abi)] pub struct Cell { - pub(crate) id: DerivedId, + pub id: DerivedId, } #[derive(Error, Debug)] @@ -109,6 +142,6 @@ fn apply_create_grid(doc: &mut Doc, op_id: &Uuid, data: &CreateGrid) -> ApplyOpR Ok(()) } -fn apply_change_subdivisions(doc: &mut Doc, data: &ChangeSubdivisions) -> ApplyOpResult { +fn apply_change_subdivisions(_doc: &mut Doc, _data: &ChangeSubdivisions) -> ApplyOpResult { todo!() } diff --git a/packages/doc/src/lib.rs b/packages/doc/src/lib.rs index a1d7497..93c008a 100644 --- a/packages/doc/src/lib.rs +++ b/packages/doc/src/lib.rs @@ -43,9 +43,8 @@ impl State { })); } - pub fn to_json(&self) -> JsValue { - let doc = self.realize().unwrap(); - serde_wasm_bindgen::to_value(&doc).unwrap() + pub fn to_json(&self) -> Doc { + self.realize().unwrap() } } diff --git a/packages/doc/src/vector_clock.rs b/packages/doc/src/vector_clock.rs index f6ded56..5e0b669 100644 --- a/packages/doc/src/vector_clock.rs +++ b/packages/doc/src/vector_clock.rs @@ -22,11 +22,6 @@ impl VectorClock { m.insert(actor_id.clone(), self.get(actor_id) + 1); VectorClock(m) } - - /// Returns true if this clock is concurrent with another (neither happens before the other) - pub fn is_concurrent_with(&self, other: &VectorClock) -> bool { - self.partial_cmp(other).is_none() - } } impl PartialOrd for VectorClock { @@ -111,46 +106,4 @@ mod tests { assert!(!(clock_a > clock_b)); assert!(!(clock_a < clock_b)); } - - #[test] - fn concurrent_clocks() { - let alice_id = Uuid::now_v7(); - let bob_id = Uuid::now_v7(); - let carol_id = Uuid::now_v7(); - - // Equal clocks are not concurrent - let clock1 = VectorClock::new(); - let clock2 = VectorClock::new(); - assert!(!clock1.is_concurrent_with(&clock2)); - - // Causally ordered clocks are not concurrent - let clock_before = VectorClock::new().inc(&alice_id); - let clock_after = VectorClock::new().inc(&alice_id).inc(&bob_id); - assert!(!clock_before.is_concurrent_with(&clock_after)); - assert!(!clock_after.is_concurrent_with(&clock_before)); - - // Clocks from different actors are concurrent - let alice_clock = VectorClock::new().inc(&alice_id); - let bob_clock = VectorClock::new().inc(&bob_id); - assert!(alice_clock.is_concurrent_with(&bob_clock)); - assert!(bob_clock.is_concurrent_with(&alice_clock)); - - // Complex concurrent case: diverged branches - let clock_a = VectorClock::new() - .inc(&alice_id) - .inc(&alice_id) - .inc(&bob_id) - .inc(&carol_id); - - let clock_b = VectorClock::new() - .inc(&alice_id) - .inc(&alice_id) - .inc(&bob_id) - .inc(&bob_id); - - // clock_a: {alice: 2, bob: 1, carol: 1} - // clock_b: {alice: 2, bob: 2} - // carol: 1 > 0, but bob: 1 < 2 → concurrent - assert!(clock_a.is_concurrent_with(&clock_b)); - } } |
