From 1b46e5d2370c8abfca30d3d1019c6bea00f3a8f3 Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Sun, 26 Oct 2025 21:44:43 +0000 Subject: [PATCH] Expose to JS --- Cargo.lock | 38 ++++++++++++++++++--- Cargo.toml | 5 +-- reconcile-js/src/index.ts | 21 ++++++++++++ src/lib.rs | 2 +- src/operation_transformation/edited_text.rs | 6 ++-- src/operation_transformation/transport.rs | 2 +- src/wasm.rs | 19 +++++++++++ tests/test.rs | 9 +++-- tests/wasm.rs | 10 +++++- 9 files changed, 95 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bf51cb7..83a551f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -124,6 +124,12 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + [[package]] name = "memory_units" version = "0.4.0" @@ -182,6 +188,7 @@ dependencies = [ "insta", "pretty_assertions", "serde", + "serde_json", "serde_yaml", "test-case", "wasm-bindgen", @@ -212,24 +219,47 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[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.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 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 = "serde_yaml" version = "0.9.34+deprecated" diff --git a/Cargo.toml b/Cargo.toml index 50a861f..0d625a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ path = "examples/merge-file.rs" serde = { version = "1.0.219", optional = true, features = ["derive"] } wasm-bindgen = { version = "0.2.99", optional = true } +serde_json = { version = "1.0.145", optional = true } # The `console_error_panic_hook` crate provides better debugging of panics by # logging them with `console.error`. This is great for development, but requires @@ -36,9 +37,9 @@ wee_alloc = { version = "0.4.2", optional = true } [features] default = [] serde = [ "dep:serde" ] -wasm = [ "dep:wasm-bindgen", "dep:wee_alloc" ] +wasm = [ "dep:wasm-bindgen", "dep:wee_alloc", "dep:serde_json", "serde" ] console_error_panic_hook = [ "dep:console_error_panic_hook" ] -all = [ "serde", "wasm", "console_error_panic_hook" ] +all = [ "wasm", "console_error_panic_hook" ] [dev-dependencies] insta = "1.42.2" diff --git a/reconcile-js/src/index.ts b/reconcile-js/src/index.ts index efc18ae..3b267ce 100644 --- a/reconcile-js/src/index.ts +++ b/reconcile-js/src/index.ts @@ -5,6 +5,7 @@ import { SpanWithHistory as wasmSpanWithHistory, reconcileWithHistory as wasmReconcileWithHistory, isBinary as wasmIsBinary, + getCompactDiff as wasmGetCompactDiff, initSync, } from 'reconcile-text'; @@ -179,6 +180,26 @@ export function reconcile( return jsResult; } +export function getCompactDiff( + original: string, + changed: string | TextWithOptionalCursors, + tokenizer: BuiltinTokenizer = 'Word' +): string { + init(); + + if (!BUILTIN_TOKENIZERS.includes(tokenizer)) { + throw new Error(UNSUPPORTED_TOKENIZER_ERROR); + } + + const changedWasm = toWasmTextWithCursors(changed); + + const result = wasmGetCompactDiff(original, changedWasm, tokenizer); + + changedWasm.free(); + + return result; +} + /** * Merges three versions of text and returns detailed provenance information. * diff --git a/src/lib.rs b/src/lib.rs index cbe354f..1dd78ff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -170,7 +170,7 @@ //! &changes.into() //! ); //! -//! let serialized = serde_yaml::to_string(&result.serialise_as_change_set()).unwrap(); +//! let serialized = serde_yaml::to_string(&result.to_change_set()).unwrap(); //! assert_eq!( //! serialized, //! concat!( diff --git a/src/operation_transformation/edited_text.rs b/src/operation_transformation/edited_text.rs index 8e1ddfe..a6465fe 100644 --- a/src/operation_transformation/edited_text.rs +++ b/src/operation_transformation/edited_text.rs @@ -350,7 +350,7 @@ where /// This is useful for sending changes over the network if there's /// a clear consensus on the original text. #[must_use] - pub fn serialise_as_change_set(&self) -> ChangeSet { + pub fn to_change_set(&self) -> ChangeSet { ChangeSet::new( SimpleOperation::from_operations(&self.operations), self.cursors.clone(), @@ -428,7 +428,7 @@ mod tests { let original = "Merging text is hard!"; let changes = "Merging text is easy with reconcile!"; let result = EditedText::from_strings(original, &changes.into()); - let serialized = serde_yaml::to_string(&result.serialise_as_change_set()).unwrap(); + let serialized = serde_yaml::to_string(&result.to_change_set()).unwrap(); let expected = concat!( "operations:\n", @@ -448,7 +448,7 @@ mod tests { let edited_text = EditedText::from_strings(original, &updated.into()); - let change_set = edited_text.serialise_as_change_set(); + let change_set = edited_text.to_change_set(); let deserialized_edited_text = EditedText::from_change_set(original, change_set, &*BuiltinTokenizer::Word); diff --git a/src/operation_transformation/transport.rs b/src/operation_transformation/transport.rs index eef44fd..97212f9 100644 --- a/src/operation_transformation/transport.rs +++ b/src/operation_transformation/transport.rs @@ -147,7 +147,7 @@ impl<'de> Deserialize<'de> for SimpleOperation { type Value = SimpleOperation; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str("an integer between -2^64 and 2^63 or a string") + formatter.write_str("an integer between -2^63 and 2^64-1 or a string") } fn visit_u64(self, value: u64) -> Result diff --git a/src/wasm.rs b/src/wasm.rs index 8cf080a..0fd0aca 100644 --- a/src/wasm.rs +++ b/src/wasm.rs @@ -87,6 +87,25 @@ pub fn generic_reconcile( } } +/// WASM wrapper around getting a compact diff representation as a JSON string +/// +/// # Panics +/// +/// If serialization to JSON fails which should not happen +#[wasm_bindgen(js_name = getCompactDiff)] +#[must_use] +pub fn get_compact_diff( + parent: &str, + changed: &TextWithCursors, + tokenizer: BuiltinTokenizer, +) -> String { + set_panic_hook(); + let edited_text = crate::EditedText::from_strings_with_tokenizer(parent, changed, &*tokenizer); + let change_set = edited_text.to_change_set(); + + serde_json::to_string(&change_set).expect("Failed to serialize change set") +} + /// Heuristically determine if the given data is a binary or a text file's /// content. #[wasm_bindgen(js_name = isBinary)] diff --git a/tests/test.rs b/tests/test.rs index 00f5163..e8fae7d 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -46,12 +46,11 @@ fn test_document_one_way_with_cursors_and_serialisation() { &*BuiltinTokenizer::Word, ); - let serialised_left = serde_yaml::from_str( - &serde_yaml::to_string(&left_operations.serialise_as_change_set()).unwrap(), - ) - .unwrap(); + let serialised_left = + serde_yaml::from_str(&serde_yaml::to_string(&left_operations.to_change_set()).unwrap()) + .unwrap(); let serialised_right = serde_yaml::from_str( - &serde_yaml::to_string(&right_operations.serialise_as_change_set()).unwrap(), + &serde_yaml::to_string(&right_operations.to_change_set()).unwrap(), ) .unwrap(); diff --git a/tests/wasm.rs b/tests/wasm.rs index 5ec5f35..6a9d556 100644 --- a/tests/wasm.rs +++ b/tests/wasm.rs @@ -46,7 +46,7 @@ fn test_merge_text_with_cursors() { } #[wasm_bindgen_test(unsupported = test)] -fn merge_binary() { +fn test_merge_binary() { let left = [0, 1, 2]; let right = [3, 4, 5]; assert_eq!( @@ -62,6 +62,14 @@ fn test_is_binary() { assert!(!is_binary(b"hello")); } +#[wasm_bindgen_test(unsupported = test)] +fn test_get_compact_diff() { + let parent = "hello "; + let changed = "world"; + let result = get_compact_diff(parent, &changed.into(), BuiltinTokenizer::Word); + assert_eq!(result, "{\"operations\":[-6,\"world\"],\"cursors\":[]}"); +} + #[wasm_bindgen_test(unsupported = test)] fn test_is_binary_empty() { assert!(!is_binary(b""));