From c2a6efb1b761014a90d90373cad47a14054af40b Mon Sep 17 00:00:00 2001 From: Josh Kingsley Date: Sun, 23 Nov 2025 17:27:44 +0200 Subject: feat(crdt): add wasm-bindgen --- Cargo.lock | 44 +++++++++++++++++++++++++++++++++++++++++++ crdt/Cargo.toml | 6 ++++++ crdt/src/doc.rs | 15 ++++++++++++++- crdt/src/lib.rs | 58 ++++++++++++++++++++++++++++++++++++++------------------- 4 files changed, 103 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4156a98..71ed3e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -53,8 +53,11 @@ name = "notive-crdt" version = "0.1.0" dependencies = [ "num-rational", + "serde", + "serde-wasm-bindgen", "thiserror", "uuid", + "wasm-bindgen", ] [[package]] @@ -132,6 +135,47 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "syn" version = "2.0.110" diff --git a/crdt/Cargo.toml b/crdt/Cargo.toml index fe50142..71f745a 100644 --- a/crdt/Cargo.toml +++ b/crdt/Cargo.toml @@ -3,7 +3,13 @@ name = "notive-crdt" version = "0.1.0" edition = "2024" +[lib] +crate-type = ["cdylib"] + [dependencies] num-rational = "0.4.2" +serde = { version = "1.0.228", features = ["derive"] } +serde-wasm-bindgen = "0.6.5" thiserror = "2.0.17" uuid = { version = "1.18.1", features = ["v7"] } +wasm-bindgen = "0.2.105" diff --git a/crdt/src/doc.rs b/crdt/src/doc.rs index bbd24d0..fcca1d8 100644 --- a/crdt/src/doc.rs +++ b/crdt/src/doc.rs @@ -1,3 +1,4 @@ +use serde::{Deserialize, Serialize}; use thiserror::Error; use uuid::Uuid; @@ -41,21 +42,33 @@ impl DerivableId for DerivedId { } } -#[derive(Default)] +impl Serialize for DerivedId { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +#[derive(Default, Serialize)] pub struct Doc { pub(crate) grids: Vec, } +#[derive(Serialize)] pub struct Grid { pub(crate) id: DerivedId, pub(crate) rows: Vec, } +#[derive(Serialize)] pub struct Row { pub(crate) id: DerivedId, pub(crate) cells: Vec, } +#[derive(Serialize)] pub struct Cell { pub(crate) id: DerivedId, } diff --git a/crdt/src/lib.rs b/crdt/src/lib.rs index 977763f..3c89ce9 100644 --- a/crdt/src/lib.rs +++ b/crdt/src/lib.rs @@ -1,13 +1,10 @@ -//! This crate defines the distributed state management system for -//! Notive. The state is an operation-based CRDT, which given the same -//! set of operations on any client, will realize the same state. - use thiserror::Error; use uuid::Uuid; +use wasm_bindgen::prelude::*; use crate::{ doc::{ApplyOpError, Doc}, - op::{Op, OpKind}, + op::{CreateGrid, Op, OpKind}, vector_clock::VectorClock, }; @@ -21,18 +18,28 @@ pub enum Error { RealizeError(#[from] ApplyOpError), } -#[derive(Default)] +#[wasm_bindgen] pub struct State { + actor_id: Uuid, ops: Vec, } impl State { - pub fn append_op(&mut self, actor_id: &Uuid, kind: OpKind) { + pub fn new() -> Self { + let actor_id = Uuid::now_v7(); + + Self { + actor_id, + ops: vec![], + } + } + + pub fn append_op(&mut self, kind: OpKind) { let clock = self .ops .last() - .map(|op| op.clock.inc(actor_id)) - .unwrap_or_else(|| VectorClock::new().inc(actor_id)); + .map(|op| op.clock.inc(&self.actor_id)) + .unwrap_or_else(|| VectorClock::new().inc(&self.actor_id)); self.ops.push(Op { id: Uuid::now_v7(), @@ -52,6 +59,24 @@ impl State { } } +#[wasm_bindgen] +pub fn make_state() -> State { + State::new() +} + +#[wasm_bindgen] +pub fn create_grid(state: &mut State) { + state.append_op(OpKind::CreateGrid(CreateGrid { + rows: 4, + base_cells_per_row: 16 + })); +} + +pub fn realize(state: &State) -> JsValue { + let doc = state.realize().unwrap(); + serde_wasm_bindgen::to_value(&doc).unwrap() +} + #[cfg(test)] mod tests { use crate::op::CreateGrid; @@ -60,17 +85,12 @@ mod tests { #[test] fn test() { - let alice = Uuid::now_v7(); - - let mut state = State::default(); + let mut state = State::new(); - state.append_op( - &alice, - OpKind::CreateGrid(CreateGrid { - rows: 4, - base_cells_per_row: 16, - }), - ); + state.append_op(OpKind::CreateGrid(CreateGrid { + rows: 4, + base_cells_per_row: 16, + })); let doc = state.realize().unwrap(); let grid = doc.grids.first().unwrap(); -- cgit v1.2.3