diff --git a/.editorconfig b/.editorconfig index 6e87a003da..94d1d40e9c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,6 +1,8 @@ # Editor configuration, see http://editorconfig.org root = true +[*.rs] +max_line_length = 120 [*] charset = utf-8 indent_style = space diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 4add0443df..cae2d6cd62 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -798,49 +798,6 @@ jobs: name: fuzz-artifact path: packages/common/y-octo/utils/fuzz/artifacts/**/* - y-octo-binding-test: - name: y-octo binding test on ${{ matrix.settings.target }} - runs-on: ${{ matrix.settings.os }} - strategy: - fail-fast: false - matrix: - settings: - - { target: 'x86_64-unknown-linux-gnu', os: 'ubuntu-latest' } - - { target: 'aarch64-unknown-linux-gnu', os: 'ubuntu-24.04-arm' } - - { target: 'x86_64-apple-darwin', os: 'macos-15-intel' } - - { target: 'aarch64-apple-darwin', os: 'macos-latest' } - - { target: 'x86_64-pc-windows-msvc', os: 'windows-latest' } - - { target: 'aarch64-pc-windows-msvc', os: 'windows-11-arm' } - steps: - - uses: actions/checkout@v4 - - name: Setup Node.js - uses: ./.github/actions/setup-node - with: - extra-flags: workspaces focus @affine-tools/cli @affine/monorepo @y-octo/node - electron-install: false - - name: Install rustup (Windows 11 ARM) - if: matrix.settings.os == 'windows-11-arm' - shell: pwsh - run: | - Invoke-WebRequest -Uri "https://static.rust-lang.org/rustup/dist/aarch64-pc-windows-msvc/rustup-init.exe" -OutFile rustup-init.exe - .\rustup-init.exe --default-toolchain none -y - "$env:USERPROFILE\.cargo\bin" | Out-File -Append -Encoding ascii $env:GITHUB_PATH - "CARGO_HOME=$env:USERPROFILE\.cargo" | Out-File -Append -Encoding ascii $env:GITHUB_ENV - - name: Install Rust (Windows 11 ARM) - if: matrix.settings.os == 'windows-11-arm' - shell: pwsh - run: | - rustup install stable - rustup target add ${{ matrix.settings.target }} - cargo --version - - name: Build Rust - uses: ./.github/actions/build-rust - with: - target: ${{ matrix.settings.target }} - package: '@y-octo/node' - - name: Run tests - run: yarn affine @y-octo/node test - rust-test: name: Run native tests runs-on: ubuntu-latest @@ -1387,7 +1344,6 @@ jobs: - miri - loom - fuzzing - - y-octo-binding-test - server-test - server-e2e-test - rust-test diff --git a/.taplo.toml b/.taplo.toml index 20c3b8ad70..aea4ee9bd9 100644 --- a/.taplo.toml +++ b/.taplo.toml @@ -1,4 +1,8 @@ -exclude = ["node_modules/**/*.toml", "target/**/*.toml"] +exclude = [ + "node_modules/**/*.toml", + "target/**/*.toml", + "packages/frontend/apps/ios/App/Packages/AffineGraphQL/**/*.toml", +] # https://taplo.tamasfe.dev/configuration/formatter-options.html [formatting] diff --git a/Cargo.lock b/Cargo.lock index 2644fecb34..8169016b77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6054,7 +6054,7 @@ checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" [[package]] name = "y-octo" -version = "0.0.1" +version = "0.0.2" dependencies = [ "ahash", "arbitrary", @@ -6081,17 +6081,6 @@ dependencies = [ "yrs", ] -[[package]] -name = "y-octo-node" -version = "0.0.1" -dependencies = [ - "anyhow", - "napi", - "napi-build", - "napi-derive", - "y-octo", -] - [[package]] name = "y-octo-utils" version = "0.0.1" diff --git a/Cargo.toml b/Cargo.toml index 8cea42776c..03ffaf13ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,6 @@ members = [ "./packages/backend/native", "./packages/common/native", "./packages/common/y-octo/core", - "./packages/common/y-octo/node", "./packages/common/y-octo/utils", "./packages/frontend/mobile-native", "./packages/frontend/native", diff --git a/package.json b/package.json index 333756af9f..6dc76fa741 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,6 @@ ".", "blocksuite/**/*", "packages/*/*", - "packages/common/y-octo/node", "packages/frontend/apps/*", "tools/*", "docs/reference", diff --git a/packages/backend/native/Cargo.toml b/packages/backend/native/Cargo.toml index 4f32224e3d..df3493f835 100644 --- a/packages/backend/native/Cargo.toml +++ b/packages/backend/native/Cargo.toml @@ -1,5 +1,5 @@ [package] -edition = "2021" +edition = "2024" license-file = "LICENSE" name = "affine_server_native" version = "1.0.0" diff --git a/packages/backend/native/src/doc.rs b/packages/backend/native/src/doc.rs index 431d99bfac..418d4ab78a 100644 --- a/packages/backend/native/src/doc.rs +++ b/packages/backend/native/src/doc.rs @@ -1,6 +1,4 @@ -use affine_common::doc_parser::{ - self, BlockInfo, CrawlResult, MarkdownResult, PageDocContent, WorkspaceDocContent, -}; +use affine_common::doc_parser::{self, BlockInfo, CrawlResult, MarkdownResult, PageDocContent, WorkspaceDocContent}; use napi::bindgen_prelude::*; use napi_derive::napi; @@ -103,10 +101,7 @@ pub fn parse_doc_from_binary(doc_bin: Buffer, doc_id: String) -> Result, -) -> Result> { +pub fn parse_page_doc(doc_bin: Buffer, max_summary_length: Option) -> Result> { let result = doc_parser::parse_page_doc(doc_bin.into(), max_summary_length.map(|v| v as isize)) .map_err(|e| Error::new(Status::GenericFailure, e.to_string()))?; Ok(result.map(Into::into)) @@ -114,8 +109,8 @@ pub fn parse_page_doc( #[napi] pub fn parse_workspace_doc(doc_bin: Buffer) -> Result> { - let result = doc_parser::parse_workspace_doc(doc_bin.into()) - .map_err(|e| Error::new(Status::GenericFailure, e.to_string()))?; + let result = + doc_parser::parse_workspace_doc(doc_bin.into()).map_err(|e| Error::new(Status::GenericFailure, e.to_string()))?; Ok(result.map(Into::into)) } @@ -126,21 +121,13 @@ pub fn parse_doc_to_markdown( ai_editable: Option, doc_url_prefix: Option, ) -> Result { - let result = doc_parser::parse_doc_to_markdown( - doc_bin.into(), - doc_id, - ai_editable.unwrap_or(false), - doc_url_prefix, - ) - .map_err(|e| Error::new(Status::GenericFailure, e.to_string()))?; + let result = doc_parser::parse_doc_to_markdown(doc_bin.into(), doc_id, ai_editable.unwrap_or(false), doc_url_prefix) + .map_err(|e| Error::new(Status::GenericFailure, e.to_string()))?; Ok(result.into()) } #[napi] -pub fn read_all_doc_ids_from_root_doc( - doc_bin: Buffer, - include_trash: Option, -) -> Result> { +pub fn read_all_doc_ids_from_root_doc(doc_bin: Buffer, include_trash: Option) -> Result> { let result = doc_parser::get_doc_ids_from_binary(doc_bin.into(), include_trash.unwrap_or(false)) .map_err(|e| Error::new(Status::GenericFailure, e.to_string()))?; Ok(result) diff --git a/packages/backend/native/src/doc_loader.rs b/packages/backend/native/src/doc_loader.rs index e254d7050b..d6fb5352a7 100644 --- a/packages/backend/native/src/doc_loader.rs +++ b/packages/backend/native/src/doc_loader.rs @@ -1,8 +1,8 @@ use affine_common::doc_loader::Doc; use napi::{ + Env, Result, Task, anyhow::anyhow, bindgen_prelude::{AsyncTask, Buffer}, - Env, Result, Task, }; #[napi(object)] diff --git a/packages/backend/native/src/file_type.rs b/packages/backend/native/src/file_type.rs index 2aeecf9c55..4f87b2412c 100644 --- a/packages/backend/native/src/file_type.rs +++ b/packages/backend/native/src/file_type.rs @@ -1,4 +1,4 @@ -use mp4parse::{read_mp4, TrackType}; +use mp4parse::{TrackType, read_mp4}; use napi_derive::napi; #[napi] @@ -6,9 +6,7 @@ pub fn get_mime(input: &[u8]) -> String { let mimetype = if let Some(kind) = infer::get(&input[..4096.min(input.len())]) { kind.mime_type().to_string() } else { - file_format::FileFormat::from_bytes(input) - .media_type() - .to_string() + file_format::FileFormat::from_bytes(input).media_type().to_string() }; if mimetype == "video/mp4" { detect_mp4_flavor(input) diff --git a/packages/backend/native/src/hashcash.rs b/packages/backend/native/src/hashcash.rs index 3ad339ba58..c8981ab4d2 100644 --- a/packages/backend/native/src/hashcash.rs +++ b/packages/backend/native/src/hashcash.rs @@ -1,7 +1,7 @@ use std::convert::TryFrom; use affine_common::hashcash::Stamp; -use napi::{bindgen_prelude::AsyncTask, Env, Result as NapiResult, Task}; +use napi::{Env, Result as NapiResult, Task, bindgen_prelude::AsyncTask}; use napi_derive::napi; pub struct AsyncVerifyChallengeResponse { @@ -61,9 +61,6 @@ impl Task for AsyncMintChallengeResponse { } #[napi] -pub fn mint_challenge_response( - resource: String, - bits: Option, -) -> AsyncTask { +pub fn mint_challenge_response(resource: String, bits: Option) -> AsyncTask { AsyncTask::new(AsyncMintChallengeResponse { bits, resource }) } diff --git a/packages/backend/native/src/lib.rs b/packages/backend/native/src/lib.rs index c6740387bc..e02a926ca0 100644 --- a/packages/backend/native/src/lib.rs +++ b/packages/backend/native/src/lib.rs @@ -11,7 +11,7 @@ pub mod tiktoken; use std::fmt::{Debug, Display}; -use napi::{bindgen_prelude::*, Error, Result, Status}; +use napi::{Error, Result, Status, bindgen_prelude::*}; use y_octo::Doc; #[cfg(not(target_arch = "arm"))] @@ -58,5 +58,4 @@ pub fn merge_updates_in_apply_way(updates: Vec) -> Result { pub const AFFINE_PRO_PUBLIC_KEY: Option<&'static str> = std::option_env!("AFFINE_PRO_PUBLIC_KEY"); #[napi] -pub const AFFINE_PRO_LICENSE_AES_KEY: Option<&'static str> = - std::option_env!("AFFINE_PRO_LICENSE_AES_KEY"); +pub const AFFINE_PRO_LICENSE_AES_KEY: Option<&'static str> = std::option_env!("AFFINE_PRO_LICENSE_AES_KEY"); diff --git a/packages/backend/native/src/utils.rs b/packages/backend/native/src/utils.rs index 2f3074c200..143f1ff6bc 100644 --- a/packages/backend/native/src/utils.rs +++ b/packages/backend/native/src/utils.rs @@ -57,11 +57,11 @@ fn try_remove_label(s: &str, i: usize) -> Option { return None; } - if let Some(ch) = s[next_idx..].chars().next() { - if ch == '.' { - next_idx += ch.len_utf8(); - return Some(next_idx); - } + if let Some(ch) = s[next_idx..].chars().next() + && ch == '.' + { + next_idx += ch.len_utf8(); + return Some(next_idx); } None } @@ -84,9 +84,7 @@ fn remove_label(s: &str) -> String { pub fn clean_content(content: &str) -> String { let content = content.replace("\x00", ""); - remove_label(&collapse_whitespace(&content)) - .trim() - .to_string() + remove_label(&collapse_whitespace(&content)).trim().to_string() } #[cfg(test)] diff --git a/packages/common/native/Cargo.toml b/packages/common/native/Cargo.toml index 86342d5953..7f8ad87d5e 100644 --- a/packages/common/native/Cargo.toml +++ b/packages/common/native/Cargo.toml @@ -1,5 +1,5 @@ [package] -edition = "2021" +edition = "2024" license-file = "LICENSE" name = "affine_common" version = "0.1.0" diff --git a/packages/common/native/benches/hashcash.rs b/packages/common/native/benches/hashcash.rs index 7978c905cb..c575d22a90 100644 --- a/packages/common/native/benches/hashcash.rs +++ b/packages/common/native/benches/hashcash.rs @@ -1,7 +1,7 @@ use std::hint::black_box; use affine_common::hashcash::Stamp; -use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; +use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main}; fn bench_hashcash(c: &mut Criterion) { let mut group = c.benchmark_group("hashcash"); diff --git a/packages/common/native/src/doc_loader/document.rs b/packages/common/native/src/doc_loader/document.rs index 84e29acc51..f974d30683 100644 --- a/packages/common/native/src/doc_loader/document.rs +++ b/packages/common/native/src/doc_loader/document.rs @@ -1,6 +1,6 @@ use std::{ io::Cursor, - panic::{catch_unwind, AssertUnwindSafe}, + panic::{AssertUnwindSafe, catch_unwind}, path::PathBuf, }; @@ -22,9 +22,7 @@ pub struct DocOptions { impl Default for DocOptions { fn default() -> Self { - Self { - code_threshold: 1000, - } + Self { code_threshold: 1000 } } } @@ -39,9 +37,7 @@ impl Doc { } pub fn with_options(file_path: &str, doc: &[u8], options: DocOptions) -> LoaderResult { - if let Some(kind) = - infer::get(&doc[..4096.min(doc.len())]).or(infer::get_from_path(file_path).ok().flatten()) - { + if let Some(kind) = infer::get(&doc[..4096.min(doc.len())]).or(infer::get_from_path(file_path).ok().flatten()) { if kind.extension() == "pdf" { return Self::load_pdf(file_path, doc); } else if kind.extension() == "docx" { @@ -66,11 +62,10 @@ impl Doc { } "rs" | "c" | "cpp" | "h" | "hpp" | "js" | "ts" | "tsx" | "go" | "py" => { let name = path.full_str().to_string(); - let loader = - SourceCodeLoader::from_string(string).with_parser_option(LanguageParserOptions { - language: get_language_by_filename(&name)?, - parser_threshold: options.code_threshold, - }); + let loader = SourceCodeLoader::from_string(string).with_parser_option(LanguageParserOptions { + language: get_language_by_filename(&name)?, + parser_threshold: options.code_threshold, + }); let splitter = TokenSplitter::default(); return Self::from_loader(file_path, loader, splitter); } @@ -89,10 +84,7 @@ impl Doc { splitter: impl TextSplitter + 'static, ) -> Result { let name = file_path.to_string(); - let chunks = catch_unwind(AssertUnwindSafe(|| { - Self::get_chunks_from_loader(loader, splitter) - })) - .map_err(|e| { + let chunks = catch_unwind(AssertUnwindSafe(|| Self::get_chunks_from_loader(loader, splitter))).map_err(|e| { LoaderError::Other(match e.downcast::() { Ok(v) => *v, Err(e) => match e.downcast::<&str>() { @@ -124,8 +116,7 @@ impl Doc { } fn load_docx(file_path: &str, doc: &[u8]) -> LoaderResult { - let loader = DocxLoader::new(Cursor::new(doc)) - .ok_or(LoaderError::Other("Failed to parse docx document".into()))?; + let loader = DocxLoader::new(Cursor::new(doc)).ok_or(LoaderError::Other("Failed to parse docx document".into()))?; let splitter = TokenSplitter::default(); Self::from_loader(file_path, loader, splitter) } @@ -175,8 +166,7 @@ mod tests { let buffer = read(fixtures.join(fixture)).unwrap(); let doc = Doc::with_options(fixture, &buffer, DocOptions { code_threshold: 0 }).unwrap(); for chunk in doc.chunks.iter() { - let output = - read_to_string(fixtures.join(format!("{}.{}.md", fixture, chunk.index))).unwrap(); + let output = read_to_string(fixtures.join(format!("{}.{}.md", fixture, chunk.index))).unwrap(); assert_eq!(chunk.content, output); } } diff --git a/packages/common/native/src/doc_loader/loader/docx.rs b/packages/common/native/src/doc_loader/loader/docx.rs index 521a4b373c..eb8a1074d1 100644 --- a/packages/common/native/src/doc_loader/loader/docx.rs +++ b/packages/common/native/src/doc_loader/loader/docx.rs @@ -61,9 +61,7 @@ mod tests { for (idx, doc) in documents.into_iter().enumerate() { assert_eq!( doc.page_content, - String::from_utf8_lossy( - &read(get_fixtures_path().join(format!("demo.docx.{}.md", idx))).unwrap() - ) + String::from_utf8_lossy(&read(get_fixtures_path().join(format!("demo.docx.{}.md", idx))).unwrap()) ); } } diff --git a/packages/common/native/src/doc_loader/loader/html.rs b/packages/common/native/src/doc_loader/loader/html.rs index a9c15a9e84..2b6ed061fd 100644 --- a/packages/common/native/src/doc_loader/loader/html.rs +++ b/packages/common/native/src/doc_loader/loader/html.rs @@ -29,10 +29,8 @@ impl HtmlLoader { impl Loader for HtmlLoader { fn load(mut self) -> LoaderResult> { let cleaned_html = readability::extractor::extract(&mut self.html, &self.url)?; - let doc = - Document::new(format!("{}\n{}", cleaned_html.title, cleaned_html.text)).with_metadata( - HashMap::from([("source".to_string(), Value::from(self.url.as_str()))]), - ); + let doc = Document::new(format!("{}\n{}", cleaned_html.title, cleaned_html.text)) + .with_metadata(HashMap::from([("source".to_string(), Value::from(self.url.as_str()))])); Ok(vec![doc]) } @@ -46,10 +44,7 @@ mod tests { fn test_html_loader() { let input = "

Hello world!

"; - let html_loader = HtmlLoader::new( - input.as_bytes(), - Url::parse("https://example.com/").unwrap(), - ); + let html_loader = HtmlLoader::new(input.as_bytes(), Url::parse("https://example.com/").unwrap()); let documents = html_loader.load().unwrap(); @@ -66,16 +61,14 @@ mod tests { #[test] fn test_html_load_from_path() { let buffer = include_bytes!("../../../fixtures/sample.html"); - let html_loader = HtmlLoader::new( - Cursor::new(buffer), - Url::parse("https://example.com/").unwrap(), - ); + let html_loader = HtmlLoader::new(Cursor::new(buffer), Url::parse("https://example.com/").unwrap()); let documents = html_loader.load().unwrap(); - let expected = "Example Domain\n\n This domain is for use in illustrative examples in \ - documents. You may\n use this domain in literature without prior \ - coordination or asking for\n permission.\n More information..."; + let expected = + "Example Domain\n\n This domain is for use in illustrative examples in documents. You may\n use \ + this domain in literature without prior coordination or asking for\n permission.\n More \ + information..."; assert_eq!(documents.len(), 1); assert_eq!( diff --git a/packages/common/native/src/doc_loader/loader/mod.rs b/packages/common/native/src/doc_loader/loader/mod.rs index 8a36c23312..285fb9b4a1 100644 --- a/packages/common/native/src/doc_loader/loader/mod.rs +++ b/packages/common/native/src/doc_loader/loader/mod.rs @@ -23,6 +23,6 @@ pub trait Loader: Send + Sync { pub use docx::DocxLoader; pub use html::HtmlLoader; pub use pdf::PdfExtractLoader; -pub use source::{get_language_by_filename, LanguageParserOptions, SourceCodeLoader}; +pub use source::{LanguageParserOptions, SourceCodeLoader, get_language_by_filename}; pub use text::TextLoader; pub use url::Url; diff --git a/packages/common/native/src/doc_loader/loader/pdf.rs b/packages/common/native/src/doc_loader/loader/pdf.rs index 9feaeee914..96c4e1b586 100644 --- a/packages/common/native/src/doc_loader/loader/pdf.rs +++ b/packages/common/native/src/doc_loader/loader/pdf.rs @@ -1,4 +1,4 @@ -use pdf_extract::{output_doc, output_doc_encrypted, PlainTextOutput}; +use pdf_extract::{PlainTextOutput, output_doc, output_doc_encrypted}; /** * modified from https://github.com/Abraxas-365/langchain-rust/tree/v4.6.0/src/document_loaders @@ -72,8 +72,7 @@ mod tests { assert_eq!(docs.len(), 1); assert_eq!( &docs[0].page_content[..100], - "\n\nSample PDF\nThis is a simple PDF file. Fun fun fun.\n\nLorem ipsum dolor sit amet, \ - consectetuer a" + "\n\nSample PDF\nThis is a simple PDF file. Fun fun fun.\n\nLorem ipsum dolor sit amet, consectetuer a" ); } diff --git a/packages/common/native/src/doc_loader/loader/source/mod.rs b/packages/common/native/src/doc_loader/loader/source/mod.rs index 2c549b1fcc..3077fa3ed9 100644 --- a/packages/common/native/src/doc_loader/loader/source/mod.rs +++ b/packages/common/native/src/doc_loader/loader/source/mod.rs @@ -3,7 +3,7 @@ */ mod parser; -pub use parser::{get_language_by_filename, LanguageParser, LanguageParserOptions}; +pub use parser::{LanguageParser, LanguageParserOptions, get_language_by_filename}; use super::*; diff --git a/packages/common/native/src/doc_loader/loader/source/parser.rs b/packages/common/native/src/doc_loader/loader/source/parser.rs index 2fcb1c7302..6473c9403b 100644 --- a/packages/common/native/src/doc_loader/loader/source/parser.rs +++ b/packages/common/native/src/doc_loader/loader/source/parser.rs @@ -59,11 +59,7 @@ pub struct LanguageParser { impl Debug for LanguageParser { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "LanguageParser {{ language: {:?} }}", - self.parser_options.language - ) + write!(f, "LanguageParser {{ language: {:?} }}", self.parser_options.language) } } @@ -77,10 +73,7 @@ impl Clone for LanguageParser { } pub fn get_language_by_filename(name: &str) -> LoaderResult { - let extension = name - .split('.') - .next_back() - .ok_or(LoaderError::UnsupportedLanguage)?; + let extension = name.split('.').next_back().ok_or(LoaderError::UnsupportedLanguage)?; let language = match extension.to_lowercase().as_str() { "rs" => Language::Rust, "c" => Language::C, @@ -133,10 +126,7 @@ impl LanguageParser { impl LanguageParser { pub fn parse_code(&mut self, code: &String) -> LoaderResult> { - let tree = self - .parser - .parse(code, None) - .ok_or(LoaderError::UnsupportedLanguage)?; + let tree = self.parser.parse(code, None).ok_or(LoaderError::UnsupportedLanguage)?; if self.parser_options.parser_threshold > tree.root_node().end_position().row as u64 { return Ok(vec![Document::new(code).with_metadata(HashMap::from([ ( @@ -152,11 +142,7 @@ impl LanguageParser { self.extract_functions_classes(tree, code) } - pub fn extract_functions_classes( - &self, - tree: Tree, - code: &String, - ) -> LoaderResult> { + pub fn extract_functions_classes(&self, tree: Tree, code: &String) -> LoaderResult> { let mut chunks = Vec::new(); let count = tree.root_node().child_count(); diff --git a/packages/common/native/src/doc_loader/loader/text.rs b/packages/common/native/src/doc_loader/loader/text.rs index 62fdfde89b..1746704b8e 100644 --- a/packages/common/native/src/doc_loader/loader/text.rs +++ b/packages/common/native/src/doc_loader/loader/text.rs @@ -10,9 +10,7 @@ pub struct TextLoader { impl TextLoader { pub fn new>(input: T) -> Self { - Self { - content: input.into(), - } + Self { content: input.into() } } } diff --git a/packages/common/native/src/doc_loader/mod.rs b/packages/common/native/src/doc_loader/mod.rs index c164cb8ce8..8cc5878a31 100644 --- a/packages/common/native/src/doc_loader/mod.rs +++ b/packages/common/native/src/doc_loader/mod.rs @@ -7,8 +7,8 @@ mod types; pub use document::{Chunk, Doc}; pub use error::{LoaderError, LoaderResult}; use loader::{ - get_language_by_filename, DocxLoader, HtmlLoader, LanguageParserOptions, Loader, - PdfExtractLoader, SourceCodeLoader, TextLoader, Url, + DocxLoader, HtmlLoader, LanguageParserOptions, Loader, PdfExtractLoader, SourceCodeLoader, TextLoader, Url, + get_language_by_filename, }; use splitter::{MarkdownSplitter, TextSplitter, TextSplitterError, TokenSplitter}; use types::Document; diff --git a/packages/common/native/src/doc_loader/splitter/options.rs b/packages/common/native/src/doc_loader/splitter/options.rs index eba6cf2a27..0c28da6ec2 100644 --- a/packages/common/native/src/doc_loader/splitter/options.rs +++ b/packages/common/native/src/doc_loader/splitter/options.rs @@ -2,7 +2,7 @@ * modified from https://github.com/Abraxas-365/langchain-rust/tree/v4.6.0/src/text_splitter */ use text_splitter::ChunkConfig; -use tiktoken_rs::{get_bpe_from_model, get_bpe_from_tokenizer, tokenizer::Tokenizer, CoreBPE}; +use tiktoken_rs::{CoreBPE, get_bpe_from_model, get_bpe_from_tokenizer, tokenizer::Tokenizer}; use super::TextSplitterError; @@ -79,8 +79,8 @@ impl TryFrom<&SplitterOptions> for ChunkConfig { fn try_from(options: &SplitterOptions) -> Result { let tk = if !options.encoding_name.is_empty() { - let tokenizer = SplitterOptions::get_tokenizer_from_str(&options.encoding_name) - .ok_or(TextSplitterError::TokenizerNotFound)?; + let tokenizer = + SplitterOptions::get_tokenizer_from_str(&options.encoding_name).ok_or(TextSplitterError::TokenizerNotFound)?; get_bpe_from_tokenizer(tokenizer).map_err(|_| TextSplitterError::InvalidTokenizer)? } else { diff --git a/packages/common/native/src/doc_parser/affine.rs b/packages/common/native/src/doc_parser/affine.rs index dc36a210ce..a9973c089a 100644 --- a/packages/common/native/src/doc_parser/affine.rs +++ b/packages/common/native/src/doc_parser/affine.rs @@ -5,16 +5,13 @@ use y_octo::{Any, DocOptions, JwstCodecError, Map, Value}; use super::{ blocksuite::{ - collect_child_ids, get_block_id, get_flavour, get_list_depth, get_string, nearest_by_flavour, - DocContext, + DocContext, collect_child_ids, get_block_id, get_flavour, get_list_depth, get_string, nearest_by_flavour, }, delta_markdown::{ - delta_value_to_inline_markdown, extract_inline_references, text_to_inline_markdown, - text_to_markdown, DeltaToMdOptions, - }, - value::{ - any_as_string, any_truthy, build_reference_payload, params_value_to_json, value_to_string, + DeltaToMdOptions, delta_value_to_inline_markdown, extract_inline_references, text_to_inline_markdown, + text_to_markdown, }, + value::{any_as_string, any_truthy, build_reference_payload, params_value_to_json, value_to_string}, }; const SUMMARY_LIMIT: usize = 1000; @@ -402,17 +399,10 @@ pub fn parse_doc_from_binary(doc_bin: Vec, doc_id: String) -> Result continue, }; - let parent_block = parent_block_id - .as_ref() - .and_then(|id| context.block_pool.get(id)); + let parent_block = parent_block_id.as_ref().and_then(|id| context.block_pool.get(id)); let parent_flavour = parent_block.and_then(get_flavour); - let note_block = nearest_by_flavour( - &block_id, - NOTE_FLAVOUR, - &context.parent_lookup, - &context.block_pool, - ); + let note_block = nearest_by_flavour(&block_id, NOTE_FLAVOUR, &context.parent_lookup, &context.block_pool); let note_block_id = note_block.as_ref().and_then(get_block_id); let display_mode = determine_display_mode(note_block.as_ref()); @@ -438,14 +428,9 @@ pub fn parse_doc_from_binary(doc_bin: Vec, doc_id: String) -> Result, doc_id: String) -> Result, doc_id: String) -> Result, doc_id: String) -> Result, - include_trash: bool, -) -> Result, ParseError> { +pub fn get_doc_ids_from_binary(doc_bin: Vec, include_trash: bool) -> Result, ParseError> { if doc_bin.is_empty() || doc_bin == [0, 0] { return Err(ParseError::InvalidBinary); } @@ -695,10 +670,7 @@ fn compose_additional( database_name: Option<&String>, ) -> Option { let mut payload = JsonMap::new(); - payload.insert( - "displayMode".into(), - JsonValue::String(display_mode.to_string()), - ); + payload.insert("displayMode".into(), JsonValue::String(display_mode.to_string())); if let Some(note_id) = note_block_id { payload.insert("noteBlockId".into(), JsonValue::String(note_id.clone())); } @@ -721,10 +693,7 @@ fn apply_doc_ref(info: &mut BlockInfo, page_id: String, payload: Option) } fn embed_ref_payload(block: &Map, page_id: &str) -> Option { - let params = block - .get("prop:params") - .as_ref() - .and_then(params_value_to_json); + let params = block.get("prop:params").as_ref().and_then(params_value_to_json); Some(build_reference_payload(page_id, params)) } @@ -746,10 +715,10 @@ fn gather_surface_texts(block: &Map) -> Vec { if let Some(value_map) = elements.get("value").and_then(|value| value.to_map()) { for value in value_map.values() { - if let Some(element) = value.to_map() { - if let Some(text) = element.get("text").and_then(|value| value.to_text()) { - texts.push(text.to_string()); - } + if let Some(element) = value.to_map() + && let Some(text) = element.get("text").and_then(|value| value.to_text()) + { + texts.push(text.to_string()); } } } @@ -784,12 +753,12 @@ fn gather_database_texts(block: &Map) -> (Vec, Option) { fn gather_table_contents(block: &Map) -> Vec { let mut contents = Vec::new(); for key in block.keys() { - if key.starts_with("prop:cells.") && key.ends_with(".text") { - if let Some(value) = block.get(key).and_then(|value| value_to_string(&value)) { - if !value.is_empty() { - contents.push(value); - } - } + if key.starts_with("prop:cells.") + && key.ends_with(".text") + && let Some(value) = block.get(key).and_then(|value| value_to_string(&value)) + && !value.is_empty() + { + contents.push(value); } } contents @@ -800,11 +769,7 @@ struct DatabaseTable { rows: Vec>, } -fn build_database_table( - block: &Map, - context: &DocContext, - md_options: &DeltaToMdOptions, -) -> Option { +fn build_database_table(block: &Map, context: &DocContext, md_options: &DeltaToMdOptions) -> Option { let columns = parse_database_columns(block)?; let cells_map = block.get("prop:cells").and_then(|v| v.to_map())?; let child_ids = collect_child_ids(block); @@ -826,15 +791,14 @@ fn build_database_table( cell_text = text; } } - } else if let Some(row_cells) = &row_cells { - if let Some(cell_val) = row_cells.get(&column.id).and_then(|v| v.to_map()) { - if let Some(value) = cell_val.get("value") { - if let Some(text_md) = delta_value_to_inline_markdown(&value, md_options) { - cell_text = text_md; - } else { - cell_text = format_cell_value(&value, column); - } - } + } else if let Some(row_cells) = &row_cells + && let Some(cell_val) = row_cells.get(&column.id).and_then(|v| v.to_map()) + && let Some(value) = cell_val.get("value") + { + if let Some(text_md) = delta_value_to_inline_markdown(&value, md_options) { + cell_text = text_md; + } else { + cell_text = format_cell_value(&value, column); } } @@ -852,26 +816,26 @@ fn append_database_summary(summary: &mut String, block: &Map, context: &DocConte return; }; - if let Some(title) = get_string(block, "prop:title") { - if !title.is_empty() { - summary.push_str(&title); - summary.push('|'); - } + if let Some(title) = get_string(block, "prop:title") + && !title.is_empty() + { + summary.push_str(&title); + summary.push('|'); } for column in table.columns.iter() { - if let Some(name) = column.name.as_ref() { - if !name.is_empty() { - summary.push_str(name); - summary.push('|'); - } + if let Some(name) = column.name.as_ref() + && !name.is_empty() + { + summary.push_str(name); + summary.push('|'); } for option in column.options.iter() { - if let Some(value) = option.value.as_ref() { - if !value.is_empty() { - summary.push_str(value); - summary.push('|'); - } + if let Some(value) = option.value.as_ref() + && !value.is_empty() + { + summary.push_str(value); + summary.push('|'); } } } @@ -920,9 +884,7 @@ struct DatabaseColumn { } fn parse_database_columns(block: &Map) -> Option> { - let columns = block - .get("prop:columns") - .and_then(|value| value.to_array())?; + let columns = block.get("prop:columns").and_then(|value| value.to_array())?; let mut parsed = Vec::new(); for column_value in columns.iter() { if let Some(column) = column_value.to_map() { @@ -967,9 +929,7 @@ fn format_option_tag(option: &DatabaseOption) -> String { let value = option.value.as_deref().unwrap_or_default(); let color = option.color.as_deref().unwrap_or_default(); - format!( - "{value}" - ) + format!("{value}") } fn format_cell_value(value: &Value, column: &DatabaseColumn) -> String { @@ -991,15 +951,8 @@ fn format_cell_value(value: &Value, column: &DatabaseColumn) -> String { } "multi-select" => { let ids: Vec = match value { - Value::Any(Any::Array(ids)) => ids - .iter() - .filter_map(any_as_string) - .map(str::to_string) - .collect(), - Value::Array(array) => array - .iter() - .filter_map(|id_val| value_to_string(&id_val)) - .collect(), + Value::Any(Any::Array(ids)) => ids.iter().filter_map(any_as_string).map(str::to_string).collect(), + Value::Array(array) => array.iter().filter_map(|id_val| value_to_string(&id_val)).collect(), _ => Vec::new(), }; diff --git a/packages/common/native/src/doc_parser/blocksuite.rs b/packages/common/native/src/doc_parser/blocksuite.rs index b7d149c6ea..a4f15a1a73 100644 --- a/packages/common/native/src/doc_parser/blocksuite.rs +++ b/packages/common/native/src/doc_parser/blocksuite.rs @@ -25,13 +25,13 @@ pub(super) fn build_block_index(blocks_map: &Map) -> BlockIndex { let mut parent_lookup: HashMap = HashMap::new(); for (_, value) in blocks_map.iter() { - if let Some(block_map) = value.to_map() { - if let Some(block_id) = get_block_id(&block_map) { - for child_id in collect_child_ids(&block_map) { - parent_lookup.insert(child_id, block_id.clone()); - } - block_pool.insert(block_id, block_map); + if let Some(block_map) = value.to_map() + && let Some(block_id) = get_block_id(&block_map) + { + for child_id in collect_child_ids(&block_map) { + parent_lookup.insert(child_id, block_id.clone()); } + block_pool.insert(block_id, block_map); } } @@ -80,18 +80,13 @@ impl BlockWalker { let mut child_ids = collect_child_ids(block); for child_id in child_ids.drain(..).rev() { if self.visited.insert(child_id.clone()) { - self - .queue - .push((Some(parent_block_id.to_string()), child_id)); + self.queue.push((Some(parent_block_id.to_string()), child_id)); } } } } -pub(super) fn find_block_id_by_flavour( - block_pool: &HashMap, - flavour: &str, -) -> Option { +pub(super) fn find_block_id_by_flavour(block_pool: &HashMap, flavour: &str) -> Option { block_pool.iter().find_map(|(id, block)| { get_flavour(block) .filter(|block_flavour| block_flavour == flavour) @@ -133,12 +128,12 @@ pub(super) fn get_list_depth( let mut current_id = block_id.to_string(); while let Some(parent_id) = parent_lookup.get(¤t_id) { - if let Some(parent_block) = blocks.get(parent_id) { - if get_flavour(parent_block).as_deref() == Some("affine:list") { - depth += 1; - current_id = parent_id.clone(); - continue; - } + if let Some(parent_block) = blocks.get(parent_id) + && get_flavour(parent_block).as_deref() == Some("affine:list") + { + depth += 1; + current_id = parent_id.clone(); + continue; } break; } @@ -153,10 +148,10 @@ pub(super) fn nearest_by_flavour( ) -> Option { let mut cursor = Some(start.to_string()); while let Some(node) = cursor { - if let Some(block) = blocks.get(&node) { - if get_flavour(block).as_deref() == Some(flavour) { - return Some(block.clone()); - } + if let Some(block) = blocks.get(&node) + && get_flavour(block).as_deref() == Some(flavour) + { + return Some(block.clone()); } cursor = parent_lookup.get(&node).cloned(); } diff --git a/packages/common/native/src/doc_parser/delta_markdown.rs b/packages/common/native/src/doc_parser/delta_markdown.rs index 6a0ddeafaa..235d083050 100644 --- a/packages/common/native/src/doc_parser/delta_markdown.rs +++ b/packages/common/native/src/doc_parser/delta_markdown.rs @@ -7,8 +7,7 @@ use std::{ use y_octo::{AHashMap, Any, Map, Text, TextAttributes, TextDeltaOp, TextInsert, Value}; use super::value::{ - any_as_string, any_as_u64, any_truthy, build_reference_payload, params_any_map_to_json, - value_to_any, + any_as_string, any_as_u64, any_truthy, build_reference_payload, params_any_map_to_json, value_to_any, }; #[derive(Debug, Clone)] @@ -45,12 +44,7 @@ impl DeltaToMdOptions { } let mut parts = Vec::new(); - parts.push( - reference - .ref_type - .clone() - .unwrap_or_else(|| "LinkedPage".into()), - ); + parts.push(reference.ref_type.clone().unwrap_or_else(|| "LinkedPage".into())); parts.push(reference.page_id.clone()); if let Some(mode) = reference.mode.as_ref() { parts.push(mode.clone()); @@ -60,22 +54,14 @@ impl DeltaToMdOptions { } } -pub(super) fn text_to_markdown( - block: &Map, - key: &str, - options: &DeltaToMdOptions, -) -> Option { +pub(super) fn text_to_markdown(block: &Map, key: &str, options: &DeltaToMdOptions) -> Option { block .get(key) .and_then(|value| value.to_text()) .map(|text| delta_to_markdown(&text, options)) } -pub(super) fn text_to_inline_markdown( - block: &Map, - key: &str, - options: &DeltaToMdOptions, -) -> Option { +pub(super) fn text_to_inline_markdown(block: &Map, key: &str, options: &DeltaToMdOptions) -> Option { block .get(key) .and_then(|value| value.to_text()) @@ -89,8 +75,7 @@ pub(super) fn extract_inline_references(delta: &[TextDeltaOp]) -> Vec format, _ => continue, }; @@ -123,10 +108,7 @@ fn parse_inline_reference(value: &Any) -> Option { _ => return None, }; - let page_id = map - .get("pageId") - .and_then(any_as_string) - .map(str::to_string)?; + let page_id = map.get("pageId").and_then(any_as_string).map(str::to_string)?; let title = map.get("title").and_then(any_as_string).map(str::to_string); let ref_type = map.get("type").and_then(any_as_string).map(str::to_string); let params = map.get("params").and_then(|value| match value { @@ -161,20 +143,12 @@ fn delta_to_inline_markdown(text: &Text, options: &DeltaToMdOptions) -> String { delta_to_markdown_with_options(&text.to_delta(), options, false) } -fn delta_to_markdown_with_options( - delta: &[TextDeltaOp], - options: &DeltaToMdOptions, - trailing_newline: bool, -) -> String { +fn delta_to_markdown_with_options(delta: &[TextDeltaOp], options: &DeltaToMdOptions, trailing_newline: bool) -> String { let ops = build_delta_ops(delta); delta_ops_to_markdown_with_options(&ops, options, trailing_newline) } -fn delta_ops_to_markdown_with_options( - ops: &[DeltaOp], - options: &DeltaToMdOptions, - trailing_newline: bool, -) -> String { +fn delta_ops_to_markdown_with_options(ops: &[DeltaOp], options: &DeltaToMdOptions, trailing_newline: bool) -> String { let root = convert_delta_ops(ops, options); let mut rendered = render_node(&root); rendered = rendered.trim_end().to_string(); @@ -235,10 +209,7 @@ fn delta_op_from_any(value: &Any) -> Option { _ => DeltaInsert::Embed(vec![insert_value.clone()]), }; - let attributes = map - .get("attributes") - .and_then(any_to_attributes) - .unwrap_or_default(); + let attributes = map.get("attributes").and_then(any_to_attributes).unwrap_or_default(); Some(DeltaOp { insert, attributes }) } @@ -260,10 +231,7 @@ fn delta_any_to_inline_markdown(value: &Any, options: &DeltaToMdOptions) -> Opti delta_ops_from_any(value).map(|ops| delta_ops_to_markdown_with_options(&ops, options, false)) } -pub(super) fn delta_value_to_inline_markdown( - value: &Value, - options: &DeltaToMdOptions, -) -> Option { +pub(super) fn delta_value_to_inline_markdown(value: &Value, options: &DeltaToMdOptions) -> Option { if let Some(text) = value.to_text() { return Some(delta_to_inline_markdown(&text, options)); } @@ -428,13 +396,7 @@ fn convert_delta_ops(ops: &[DeltaOp], options: &DeltaToMdOptions) -> Rc Option>> { +fn inline_node_for_attr(attr: &str, attrs: &TextAttributes, options: &DeltaToMdOptions) -> Option>> { match attr { "italic" => Some(Node::new_inline("_", "_")), "bold" => Some(Node::new_inline("**", "**")), @@ -544,13 +502,10 @@ fn inline_node_for_attr( .get(attr) .and_then(any_as_string) .map(|url| Node::new_inline("[", &format!("]({url})"))), - "reference" => attrs - .get(attr) - .and_then(parse_inline_reference) - .map(|reference| { - let (title, link) = options.build_reference_link(&reference); - Node::new_inline("[", &format!("{title}]({link})")) - }), + "reference" => attrs.get(attr).and_then(parse_inline_reference).map(|reference| { + let (title, link) = options.build_reference_link(&reference); + Node::new_inline("[", &format!("{title}]({link})")) + }), "strike" => Some(Node::new_inline("~~", "~~")), "code" => Some(Node::new_inline("`", "`")), _ => None, @@ -562,10 +517,7 @@ fn has_block_level_attribute(attrs: &TextAttributes) -> bool { } fn is_inline_attribute(attr: &str) -> bool { - matches!( - attr, - "italic" | "bold" | "link" | "reference" | "strike" | "code" - ) + matches!(attr, "italic" | "bold" | "link" | "reference" | "strike" | "code") } fn encode_link(link: &str) -> String { @@ -683,9 +635,7 @@ impl Node { fn append(parent: &Rc>, child: Rc>) { if let Some(old_parent) = child.borrow().parent.as_ref().and_then(|p| p.upgrade()) { let mut old_parent = old_parent.borrow_mut(); - old_parent - .children - .retain(|existing| !Rc::ptr_eq(existing, &child)); + old_parent.children.retain(|existing| !Rc::ptr_eq(existing, &child)); } child.borrow_mut().parent = Some(Rc::downgrade(parent)); diff --git a/packages/common/native/src/doc_parser/mod.rs b/packages/common/native/src/doc_parser/mod.rs index 66339e155a..a5e8761a3b 100644 --- a/packages/common/native/src/doc_parser/mod.rs +++ b/packages/common/native/src/doc_parser/mod.rs @@ -4,7 +4,6 @@ mod delta_markdown; mod value; pub use affine::{ - get_doc_ids_from_binary, parse_doc_from_binary, parse_doc_to_markdown, parse_page_doc, - parse_workspace_doc, BlockInfo, CrawlResult, MarkdownResult, PageDocContent, ParseError, - WorkspaceDocContent, + BlockInfo, CrawlResult, MarkdownResult, PageDocContent, ParseError, WorkspaceDocContent, get_doc_ids_from_binary, + parse_doc_from_binary, parse_doc_to_markdown, parse_page_doc, parse_workspace_doc, }; diff --git a/packages/common/native/src/hashcash.rs b/packages/common/native/src/hashcash.rs index acd4a0877b..a01c4ada8a 100644 --- a/packages/common/native/src/hashcash.rs +++ b/packages/common/native/src/hashcash.rs @@ -34,11 +34,7 @@ impl Stamp { } pub fn check>(&self, bits: u32, resource: S) -> bool { - if self.version == "1" - && bits <= self.claim - && self.check_expiration() - && self.resource == resource.as_ref() - { + if self.version == "1" && bits <= self.claim && self.check_expiration() && self.resource == resource.as_ref() { let hex_digits = ((self.claim as f32) / 4.).floor() as usize; // check challenge @@ -64,12 +60,7 @@ impl Stamp { let now = Utc::now(); let ts = now.format("%Y%m%d%H%M%S"); let bits = bits.unwrap_or(20); - let rand = String::from_iter( - Alphanumeric - .sample_iter(rng()) - .take(SALT_LENGTH) - .map(char::from), - ); + let rand = String::from_iter(Alphanumeric.sample_iter(rng()).take(SALT_LENGTH).map(char::from)); let challenge = format!("{}:{}:{}:{}:{}:{}", version, bits, ts, &resource, "", rand); Stamp { @@ -102,22 +93,12 @@ impl TryFrom<&str> for Stamp { fn try_from(value: &str) -> Result { let stamp_vec = value.split(':').collect::>(); - if stamp_vec.len() != 7 - || stamp_vec - .iter() - .enumerate() - .any(|(i, s)| i != 4 && s.is_empty()) - { - return Err(format!( - "Malformed stamp, expected 6 parts, got {}", - stamp_vec.len() - )); + if stamp_vec.len() != 7 || stamp_vec.iter().enumerate().any(|(i, s)| i != 4 && s.is_empty()) { + return Err(format!("Malformed stamp, expected 6 parts, got {}", stamp_vec.len())); } Ok(Stamp { version: stamp_vec[0].to_string(), - claim: stamp_vec[1] - .parse() - .map_err(|_| "Malformed stamp".to_string())?, + claim: stamp_vec[1].parse().map_err(|_| "Malformed stamp".to_string())?, ts: stamp_vec[2].to_string(), resource: stamp_vec[3].to_string(), ext: stamp_vec[4].to_string(), @@ -129,7 +110,7 @@ impl TryFrom<&str> for Stamp { #[cfg(test)] mod tests { - use rand::{distr::Alphanumeric, Rng}; + use rand::{Rng, distr::Alphanumeric}; use rayon::prelude::*; use super::Stamp; @@ -139,9 +120,7 @@ mod tests { { let response = Stamp::mint("test".into(), Some(20)).format(); assert!( - Stamp::try_from(response.as_str()) - .unwrap() - .check(20, "test"), + Stamp::try_from(response.as_str()).unwrap().check(20, "test"), "should pass" ); } @@ -149,18 +128,14 @@ mod tests { { let response = Stamp::mint("test".into(), Some(19)).format(); assert!( - !Stamp::try_from(response.as_str()) - .unwrap() - .check(20, "test"), + !Stamp::try_from(response.as_str()).unwrap().check(20, "test"), "should fail with lower bits" ); } { let response = Stamp::mint("test".into(), Some(20)).format(); assert!( - !Stamp::try_from(response.as_str()) - .unwrap() - .check(20, "test2"), + !Stamp::try_from(response.as_str()).unwrap().check(20, "test2"), "should fail with different resource" ); } @@ -177,10 +152,7 @@ mod tests { let response = Stamp::mint("test".into(), Some(20)); assert_eq!( response.format(), - format!( - "1:20:{}:test::{}:{}", - response.ts, response.rand, response.counter - ) + format!("1:20:{}:test::{}:{}", response.ts, response.rand, response.counter) ); } @@ -195,9 +167,7 @@ mod tests { .collect::(); let response = Stamp::mint(resource.clone(), Some(bit)).format(); assert!( - Stamp::try_from(response.as_str()) - .unwrap() - .check(bit, resource), + Stamp::try_from(response.as_str()).unwrap().check(bit, resource), "should pass" ); }); diff --git a/packages/common/y-octo/core/Cargo.toml b/packages/common/y-octo/core/Cargo.toml index 682ee019c1..d622aacbc7 100644 --- a/packages/common/y-octo/core/Cargo.toml +++ b/packages/common/y-octo/core/Cargo.toml @@ -6,15 +6,15 @@ authors = [ "Brooklyn ", ] description = "High-performance and thread-safe CRDT implementation compatible with Yjs" -edition = "2021" +edition = "2024" homepage = "https://github.com/toeverything/y-octo" -include = ["LICENSE", "README.md", "benches/**/*", "bin/**/*", "src/**/*"] -keywords = ["collaboration", "crdt", "crdts", "yata", "yjs"] +include = ["src/**/*", "benches/**/*", "bin/**/*", "LICENSE", "README.md"] +keywords = ["collaboration", "crdt", "crdts", "yjs", "yata"] license = "MIT" name = "y-octo" readme = "README.md" repository = "https://github.com/toeverything/y-octo" -version = "0.0.1" +version = "0.0.2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -36,9 +36,11 @@ thiserror = { workspace = true } [features] bench = [] debug = [] +default = [] events = [] large_refs = [] serde_json = [] +subscribe = [] [target.'cfg(fuzzing)'.dependencies] arbitrary = { workspace = true } diff --git a/packages/common/y-octo/core/benches/apply_benchmarks.rs b/packages/common/y-octo/core/benches/apply_benchmarks.rs index 2409aefb04..f922facb2d 100644 --- a/packages/common/y-octo/core/benches/apply_benchmarks.rs +++ b/packages/common/y-octo/core/benches/apply_benchmarks.rs @@ -2,7 +2,7 @@ mod utils; use std::time::Duration; -use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; +use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main}; use path_ext::PathExt; use utils::Files; diff --git a/packages/common/y-octo/core/benches/array_ops_benchmarks.rs b/packages/common/y-octo/core/benches/array_ops_benchmarks.rs index c931974e0d..1ac4a2c76f 100644 --- a/packages/common/y-octo/core/benches/array_ops_benchmarks.rs +++ b/packages/common/y-octo/core/benches/array_ops_benchmarks.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use criterion::{criterion_group, criterion_main, Criterion}; +use criterion::{Criterion, criterion_group, criterion_main}; use rand::{Rng, SeedableRng}; fn operations(c: &mut Criterion) { diff --git a/packages/common/y-octo/core/benches/codec_benchmarks.rs b/packages/common/y-octo/core/benches/codec_benchmarks.rs index c4dea3ec54..a305cbe55e 100644 --- a/packages/common/y-octo/core/benches/codec_benchmarks.rs +++ b/packages/common/y-octo/core/benches/codec_benchmarks.rs @@ -1,4 +1,4 @@ -use criterion::{criterion_group, criterion_main, Criterion, SamplingMode}; +use criterion::{Criterion, SamplingMode, criterion_group, criterion_main}; use y_octo::{read_var_i32, read_var_u64, write_var_i32, write_var_u64}; const BENCHMARK_SIZE: u32 = 100000; diff --git a/packages/common/y-octo/core/benches/map_ops_benchmarks.rs b/packages/common/y-octo/core/benches/map_ops_benchmarks.rs index 6451223769..4b87bbdd7b 100644 --- a/packages/common/y-octo/core/benches/map_ops_benchmarks.rs +++ b/packages/common/y-octo/core/benches/map_ops_benchmarks.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use criterion::{criterion_group, criterion_main, Criterion}; +use criterion::{Criterion, criterion_group, criterion_main}; fn operations(c: &mut Criterion) { let mut group = c.benchmark_group("ops/map"); diff --git a/packages/common/y-octo/core/benches/text_ops_benchmarks.rs b/packages/common/y-octo/core/benches/text_ops_benchmarks.rs index ed031d1b1a..9390f1ff51 100644 --- a/packages/common/y-octo/core/benches/text_ops_benchmarks.rs +++ b/packages/common/y-octo/core/benches/text_ops_benchmarks.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use criterion::{criterion_group, criterion_main, Criterion}; +use criterion::{Criterion, criterion_group, criterion_main}; use rand::{Rng, SeedableRng}; fn operations(c: &mut Criterion) { diff --git a/packages/common/y-octo/core/benches/update_benchmarks.rs b/packages/common/y-octo/core/benches/update_benchmarks.rs index e9820077c4..18062eb038 100644 --- a/packages/common/y-octo/core/benches/update_benchmarks.rs +++ b/packages/common/y-octo/core/benches/update_benchmarks.rs @@ -2,7 +2,7 @@ mod utils; use std::time::Duration; -use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; +use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main}; use path_ext::PathExt; use utils::Files; diff --git a/packages/common/y-octo/core/src/codec/buffer.rs b/packages/common/y-octo/core/src/codec/buffer.rs index ed993a3312..42c2b197d8 100644 --- a/packages/common/y-octo/core/src/codec/buffer.rs +++ b/packages/common/y-octo/core/src/codec/buffer.rs @@ -19,8 +19,8 @@ pub fn write_var_buffer(buffer: &mut W, data: &[u8]) -> Result<(), Err #[cfg(test)] mod tests { use nom::{ - error::{Error, ErrorKind}, AsBytes, Err, + error::{Error, ErrorKind}, }; use super::*; @@ -36,26 +36,17 @@ mod tests { // Test case 2: truncated input, missing buffer let input = [0x05, 0x01, 0x02, 0x03]; let result = read_var_buffer(&input); - assert_eq!( - result, - Err(Err::Error(Error::new(&input[1..], ErrorKind::Eof))) - ); + assert_eq!(result, Err(Err::Error(Error::new(&input[1..], ErrorKind::Eof)))); // Test case 3: invalid input let input = [0xFF, 0x01, 0x02, 0x03]; let result = read_var_buffer(&input); - assert_eq!( - result, - Err(Err::Error(Error::new(&input[2..], ErrorKind::Eof))) - ); + assert_eq!(result, Err(Err::Error(Error::new(&input[2..], ErrorKind::Eof)))); // Test case 4: invalid var int encoding let input = [0xFF, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01]; let result = read_var_buffer(&input); - assert_eq!( - result, - Err(Err::Error(Error::new(&input[7..], ErrorKind::Eof))) - ); + assert_eq!(result, Err(Err::Error(Error::new(&input[7..], ErrorKind::Eof)))); } #[test] @@ -66,7 +57,7 @@ mod tests { #[cfg(not(miri))] { - use rand::{rng, Rng}; + use rand::{Rng, rng}; let mut rng = rng(); for _ in 0..100 { test_var_buf_enc_dec(&{ diff --git a/packages/common/y-octo/core/src/codec/string.rs b/packages/common/y-octo/core/src/codec/string.rs index f150076049..710ee5abce 100644 --- a/packages/common/y-octo/core/src/codec/string.rs +++ b/packages/common/y-octo/core/src/codec/string.rs @@ -1,6 +1,6 @@ use std::io::{Error, Write}; -use nom::{combinator::map_res, Parser}; +use nom::{Parser, combinator::map_res}; use super::*; @@ -17,8 +17,8 @@ pub fn write_var_string>(buffer: &mut W, input: S) -> Re #[cfg(test)] mod tests { use nom::{ - error::{Error, ErrorKind}, AsBytes, Err, + error::{Error, ErrorKind}, }; use super::*; @@ -34,42 +34,27 @@ mod tests { // Test case 2: missing string length let input = [0x68, 0x65, 0x6C, 0x6C, 0x6F]; let result = read_var_string(&input); - assert_eq!( - result, - Err(Err::Error(Error::new(&input[1..], ErrorKind::Eof))) - ); + assert_eq!(result, Err(Err::Error(Error::new(&input[1..], ErrorKind::Eof)))); // Test case 3: truncated input let input = [0x05, 0x68, 0x65, 0x6C, 0x6C]; let result = read_var_string(&input); - assert_eq!( - result, - Err(Err::Error(Error::new(&input[1..], ErrorKind::Eof))) - ); + assert_eq!(result, Err(Err::Error(Error::new(&input[1..], ErrorKind::Eof)))); // Test case 4: invalid input let input = [0xFF, 0x01, 0x02, 0x03, 0x04]; let result = read_var_string(&input); - assert_eq!( - result, - Err(Err::Error(Error::new(&input[2..], ErrorKind::Eof))) - ); + assert_eq!(result, Err(Err::Error(Error::new(&input[2..], ErrorKind::Eof)))); // Test case 5: invalid var int encoding let input = [0xFF, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01]; let result = read_var_string(&input); - assert_eq!( - result, - Err(Err::Error(Error::new(&input[7..], ErrorKind::Eof))) - ); + assert_eq!(result, Err(Err::Error(Error::new(&input[7..], ErrorKind::Eof)))); // Test case 6: invalid input, invalid UTF-8 encoding let input = [0x05, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]; let result = read_var_string(&input); - assert_eq!( - result, - Err(Err::Error(Error::new(&input[..], ErrorKind::MapRes))) - ); + assert_eq!(result, Err(Err::Error(Error::new(&input[..], ErrorKind::MapRes)))); } #[test] diff --git a/packages/common/y-octo/core/src/doc/awareness.rs b/packages/common/y-octo/core/src/doc/awareness.rs index b4c7c024cd..64ebb93cb6 100644 --- a/packages/common/y-octo/core/src/doc/awareness.rs +++ b/packages/common/y-octo/core/src/doc/awareness.rs @@ -20,6 +20,10 @@ impl Awareness { } } + pub fn local_id(&self) -> u64 { + self.local_id + } + pub fn on_update(&mut self, f: impl Fn(&Awareness, AwarenessEvent) + Send + Sync + 'static) { self.callback = Some(Arc::new(f)); } @@ -29,10 +33,7 @@ impl Awareness { } pub fn get_local_state(&self) -> Option { - self - .awareness - .get(&self.local_id) - .map(|state| state.content.clone()) + self.awareness.get(&self.local_id).map(|state| state.content.clone()) } fn mut_local_state(&mut self) -> &mut AwarenessState { @@ -42,20 +43,14 @@ impl Awareness { pub fn set_local_state(&mut self, content: String) { self.mut_local_state().set_content(content); if let Some(cb) = self.callback.as_ref() { - cb( - self, - AwarenessEventBuilder::new().update(self.local_id).build(), - ); + cb(self, AwarenessEventBuilder::new().update(self.local_id).build()); } } pub fn clear_local_state(&mut self) { self.mut_local_state().delete(); if let Some(cb) = self.callback.as_ref() { - cb( - self, - AwarenessEventBuilder::new().remove(self.local_id).build(), - ); + cb(self, AwarenessEventBuilder::new().remove(self.local_id).build()); } } @@ -107,9 +102,7 @@ impl AwarenessEvent { pub fn get_updated(&self, states: &AwarenessStates) -> AwarenessStates { states .iter() - .filter(|(id, _)| { - self.added.contains(id) || self.updated.contains(id) || self.removed.contains(id) - }) + .filter(|(id, _)| self.added.contains(id) || self.updated.contains(id) || self.removed.contains(id)) .map(|(id, state)| (*id, state.clone())) .collect() } @@ -187,14 +180,8 @@ mod tests { assert!(awareness.get_states().contains_key(&1)); // local state will not apply - assert_eq!( - awareness.get_states().get(&0).unwrap().content, - "null".to_string() - ); - assert_eq!( - awareness.get_states().get(&1).unwrap().content, - "test1".to_string() - ); + assert_eq!(awareness.get_states().get(&0).unwrap().content, "null".to_string()); + assert_eq!(awareness.get_states().get(&1).unwrap().content, "test1".to_string()); } { diff --git a/packages/common/y-octo/core/src/doc/batch.rs b/packages/common/y-octo/core/src/doc/batch.rs new file mode 100644 index 0000000000..e2c7fd5b8f --- /dev/null +++ b/packages/common/y-octo/core/src/doc/batch.rs @@ -0,0 +1,119 @@ +use super::*; + +#[derive(Debug, PartialEq)] +pub struct Batch { + doc: Doc, + before_state: StateVector, + after_state: StateVector, + changed: HashMap>, +} + +impl Batch { + pub fn new(doc: Doc) -> Self { + let current_state = doc.get_state_vector(); + + Batch { + doc, + before_state: current_state.clone(), + after_state: current_state, + changed: HashMap::new(), + } + } + + pub fn with_batch(&mut self, f: F) -> T + where + F: FnOnce(Doc) -> T, + { + let ret = f(self.doc.clone()); + for (k, v) in self.doc.get_changed() { + self.changed.entry(k).or_default().extend(v.iter().cloned()); + } + ret + } +} + +pub fn batch_commit(mut doc: Doc, f: F) -> Option +where + F: FnOnce(Doc) -> T, +{ + // Initialize batch cleanups list + let mut batch_cleanups = vec![]; + + // Initial call and result initialization + let mut initial_call = false; + + { + if doc.batch.is_none() { + initial_call = true; + + // Start a new batch + let batch = Batch::new(doc.clone()); + doc.batch = Somr::new(batch); + batch_cleanups.push(doc.batch.clone()); + } + } + + let batch = doc.batch.get_mut()?; + let result = Some(batch.with_batch(f)); + + if initial_call + && let Some(current_batch) = doc.batch.get() + && Some(current_batch) == batch_cleanups[0].get() + { + // Process observer calls and perform cleanup if this is the initial call + cleanup_batches(&mut batch_cleanups); + doc.batch.swap_take(); + } + + result +} + +fn cleanup_batches(batch_cleanups: &mut Vec>) { + for batch in batch_cleanups.drain(..) { + if let Some(batch) = batch.get() { + println!("changed: {:?}", batch.changed); + } else { + panic!("Batch not initialized"); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn should_get_changed_items() { + loom_model!({ + let doc = DocOptions::new().with_client_id(1).build(); + + batch_commit(doc.clone(), |d| { + let mut arr = d.get_or_create_array("arr").unwrap(); + let mut text = d.create_text().unwrap(); + let mut map = d.create_map().unwrap(); + + batch_commit(doc.clone(), |_| { + arr.insert(0, Value::from(text.clone())).unwrap(); + arr.insert(1, Value::from(map.clone())).unwrap(); + }); + + batch_commit(doc.clone(), |_| { + text.insert(0, "hello world").unwrap(); + text.remove(5, 6).unwrap(); + }); + + batch_commit(doc.clone(), |_| { + map.insert("key".into(), 123).unwrap(); + }); + + batch_commit(doc.clone(), |_| { + map.remove("key"); + }); + + batch_commit(doc.clone(), |_| { + arr.remove(0, 1).unwrap(); + }); + }); + }); + } +} diff --git a/packages/common/y-octo/core/src/doc/codec/any.rs b/packages/common/y-octo/core/src/doc/codec/any.rs index 40f0c47671..6fd9ff73c9 100644 --- a/packages/common/y-octo/core/src/doc/codec/any.rs +++ b/packages/common/y-octo/core/src/doc/codec/any.rs @@ -40,10 +40,10 @@ impl CrdtRead for Any { 0 => Ok(Any::Undefined), 1 => Ok(Any::Null), // in yjs implementation, flag 2 only save 32bit integer - 2 => Ok(Any::Integer(reader.read_var_i32()?)), // Integer + 2 => Ok(Any::Integer(reader.read_var_i32()?)), // Integer 3 => Ok(Any::Float32(reader.read_f32_be()?.into())), // Float32 4 => Ok(Any::Float64(reader.read_f64_be()?.into())), // Float64 - 5 => Ok(Any::BigInt64(reader.read_i64_be()?)), // BigInt64 + 5 => Ok(Any::BigInt64(reader.read_i64_be()?)), // BigInt64 6 => Ok(Any::False), 7 => Ok(Any::True), 8 => Ok(Any::String(reader.read_var_string()?)), // String @@ -57,9 +57,7 @@ impl CrdtRead for Any { } // Object 10 => { let len = reader.read_var_u64()?; - let any = (0..len) - .map(|_| Self::read(reader)) - .collect::, _>>()?; + let any = (0..len).map(|_| Self::read(reader)).collect::, _>>()?; Ok(Any::Array(any)) } // Array @@ -250,11 +248,7 @@ impl From for Any { impl From for Any { fn from(value: bool) -> Self { - if value { - Self::True - } else { - Self::False - } + if value { Self::True } else { Self::False } } } @@ -344,11 +338,7 @@ impl From<&[u8]> for Any { // TODO: impl for Any::Undefined impl> From> for Any { fn from(value: Option) -> Self { - if let Some(val) = value { - val.into() - } else { - Any::Null - } + if let Some(val) = value { val.into() } else { Any::Null } } } @@ -374,12 +364,8 @@ impl From for Any { } } serde_json::Value::String(s) => Self::String(s), - serde_json::Value::Array(vec) => { - Self::Array(vec.into_iter().map(|v| v.into()).collect::>()) - } - serde_json::Value::Object(obj) => { - Self::Object(obj.into_iter().map(|(k, v)| (k, v.into())).collect()) - } + serde_json::Value::Array(vec) => Self::Array(vec.into_iter().map(|v| v.into()).collect::>()), + serde_json::Value::Object(obj) => Self::Object(obj.into_iter().map(|(k, v)| (k, v.into())).collect()), } } } @@ -587,10 +573,7 @@ mod tests { Any::Object( vec![ ("type".to_string(), Any::String("Email".to_string())), - ( - "address".to_string(), - Any::String("alice@example.com".to_string()), - ), + ("address".to_string(), Any::String("alice@example.com".to_string())), ] .into_iter() .collect(), @@ -698,19 +681,11 @@ mod tests { ); assert_eq!( - vec![("key".to_string(), 10u64.into())] - .into_iter() - .collect::(), - Any::Object(HashMap::from_iter(vec![( - "key".to_string(), - Any::Integer(10) - )])) + vec![("key".to_string(), 10u64.into())].into_iter().collect::(), + Any::Object(HashMap::from_iter(vec![("key".to_string(), Any::Integer(10))])) ); let any: Any = 10u64.into(); - assert_eq!( - [any].iter().collect::(), - Any::Array(vec![Any::Integer(10)]) - ); + assert_eq!([any].iter().collect::(), Any::Array(vec![Any::Integer(10)])); } } diff --git a/packages/common/y-octo/core/src/doc/codec/content.rs b/packages/common/y-octo/core/src/doc/codec/content.rs index 2dec2203cb..a5bd7c96e1 100644 --- a/packages/common/y-octo/core/src/doc/codec/content.rs +++ b/packages/common/y-octo/core/src/doc/codec/content.rs @@ -90,16 +90,9 @@ impl std::fmt::Debug for Content { .field("key", key) .field("value", value) .finish(), - Self::Type(arg0) => f - .debug_tuple("Type") - .field(&arg0.ty().unwrap().kind()) - .finish(), + Self::Type(arg0) => f.debug_tuple("Type").field(&arg0.ty().unwrap().kind()).finish(), Self::Any(arg0) => f.debug_tuple("Any").field(arg0).finish(), - Self::Doc { guid, opts } => f - .debug_struct("Doc") - .field("guid", guid) - .field("opts", opts) - .finish(), + Self::Doc { guid, opts } => f.debug_struct("Doc").field("guid", guid).field("opts", opts).finish(), } } } @@ -111,11 +104,7 @@ impl Content { 2 => { let len = decoder.read_var_u64()?; let strings = (0..len) - .map(|_| { - decoder - .read_var_string() - .map(|s| (s != "undefined").then_some(s)) - }) + .map(|_| decoder.read_var_string().map(|s| (s != "undefined").then_some(s))) .collect::, _>>()?; Ok(Self::Json(strings)) @@ -124,16 +113,14 @@ impl Content { 4 => Ok(Self::String(decoder.read_var_string()?)), // String 5 => { let string = decoder.read_var_string()?; - let json = - serde_json::from_str(&string).map_err(|_| JwstCodecError::DamagedDocumentJson)?; + let json = serde_json::from_str(&string).map_err(|_| JwstCodecError::DamagedDocumentJson)?; Ok(Self::Embed(json)) } // Embed 6 => { let key = decoder.read_var_string()?; let value = decoder.read_var_string()?; - let value = - serde_json::from_str(&value).map_err(|_| JwstCodecError::DamagedDocumentJson)?; + let value = serde_json::from_str(&value).map_err(|_| JwstCodecError::DamagedDocumentJson)?; Ok(Self::Format { key, value }) } // Format @@ -199,15 +186,11 @@ impl Content { encoder.write_var_string(string)?; } Self::Embed(val) => { - encoder.write_var_string( - serde_json::to_string(val).map_err(|_| JwstCodecError::DamagedDocumentJson)?, - )?; + encoder.write_var_string(serde_json::to_string(val).map_err(|_| JwstCodecError::DamagedDocumentJson)?)?; } Self::Format { key, value } => { encoder.write_var_string(key)?; - encoder.write_var_string( - serde_json::to_string(value).map_err(|_| JwstCodecError::DamagedDocumentJson)?, - )?; + encoder.write_var_string(serde_json::to_string(value).map_err(|_| JwstCodecError::DamagedDocumentJson)?)?; } Self::Type(ty) => { if let Some(ty) = ty.ty() { @@ -237,9 +220,7 @@ impl Content { // TODO: need a custom wrapper with length cached, this cost too much Self::String(string) => string.chars().map(|c| c.len_utf16()).sum::() as u64, Self::Any(any) => any.len() as u64, - Self::Binary(_) | Self::Embed(_) | Self::Format { .. } | Self::Type(_) | Self::Doc { .. } => { - 1 - } + Self::Binary(_) | Self::Embed(_) | Self::Format { .. } | Self::Type(_) | Self::Doc { .. } => 1, } } @@ -249,20 +230,14 @@ impl Content { #[allow(dead_code)] pub fn splittable(&self) -> bool { - matches!( - self, - Self::String { .. } | Self::Any { .. } | Self::Json { .. } - ) + matches!(self, Self::String { .. } | Self::Any { .. } | Self::Json { .. }) } pub fn split(&self, diff: u64) -> JwstCodecResult<(Self, Self)> { match self { Self::String(str) => { let (left, right) = Self::split_as_utf16_str(str.as_str(), diff); - Ok(( - Self::String(left.to_string()), - Self::String(right.to_string()), - )) + Ok((Self::String(left.to_string()), Self::String(right.to_string()))) } Self::Json(vec) => { let (left, right) = vec.split_at(diff as usize); @@ -321,11 +296,7 @@ mod tests { loom_model!({ let contents = [ Content::Deleted(42), - Content::Json(vec![ - None, - Some("test_1".to_string()), - Some("test_2".to_string()), - ]), + Content::Json(vec![None, Some("test_1".to_string()), Some("test_2".to_string())]), Content::Binary(vec![1, 2, 3]), Content::String("hello".to_string()), Content::Embed(Any::True), @@ -336,10 +307,7 @@ mod tests { Content::Type(YTypeRef::new(YTypeKind::Array, None)), Content::Type(YTypeRef::new(YTypeKind::Map, None)), Content::Type(YTypeRef::new(YTypeKind::Text, None)), - Content::Type(YTypeRef::new( - YTypeKind::XMLElement, - Some("test".to_string()), - )), + Content::Type(YTypeRef::new(YTypeKind::XMLElement, Some("test".to_string()))), Content::Type(YTypeRef::new(YTypeKind::XMLFragment, None)), Content::Type(YTypeRef::new(YTypeKind::XMLHook, Some("test".to_string()))), Content::Type(YTypeRef::new(YTypeKind::XMLText, None)), @@ -360,11 +328,7 @@ mod tests { fn test_content_split() { let contents = [ Content::String("hello".to_string()), - Content::Json(vec![ - None, - Some("test_1".to_string()), - Some("test_2".to_string()), - ]), + Content::Json(vec![None, Some("test_1".to_string()), Some("test_2".to_string())]), Content::Any(vec![Any::BigInt64(42), Any::String("Test Any".to_string())]), Content::Binary(vec![]), ]; @@ -390,18 +354,12 @@ mod tests { let (left, right) = contents[2].split(1).unwrap(); assert!(contents[2].splittable()); assert_eq!(left, Content::Any(vec![Any::BigInt64(42)])); - assert_eq!( - right, - Content::Any(vec![Any::String("Test Any".to_string())]) - ); + assert_eq!(right, Content::Any(vec![Any::String("Test Any".to_string())])); } { assert!(!contents[3].splittable()); - assert_eq!( - contents[3].split(2), - Err(JwstCodecError::ContentSplitNotSupport(2)) - ); + assert_eq!(contents[3].split(2), Err(JwstCodecError::ContentSplitNotSupport(2))); } } diff --git a/packages/common/y-octo/core/src/doc/codec/delete_set.rs b/packages/common/y-octo/core/src/doc/codec/delete_set.rs index f3f3324570..5de3ee8b34 100644 --- a/packages/common/y-octo/core/src/doc/codec/delete_set.rs +++ b/packages/common/y-octo/core/src/doc/codec/delete_set.rs @@ -1,5 +1,5 @@ use std::{ - collections::{hash_map::Entry, VecDeque}, + collections::{VecDeque, hash_map::Entry}, ops::{Deref, DerefMut, Range}, }; @@ -188,10 +188,7 @@ mod tests { { let mut delete_set = delete_set; delete_set.add(1, 5, 10); - assert_eq!( - delete_set.get(&1), - Some(&OrderRange::from(vec![0..15, 20..30])) - ); + assert_eq!(delete_set.get(&1), Some(&OrderRange::from(vec![0..15, 20..30]))); } } @@ -213,10 +210,7 @@ mod tests { { let mut delete_set = delete_set; delete_set.batch_add_ranges(1, vec![40..50, 10..20]); - assert_eq!( - delete_set.get(&1), - Some(&OrderRange::from(vec![0..30, 40..50])) - ); + assert_eq!(delete_set.get(&1), Some(&OrderRange::from(vec![0..30, 40..50]))); } } diff --git a/packages/common/y-octo/core/src/doc/codec/io/codec_v1.rs b/packages/common/y-octo/core/src/doc/codec/io/codec_v1.rs index 726def1be5..abcb8cad37 100644 --- a/packages/common/y-octo/core/src/doc/codec/io/codec_v1.rs +++ b/packages/common/y-octo/core/src/doc/codec/io/codec_v1.rs @@ -48,11 +48,7 @@ impl<'b> RawDecoder<'b> { let pos = self.buffer.position() as usize; let buf = self.buffer.into_inner(); - if pos == 0 { - buf - } else { - &buf[pos..] - } + if pos == 0 { buf } else { &buf[pos..] } } } @@ -88,24 +84,15 @@ impl CrdtReader for RawDecoder<'_> { } fn read_f32_be(&mut self) -> JwstCodecResult { - self - .buffer - .read_f32::() - .map_err(reader::map_read_error) + self.buffer.read_f32::().map_err(reader::map_read_error) } fn read_f64_be(&mut self) -> JwstCodecResult { - self - .buffer - .read_f64::() - .map_err(reader::map_read_error) + self.buffer.read_f64::().map_err(reader::map_read_error) } fn read_i64_be(&mut self) -> JwstCodecResult { - self - .buffer - .read_i64::() - .map_err(reader::map_read_error) + self.buffer.read_i64::().map_err(reader::map_read_error) } #[inline(always)] @@ -151,22 +138,13 @@ impl CrdtWriter for RawEncoder { Ok(()) } fn write_f32_be(&mut self, num: f32) -> JwstCodecResult { - self - .buffer - .write_f32::(num) - .map_err(writer::map_write_error) + self.buffer.write_f32::(num).map_err(writer::map_write_error) } fn write_f64_be(&mut self, num: f64) -> JwstCodecResult { - self - .buffer - .write_f64::(num) - .map_err(writer::map_write_error) + self.buffer.write_f64::(num).map_err(writer::map_write_error) } fn write_i64_be(&mut self, num: i64) -> JwstCodecResult { - self - .buffer - .write_i64::(num) - .map_err(writer::map_write_error) + self.buffer.write_i64::(num).map_err(writer::map_write_error) } #[inline(always)] @@ -197,10 +175,7 @@ mod tests { let mut reader = RawDecoder::new(&[0x5, b'h', b'e', b'l', b'l', b'o']); assert_eq!(reader.clone().read_var_string().unwrap(), "hello"); - assert_eq!( - reader.clone().read_var_buffer().unwrap().as_slice(), - b"hello" - ); + assert_eq!(reader.clone().read_var_buffer().unwrap().as_slice(), b"hello"); assert_eq!(reader.read_u8().unwrap(), 5); assert_eq!(reader.read_u8().unwrap(), b'h'); diff --git a/packages/common/y-octo/core/src/doc/codec/item.rs b/packages/common/y-octo/core/src/doc/codec/item.rs index 90b91297d5..260a77e86b 100644 --- a/packages/common/y-octo/core/src/doc/codec/item.rs +++ b/packages/common/y-octo/core/src/doc/codec/item.rs @@ -232,12 +232,7 @@ impl Item { !has_id && self.parent.is_some() || has_id && self.parent.is_none() && self.parent_sub.is_none() } - pub fn read( - decoder: &mut R, - id: Id, - info: u8, - first_5_bit: u8, - ) -> JwstCodecResult { + pub fn read(decoder: &mut R, id: Id, info: u8, first_5_bit: u8) -> JwstCodecResult { let flags: ItemFlag = info.into(); let has_left_id = flags.check(item_flags::ITEM_HAS_LEFT_ID); let has_right_id = flags.check(item_flags::ITEM_HAS_RIGHT_ID); @@ -350,6 +345,22 @@ impl Item { Ok(()) } + + pub fn deep_compare(&self, other: &Self) -> bool { + if self.id != other.id + || self.deleted() != other.deleted() + || self.len() != other.len() + || self.left.get().map(|l| l.last_id()) != other.left.get().map(|l| l.last_id()) + || self.right.get().map(|r| r.id) != other.right.get().map(|r| r.id) + || self.origin_left_id != other.origin_left_id + || self.origin_right_id != other.origin_right_id + || self.parent_sub != other.parent_sub + { + return false; + } + + true + } } #[allow(dead_code)] diff --git a/packages/common/y-octo/core/src/doc/codec/mod.rs b/packages/common/y-octo/core/src/doc/codec/mod.rs index 8c08854018..c38638ad4f 100644 --- a/packages/common/y-octo/core/src/doc/codec/mod.rs +++ b/packages/common/y-octo/core/src/doc/codec/mod.rs @@ -16,7 +16,7 @@ pub use delete_set::DeleteSet; pub use id::{Client, Clock, Id}; pub use io::{CrdtRead, CrdtReader, CrdtWrite, CrdtWriter, RawDecoder, RawEncoder}; pub(crate) use item::{Item, ItemRef, Parent}; -pub(crate) use item_flag::{item_flags, ItemFlag}; +pub(crate) use item_flag::{ItemFlag, item_flags}; pub(crate) use refs::Node; pub use update::Update; #[cfg(test)] diff --git a/packages/common/y-octo/core/src/doc/codec/refs.rs b/packages/common/y-octo/core/src/doc/codec/refs.rs index c9196fada8..c15b6b4c96 100644 --- a/packages/common/y-octo/core/src/doc/codec/refs.rs +++ b/packages/common/y-octo/core/src/doc/codec/refs.rs @@ -79,10 +79,10 @@ impl Node { _ => { let item = Somr::new(Item::read(decoder, id, info, first_5_bit)?); - if let Content::Type(ty) = &item.get().unwrap().content { - if let Some(mut ty) = ty.ty_mut() { - ty.item = item.clone(); - } + if let Content::Type(ty) = &item.get().unwrap().content + && let Some(mut ty) = ty.ty_mut() + { + ty.item = item.clone(); } Ok(Node::Item(item)) @@ -282,8 +282,7 @@ impl Node { l.extend(r.drain(0..)); } (Content::String(l), Content::String(r)) => { - let allow_merge_string = - matches!(parent_kind, Some(YTypeKind::Text | YTypeKind::XMLText)); + let allow_merge_string = matches!(parent_kind, Some(YTypeKind::Text | YTypeKind::XMLText)); if !allow_merge_string { return false; @@ -299,12 +298,11 @@ impl Node { } } - if let Some(Parent::Type(p)) = &litem.parent { - if let Some(parent) = p.ty_mut() { - if let Some(markers) = &parent.markers { - markers.replace_marker(rref.clone(), lref.clone(), -(llen as i64)); - } - } + if let Some(Parent::Type(p)) = &litem.parent + && let Some(parent) = p.ty_mut() + && let Some(markers) = &parent.markers + { + markers.replace_marker(rref.clone(), lref.clone(), -(llen as i64)); } if ritem.keep() { @@ -455,15 +453,15 @@ mod tests { #[cfg(not(loom))] fn struct_info_round_trip(info: &mut Node) -> JwstCodecResult { - if let Node::Item(item) = info { - if let Some(item) = item.get_mut() { - if !item.is_valid() { - return Ok(()); - } + if let Node::Item(item) = info + && let Some(item) = item.get_mut() + { + if !item.is_valid() { + return Ok(()); + } - if item.content.countable() { - item.flags.set_countable(); - } + if item.content.countable() { + item.flags.set_countable(); } } let mut encoder = RawEncoder::default(); diff --git a/packages/common/y-octo/core/src/doc/codec/update.rs b/packages/common/y-octo/core/src/doc/codec/update.rs index abfe71c719..8171139472 100644 --- a/packages/common/y-octo/core/src/doc/codec/update.rs +++ b/packages/common/y-octo/core/src/doc/codec/update.rs @@ -47,9 +47,7 @@ impl CrdtRead for Update { let delete_set = DeleteSet::read(decoder)?; if !decoder.is_empty() { - return Err(JwstCodecError::UpdateNotFullyConsumed( - decoder.len() as usize - )); + return Err(JwstCodecError::UpdateNotFullyConsumed(decoder.len() as usize)); } Ok(Update { @@ -282,23 +280,24 @@ impl<'a> UpdateIterator<'a> { fn get_missing_dep(&self, struct_info: &Node) -> Option { if let Some(item) = struct_info.as_item().get() { let id = item.id; - if let Some(left) = &item.origin_left_id { - if left.client != id.client && left.clock >= self.state.get(&left.client) { - return Some(left.client); - } + if let Some(left) = &item.origin_left_id + && left.client != id.client + && left.clock >= self.state.get(&left.client) + { + return Some(left.client); } - if let Some(right) = &item.origin_right_id { - if right.client != id.client && right.clock >= self.state.get(&right.client) { - return Some(right.client); - } + if let Some(right) = &item.origin_right_id + && right.client != id.client + && right.clock >= self.state.get(&right.client) + { + return Some(right.client); } if let Some(parent) = &item.parent { match parent { Parent::Id(parent_id) - if parent_id.client != id.client - && parent_id.clock >= self.state.get(&parent_id.client) => + if parent_id.client != id.client && parent_id.clock >= self.state.get(&parent_id.client) => { return Some(parent_id.client); } @@ -319,15 +318,7 @@ impl<'a> UpdateIterator<'a> { // Safety: // client index of updates and update length are both checked in next_client // safe to use unwrap - cur.replace( - self - .update - .structs - .get_mut(&client) - .unwrap() - .pop_front() - .unwrap(), - ); + cur.replace(self.update.structs.get_mut(&client).unwrap().pop_front().unwrap()); } cur @@ -437,10 +428,7 @@ impl Iterator for DeleteSetIterator<'_> { return Some((client, range)); } else { // all state missing - self - .update - .pending_delete_set - .add(client, start, end - start); + self.update.pending_delete_set.add(client, start, end - start); } } @@ -478,17 +466,9 @@ mod tests { fn test_parse_doc() { let docs = [ (include_bytes!("../../fixtures/basic.bin").to_vec(), 1, 188), - ( - include_bytes!("../../fixtures/database.bin").to_vec(), - 1, - 149, - ), + (include_bytes!("../../fixtures/database.bin").to_vec(), 1, 149), (include_bytes!("../../fixtures/large.bin").to_vec(), 1, 9036), - ( - include_bytes!("../../fixtures/with-subdoc.bin").to_vec(), - 2, - 30, - ), + (include_bytes!("../../fixtures/with-subdoc.bin").to_vec(), 2, 30), ( include_bytes!("../../fixtures/edge-case-left-right-same-node.bin").to_vec(), 2, @@ -500,10 +480,7 @@ mod tests { let update = parse_doc_update(doc).unwrap(); assert_eq!(update.structs.len(), clients); - assert_eq!( - update.structs.iter().map(|s| s.1.len()).sum::(), - structs - ); + assert_eq!(update.structs.iter().map(|s| s.1.len()).sum::(), structs); } } @@ -526,9 +503,7 @@ mod tests { #[ignore = "just for local data test"] #[test] fn test_parse_local_doc() { - let json = - serde_json::from_slice::>(include_bytes!("../../fixtures/local_docs.json")) - .unwrap(); + let json = serde_json::from_slice::>(include_bytes!("../../fixtures/local_docs.json")).unwrap(); for ws in json { let data = &ws.blob[5..=(ws.blob.len() - 2)]; @@ -609,13 +584,7 @@ mod tests { assert_eq!(iter.next(), None); assert!(!update.pending_structs.is_empty()); assert_eq!( - update - .pending_structs - .get_mut(&0) - .unwrap() - .pop_front() - .unwrap() - .id(), + update.pending_structs.get_mut(&0).unwrap().pop_front().unwrap().id(), (0, 4).into() ); assert!(!update.missing_state.is_empty()); diff --git a/packages/common/y-octo/core/src/doc/codec/utils/items.rs b/packages/common/y-octo/core/src/doc/codec/utils/items.rs index 1dd1f0113f..e765d7b822 100644 --- a/packages/common/y-octo/core/src/doc/codec/utils/items.rs +++ b/packages/common/y-octo/core/src/doc/codec/utils/items.rs @@ -7,9 +7,7 @@ pub(crate) struct ItemBuilder { #[allow(dead_code)] impl ItemBuilder { pub fn new() -> ItemBuilder { - Self { - item: Item::default(), - } + Self { item: Item::default() } } pub fn id(mut self, id: Id) -> ItemBuilder { @@ -93,10 +91,7 @@ mod tests { assert_eq!(item.origin_right_id, Some(Id::new(4, 5))); assert!(matches!(item.parent, Some(Parent::String(text)) if text == "test")); assert_eq!(item.parent_sub, None); - assert_eq!( - item.content, - Content::Any(vec![Any::String("Hello".into())]) - ); + assert_eq!(item.content, Content::Any(vec![Any::String("Hello".into())])); }); } } diff --git a/packages/common/y-octo/core/src/doc/common/range.rs b/packages/common/y-octo/core/src/doc/common/range.rs index ec822d0909..59106baa3c 100644 --- a/packages/common/y-octo/core/src/doc/common/range.rs +++ b/packages/common/y-octo/core/src/doc/common/range.rs @@ -73,10 +73,10 @@ impl OrderRange { } } next_old = old_iter.next(); - if let Some(next_old) = &next_old { - if next_old.start > new_range.end { - continue; - } + if let Some(next_old) = &next_old + && next_old.start > new_range.end + { + continue; } } next_new = new_iter.next(); @@ -184,10 +184,10 @@ impl OrderRange { } fn make_single(&mut self) { - if let OrderRange::Fragment(ranges) = self { - if ranges.len() == 1 { - *self = OrderRange::Range(ranges[0].clone()); - } + if let OrderRange::Fragment(ranges) = self + && ranges.len() == 1 + { + *self = OrderRange::Range(ranges[0].clone()); } } @@ -278,10 +278,7 @@ impl<'a> IntoIterator for &'a OrderRange { type IntoIter = OrderRangeIter<'a>; fn into_iter(self) -> Self::IntoIter { - OrderRangeIter { - range: self, - idx: 0, - } + OrderRangeIter { range: self, idx: 0 } } } @@ -394,18 +391,9 @@ mod tests { assert!(OrderRange::check_range_covered(&[0..1], &[0..3])); assert!(OrderRange::check_range_covered(&[1..2], &[0..3])); assert!(OrderRange::check_range_covered(&[1..2, 2..3], &[0..3])); - assert!(!OrderRange::check_range_covered( - &[1..2, 2..3, 3..4], - &[0..3] - )); - assert!(OrderRange::check_range_covered( - &[0..1, 2..3], - &[0..2, 2..4] - )); - assert!(OrderRange::check_range_covered( - &[0..1, 2..3, 3..4], - &[0..2, 2..4] - ),); + assert!(!OrderRange::check_range_covered(&[1..2, 2..3, 3..4], &[0..3])); + assert!(OrderRange::check_range_covered(&[0..1, 2..3], &[0..2, 2..4])); + assert!(OrderRange::check_range_covered(&[0..1, 2..3, 3..4], &[0..2, 2..4]),); } #[test] @@ -469,10 +457,7 @@ mod tests { fn iter() { let range: OrderRange = vec![(0..10), (20..30)].into(); - assert_eq!( - range.into_iter().collect::>(), - vec![(0..10), (20..30)] - ); + assert_eq!(range.into_iter().collect::>(), vec![(0..10), (20..30)]); let range: OrderRange = OrderRange::Range(0..10); diff --git a/packages/common/y-octo/core/src/doc/common/somr.rs b/packages/common/y-octo/core/src/doc/common/somr.rs index ed7781aa50..9b22d60650 100644 --- a/packages/common/y-octo/core/src/doc/common/somr.rs +++ b/packages/common/y-octo/core/src/doc/common/somr.rs @@ -289,8 +289,7 @@ impl FlattenGet for Option> { impl PartialEq for Somr { fn eq(&self, other: &Self) -> bool { - self.ptr() == other.ptr() - || !self.dangling() && !other.dangling() && self.inner() == other.inner() + self.ptr() == other.ptr() || !self.dangling() && !other.dangling() && self.inner() == other.inner() } } @@ -385,10 +384,7 @@ mod tests { let five_ref = five.clone(); assert!(!five_ref.is_owned()); assert_eq!(five_ref.get(), Some(&5)); - assert_eq!( - five_ref.ptr().as_ptr() as usize, - five.ptr().as_ptr() as usize - ); + assert_eq!(five_ref.ptr().as_ptr() as usize, five.ptr().as_ptr() as usize); drop(five); // owner released diff --git a/packages/common/y-octo/core/src/doc/common/state.rs b/packages/common/y-octo/core/src/doc/common/state.rs index b7e7b54b05..c113648765 100644 --- a/packages/common/y-octo/core/src/doc/common/state.rs +++ b/packages/common/y-octo/core/src/doc/common/state.rs @@ -1,8 +1,8 @@ use std::ops::{Deref, DerefMut}; use super::{ - Client, ClientMap, Clock, CrdtRead, CrdtReader, CrdtWrite, CrdtWriter, HashMapExt, Id, - JwstCodecResult, HASHMAP_SAFE_CAPACITY, + Client, ClientMap, Clock, CrdtRead, CrdtReader, CrdtWrite, CrdtWriter, HASHMAP_SAFE_CAPACITY, HashMapExt, Id, + JwstCodecResult, }; #[derive(Default, Debug, PartialEq, Clone)] diff --git a/packages/common/y-octo/core/src/doc/document.rs b/packages/common/y-octo/core/src/doc/document.rs index f1d86713b9..baa6c0b0f9 100644 --- a/packages/common/y-octo/core/src/doc/document.rs +++ b/packages/common/y-octo/core/src/doc/document.rs @@ -1,7 +1,11 @@ #[cfg(feature = "events")] use publisher::DocPublisher; -use super::{history::StoreHistory, store::StoreRef, *}; +use super::{ + history::StoreHistory, + store::{ChangedTypeRefs, StoreRef}, + *, +}; use crate::sync::{Arc, RwLock}; #[cfg(feature = "debug")] @@ -43,24 +47,6 @@ impl Default for DocOptions { gc: true, } } else { - /// It tends to generate small numbers. - /// Since the client id will be included in all crdt items, the - /// small client helps to reduce the binary size. - /// - /// NOTE: The probability of 36% of the random number generated by - /// this function is greater than [u32::MAX] - fn prefer_small_random() -> u64 { - use rand::{distr::Distribution, rng}; - use rand_distr::Exp; - - let scale_factor = u16::MAX as f64; - let v: f64 = Exp::new(1.0 / scale_factor) - .map(|exp| exp.sample(&mut rng())) - .unwrap_or_else(|_| rand::random()); - - (v * scale_factor) as u64 - } - Self { client_id: prefer_small_random(), guid: nanoid::nanoid!(), @@ -138,6 +124,7 @@ pub struct Doc { pub(crate) store: StoreRef, #[cfg(feature = "events")] pub publisher: Arc, + pub(crate) batch: Somr, } unsafe impl Send for Doc {} @@ -171,6 +158,7 @@ impl Doc { store, #[cfg(feature = "events")] publisher, + batch: Somr::none(), } } @@ -182,6 +170,14 @@ impl Doc { self.client_id } + pub fn set_client(&mut self, client_id: u64) { + self.client_id = client_id; + } + + pub fn renew_client(&mut self) { + self.client_id = prefer_small_random(); + } + pub fn clients(&self) -> Vec { self.store.read().unwrap().clients() } @@ -205,6 +201,17 @@ impl Doc { } } + pub(crate) fn get_changed(&self) -> ChangedTypeRefs { + self.store.write().unwrap().get_changed() + } + + pub fn store_compare(&self, other: &Doc) -> bool { + let store = self.store.read().unwrap(); + let other_store = other.store.read().unwrap(); + + store.deep_compare(&other_store) + } + pub fn options(&self) -> &DocOptions { &self.opts } @@ -220,10 +227,7 @@ impl Doc { Self::try_from_binary_v1_with_options(binary, DocOptions::default()) } - pub fn try_from_binary_v1_with_options>( - binary: T, - options: DocOptions, - ) -> JwstCodecResult { + pub fn try_from_binary_v1_with_options>(binary: T, options: DocOptions) -> JwstCodecResult { let mut doc = Doc::with_options(options); doc.apply_update_from_binary_v1(binary)?; Ok(doc) @@ -316,9 +320,7 @@ impl Doc { } pub fn create_text(&self) -> JwstCodecResult { - YTypeBuilder::new(self.store.clone()) - .with_kind(YTypeKind::Text) - .build() + YTypeBuilder::new(self.store.clone()).with_kind(YTypeKind::Text).build() } pub fn get_or_create_array>(&self, str: S) -> JwstCodecResult { @@ -342,9 +344,7 @@ impl Doc { } pub fn create_map(&self) -> JwstCodecResult { - YTypeBuilder::new(self.store.clone()) - .with_kind(YTypeKind::Map) - .build() + YTypeBuilder::new(self.store.clone()).with_kind(YTypeKind::Map).build() } pub fn get_map(&self, str: &str) -> JwstCodecResult { @@ -378,6 +378,10 @@ impl Doc { self.store.read().unwrap().get_state_vector() } + pub fn get_delete_sets(&self) -> DeleteSet { + self.store.read().unwrap().get_delete_sets() + } + #[cfg(feature = "events")] pub fn subscribe(&self, cb: impl Fn(&[u8], &[History]) + Sync + Send + 'static) { self.publisher.subscribe(cb); @@ -393,6 +397,11 @@ impl Doc { self.publisher.count() } + #[cfg(feature = "events")] + pub fn subscriber_count(&self) -> usize { + Arc::::strong_count(&self.publisher) + } + pub fn gc(&self) -> JwstCodecResult<()> { self.store.write().unwrap().optimize() } @@ -400,7 +409,7 @@ impl Doc { #[cfg(test)] mod tests { - use yrs::{types::ToJson, updates::decoder::Decode, Array, Map, Options, Transact}; + use yrs::{Array, Map, Options, Transact, types::ToJson, updates::decoder::Decode}; use super::*; @@ -443,23 +452,14 @@ mod tests { let mut doc = Doc::try_from_binary_v1(binary).unwrap(); let mut doc_new = Doc::try_from_binary_v1(binary_new).unwrap(); - let diff_update = doc_new - .encode_state_as_update_v1(&doc.get_state_vector()) - .unwrap(); + let diff_update = doc_new.encode_state_as_update_v1(&doc.get_state_vector()).unwrap(); - let diff_update_reverse = doc - .encode_state_as_update_v1(&doc_new.get_state_vector()) - .unwrap(); + let diff_update_reverse = doc.encode_state_as_update_v1(&doc_new.get_state_vector()).unwrap(); doc.apply_update_from_binary_v1(diff_update).unwrap(); - doc_new - .apply_update_from_binary_v1(diff_update_reverse) - .unwrap(); + doc_new.apply_update_from_binary_v1(diff_update_reverse).unwrap(); - assert_eq!( - doc.encode_update_v1().unwrap(), - doc_new.encode_update_v1().unwrap() - ); + assert_eq!(doc.encode_update_v1().unwrap(), doc_new.encode_update_v1().unwrap()); }); } @@ -491,43 +491,43 @@ mod tests { assert_json_diff::assert_json_matches!(array.to_json(&doc.transact()), json, config); }; - let binary = { - let doc = Doc::new(); - let mut array = doc.get_or_create_array("abc").unwrap(); - array.insert(0, 42).unwrap(); - array.insert(1, -42).unwrap(); - array.insert(2, true).unwrap(); - array.insert(3, false).unwrap(); - array.insert(4, "hello").unwrap(); - array.insert(5, "world").unwrap(); + { + let binary = { + let doc = Doc::new(); + let mut array = doc.get_or_create_array("abc").unwrap(); + array.insert(0, 42).unwrap(); + array.insert(1, -42).unwrap(); + array.insert(2, true).unwrap(); + array.insert(3, false).unwrap(); + array.insert(4, "hello").unwrap(); + array.insert(5, "world").unwrap(); - let mut sub_array = doc.create_array().unwrap(); - array.insert(6, sub_array.clone()).unwrap(); - // FIXME: array need insert first to compatible with yrs - sub_array.insert(0, 1).unwrap(); + let mut sub_array = doc.create_array().unwrap(); + array.insert(6, sub_array.clone()).unwrap(); + // FIXME: array need insert first to compatible with yrs + sub_array.insert(0, 1).unwrap(); - doc.encode_update_v1().unwrap() - }; + doc.encode_update_v1().unwrap() + }; - let ydoc = yrs::Doc::with_options(yrs_options); - let array = ydoc.get_or_insert_array("abc"); - let mut trx = ydoc.transact_mut(); - trx - .apply_update(yrs::Update::decode_v1(&binary).unwrap()) - .unwrap(); + let ydoc = yrs::Doc::with_options(yrs_options); + let array = ydoc.get_or_insert_array("abc"); + let mut trx = ydoc.transact_mut(); + trx.apply_update(yrs::Update::decode_v1(&binary).unwrap()).unwrap(); - let config = assert_json_diff::Config::new(assert_json_diff::CompareMode::Strict) - .numeric_mode(assert_json_diff::NumericMode::AssumeFloat); - assert_json_diff::assert_json_matches!(array.to_json(&trx), json, config); + let config = assert_json_diff::Config::new(assert_json_diff::CompareMode::Strict) + .numeric_mode(assert_json_diff::NumericMode::AssumeFloat); + assert_json_diff::assert_json_matches!(array.to_json(&trx), json, config); - let mut doc = Doc::new(); - let array = doc.get_or_create_array("abc").unwrap(); - doc.apply_update_from_binary_v1(binary).unwrap(); + let mut doc = Doc::new(); + let array = doc.get_or_create_array("abc").unwrap(); + doc.apply_update_from_binary_v1(binary).unwrap(); - let list = array.iter().collect::>(); + let list = array.iter().collect::>(); - assert!(list.len() == 7); - assert!(matches!(list[6], Value::Array(_))); + assert!(list.len() == 7); + assert!(matches!(list[6], Value::Array(_))); + } } #[test] @@ -551,11 +551,7 @@ mod tests { count_clone2.fetch_add(1, Ordering::SeqCst); }); - doc_clone - .get_or_create_array("abc") - .unwrap() - .insert(0, 42) - .unwrap(); + doc_clone.get_or_create_array("abc").unwrap().insert(0, 42).unwrap(); // wait observer, cycle once every 100mm std::thread::sleep(std::time::Duration::from_millis(200)); @@ -594,8 +590,8 @@ mod tests { doc .apply_update_from_binary_v1(vec![ - 1, 1, 1, 1, 40, 0, 1, 0, 11, 115, 117, 98, 95, 109, 97, 112, 95, 107, 101, 121, 1, 119, - 13, 115, 117, 98, 95, 109, 97, 112, 95, 118, 97, 108, 117, 101, 0, + 1, 1, 1, 1, 40, 0, 1, 0, 11, 115, 117, 98, 95, 109, 97, 112, 95, 107, 101, 121, 1, 119, 13, 115, 117, 98, 95, + 109, 97, 112, 95, 118, 97, 108, 117, 101, 0, ]) .unwrap(); @@ -612,8 +608,8 @@ mod tests { .sum::(); doc .apply_update_from_binary_v1(vec![ - 1, 1, 1, 1, 40, 0, 1, 0, 11, 115, 117, 98, 95, 109, 97, 112, 95, 107, 101, 121, 1, 119, - 13, 115, 117, 98, 95, 109, 97, 112, 95, 118, 97, 108, 117, 101, 0, + 1, 1, 1, 1, 40, 0, 1, 0, 11, 115, 117, 98, 95, 109, 97, 112, 95, 107, 101, 121, 1, 119, 13, 115, 117, 98, 95, + 109, 97, 112, 95, 118, 97, 108, 117, 101, 0, ]) .unwrap(); diff --git a/packages/common/y-octo/core/src/doc/history.rs b/packages/common/y-octo/core/src/doc/history.rs index f0b947fbfd..44f52b504b 100644 --- a/packages/common/y-octo/core/src/doc/history.rs +++ b/packages/common/y-octo/core/src/doc/history.rs @@ -69,11 +69,7 @@ impl StoreHistory { self.parse_items(store_items) } - pub fn parse_delete_sets( - &self, - old_sets: &ClientMap, - new_sets: &ClientMap, - ) -> Vec { + pub fn parse_delete_sets(&self, old_sets: &ClientMap, new_sets: &ClientMap) -> Vec { let store = self.store.read().unwrap(); let deleted_items = new_sets .iter() @@ -109,11 +105,7 @@ impl StoreHistory { let store = self.store.read().unwrap(); let mut sort_iter: Box> = Box::new( SortedNodes::new(if let Some(client) = client { - store - .items - .get(client) - .map(|i| vec![(client, i)]) - .unwrap_or_default() + store.items.get(client).map(|i| vec![(client, i)]).unwrap_or_default() } else { store.items.iter().collect::>() }) @@ -273,10 +265,10 @@ impl Iterator for SortedNodes<'_> { type Item = Node; fn next(&mut self) -> Option { - if let Some(current) = self.current.as_mut() { - if let Some(node) = current.pop_back() { - return Some(node); - } + if let Some(current) = self.current.as_mut() + && let Some(node) = current.pop_back() + { + return Some(node); } if let Some((_, nodes)) = self.nodes.pop() { @@ -318,10 +310,7 @@ mod test { let update = doc.encode_update().unwrap(); - assert_eq!( - history.parse_store(Default::default()), - history.parse_update(&update,) - ); + assert_eq!(history.parse_store(Default::default()), history.parse_update(&update,)); }); } } diff --git a/packages/common/y-octo/core/src/doc/mod.rs b/packages/common/y-octo/core/src/doc/mod.rs index e0ebe607a0..540107d190 100644 --- a/packages/common/y-octo/core/src/doc/mod.rs +++ b/packages/common/y-octo/core/src/doc/mod.rs @@ -1,4 +1,5 @@ mod awareness; +mod batch; mod codec; mod common; mod document; @@ -12,6 +13,7 @@ mod utils; pub use ahash::{HashMap, HashMapExt, HashSet, HashSetExt}; pub use awareness::{Awareness, AwarenessEvent}; +pub use batch::{Batch, batch_commit}; pub use codec::*; pub use common::*; pub use document::{Doc, DocOptions}; diff --git a/packages/common/y-octo/core/src/doc/publisher.rs b/packages/common/y-octo/core/src/doc/publisher.rs index 0f6658cc58..08865c8a1b 100644 --- a/packages/common/y-octo/core/src/doc/publisher.rs +++ b/packages/common/y-octo/core/src/doc/publisher.rs @@ -34,7 +34,10 @@ impl DocPublisher { observing: Arc::new(AtomicBool::new(false)), }; - if cfg!(not(any(feature = "bench", fuzzing, loom, miri))) { + if cfg!(all( + feature = "subscribe", + not(any(feature = "bench", fuzzing, loom, miri)) + )) { publisher.start(); } @@ -111,7 +114,7 @@ impl DocPublisher { last_deletes = deletes; for cb in subscribers.iter() { - use std::panic::{catch_unwind, AssertUnwindSafe}; + use std::panic::{AssertUnwindSafe, catch_unwind}; // catch panic if callback throw catch_unwind(AssertUnwindSafe(|| { cb(&binary, &history); @@ -177,10 +180,7 @@ mod tests { let ret = [ vec![vec!["(1, 0)", "test.key1", "val1"]], - vec![ - vec!["(1, 1)", "test.key2", "val2"], - vec!["(1, 2)", "test.key3", "val3"], - ], + vec![vec!["(1, 1)", "test.key2", "val2"], vec!["(1, 2)", "test.key3", "val3"]], vec![ vec!["(1, 3)", "array.0", "val1"], vec!["(1, 4)", "array.1", "val2"], @@ -205,12 +205,7 @@ mod tests { let ret = ret[cycle].clone(); for (i, h) in history.iter().enumerate() { - println!( - "history change by {} at {}: {}", - h.id, - h.parent.join("."), - h.content - ); + println!("history change by {} at {}: {}", h.id, h.parent.join("."), h.content); // lost first update by unknown reason in asan test, skip it if asan enabled if option_env!("ASAN_OPTIONS").is_none() { let ret = &ret[i]; diff --git a/packages/common/y-octo/core/src/doc/store.rs b/packages/common/y-octo/core/src/doc/store.rs index 23fa03064e..3c1b8d5809 100644 --- a/packages/common/y-octo/core/src/doc/store.rs +++ b/packages/common/y-octo/core/src/doc/store.rs @@ -1,5 +1,5 @@ use std::{ - collections::{hash_map::Entry, VecDeque}, + collections::{VecDeque, hash_map::Entry}, mem, ops::{Deref, Range}, }; @@ -10,6 +10,8 @@ use crate::{ sync::{Arc, RwLock, RwLockWriteGuard, Weak}, }; +pub type ChangedTypeRefs = HashMap>; + unsafe impl Send for DocStore {} unsafe impl Sync for DocStore {} @@ -26,6 +28,8 @@ pub(crate) struct DocStore { pub dangling_types: HashMap, pub pending: Option, pub last_optimized_state: StateVector, + // changed item's parent, value is the parent's sub key if exists + pub changed: ChangedTypeRefs, } pub(crate) type StoreRef = Arc>; @@ -102,6 +106,10 @@ impl DocStore { Self::items_as_state_vector(&self.items) } + pub fn get_delete_sets(&self) -> DeleteSet { + self.delete_set.clone() + } + fn items_as_state_vector(items: &ClientMap>) -> StateVector { let mut state = StateVector::default(); for (client, structs) in items.iter() { @@ -175,10 +183,10 @@ impl DocStore { let id = (self.client(), self.get_state(self.client())).into(); let item = Somr::new(Item::new(id, content, left, right, parent, parent_sub)); - if let Content::Type(ty) = &item.get().unwrap().content { - if let Some(mut ty) = ty.ty_mut() { - ty.item = item.clone(); - } + if let Content::Type(ty) = &item.get().unwrap().content + && let Some(mut ty) = ty.ty_mut() + { + ty.item = item.clone(); } item @@ -190,10 +198,10 @@ impl DocStore { pub fn get_node_with_idx>(&self, id: I) -> Option<(Node, usize)> { let id = id.into(); - if let Some(items) = self.items.get(&id.client) { - if let Some(index) = Self::get_node_index(items, id.clock) { - return items.get(index).map(|item| (item.clone(), index)); - } + if let Some(items) = self.items.get(&id.client) + && let Some(index) = Self::get_node_index(items, id.clock) + { + return items.get(index).map(|item| (item.clone(), index)); } None @@ -204,20 +212,16 @@ impl DocStore { let id = id.into(); - if let Some(items) = self.items.get_mut(&id.client) { - if let Some(idx) = Self::get_node_index(items, id.clock) { - return Self::split_node_at(items, idx, diff); - } + if let Some(items) = self.items.get_mut(&id.client) + && let Some(idx) = Self::get_node_index(items, id.clock) + { + return Self::split_node_at(items, idx, diff); } Err(JwstCodecError::StructSequenceNotExists(id.client)) } - pub fn split_node_at( - items: &mut VecDeque, - idx: usize, - diff: u64, - ) -> JwstCodecResult<(Node, Node)> { + pub fn split_node_at(items: &mut VecDeque, idx: usize, diff: u64) -> JwstCodecResult<(Node, Node)> { debug_assert!(diff > 0); let node = items.get(idx).unwrap().clone(); @@ -263,16 +267,16 @@ impl DocStore { pub fn split_at_and_get_right>(&mut self, id: I) -> JwstCodecResult { let id = id.into(); - if let Some(items) = self.items.get_mut(&id.client) { - if let Some(index) = Self::get_node_index(items, id.clock) { - let item = items.get(index).unwrap().clone(); - let offset = id.clock - item.clock(); - if offset > 0 && item.is_item() { - let (_, right) = Self::split_node_at(items, index, offset)?; - return Ok(right); - } else { - return Ok(item); - } + if let Some(items) = self.items.get_mut(&id.client) + && let Some(index) = Self::get_node_index(items, id.clock) + { + let item = items.get(index).unwrap().clone(); + let offset = id.clock - item.clock(); + if offset > 0 && item.is_item() { + let (_, right) = Self::split_node_at(items, index, offset)?; + return Ok(right); + } else { + return Ok(item); } } @@ -281,16 +285,16 @@ impl DocStore { pub fn split_at_and_get_left>(&mut self, id: I) -> JwstCodecResult { let id = id.into(); - if let Some(items) = self.items.get_mut(&id.client) { - if let Some(index) = Self::get_node_index(items, id.clock) { - let item = items.get(index).unwrap().clone(); - let offset = id.clock - item.clock(); - if offset != item.len() - 1 && !item.is_gc() { - let (left, _) = Self::split_node_at(items, index, offset + 1)?; - return Ok(left); - } else { - return Ok(item); - } + if let Some(items) = self.items.get_mut(&id.client) + && let Some(index) = Self::get_node_index(items, id.clock) + { + let item = items.get(index).unwrap().clone(); + let offset = id.clock - item.clock(); + if offset != item.len() - 1 && !item.is_gc() { + let (left, _) = Self::split_node_at(items, index, offset + 1)?; + return Ok(left); + } else { + return Ok(item); } } @@ -430,12 +434,7 @@ impl DocStore { Ok(()) } - pub fn integrate( - &mut self, - mut node: Node, - offset: u64, - parent: Option<&mut YType>, - ) -> JwstCodecResult { + pub fn integrate(&mut self, mut node: Node, offset: u64, parent: Option<&mut YType>) -> JwstCodecResult { match &mut node { Node::Item(item_owner_ref) => { assert!( @@ -451,9 +450,7 @@ impl DocStore { if offset > 0 { this.id.clock += offset; - if let Node::Item(left_ref) = - self.split_at_and_get_left(Id::new(this.id.client, this.id.clock - 1))? - { + if let Node::Item(left_ref) = self.split_at_and_get_left(Id::new(this.id.client, this.id.clock - 1))? { this.origin_left_id = left_ref.get().map(|left| left.last_id()); this.left = left_ref; } @@ -550,11 +547,7 @@ impl DocStore { } else { // no left, parent.start = this right = if let Some(parent_sub) = &this.parent_sub { - parent - .map - .get(parent_sub) - .map(|n| Node::Item(n.clone()).head()) - .into() + parent.map.get(parent_sub).map(|n| Node::Item(n.clone()).head()).into() } else { mem::replace(&mut parent.start, item_owner_ref.clone()) }; @@ -571,9 +564,7 @@ impl DocStore { } else { // no right, parent.start = this, delete this.left if let Some(parent_sub) = &this.parent_sub { - parent - .map - .insert(parent_sub.clone(), item_owner_ref.clone()); + parent.map.insert(parent_sub.clone(), item_owner_ref.clone()); if let Some(left) = this.left.get() { self.delete_item(left, Some(parent)); @@ -582,11 +573,7 @@ impl DocStore { } this.right = right.clone(); - let parent_deleted = parent - .item - .get() - .map(|item| item.deleted()) - .unwrap_or(false); + let parent_deleted = parent.item.get().map(|item| item.deleted()).unwrap_or(false); // should delete if parent_deleted || this.parent_sub.is_some() && this.right.is_some() { @@ -599,6 +586,9 @@ impl DocStore { } parent_lock.take(); + + // mark changed item's parent + Self::mark_changed(&mut self.changed, ty.clone(), this.parent_sub.clone()); } else { // if parent not exists, integrate GC node instead // don't delete it because it may referenced by other nodes @@ -621,7 +611,7 @@ impl DocStore { pub fn delete_item(&mut self, item: &Item, parent: Option<&mut YType>) { let mut pending_delete_sets = HashMap::new(); - Self::delete_item_inner(&mut pending_delete_sets, item, parent); + Self::delete_item_inner(&mut pending_delete_sets, &mut self.changed, item, parent); for (client, ranges) in pending_delete_sets { self.delete_set.batch_add_ranges(client, ranges); } @@ -629,6 +619,7 @@ impl DocStore { fn delete_item_inner( delete_set: &mut HashMap>>, + changed: &mut ChangedTypeRefs, item: &Item, parent: Option<&mut YType>, ) { @@ -663,7 +654,7 @@ impl DocStore { let mut item_ref = ty.start.clone(); while let Some(item) = item_ref.get() { if !item.deleted() { - Self::delete_item_inner(delete_set, item, Some(&mut ty)); + Self::delete_item_inner(delete_set, changed, item, Some(&mut ty)); } item_ref = item.right.clone(); @@ -671,10 +662,10 @@ impl DocStore { let map_values = ty.map.values().cloned().collect::>(); for item in map_values { - if let Some(item) = item.get() { - if !item.deleted() { - Self::delete_item_inner(delete_set, item, Some(&mut ty)); - } + if let Some(item) = item.get() + && !item.deleted() + { + Self::delete_item_inner(delete_set, changed, item, Some(&mut ty)); } } } @@ -684,6 +675,11 @@ impl DocStore { } _ => {} } + + // mark deleted item's parent + if let Some(Parent::Type(ty)) = &item.parent { + Self::mark_changed(changed, ty.clone(), item.parent_sub.clone()); + } } pub fn delete_node(&mut self, struct_info: &Node, parent: Option<&mut YType>) { @@ -696,59 +692,55 @@ impl DocStore { let start = range.start; let end = range.end; - if let Some(items) = self.items.get_mut(&client) { - if let Some(mut idx) = DocStore::get_node_index(items, start) { - { - // id.clock <= range.start < id.end - // need to split the item and delete the right part - // -----item----- - // ^start - let node = &items[idx]; - let id = node.id(); - - if !node.deleted() && id.clock < start { - DocStore::split_node_at(items, idx, start - id.clock)?; - idx += 1; - } - }; - - let mut pending_delete_sets = HashMap::new(); - while idx < items.len() { - let node = items[idx].clone(); - let id = node.id(); - - if id.clock < end { - if !node.deleted() { - if let Some(item) = node.as_item().get() { - // need to split the item - // -----item----- - // ^end - if end < id.clock + node.len() { - DocStore::split_node_at(items, idx, end - id.clock)?; - } - - Self::delete_item_inner(&mut pending_delete_sets, item, None); - } - } - } else { - break; - } + if let Some(items) = self.items.get_mut(&client) + && let Some(mut idx) = DocStore::get_node_index(items, start) + { + { + // id.clock <= range.start < id.end + // need to split the item and delete the right part + // -----item----- + // ^start + let node = &items[idx]; + let id = node.id(); + if !node.deleted() && id.clock < start { + DocStore::split_node_at(items, idx, start - id.clock)?; idx += 1; } - for (client, ranges) in pending_delete_sets { - self.delete_set.batch_add_ranges(client, ranges); + }; + + let mut pending_delete_sets = HashMap::new(); + while idx < items.len() { + let node = items[idx].clone(); + let id = node.id(); + + if id.clock < end { + if !node.deleted() + && let Some(item) = node.as_item().get() + { + // need to split the item + // -----item----- + // ^end + if end < id.clock + node.len() { + DocStore::split_node_at(items, idx, end - id.clock)?; + } + + Self::delete_item_inner(&mut pending_delete_sets, &mut self.changed, item, None); + } + } else { + break; } + idx += 1; } - } + for (client, ranges) in pending_delete_sets { + self.delete_set.batch_add_ranges(client, ranges); + } + }; Ok(()) } - fn diff_state_vectors( - local_state_vector: &StateVector, - remote_state_vector: &StateVector, - ) -> Vec<(Client, Clock)> { + fn diff_state_vectors(local_state_vector: &StateVector, remote_state_vector: &StateVector) -> Vec<(Client, Clock)> { let mut diff = Vec::new(); for (client, &remote_clock) in remote_state_vector.iter() { @@ -776,19 +768,28 @@ impl DocStore { ..Update::default() }; - if with_pending { - if let Some(pending) = &self.pending { - Update::merge_into(&mut update, [pending.clone()]) - } + if with_pending && let Some(pending) = &self.pending { + Update::merge_into(&mut update, [pending.clone()]) } Ok(update) } - fn diff_structs( - map: &ClientMap>, - sv: &StateVector, - ) -> JwstCodecResult>> { + fn mark_changed(changed: &mut ChangedTypeRefs, parent: YTypeRef, parent_sub: Option) { + if parent.inner.is_some() { + let vec = changed.entry(parent).or_default(); + if let Some(parent_sub) = parent_sub { + // only record the sub key if exists + vec.push(parent_sub); + } + } + } + + pub fn get_changed(&mut self) -> ChangedTypeRefs { + mem::replace(&mut self.changed, HashMap::new()) + } + + fn diff_structs(map: &ClientMap>, sv: &StateVector) -> JwstCodecResult>> { let local_state_vector = Self::items_as_state_vector(map); let diff = Self::diff_state_vectors(&local_state_vector, sv); let mut update_structs = ClientMap::new(); @@ -915,11 +916,11 @@ impl DocStore { } fn gc_content(content: &Content) -> JwstCodecResult { - if let Content::Type(ty) = content { - if let Some(mut ty) = ty.ty_mut() { - ty.start = Somr::none(); - ty.map.clear(); - } + if let Content::Type(ty) = content + && let Some(mut ty) = ty.ty_mut() + { + ty.start = Somr::none(); + ty.map.clear(); } Ok(()) @@ -935,9 +936,7 @@ impl DocStore { } let nodes = self.items.get_mut(client).unwrap(); - let first_change = Self::get_node_index(nodes, before_state) - .unwrap_or(1) - .max(1); + let first_change = Self::get_node_index(nodes, before_state).unwrap_or(1).max(1); let mut idx = nodes.len() - 1; while idx > 0 && idx >= first_change { @@ -969,6 +968,39 @@ impl DocStore { // return the index of processed items idx - pos } + + pub fn deep_compare(&self, other: &Self) -> bool { + if self.items.len() != other.items.len() { + return false; + } + + for (client, structs) in self.items.iter() { + if let Some(other_structs) = other.items.get(client) { + if structs.len() != other_structs.len() { + return false; + } + + for (struct_info, other_struct_info) in structs.iter().zip(other_structs.iter()) { + if struct_info != other_struct_info { + return false; + } + if let (Node::Item(item), Node::Item(other_item)) = (struct_info, other_struct_info) + && !match (item.get(), other_item.get()) { + (Some(item), Some(other_item)) => item.deep_compare(other_item), + (None, None) => true, + _ => false, + } + { + return false; + } + } + } else { + return false; + } + } + + true + } } #[cfg(test)] @@ -991,10 +1023,9 @@ mod tests { let struct_info1 = Node::new_gc(Id::new(1, 1), 5); let struct_info2 = Node::new_skip(Id::new(1, 6), 7); - doc_store.items.insert( - client_id, - VecDeque::from([struct_info1, struct_info2.clone()]), - ); + doc_store + .items + .insert(client_id, VecDeque::from([struct_info1, struct_info2.clone()])); let state = doc_store.get_state(client_id); @@ -1022,24 +1053,15 @@ mod tests { let struct_info2 = Node::new_gc((2, 0).into(), 6); let struct_info3 = Node::new_skip((2, 6).into(), 1); + doc_store.items.insert(client1, VecDeque::from([struct_info1.clone()])); doc_store .items - .insert(client1, VecDeque::from([struct_info1.clone()])); - doc_store.items.insert( - client2, - VecDeque::from([struct_info2, struct_info3.clone()]), - ); + .insert(client2, VecDeque::from([struct_info2, struct_info3.clone()])); let state_map = doc_store.get_state_vector(); - assert_eq!( - state_map.get(&client1), - struct_info1.clock() + struct_info1.len() - ); - assert_eq!( - state_map.get(&client2), - struct_info3.clock() + struct_info3.len() - ); + assert_eq!(state_map.get(&client1), struct_info1.clock() + struct_info1.len()); + assert_eq!(state_map.get(&client2), struct_info3.clock() + struct_info3.len()); assert!(doc_store.self_check().is_ok()); }); @@ -1059,10 +1081,7 @@ mod tests { assert!(doc_store.add_node(struct_info2).is_ok()); assert_eq!( doc_store.add_node(struct_info3_err), - Err(JwstCodecError::StructClockInvalid { - expect: 6, - actually: 5 - }) + Err(JwstCodecError::StructClockInvalid { expect: 6, actually: 5 }) ); assert!(doc_store.add_node(struct_info3.clone()).is_ok()); assert_eq!( @@ -1163,15 +1182,64 @@ mod tests { // s1 used to be (1, 4), but it actually ref of first item in store, so now it // should be (1, 2) - assert_eq!( - s1, left, - "doc internal mutation should not modify the pointer" - ); + assert_eq!(s1, left, "doc internal mutation should not modify the pointer"); let right = doc_store.split_at_and_get_right((1, 5)).unwrap(); assert_eq!(right.len(), 3); // base => b_ase }); } + #[test] + fn should_mark_changed_items() { + loom_model!({ + let doc = DocOptions::new().with_client_id(1).build(); + + let mut arr = doc.get_or_create_array("arr").unwrap(); + let mut text = doc.create_text().unwrap(); + let mut map = doc.create_map().unwrap(); + + arr.insert(0, Value::from(text.clone())).unwrap(); + arr.insert(1, Value::from(map.clone())).unwrap(); + { + let changed = doc.store.write().unwrap().get_changed(); + // for array, we will only record the type ref itself + assert_eq!(changed.len(), 1); + assert_eq!(changed.get(&arr.0), Some(&vec![])); + } + + text.insert(0, "hello world").unwrap(); + text.remove(5, 6).unwrap(); + { + let changed = doc.store.write().unwrap().get_changed(); + assert_eq!(changed.len(), 1); + assert_eq!(changed.get(&text.0), Some(&vec![])); + } + + map.insert("key".into(), 123).unwrap(); + { + let changed = doc.store.write().unwrap().get_changed(); + assert_eq!(changed.len(), 1); + assert_eq!(changed.get(&map.0), Some(&vec!["key".into()])); + } + + map.remove("key"); + { + let changed = doc.store.write().unwrap().get_changed(); + assert_eq!(changed.len(), 1); + assert_eq!(changed.get(&map.0), Some(&vec!["key".into()])); + } + + arr.remove(0, 1).unwrap(); + { + let changed = doc.store.write().unwrap().get_changed(); + assert_eq!(changed.len(), 2); + // text's children mark parent(text) changed + assert_eq!(changed.get(&text.0), Some(&vec![])); + // text mark parent(arr) changed + assert_eq!(changed.get(&arr.0), Some(&vec![])); + } + }); + } + #[test] fn should_replace_gc_item_with_content_deleted() { loom_model!({ @@ -1195,13 +1263,7 @@ mod tests { store.gc_delete_set().unwrap(); assert_eq!( - &store - .get_node((1, 0)) - .unwrap() - .as_item() - .get() - .unwrap() - .content, + &store.get_node((1, 0)).unwrap().as_item().get().unwrap().content, &Content::Deleted(4) ); }); @@ -1226,13 +1288,7 @@ mod tests { assert_eq!(arr.len(), 0); assert_eq!( - &store - .get_node((1, 0)) - .unwrap() - .as_item() - .get() - .unwrap() - .content, + &store.get_node((1, 0)).unwrap().as_item().get().unwrap().content, &Content::Deleted(1) ); @@ -1256,9 +1312,7 @@ mod tests { let mut pages = doc.get_or_create_map("pages").unwrap(); let page1 = doc.create_text().unwrap(); let mut page1_ref = page1.clone(); - pages - .insert("page1".to_string(), Value::from(page1)) - .unwrap(); + pages.insert("page1".to_string(), Value::from(page1)).unwrap(); page1_ref.insert(0, "hello").unwrap(); doc.encode_update_v1().unwrap() }; @@ -1276,13 +1330,7 @@ mod tests { store.gc_delete_set().unwrap(); assert_eq!( - &store - .get_node((1, 0)) - .unwrap() - .as_item() - .get() - .unwrap() - .content, + &store.get_node((1, 0)).unwrap().as_item().get().unwrap().content, &Content::Deleted(1) ); diff --git a/packages/common/y-octo/core/src/doc/types/array.rs b/packages/common/y-octo/core/src/doc/types/array.rs index e11f8f730d..e67f2fc52a 100644 --- a/packages/common/y-octo/core/src/doc/types/array.rs +++ b/packages/common/y-octo/core/src/doc/types/array.rs @@ -52,6 +52,11 @@ impl Iterator for ArrayIter<'_> { } impl Array { + #[inline(always)] + pub fn id(&self) -> Option { + self._id() + } + #[inline] pub fn len(&self) -> u64 { self.content_len() @@ -126,15 +131,26 @@ mod tests { array.insert(0, "Hello").unwrap(); array.insert(2, "World").unwrap(); - assert_eq!( - array.get(0).unwrap(), - Value::Any(Any::String("Hello".into())) - ); + assert_eq!(array.get(0).unwrap(), Value::Any(Any::String("Hello".into()))); assert_eq!(array.get(1).unwrap(), Value::Any(Any::String(" ".into()))); - assert_eq!( - array.get(2).unwrap(), - Value::Any(Any::String("World".into())) - ); + assert_eq!(array.get(2).unwrap(), Value::Any(Any::String("World".into()))); + }); + } + + #[test] + fn test_yarray_delete() { + let options = DocOptions::default(); + + loom_model!({ + let doc = Doc::with_options(options.clone()); + let mut array = doc.get_or_create_array("abc").unwrap(); + + array.insert(0, " ").unwrap(); + array.insert(0, "Hello").unwrap(); + array.insert(2, "World").unwrap(); + array.remove(0, 2).unwrap(); + + assert_eq!(array.get(0).unwrap(), Value::Any(Any::String("World".into()))); }); } @@ -163,15 +179,9 @@ mod tests { doc.apply_update(update).unwrap(); let array = doc.get_or_create_array("abc").unwrap(); - assert_eq!( - array.get(0).unwrap(), - Value::Any(Any::String("Hello".into())) - ); + assert_eq!(array.get(0).unwrap(), Value::Any(Any::String("Hello".into()))); assert_eq!(array.get(5).unwrap(), Value::Any(Any::String(" ".into()))); - assert_eq!( - array.get(6).unwrap(), - Value::Any(Any::String("World".into())) - ); + assert_eq!(array.get(6).unwrap(), Value::Any(Any::String("World".into()))); assert_eq!(array.get(11).unwrap(), Value::Any(Any::String("!".into()))); }); @@ -196,15 +206,9 @@ mod tests { doc.apply_update(update).unwrap(); let array = doc.get_or_create_array("abc").unwrap(); - assert_eq!( - array.get(0).unwrap(), - Value::Any(Any::String("Hello".into())) - ); + assert_eq!(array.get(0).unwrap(), Value::Any(Any::String("Hello".into()))); assert_eq!(array.get(5).unwrap(), Value::Any(Any::String(" ".into()))); - assert_eq!( - array.get(6).unwrap(), - Value::Any(Any::String("World".into())) - ); + assert_eq!(array.get(6).unwrap(), Value::Any(Any::String("World".into()))); assert_eq!(array.get(11).unwrap(), Value::Any(Any::String("!".into()))); }); } @@ -237,10 +241,7 @@ mod tests { .unwrap(); let arr = doc.get_or_create_array("abc").unwrap(); - assert_eq!( - arr.get(2).unwrap(), - Value::Any(Any::String("world".to_string())) - ) + assert_eq!(arr.get(2).unwrap(), Value::Any(Any::String("world".to_string()))) }); } } diff --git a/packages/common/y-octo/core/src/doc/types/list/mod.rs b/packages/common/y-octo/core/src/doc/types/list/mod.rs index 380d324adf..c56e06b24b 100644 --- a/packages/common/y-octo/core/src/doc/types/list/mod.rs +++ b/packages/common/y-octo/core/src/doc/types/list/mod.rs @@ -6,6 +6,7 @@ pub(crate) use search_marker::MarkerList; use super::*; +#[derive(Debug)] pub(crate) struct ItemPosition { pub parent: YTypeRef, pub left: ItemRef, @@ -55,6 +56,11 @@ impl ItemPosition { } pub(crate) trait ListType: AsInner { + #[inline(always)] + fn _id(&self) -> Option { + self.as_inner().ty().and_then(|ty| ty.item.get().map(|item| item.id)) + } + #[inline(always)] fn content_len(&self) -> u64 { self.as_inner().ty().unwrap().len @@ -84,23 +90,29 @@ pub(crate) trait ListType: AsInner { return Some(pos); } - if let Some(markers) = &inner.markers { - if let Some(marker) = markers.find_marker(inner, index) { - if marker.index > remaining { - remaining = 0 - } else { - remaining -= marker.index; - } - pos.index = marker.index; - pos.left = marker - .ptr - .get() - .map(|ptr| ptr.left.clone()) - .unwrap_or_default(); - pos.right = marker.ptr; + if let Some(markers) = &inner.markers + && let Some(marker) = markers.find_marker(inner, index) + { + if marker.index > remaining { + remaining = 0 + } else { + remaining -= marker.index; } + pos.index = marker.index; + pos.left = marker.ptr.get().map(|ptr| ptr.left.clone()).unwrap_or_default(); + pos.right = marker.ptr; }; + // avoid the first item of the list being deleted + while let Some(item) = pos.right.get() { + if item.deleted() { + pos.right = item.right.clone(); + continue; + } else { + break; + } + } + while remaining > 0 { if let Some(item) = pos.right.get() { if item.indexable() { @@ -141,16 +153,11 @@ pub(crate) trait ListType: AsInner { Ok(()) } - fn insert_after( - ty: &mut YType, - store: &mut DocStore, - pos: ItemPosition, - content: Content, - ) -> JwstCodecResult { - if let Some(markers) = &ty.markers { - if content.countable() { - markers.update_marker_changes(pos.index, content.clock_len() as i64); - } + fn insert_after(ty: &mut YType, store: &mut DocStore, pos: ItemPosition, content: Content) -> JwstCodecResult { + if let Some(markers) = &ty.markers + && content.countable() + { + markers.update_marker_changes(pos.index, content.clock_len() as i64); } let item = store.create_item( @@ -189,7 +196,12 @@ pub(crate) trait ListType: AsInner { return Ok(()); } - if idx >= self.content_len() { + let content_len = self.content_len(); + if content_len == 0 { + return Ok(()); + } + + if idx >= content_len { return Err(JwstCodecError::IndexOutOfBound(idx)); } @@ -204,34 +216,32 @@ pub(crate) trait ListType: AsInner { Ok(()) } - fn remove_after( - ty: &mut YType, - store: &mut DocStore, - mut pos: ItemPosition, - len: u64, - ) -> JwstCodecResult { + fn remove_after(ty: &mut YType, store: &mut DocStore, mut pos: ItemPosition, len: u64) -> JwstCodecResult { pos.normalize(store)?; let mut remaining = len; while remaining > 0 { - if let Some(item) = pos.right.get() { - if item.indexable() { - let content_len = item.len(); - if remaining < content_len { - store.split_node(item.id, remaining)?; - remaining = 0; - } else { - remaining -= content_len; - } + let item_ref = pos.right.clone(); + let Some((indexable, content_len, item_id)) = item_ref.get().map(|item| (item.indexable(), item.len(), item.id)) + else { + break; + }; - store.delete_item(item, Some(ty)); + if indexable { + if remaining < content_len { + store.split_node(item_id, remaining)?; + remaining = 0; + } else { + remaining -= content_len; } - pos.forward(); - } else { - break; + if let Some(item) = item_ref.get() { + store.delete_item(item, Some(ty)); + } } + + pos.forward(); } if let Some(markers) = &ty.markers { diff --git a/packages/common/y-octo/core/src/doc/types/list/search_marker.rs b/packages/common/y-octo/core/src/doc/types/list/search_marker.rs index 5fc9a1797c..721f685a65 100644 --- a/packages/common/y-octo/core/src/doc/types/list/search_marker.rs +++ b/packages/common/y-octo/core/src/doc/types/list/search_marker.rs @@ -69,11 +69,7 @@ impl MarkerList { } // mark pos and push to the end of the linked list - fn mark_position( - list: &mut VecDeque, - ptr: Somr, - index: u64, - ) -> Option { + fn mark_position(list: &mut VecDeque, ptr: Somr, index: u64) -> Option { if list.len() >= MAX_SEARCH_MARKER { let mut oldest_marker = list.pop_front().unwrap(); oldest_marker.overwrite_marker(ptr, index); @@ -126,9 +122,7 @@ impl MarkerList { let mut list = self.borrow_mut(); - let marker = list - .iter_mut() - .min_by_key(|m| (index as i64 - m.index as i64).abs()); + let marker = list.iter_mut().min_by_key(|m| (index as i64 - m.index as i64).abs()); let mut marker_index = marker.as_ref().map(|m| m.index).unwrap_or(0); @@ -201,8 +195,7 @@ impl MarkerList { match marker { Some(marker) - if (marker.index as f64 - marker_index as f64).abs() - < parent.len as f64 / MAX_SEARCH_MARKER as f64 => + if (marker.index as f64 - marker_index as f64).abs() < parent.len as f64 / MAX_SEARCH_MARKER as f64 => { // adjust existing marker marker.overwrite_marker(item_ptr, marker_index); diff --git a/packages/common/y-octo/core/src/doc/types/map.rs b/packages/common/y-octo/core/src/doc/types/map.rs index cafcb5e502..3760fc669f 100644 --- a/packages/common/y-octo/core/src/doc/types/map.rs +++ b/packages/common/y-octo/core/src/doc/types/map.rs @@ -2,13 +2,18 @@ use std::{collections::hash_map::Iter, rc::Rc}; use super::*; use crate::{ + JwstCodecResult, doc::{AsInner, Node, Parent, YTypeRef}, - impl_type, JwstCodecResult, + impl_type, }; impl_type!(Map); pub(crate) trait MapType: AsInner { + fn _id(&self) -> Option { + self.as_inner().ty().and_then(|ty| ty.item.get().map(|item| item.id)) + } + fn _insert>(&mut self, key: String, value: V) -> JwstCodecResult { if let Some((mut store, mut ty)) = self.as_inner().write() { let left = ty.map.get(&SmolStr::new(&key)).cloned(); @@ -54,12 +59,11 @@ pub(crate) trait MapType: AsInner { } fn _remove(&mut self, key: &str) { - if let Some((mut store, mut ty)) = self.as_inner().write() { - if let Some(item) = ty.map.get(key).cloned() { - if let Some(item) = item.get() { - store.delete_item(item, Some(&mut ty)); - } - } + if let Some((mut store, mut ty)) = self.as_inner().write() + && let Some(item) = ty.map.get(key).cloned() + && let Some(item) = item.get() + { + store.delete_item(item, Some(&mut ty)); } } @@ -113,10 +117,10 @@ impl<'a> Iterator for EntriesInnerIterator<'a> { fn next(&mut self) -> Option { if let Some(iter) = &mut self.iter { for (k, v) in iter { - if let Some(item) = v.get() { - if !item.deleted() { - return Some((k.as_str(), item)); - } + if let Some(item) = v.get() + && !item.deleted() + { + return Some((k.as_str(), item)); } } @@ -154,6 +158,11 @@ impl<'a> Iterator for EntriesIterator<'a> { impl MapType for Map {} impl Map { + #[inline(always)] + pub fn id(&self) -> Option { + self._id() + } + #[inline(always)] pub fn insert>(&mut self, key: String, value: V) -> JwstCodecResult { self._insert(key, value) @@ -220,7 +229,7 @@ impl serde::Serialize for Map { #[cfg(test)] mod tests { use super::*; - use crate::{loom_model, Any, Doc}; + use crate::{Any, Doc, loom_model}; #[test] fn test_map_basic() { @@ -228,10 +237,7 @@ mod tests { let doc = Doc::new(); let mut map = doc.get_or_create_map("map").unwrap(); map.insert("1".to_string(), "value").unwrap(); - assert_eq!( - map.get("1").unwrap(), - Value::Any(Any::String("value".to_string())) - ); + assert_eq!(map.get("1").unwrap(), Value::Any(Any::String("value".to_string()))); assert!(!map.contains_key("nonexistent_key")); assert_eq!(map.len(), 1); assert!(map.contains_key("1")); @@ -252,10 +258,7 @@ mod tests { let binary = doc.encode_update_v1().unwrap(); let new_doc = Doc::try_from_binary_v1(binary).unwrap(); let map = new_doc.get_or_create_map("map").unwrap(); - assert_eq!( - map.get("1").unwrap(), - Value::Any(Any::String("value".to_string())) - ); + assert_eq!(map.get("1").unwrap(), Value::Any(Any::String("value".to_string()))); assert_eq!(map.get("2").unwrap(), Value::Any(Any::False)); assert_eq!(map.len(), 2); }); @@ -268,10 +271,7 @@ mod tests { let mut map = doc.get_or_create_map("map").unwrap(); map.insert("1".to_string(), "value").unwrap(); map.insert("1".to_string(), "value2").unwrap(); - assert_eq!( - map.get("1").unwrap(), - Value::Any(Any::String("value2".to_string())) - ); + assert_eq!(map.get("1").unwrap(), Value::Any(Any::String("value2".to_string()))); assert_eq!(map.len(), 1); }); } @@ -290,14 +290,8 @@ mod tests { { let doc = Doc::try_from_binary_v1(binary).unwrap(); let map = doc.get_or_create_map("map").unwrap(); - assert_eq!( - map.get("1").unwrap(), - Value::Any(Any::String("value1".to_string())) - ); - assert_eq!( - map.get("2").unwrap(), - Value::Any(Any::String("value2".to_string())) - ); + assert_eq!(map.get("1").unwrap(), Value::Any(Any::String("value1".to_string()))); + assert_eq!(map.get("2").unwrap(), Value::Any(Any::String("value2".to_string()))); } }); } diff --git a/packages/common/y-octo/core/src/doc/types/mod.rs b/packages/common/y-octo/core/src/doc/types/mod.rs index a4fdf69595..2322643431 100644 --- a/packages/common/y-octo/core/src/doc/types/mod.rs +++ b/packages/common/y-octo/core/src/doc/types/mod.rs @@ -5,7 +5,11 @@ mod text; mod value; mod xml; -use std::{collections::hash_map::Entry, sync::Weak}; +use std::{ + collections::hash_map::Entry, + hash::{Hash, Hasher}, + sync::Weak, +}; pub use array::*; use list::*; @@ -19,8 +23,8 @@ use super::{ *, }; use crate::{ - sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}, Item, JwstCodecError, JwstCodecResult, + sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}, }; #[derive(Debug, Default)] @@ -45,9 +49,7 @@ pub(crate) struct YTypeRef { impl PartialEq for YType { fn eq(&self, other: &Self) -> bool { - self.root_name == other.root_name - || (self.start.is_some() && self.start == other.start) - || self.map == other.map + self.root_name == other.root_name || (self.start.is_some() && self.start == other.start) || self.map == other.map } } @@ -62,6 +64,14 @@ impl PartialEq for YTypeRef { } } +impl Eq for YTypeRef {} + +impl Hash for YTypeRef { + fn hash(&self, state: &mut H) { + self.inner.ptr().hash(state); + } +} + impl YType { pub fn new(kind: YTypeKind, tag_name: Option) -> Self { YType { @@ -129,15 +139,11 @@ impl YTypeRef { #[allow(dead_code)] pub fn read(&self) -> Option<(RwLockReadGuard<'_, DocStore>, RwLockReadGuard<'_, YType>)> { - self - .store() - .and_then(|store| self.ty().map(|ty| (store, ty))) + self.store().and_then(|store| self.ty().map(|ty| (store, ty))) } pub fn write(&self) -> Option<(RwLockWriteGuard<'_, DocStore>, RwLockWriteGuard<'_, YType>)> { - self - .store_mut() - .and_then(|store| self.ty_mut().map(|ty| (store, ty))) + self.store_mut().and_then(|store| self.ty_mut().map(|ty| (store, ty))) } } @@ -238,9 +244,7 @@ impl YTypeBuilder { let ty_ref = ty.clone(); - store - .dangling_types - .insert(ty.inner.ptr().as_ptr() as usize, ty); + store.dangling_types.insert(ty.inner.ptr().as_ptr() as usize, ty); ty_ref }; @@ -338,14 +342,10 @@ macro_rules! impl_type { inner.set_kind(super::YTypeKind::$name)?; Ok($name::new(value.clone())) } - _ => Err($crate::JwstCodecError::TypeCastError(std::stringify!( - $name - ))), + _ => Err($crate::JwstCodecError::TypeCastError(std::stringify!($name))), } } else { - Err($crate::JwstCodecError::TypeCastError(std::stringify!( - $name - ))) + Err($crate::JwstCodecError::TypeCastError(std::stringify!($name))) } } } diff --git a/packages/common/y-octo/core/src/doc/types/text.rs b/packages/common/y-octo/core/src/doc/types/text.rs index 1c271ffa0b..bfe37caede 100644 --- a/packages/common/y-octo/core/src/doc/types/text.rs +++ b/packages/common/y-octo/core/src/doc/types/text.rs @@ -1,9 +1,10 @@ use std::{collections::BTreeMap, fmt::Display}; -use super::{list::ListType, AsInner}; +use super::{AsInner, list::ListType}; use crate::{ + Any, Content, JwstCodecError, JwstCodecResult, doc::{DocStore, ItemRef, Node, Parent, Somr, YType, YTypeRef}, - impl_type, Any, Content, JwstCodecError, JwstCodecResult, + impl_type, }; impl_type!(Text); @@ -85,21 +86,12 @@ impl Text { Content::Json(values) => { let converted = values .iter() - .map(|value| { - value - .as_ref() - .map(|s| Any::String(s.clone())) - .unwrap_or(Any::Undefined) - }) + .map(|value| value.as_ref().map(|s| Any::String(s.clone())).unwrap_or(Any::Undefined)) .collect::>(); push_insert(&mut ops, TextInsert::Embed(converted), &attrs); } Content::Binary(value) => { - push_insert( - &mut ops, - TextInsert::Embed(vec![Any::Binary(value.clone())]), - &attrs, - ); + push_insert(&mut ops, TextInsert::Embed(vec![Any::Binary(value.clone())]), &attrs); } _ => {} } @@ -121,13 +113,7 @@ impl Text { let attrs = format.clone().unwrap_or_default(); match insert { TextInsert::Text(text) => { - insert_text_content( - &mut store, - &mut ty, - &mut pos, - Content::String(text.clone()), - attrs, - )?; + insert_text_content(&mut store, &mut ty, &mut pos, Content::String(text.clone()), attrs)?; } TextInsert::Embed(values) => { for value in values { @@ -225,38 +211,29 @@ fn is_nullish(value: &Any) -> bool { } fn push_insert(ops: &mut Vec, insert: TextInsert, attrs: &TextAttributes) { - let format = if attrs.is_empty() { - None - } else { - Some(attrs.clone()) - }; + let format = if attrs.is_empty() { None } else { Some(attrs.clone()) }; if let Some(TextDeltaOp::Insert { insert: TextInsert::Text(prev), format: prev_format, }) = ops.last_mut() + && let TextInsert::Text(text) = insert { - if let TextInsert::Text(text) = insert { - if prev_format.as_ref() == format.as_ref() { - prev.push_str(&text); - return; - } - ops.push(TextDeltaOp::Insert { - insert: TextInsert::Text(text), - format, - }); + if prev_format.as_ref() == format.as_ref() { + prev.push_str(&text); return; } + ops.push(TextDeltaOp::Insert { + insert: TextInsert::Text(text), + format, + }); + return; } ops.push(TextDeltaOp::Insert { insert, format }); } -fn advance_text_position( - store: &mut DocStore, - pos: &mut TextPosition, - mut remaining: u64, -) -> JwstCodecResult { +fn advance_text_position(store: &mut DocStore, pos: &mut TextPosition, mut remaining: u64) -> JwstCodecResult { while remaining > 0 { let Some(item) = pos.right.get() else { return Err(JwstCodecError::IndexOutOfBound(pos.index + remaining)); @@ -311,16 +288,11 @@ fn minimize_attribute_changes(pos: &mut TextPosition, attrs: &TextAttributes) { } } -fn insert_item( - store: &mut DocStore, - ty: &mut YType, - pos: &mut TextPosition, - content: Content, -) -> JwstCodecResult { - if let Some(markers) = &ty.markers { - if content.countable() { - markers.update_marker_changes(pos.index, content.clock_len() as i64); - } +fn insert_item(store: &mut DocStore, ty: &mut YType, pos: &mut TextPosition, content: Content) -> JwstCodecResult { + if let Some(markers) = &ty.markers + && content.countable() + { + markers.update_marker_changes(pos.index, content.clock_len() as i64); } let item = store.create_item( @@ -383,14 +355,13 @@ fn insert_negated_attributes( continue; } - if let Content::Format { key, value } = &item.content { - if let Some(negated_value) = negated.get(key.as_str()) { - if negated_value == value { - negated.remove(key.as_str()); - pos.forward(); - continue; - } - } + if let Content::Format { key, value } = &item.content + && let Some(negated_value) = negated.get(key.as_str()) + && negated_value == value + { + negated.remove(key.as_str()); + pos.forward(); + continue; } break; @@ -488,12 +459,7 @@ fn format_text( Ok(()) } -fn delete_text( - store: &mut DocStore, - ty: &mut YType, - pos: &mut TextPosition, - mut remaining: u64, -) -> JwstCodecResult { +fn delete_text(store: &mut DocStore, ty: &mut YType, pos: &mut TextPosition, mut remaining: u64) -> JwstCodecResult { if remaining == 0 { return Ok(()); } @@ -501,19 +467,23 @@ fn delete_text( let start = remaining; while remaining > 0 { - let Some(item) = pos.right.get() else { + let item_ref = pos.right.clone(); + let Some((indexable, item_len, item_id)) = item_ref.get().map(|item| (item.indexable(), item.len(), item.id)) + else { break; }; - if item.indexable() { - let item_len = item.len(); + if indexable { if remaining < item_len { - store.split_node(item.id, remaining)?; + store.split_node(item_id, remaining)?; remaining = 0; } else { remaining -= item_len; } - store.delete_item(item, Some(ty)); + + if let Some(item) = item_ref.get() { + store.delete_item(item, Some(ty)); + } } pos.forward(); @@ -535,7 +505,7 @@ mod tests { use super::{TextAttributes, TextDeltaOp, TextInsert}; #[cfg(not(loom))] use crate::sync::{Arc, AtomicUsize, Ordering}; - use crate::{loom_model, sync::thread, Any, Doc}; + use crate::{Any, Doc, loom_model, sync::thread}; #[test] fn test_manipulate_text() { @@ -676,9 +646,7 @@ mod tests { fn loom_parallel_ins_del_text() { let seed = rand::rng().random(); let mut rand = ChaCha20Rng::seed_from_u64(seed); - let ranges = (0..20) - .map(|_| rand.random_range(0..16)) - .collect::>(); + let ranges = (0..20).map(|_| rand.random_range(0..16)).collect::>(); loom_model!({ let doc = Doc::new(); diff --git a/packages/common/y-octo/core/src/doc/types/value.rs b/packages/common/y-octo/core/src/doc/types/value.rs index 420aa0bfc2..7d481fe562 100644 --- a/packages/common/y-octo/core/src/doc/types/value.rs +++ b/packages/common/y-octo/core/src/doc/types/value.rs @@ -2,7 +2,7 @@ use std::fmt::Display; use super::*; -#[derive(Debug, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub enum Value { Any(Any), Doc(Doc), @@ -45,9 +45,7 @@ impl Value { } pub fn from_vec>(el: Vec) -> Self { - Value::Any(Any::Array( - el.into_iter().map(|item| item.into()).collect::>(), - )) + Value::Any(Any::Array(el.into_iter().map(|item| item.into()).collect::>())) } } diff --git a/packages/common/y-octo/core/src/doc/utils.rs b/packages/common/y-octo/core/src/doc/utils.rs index 45e110b6eb..720ba9b8a1 100644 --- a/packages/common/y-octo/core/src/doc/utils.rs +++ b/packages/common/y-octo/core/src/doc/utils.rs @@ -16,9 +16,7 @@ pub fn encode_update_as_message(update: Vec) -> JwstCodecResult> { Ok(buffer) } -pub fn merge_updates_v1, I: IntoIterator>( - updates: I, -) -> JwstCodecResult { +pub fn merge_updates_v1, I: IntoIterator>(updates: I) -> JwstCodecResult { let updates = updates .into_iter() .map(Update::decode_v1) @@ -26,3 +24,21 @@ pub fn merge_updates_v1, I: IntoIterator>( Ok(Update::merge(updates)) } + +/// It tends to generate small numbers. +/// Since the client id will be included in all crdt items, the +/// small client helps to reduce the binary size. +/// +/// NOTE: The probability of 36% of the random number generated by +/// this function is greater than [u32::MAX] +pub fn prefer_small_random() -> u64 { + use rand::{distr::Distribution, rng}; + use rand_distr::Exp; + + let scale_factor = u16::MAX as f64; + let v: f64 = Exp::new(1.0 / scale_factor) + .map(|exp| exp.sample(&mut rng())) + .unwrap_or_else(|_| rand::random()); + + (v * scale_factor) as u64 +} diff --git a/packages/common/y-octo/core/src/lib.rs b/packages/common/y-octo/core/src/lib.rs index ba7cf0bf08..52b2db7afc 100644 --- a/packages/common/y-octo/core/src/lib.rs +++ b/packages/common/y-octo/core/src/lib.rs @@ -6,18 +6,16 @@ mod sync; pub use codec::*; pub use doc::{ - encode_awareness_as_message, encode_update_as_message, merge_updates_v1, Any, Array, Awareness, - AwarenessEvent, Client, ClientMap, Clock, CrdtRead, CrdtReader, CrdtWrite, CrdtWriter, Doc, - DocOptions, HashMap as AHashMap, HashMapExt, History, HistoryOptions, Id, Map, RawDecoder, - RawEncoder, StateVector, StoreHistory, Text, TextAttributes, TextDelta, TextDeltaOp, TextInsert, - Update, Value, + Any, Array, Awareness, AwarenessEvent, Batch, Client, ClientMap, Clock, CrdtRead, CrdtReader, CrdtWrite, CrdtWriter, + Doc, DocOptions, HashMap as AHashMap, HashMapExt, History, HistoryOptions, Id, Map, RawDecoder, RawEncoder, + StateVector, StoreHistory, Text, TextAttributes, TextDelta, TextDeltaOp, TextInsert, Update, Value, batch_commit, + encode_awareness_as_message, encode_update_as_message, merge_updates_v1, }; pub(crate) use doc::{Content, Item}; use log::{debug, warn}; use nom::IResult; pub use protocol::{ - read_sync_message, write_sync_message, AwarenessState, AwarenessStates, DocMessage, SyncMessage, - SyncMessageScanner, + AwarenessState, AwarenessStates, DocMessage, SyncMessage, SyncMessageScanner, read_sync_message, write_sync_message, }; use thiserror::Error; diff --git a/packages/common/y-octo/core/src/protocol/awareness.rs b/packages/common/y-octo/core/src/protocol/awareness.rs index f22ed41e71..d7dd47f4f0 100644 --- a/packages/common/y-octo/core/src/protocol/awareness.rs +++ b/packages/common/y-octo/core/src/protocol/awareness.rs @@ -1,4 +1,4 @@ -use nom::{multi::count, Parser}; +use nom::{Parser, multi::count}; use super::*; @@ -65,11 +65,7 @@ fn read_awareness_state(input: &[u8]) -> IResult<&[u8], (u64, AwarenessState)> { Ok((tail, (client_id, AwarenessState { clock, content }))) } -fn write_awareness_state( - buffer: &mut W, - client_id: u64, - state: &AwarenessState, -) -> Result<(), IoError> { +fn write_awareness_state(buffer: &mut W, client_id: u64, state: &AwarenessState) -> Result<(), IoError> { write_var_u64(buffer, client_id)?; write_var_u64(buffer, state.clock)?; write_var_string(buffer, state.content.clone())?; @@ -118,14 +114,8 @@ mod tests { ]; let expected = HashMap::from([ - ( - 1, - AwarenessState::new(5, String::from_utf8(vec![1]).unwrap()), - ), - ( - 2, - AwarenessState::new(10, String::from_utf8(vec![2, 3]).unwrap()), - ), + (1, AwarenessState::new(5, String::from_utf8(vec![1]).unwrap())), + (2, AwarenessState::new(10, String::from_utf8(vec![2, 3]).unwrap())), ( 5, AwarenessState::new(5, String::from_utf8(vec![1, 2, 3, 4, 5]).unwrap()), diff --git a/packages/common/y-octo/core/src/protocol/doc.rs b/packages/common/y-octo/core/src/protocol/doc.rs index 143762b6b7..6335a333d5 100644 --- a/packages/common/y-octo/core/src/protocol/doc.rs +++ b/packages/common/y-octo/core/src/protocol/doc.rs @@ -5,10 +5,9 @@ use super::*; #[cfg_attr(test, derive(proptest_derive::Arbitrary))] pub enum DocMessage { // state vector - // TODO: temporarily skipped in the test, because yrs decoding needs to ensure that the update - // in step1 is the correct state vector binary and any data can be included in our - // implementation (we will ensure the correctness of encoding and decoding in the subsequent - // decoding process) + // TODO: temporarily skipped in the test, because yrs decoding needs to ensure that the update in step1 is the + // correct state vector binary and any data can be included in our implementation (we will ensure the + // correctness of encoding and decoding in the subsequent decoding process) #[cfg_attr(test, proptest(skip))] Step1(Vec), // update diff --git a/packages/common/y-octo/core/src/protocol/mod.rs b/packages/common/y-octo/core/src/protocol/mod.rs index 1451283f73..ee2f4390c8 100644 --- a/packages/common/y-octo/core/src/protocol/mod.rs +++ b/packages/common/y-octo/core/src/protocol/mod.rs @@ -8,16 +8,16 @@ use std::{ io::{Error as IoError, Write}, }; -use awareness::{read_awareness, write_awareness}; pub use awareness::{AwarenessState, AwarenessStates}; +use awareness::{read_awareness, write_awareness}; pub use doc::DocMessage; use doc::{read_doc_message, write_doc_message}; use log::debug; use nom::{ - error::{Error, ErrorKind}, IResult, + error::{Error, ErrorKind}, }; pub use scanner::SyncMessageScanner; -pub use sync::{read_sync_message, write_sync_message, SyncMessage}; +pub use sync::{SyncMessage, read_sync_message, write_sync_message}; use super::*; diff --git a/packages/common/y-octo/core/src/sync.rs b/packages/common/y-octo/core/src/sync.rs index d670f32847..14d115d7b1 100644 --- a/packages/common/y-octo/core/src/sync.rs +++ b/packages/common/y-octo/core/src/sync.rs @@ -1,21 +1,21 @@ +pub use std::sync::{Arc, Weak}; #[allow(unused)] #[cfg(not(loom))] pub(crate) use std::sync::{ - atomic::{AtomicBool, AtomicU16, AtomicU32, AtomicU8, Ordering}, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard, + atomic::{AtomicBool, AtomicU8, AtomicU16, AtomicU32, Ordering}, }; -pub use std::sync::{Arc, Weak}; #[cfg(all(test, not(loom)))] pub(crate) use std::{ - sync::{atomic::AtomicUsize, MutexGuard}, + sync::{MutexGuard, atomic::AtomicUsize}, thread, }; #[cfg(loom)] pub(crate) use loom::{ sync::{ - atomic::{AtomicBool, AtomicU16, AtomicU8, AtomicUsize, Ordering}, Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard, + atomic::{AtomicBool, AtomicU8, AtomicU16, AtomicU32, AtomicUsize, Ordering}, }, thread, }; diff --git a/packages/common/y-octo/node/.gitignore b/packages/common/y-octo/node/.gitignore deleted file mode 100644 index 28f2c68269..0000000000 --- a/packages/common/y-octo/node/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.node -.coverage \ No newline at end of file diff --git a/packages/common/y-octo/node/Cargo.toml b/packages/common/y-octo/node/Cargo.toml deleted file mode 100644 index ca4d289be3..0000000000 --- a/packages/common/y-octo/node/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -authors = ["DarkSky "] -edition = "2021" -license = "MIT" -name = "y-octo-node" -repository = "https://github.com/toeverything/y-octo" -version = "0.0.1" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[lib] -crate-type = ["cdylib"] - -[dependencies] -anyhow = { workspace = true } -napi = { workspace = true, features = ["anyhow", "napi4"] } -napi-derive = { workspace = true } -y-octo = { workspace = true, features = ["events", "large_refs"] } - -[build-dependencies] -napi-build = { workspace = true } diff --git a/packages/common/y-octo/node/build.rs b/packages/common/y-octo/node/build.rs deleted file mode 100644 index bbfc9e4b9e..0000000000 --- a/packages/common/y-octo/node/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - napi_build::setup(); -} diff --git a/packages/common/y-octo/node/index.d.ts b/packages/common/y-octo/node/index.d.ts deleted file mode 100644 index 2d2d2f8831..0000000000 --- a/packages/common/y-octo/node/index.d.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* auto-generated by NAPI-RS */ -/* eslint-disable */ -export declare class Doc { - constructor(clientId?: number | undefined | null) - get clientId(): number - get guid(): string - get keys(): Array - getOrCreateArray(key: string): YArray - getOrCreateText(key: string): YText - getOrCreateMap(key: string): YMap - createArray(): YArray - createText(): YText - createMap(): YMap - applyUpdate(update: Uint8Array): void - encodeStateAsUpdateV1(state?: Uint8Array | undefined | null): Uint8Array - gc(): void - onUpdate(callback: (result: Uint8Array) => void): void -} - -export declare class YArray { - constructor() - get length(): number - get isEmpty(): boolean - get(index: number): T - insert(index: number, value: YArray | YMap | YText | boolean | number | string | Record | null | undefined): void - remove(index: number, len: number): void - toJson(): JsArray -} - -export declare class YMap { - get length(): 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 - toJson(): object -} - -export declare class YText { - constructor() - get len(): number - get isEmpty(): boolean - insert(index: number, str: string): void - remove(index: number, len: number): void - get length(): number - toString(): string -} diff --git a/packages/common/y-octo/node/index.js b/packages/common/y-octo/node/index.js deleted file mode 100644 index 3ae63ea69d..0000000000 --- a/packages/common/y-octo/node/index.js +++ /dev/null @@ -1,783 +0,0 @@ -// prettier-ignore -/* eslint-disable */ -// @ts-nocheck -/* auto-generated by NAPI-RS */ - -const { readFileSync } = require('node:fs') -let nativeBinding = null; -const loadErrors = []; - -const isMusl = () => { - let musl = false; - if (process.platform === 'linux') { - musl = isMuslFromFilesystem(); - if (musl === null) { - musl = isMuslFromReport(); - } - if (musl === null) { - musl = isMuslFromChildProcess(); - } - } - return musl; -}; - -const isFileMusl = f => f.includes('libc.musl-') || f.includes('ld-musl-'); - -const isMuslFromFilesystem = () => { - try { - return readFileSync('/usr/bin/ldd', 'utf-8').includes('musl'); - } catch { - return null; - } -}; - -const isMuslFromReport = () => { - let report = null; - if (typeof process.report?.getReport === 'function') { - process.report.excludeNetwork = true; - report = process.report.getReport(); - } - if (!report) { - return null; - } - if (report.header && report.header.glibcVersionRuntime) { - return false; - } - if (Array.isArray(report.sharedObjects)) { - if (report.sharedObjects.some(isFileMusl)) { - return true; - } - } - return false; -}; - -const isMuslFromChildProcess = () => { - try { - return require('child_process') - .execSync('ldd --version', { encoding: 'utf8' }) - .includes('musl'); - } catch (e) { - // If we reach this case, we don't know if the system is musl or not, so is better to just fallback to false - return false; - } -}; - -function requireNative() { - if (process.env.NAPI_RS_NATIVE_LIBRARY_PATH) { - try { - return require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH); - } catch (err) { - loadErrors.push(err); - } - } else if (process.platform === 'android') { - if (process.arch === 'arm64') { - try { - return require('./y-octo.android-arm64.node'); - } catch (e) { - loadErrors.push(e); - } - try { - const binding = require('@y-octo/node-android-arm64'); - const bindingPackageVersion = - require('@y-octo/node-android-arm64/package.json').version; - if ( - bindingPackageVersion !== '0.25.7' && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' - ) { - throw new Error( - `Native binding package version mismatch, expected 0.25.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.` - ); - } - return binding; - } catch (e) { - loadErrors.push(e); - } - } else if (process.arch === 'arm') { - try { - return require('./y-octo.android-arm-eabi.node'); - } catch (e) { - loadErrors.push(e); - } - try { - const binding = require('@y-octo/node-android-arm-eabi'); - const bindingPackageVersion = - require('@y-octo/node-android-arm-eabi/package.json').version; - if ( - bindingPackageVersion !== '0.25.7' && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' - ) { - throw new Error( - `Native binding package version mismatch, expected 0.25.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.` - ); - } - return binding; - } catch (e) { - loadErrors.push(e); - } - } else { - loadErrors.push( - new Error(`Unsupported architecture on Android ${process.arch}`) - ); - } - } else if (process.platform === 'win32') { - if (process.arch === 'x64') { - if ( - process.config?.variables?.shlib_suffix === 'dll.a' || - process.config?.variables?.node_target_type === 'shared_library' - ) { - try { - return require('./y-octo.win32-x64-gnu.node'); - } catch (e) { - loadErrors.push(e); - } - try { - const binding = require('@y-octo/node-win32-x64-gnu'); - const bindingPackageVersion = - require('@y-octo/node-win32-x64-gnu/package.json').version; - if ( - bindingPackageVersion !== '0.25.7' && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' - ) { - throw new Error( - `Native binding package version mismatch, expected 0.25.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.` - ); - } - return binding; - } catch (e) { - loadErrors.push(e); - } - } else { - try { - return require('./y-octo.win32-x64-msvc.node'); - } catch (e) { - loadErrors.push(e); - } - try { - const binding = require('@y-octo/node-win32-x64-msvc'); - const bindingPackageVersion = - require('@y-octo/node-win32-x64-msvc/package.json').version; - if ( - bindingPackageVersion !== '0.25.7' && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' - ) { - throw new Error( - `Native binding package version mismatch, expected 0.25.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.` - ); - } - return binding; - } catch (e) { - loadErrors.push(e); - } - } - } else if (process.arch === 'ia32') { - try { - return require('./y-octo.win32-ia32-msvc.node'); - } catch (e) { - loadErrors.push(e); - } - try { - const binding = require('@y-octo/node-win32-ia32-msvc'); - const bindingPackageVersion = - require('@y-octo/node-win32-ia32-msvc/package.json').version; - if ( - bindingPackageVersion !== '0.25.7' && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' - ) { - throw new Error( - `Native binding package version mismatch, expected 0.25.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.` - ); - } - return binding; - } catch (e) { - loadErrors.push(e); - } - } else if (process.arch === 'arm64') { - try { - return require('./y-octo.win32-arm64-msvc.node'); - } catch (e) { - loadErrors.push(e); - } - try { - const binding = require('@y-octo/node-win32-arm64-msvc'); - const bindingPackageVersion = - require('@y-octo/node-win32-arm64-msvc/package.json').version; - if ( - bindingPackageVersion !== '0.25.7' && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' - ) { - throw new Error( - `Native binding package version mismatch, expected 0.25.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.` - ); - } - return binding; - } catch (e) { - loadErrors.push(e); - } - } else { - loadErrors.push( - new Error(`Unsupported architecture on Windows: ${process.arch}`) - ); - } - } else if (process.platform === 'darwin') { - try { - return require('./y-octo.darwin-universal.node'); - } catch (e) { - loadErrors.push(e); - } - try { - const binding = require('@y-octo/node-darwin-universal'); - const bindingPackageVersion = - require('@y-octo/node-darwin-universal/package.json').version; - if ( - bindingPackageVersion !== '0.25.7' && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' - ) { - throw new Error( - `Native binding package version mismatch, expected 0.25.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.` - ); - } - return binding; - } catch (e) { - loadErrors.push(e); - } - if (process.arch === 'x64') { - try { - return require('./y-octo.darwin-x64.node'); - } catch (e) { - loadErrors.push(e); - } - try { - const binding = require('@y-octo/node-darwin-x64'); - const bindingPackageVersion = - require('@y-octo/node-darwin-x64/package.json').version; - if ( - bindingPackageVersion !== '0.25.7' && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' - ) { - throw new Error( - `Native binding package version mismatch, expected 0.25.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.` - ); - } - return binding; - } catch (e) { - loadErrors.push(e); - } - } else if (process.arch === 'arm64') { - try { - return require('./y-octo.darwin-arm64.node'); - } catch (e) { - loadErrors.push(e); - } - try { - const binding = require('@y-octo/node-darwin-arm64'); - const bindingPackageVersion = - require('@y-octo/node-darwin-arm64/package.json').version; - if ( - bindingPackageVersion !== '0.25.7' && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' - ) { - throw new Error( - `Native binding package version mismatch, expected 0.25.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.` - ); - } - return binding; - } catch (e) { - loadErrors.push(e); - } - } else { - loadErrors.push( - new Error(`Unsupported architecture on macOS: ${process.arch}`) - ); - } - } else if (process.platform === 'freebsd') { - if (process.arch === 'x64') { - try { - return require('./y-octo.freebsd-x64.node'); - } catch (e) { - loadErrors.push(e); - } - try { - const binding = require('@y-octo/node-freebsd-x64'); - const bindingPackageVersion = - require('@y-octo/node-freebsd-x64/package.json').version; - if ( - bindingPackageVersion !== '0.25.7' && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' - ) { - throw new Error( - `Native binding package version mismatch, expected 0.25.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.` - ); - } - return binding; - } catch (e) { - loadErrors.push(e); - } - } else if (process.arch === 'arm64') { - try { - return require('./y-octo.freebsd-arm64.node'); - } catch (e) { - loadErrors.push(e); - } - try { - const binding = require('@y-octo/node-freebsd-arm64'); - const bindingPackageVersion = - require('@y-octo/node-freebsd-arm64/package.json').version; - if ( - bindingPackageVersion !== '0.25.7' && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' - ) { - throw new Error( - `Native binding package version mismatch, expected 0.25.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.` - ); - } - return binding; - } catch (e) { - loadErrors.push(e); - } - } else { - loadErrors.push( - new Error(`Unsupported architecture on FreeBSD: ${process.arch}`) - ); - } - } else if (process.platform === 'linux') { - if (process.arch === 'x64') { - if (isMusl()) { - try { - return require('./y-octo.linux-x64-musl.node'); - } catch (e) { - loadErrors.push(e); - } - try { - const binding = require('@y-octo/node-linux-x64-musl'); - const bindingPackageVersion = - require('@y-octo/node-linux-x64-musl/package.json').version; - if ( - bindingPackageVersion !== '0.25.7' && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' - ) { - throw new Error( - `Native binding package version mismatch, expected 0.25.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.` - ); - } - return binding; - } catch (e) { - loadErrors.push(e); - } - } else { - try { - return require('./y-octo.linux-x64-gnu.node'); - } catch (e) { - loadErrors.push(e); - } - try { - const binding = require('@y-octo/node-linux-x64-gnu'); - const bindingPackageVersion = - require('@y-octo/node-linux-x64-gnu/package.json').version; - if ( - bindingPackageVersion !== '0.25.7' && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' - ) { - throw new Error( - `Native binding package version mismatch, expected 0.25.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.` - ); - } - return binding; - } catch (e) { - loadErrors.push(e); - } - } - } else if (process.arch === 'arm64') { - if (isMusl()) { - try { - return require('./y-octo.linux-arm64-musl.node'); - } catch (e) { - loadErrors.push(e); - } - try { - const binding = require('@y-octo/node-linux-arm64-musl'); - const bindingPackageVersion = - require('@y-octo/node-linux-arm64-musl/package.json').version; - if ( - bindingPackageVersion !== '0.25.7' && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' - ) { - throw new Error( - `Native binding package version mismatch, expected 0.25.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.` - ); - } - return binding; - } catch (e) { - loadErrors.push(e); - } - } else { - try { - return require('./y-octo.linux-arm64-gnu.node'); - } catch (e) { - loadErrors.push(e); - } - try { - const binding = require('@y-octo/node-linux-arm64-gnu'); - const bindingPackageVersion = - require('@y-octo/node-linux-arm64-gnu/package.json').version; - if ( - bindingPackageVersion !== '0.25.7' && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' - ) { - throw new Error( - `Native binding package version mismatch, expected 0.25.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.` - ); - } - return binding; - } catch (e) { - loadErrors.push(e); - } - } - } else if (process.arch === 'arm') { - if (isMusl()) { - try { - return require('./y-octo.linux-arm-musleabihf.node'); - } catch (e) { - loadErrors.push(e); - } - try { - const binding = require('@y-octo/node-linux-arm-musleabihf'); - const bindingPackageVersion = - require('@y-octo/node-linux-arm-musleabihf/package.json').version; - if ( - bindingPackageVersion !== '0.25.7' && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' - ) { - throw new Error( - `Native binding package version mismatch, expected 0.25.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.` - ); - } - return binding; - } catch (e) { - loadErrors.push(e); - } - } else { - try { - return require('./y-octo.linux-arm-gnueabihf.node'); - } catch (e) { - loadErrors.push(e); - } - try { - const binding = require('@y-octo/node-linux-arm-gnueabihf'); - const bindingPackageVersion = - require('@y-octo/node-linux-arm-gnueabihf/package.json').version; - if ( - bindingPackageVersion !== '0.25.7' && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' - ) { - throw new Error( - `Native binding package version mismatch, expected 0.25.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.` - ); - } - return binding; - } catch (e) { - loadErrors.push(e); - } - } - } else if (process.arch === 'loong64') { - if (isMusl()) { - try { - return require('./y-octo.linux-loong64-musl.node'); - } catch (e) { - loadErrors.push(e); - } - try { - const binding = require('@y-octo/node-linux-loong64-musl'); - const bindingPackageVersion = - require('@y-octo/node-linux-loong64-musl/package.json').version; - if ( - bindingPackageVersion !== '0.25.7' && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' - ) { - throw new Error( - `Native binding package version mismatch, expected 0.25.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.` - ); - } - return binding; - } catch (e) { - loadErrors.push(e); - } - } else { - try { - return require('./y-octo.linux-loong64-gnu.node'); - } catch (e) { - loadErrors.push(e); - } - try { - const binding = require('@y-octo/node-linux-loong64-gnu'); - const bindingPackageVersion = - require('@y-octo/node-linux-loong64-gnu/package.json').version; - if ( - bindingPackageVersion !== '0.25.7' && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' - ) { - throw new Error( - `Native binding package version mismatch, expected 0.25.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.` - ); - } - return binding; - } catch (e) { - loadErrors.push(e); - } - } - } else if (process.arch === 'riscv64') { - if (isMusl()) { - try { - return require('./y-octo.linux-riscv64-musl.node'); - } catch (e) { - loadErrors.push(e); - } - try { - const binding = require('@y-octo/node-linux-riscv64-musl'); - const bindingPackageVersion = - require('@y-octo/node-linux-riscv64-musl/package.json').version; - if ( - bindingPackageVersion !== '0.25.7' && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' - ) { - throw new Error( - `Native binding package version mismatch, expected 0.25.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.` - ); - } - return binding; - } catch (e) { - loadErrors.push(e); - } - } else { - try { - return require('./y-octo.linux-riscv64-gnu.node'); - } catch (e) { - loadErrors.push(e); - } - try { - const binding = require('@y-octo/node-linux-riscv64-gnu'); - const bindingPackageVersion = - require('@y-octo/node-linux-riscv64-gnu/package.json').version; - if ( - bindingPackageVersion !== '0.25.7' && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' - ) { - throw new Error( - `Native binding package version mismatch, expected 0.25.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.` - ); - } - return binding; - } catch (e) { - loadErrors.push(e); - } - } - } else if (process.arch === 'ppc64') { - try { - return require('./y-octo.linux-ppc64-gnu.node'); - } catch (e) { - loadErrors.push(e); - } - try { - const binding = require('@y-octo/node-linux-ppc64-gnu'); - const bindingPackageVersion = - require('@y-octo/node-linux-ppc64-gnu/package.json').version; - if ( - bindingPackageVersion !== '0.25.7' && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' - ) { - throw new Error( - `Native binding package version mismatch, expected 0.25.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.` - ); - } - return binding; - } catch (e) { - loadErrors.push(e); - } - } else if (process.arch === 's390x') { - try { - return require('./y-octo.linux-s390x-gnu.node'); - } catch (e) { - loadErrors.push(e); - } - try { - const binding = require('@y-octo/node-linux-s390x-gnu'); - const bindingPackageVersion = - require('@y-octo/node-linux-s390x-gnu/package.json').version; - if ( - bindingPackageVersion !== '0.25.7' && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' - ) { - throw new Error( - `Native binding package version mismatch, expected 0.25.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.` - ); - } - return binding; - } catch (e) { - loadErrors.push(e); - } - } else { - loadErrors.push( - new Error(`Unsupported architecture on Linux: ${process.arch}`) - ); - } - } else if (process.platform === 'openharmony') { - if (process.arch === 'arm64') { - try { - return require('./y-octo.openharmony-arm64.node'); - } catch (e) { - loadErrors.push(e); - } - try { - const binding = require('@y-octo/node-openharmony-arm64'); - const bindingPackageVersion = - require('@y-octo/node-openharmony-arm64/package.json').version; - if ( - bindingPackageVersion !== '0.25.7' && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' - ) { - throw new Error( - `Native binding package version mismatch, expected 0.25.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.` - ); - } - return binding; - } catch (e) { - loadErrors.push(e); - } - } else if (process.arch === 'x64') { - try { - return require('./y-octo.openharmony-x64.node'); - } catch (e) { - loadErrors.push(e); - } - try { - const binding = require('@y-octo/node-openharmony-x64'); - const bindingPackageVersion = - require('@y-octo/node-openharmony-x64/package.json').version; - if ( - bindingPackageVersion !== '0.25.7' && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' - ) { - throw new Error( - `Native binding package version mismatch, expected 0.25.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.` - ); - } - return binding; - } catch (e) { - loadErrors.push(e); - } - } else if (process.arch === 'arm') { - try { - return require('./y-octo.openharmony-arm.node'); - } catch (e) { - loadErrors.push(e); - } - try { - const binding = require('@y-octo/node-openharmony-arm'); - const bindingPackageVersion = - require('@y-octo/node-openharmony-arm/package.json').version; - if ( - bindingPackageVersion !== '0.25.7' && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK && - process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' - ) { - throw new Error( - `Native binding package version mismatch, expected 0.25.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.` - ); - } - return binding; - } catch (e) { - loadErrors.push(e); - } - } else { - loadErrors.push( - new Error(`Unsupported architecture on OpenHarmony: ${process.arch}`) - ); - } - } else { - loadErrors.push( - new Error( - `Unsupported OS: ${process.platform}, architecture: ${process.arch}` - ) - ); - } -} - -nativeBinding = requireNative(); - -if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) { - let wasiBinding = null; - let wasiBindingError = null; - try { - wasiBinding = require('./y-octo.wasi.cjs'); - nativeBinding = wasiBinding; - } catch (err) { - if (process.env.NAPI_RS_FORCE_WASI) { - wasiBindingError = err; - } - } - if (!nativeBinding) { - try { - wasiBinding = require('@y-octo/node-wasm32-wasi'); - nativeBinding = wasiBinding; - } catch (err) { - if (process.env.NAPI_RS_FORCE_WASI) { - wasiBindingError.cause = err; - loadErrors.push(err); - } - } - } - if (process.env.NAPI_RS_FORCE_WASI === 'error' && !wasiBinding) { - const error = new Error( - 'WASI binding not found and NAPI_RS_FORCE_WASI is set to error' - ); - error.cause = wasiBindingError; - throw error; - } -} - -if (!nativeBinding) { - if (loadErrors.length > 0) { - throw new Error( - `Cannot find native binding. ` + - `npm has a bug related to optional dependencies (https://github.com/npm/cli/issues/4828). ` + - 'Please try `npm i` again after removing both package-lock.json and node_modules directory.', - { - cause: loadErrors.reduce((err, cur) => { - cur.cause = err; - return cur; - }), - } - ); - } - throw new Error(`Failed to load native binding`); -} - -module.exports = nativeBinding; -module.exports.Doc = nativeBinding.Doc; -module.exports.YArray = nativeBinding.YArray; -module.exports.YMap = nativeBinding.YMap; -module.exports.YText = nativeBinding.YText; diff --git a/packages/common/y-octo/node/package.json b/packages/common/y-octo/node/package.json deleted file mode 100644 index a84c964134..0000000000 --- a/packages/common/y-octo/node/package.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "name": "@y-octo/node", - "private": true, - "main": "index.js", - "types": "index.d.ts", - "napi": { - "binaryName": "y-octo", - "targets": [ - "x86_64-unknown-linux-gnu", - "x86_64-apple-darwin", - "x86_64-pc-windows-msvc", - "aarch64-apple-darwin", - "aarch64-pc-windows-msvc", - "aarch64-unknown-linux-gnu", - "x86_64-unknown-linux-musl", - "aarch64-unknown-linux-musl" - ], - "ts": { - "constEnum": false - } - }, - "license": "MIT", - "devDependencies": { - "@napi-rs/cli": "3.0.0", - "@types/node": "^22.14.1", - "@types/prompts": "^2.4.9", - "c8": "^10.1.3", - "prompts": "^2.4.2", - "ts-node": "^10.9.2", - "typescript": "^5.8.3", - "yjs": "^13.6.27" - }, - "engines": { - "node": ">= 10" - }, - "scripts": { - "artifacts": "napi artifacts", - "build": "napi build --platform --release --no-const-enum", - "build:debug": "napi build --platform --no-const-enum", - "universal": "napi universal", - "test": "NODE_NO_WARNINGS=1 node ./scripts/run-test.mts all", - "test:watch": "yarn exec ts-node-esm ./scripts/run-test.ts all --watch", - "test:coverage": "NODE_OPTIONS=\"--loader ts-node/esm\" c8 node ./scripts/run-test.mts all", - "version": "napi version" - }, - "version": "0.25.7", - "sharedConfig": { - "nodeArgs": [ - "--loader", - "ts-node/esm", - "--es-module-specifier-resolution=node" - ], - "env": { - "TS_NODE_TRANSPILE_ONLY": "1", - "TS_NODE_PROJECT": "./tsconfig.json", - "NODE_ENV": "development", - "DEBUG": "napi:*" - } - }, - "c8": { - "reporter": [ - "text", - "lcov" - ], - "report-dir": ".coverage", - "exclude": [ - "scripts", - "node_modules", - "**/*.spec.ts" - ] - } -} diff --git a/packages/common/y-octo/node/scripts/run-test.mts b/packages/common/y-octo/node/scripts/run-test.mts deleted file mode 100755 index 87c326c93a..0000000000 --- a/packages/common/y-octo/node/scripts/run-test.mts +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env ts-node-esm -import { resolve } from 'node:path'; -import { spawn } from 'node:child_process'; -import { readdir } from 'node:fs/promises'; -import * as process from 'node:process'; -import { fileURLToPath } from 'node:url'; - -import prompts from 'prompts'; - -import pkg from '../package.json' with { type: 'json' }; -const root = fileURLToPath(new URL('..', import.meta.url)); -const testDir = resolve(root, 'tests'); -const files = await readdir(testDir); - -const watchMode = process.argv.includes('--watch'); - -const sharedArgs = [ - ...pkg.sharedConfig.nodeArgs, - '--test', - watchMode ? '--watch' : '', -]; - -const env = { - ...pkg.sharedConfig.env, - PATH: process.env.PATH, - NODE_ENV: 'test', - NODE_NO_WARNINGS: '1', -}; - -if (process.argv[2] === 'all') { - const cp = spawn( - 'node', - [...sharedArgs, ...files.map(f => resolve(testDir, f))], - { - cwd: root, - env, - stdio: 'inherit', - shell: true, - } - ); - cp.on('exit', code => { - process.exit(code ?? 0); - }); -} else { - const result = await prompts([ - { - type: 'select', - name: 'file', - message: 'Select a file to run', - choices: files.map(file => ({ - title: file, - value: file, - })), - initial: 1, - }, - ]); - - const target = resolve(testDir, result.file); - - const cp = spawn( - 'node', - [ - ...sharedArgs, - '--test-reporter=spec', - '--test-reporter-destination=stdout', - target, - ], - { - cwd: root, - env, - stdio: 'inherit', - shell: true, - } - ); - cp.on('exit', code => { - process.exit(code ?? 0); - }); -} diff --git a/packages/common/y-octo/node/src/array.rs b/packages/common/y-octo/node/src/array.rs deleted file mode 100644 index 98af181461..0000000000 --- a/packages/common/y-octo/node/src/array.rs +++ /dev/null @@ -1,163 +0,0 @@ -use napi::{ - bindgen_prelude::{Array as JsArray, Env, JsObjectValue, JsValue, Null, ToNapiValue, Unknown}, - ValueType, -}; -use y_octo::{Any, Array, Value}; - -use super::*; - -#[napi] -pub struct YArray { - pub(crate) array: Array, -} - -#[napi] -impl YArray { - #[allow(clippy::new_without_default)] - #[napi(constructor)] - pub fn new() -> Self { - unimplemented!() - } - - pub(crate) fn inner_new(array: Array) -> Self { - Self { array } - } - - #[napi(getter)] - pub fn length(&self) -> i64 { - self.array.len() as i64 - } - - #[napi(getter)] - pub fn is_empty(&self) -> bool { - self.array.is_empty() - } - - #[napi(ts_generic_types = "T = unknown", ts_return_type = "T")] - pub fn get<'a>(&'a self, env: &'a Env, index: i64) -> Result> { - if let Some(value) = self.array.get(index as u64) { - match value { - Value::Any(any) => get_js_unknown_from_any(env, any).map(MixedYType::D), - Value::Array(array) => Ok(MixedYType::A(YArray::inner_new(array))), - Value::Map(map) => Ok(MixedYType::B(YMap::inner_new(map))), - Value::Text(text) => Ok(MixedYType::C(YText::inner_new(text))), - _ => Null.into_unknown(env).map(MixedYType::D), - } - .map_err(anyhow::Error::from) - } else { - Ok(MixedYType::D(Null.into_unknown(env)?)) - } - } - - #[napi( - ts_args_type = "index: number, value: YArray | YMap | YText | boolean | number | string | \ - Record | null | undefined" - )] - pub fn insert(&mut self, index: i64, value: MixedRefYType) -> Result<()> { - match value { - MixedRefYType::A(array) => self - .array - .insert(index as u64, array.array.clone()) - .map_err(anyhow::Error::from), - MixedRefYType::B(map) => self - .array - .insert(index as u64, map.map.clone()) - .map_err(anyhow::Error::from), - MixedRefYType::C(text) => self - .array - .insert(index as u64, text.text.clone()) - .map_err(anyhow::Error::from), - MixedRefYType::D(unknown) => match unknown.get_type() { - Ok(value_type) => match value_type { - ValueType::Undefined | ValueType::Null => self - .array - .insert(index as u64, Any::Null) - .map_err(anyhow::Error::from), - ValueType::Boolean => match unsafe { unknown.cast::() } { - Ok(boolean) => self - .array - .insert(index as u64, boolean) - .map_err(anyhow::Error::from), - Err(e) => Err(anyhow::Error::new(e).context("Failed to coerce value to boolean")), - }, - ValueType::Number => match unknown.coerce_to_number().and_then(|v| v.get_double()) { - Ok(number) => self - .array - .insert(index as u64, number) - .map_err(anyhow::Error::from), - Err(e) => Err(anyhow::Error::new(e).context("Failed to coerce value to number")), - }, - ValueType::String => { - match unknown - .coerce_to_string() - .and_then(|v| v.into_utf8()) - .and_then(|s| s.as_str().map(|s| s.to_string())) - { - Ok(string) => self - .array - .insert(index as u64, string) - .map_err(anyhow::Error::from), - Err(e) => Err(anyhow::Error::new(e).context("Failed to coerce value to string")), - } - } - ValueType::Object => match unknown - .coerce_to_object() - .and_then(|o| o.get_array_length().map(|l| (o, l))) - { - Ok((object, length)) => { - for i in 0..length { - if let Ok(any) = object - .get_element::(i) - .and_then(get_any_from_js_unknown) - { - self - .array - .insert(index as u64 + i as u64, Value::Any(any)) - .map_err(anyhow::Error::from)?; - } - } - Ok(()) - } - Err(e) => Err(anyhow::Error::new(e).context("Failed to coerce value to object")), - }, - ValueType::BigInt => Err(anyhow::Error::msg("BigInt values are not supported")), - ValueType::Symbol => Err(anyhow::Error::msg("Symbol values are not supported")), - ValueType::Function => Err(anyhow::Error::msg("Function values are not supported")), - ValueType::External => Err(anyhow::Error::msg("External values are not supported")), - ValueType::Unknown => Err(anyhow::Error::msg("Unknown values are not supported")), - }, - Err(e) => Err(anyhow::Error::from(e)), - }, - } - } - - #[napi] - pub fn remove(&mut self, index: i64, len: i64) -> Result<()> { - self - .array - .remove(index as u64, len as u64) - .map_err(anyhow::Error::from) - } - - #[napi] - pub fn to_json<'env>(&'env self, env: &'env Env) -> Result> { - let mut js_array = env.create_array(0)?; - for value in self.array.iter() { - js_array.insert(get_js_unknown_from_value(env, value)?)?; - } - Ok(js_array) - } -} - -#[cfg(test)] -mod tests { - - use super::*; - - #[test] - fn test_array_init() { - let doc = Doc::new(None); - let array = doc.get_or_create_array("array".into()).unwrap(); - assert_eq!(array.length(), 0); - } -} diff --git a/packages/common/y-octo/node/src/doc.rs b/packages/common/y-octo/node/src/doc.rs deleted file mode 100644 index 5f163a3277..0000000000 --- a/packages/common/y-octo/node/src/doc.rs +++ /dev/null @@ -1,176 +0,0 @@ -use napi::{ - bindgen_prelude::Uint8Array, - threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode}, -}; -use y_octo::{CrdtRead, Doc as YDoc, History, RawDecoder, StateVector}; - -use super::*; - -#[napi] -pub struct Doc { - doc: YDoc, -} - -#[napi] -impl Doc { - #[napi(constructor)] - pub fn new(client_id: Option) -> Self { - Self { - doc: if let Some(client_id) = client_id { - YDoc::with_client(client_id as u64) - } else { - YDoc::default() - }, - } - } - - #[napi(getter)] - pub fn client_id(&self) -> i64 { - self.doc.client() as i64 - } - - #[napi(getter)] - pub fn guid(&self) -> &str { - self.doc.guid() - } - - #[napi(getter)] - pub fn keys(&self) -> Vec { - self.doc.keys() - } - - #[napi] - pub fn get_or_create_array(&self, key: String) -> Result { - self - .doc - .get_or_create_array(key) - .map(YArray::inner_new) - .map_err(anyhow::Error::from) - } - - #[napi] - pub fn get_or_create_text(&self, key: String) -> Result { - self - .doc - .get_or_create_text(key) - .map(YText::inner_new) - .map_err(anyhow::Error::from) - } - - #[napi] - pub fn get_or_create_map(&self, key: String) -> Result { - self - .doc - .get_or_create_map(key) - .map(YMap::inner_new) - .map_err(anyhow::Error::from) - } - - #[napi] - pub fn create_array(&self) -> Result { - self - .doc - .create_array() - .map(YArray::inner_new) - .map_err(anyhow::Error::from) - } - - #[napi] - pub fn create_text(&self) -> Result { - self - .doc - .create_text() - .map(YText::inner_new) - .map_err(anyhow::Error::from) - } - - #[napi] - pub fn create_map(&self) -> Result { - self - .doc - .create_map() - .map(YMap::inner_new) - .map_err(anyhow::Error::from) - } - - #[napi] - pub fn apply_update(&mut self, update: &[u8]) -> Result<()> { - self.doc.apply_update_from_binary_v1(update)?; - - Ok(()) - } - - #[napi] - pub fn encode_state_as_update_v1(&self, state: Option<&[u8]>) -> Result { - let result = match state { - Some(state) => { - let mut decoder = RawDecoder::new(state); - let state = StateVector::read(&mut decoder)?; - self.doc.encode_state_as_update_v1(&state) - } - None => self.doc.encode_update_v1(), - }; - - result.map(|v| v.into()).map_err(anyhow::Error::from) - } - - #[napi] - pub fn gc(&self) -> Result<()> { - self.doc.gc().map_err(anyhow::Error::from) - } - - #[napi(ts_args_type = "callback: (result: Uint8Array) => void")] - pub fn on_update(&mut self, callback: ThreadsafeFunction) -> Result<()> { - let callback = move |update: &[u8], _h: &[History]| { - callback.call( - Ok(update.to_vec().into()), - ThreadsafeFunctionCallMode::Blocking, - ); - }; - self.doc.subscribe(Box::new(callback)); - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_doc_client() { - let client_id = 1; - let doc = Doc::new(Some(client_id)); - assert_eq!(doc.client_id(), 1); - } - - #[test] - fn test_doc_guid() { - let doc = Doc::new(None); - assert_eq!(doc.guid().len(), 21); - } - - #[test] - fn test_create_array() { - let doc = Doc::new(None); - let array = doc.get_or_create_array("array".into()).unwrap(); - assert_eq!(array.length(), 0); - } - - #[test] - fn test_create_text() { - let doc = Doc::new(None); - let text = doc.get_or_create_text("text".into()).unwrap(); - assert_eq!(text.len(), 0); - } - - #[test] - fn test_keys() { - let doc = Doc::new(None); - doc.get_or_create_array("array".into()).unwrap(); - doc.get_or_create_text("text".into()).unwrap(); - doc.get_or_create_map("map".into()).unwrap(); - let mut keys = doc.keys(); - keys.sort(); - assert_eq!(keys, vec!["array", "map", "text"]); - } -} diff --git a/packages/common/y-octo/node/src/lib.rs b/packages/common/y-octo/node/src/lib.rs deleted file mode 100644 index a5c693c3fb..0000000000 --- a/packages/common/y-octo/node/src/lib.rs +++ /dev/null @@ -1,17 +0,0 @@ -use anyhow::Result; -use napi_derive::napi; - -mod array; -mod doc; -mod map; -mod text; -mod utils; - -pub use array::YArray; -pub use doc::Doc; -pub use map::YMap; -pub use text::YText; -use utils::{ - get_any_from_js_object, get_any_from_js_unknown, get_js_unknown_from_any, - get_js_unknown_from_value, MixedRefYType, MixedYType, -}; diff --git a/packages/common/y-octo/node/src/map.rs b/packages/common/y-octo/node/src/map.rs deleted file mode 100644 index 6de366c088..0000000000 --- a/packages/common/y-octo/node/src/map.rs +++ /dev/null @@ -1,128 +0,0 @@ -use napi::bindgen_prelude::{Env, JsValue, Null, Object, ToNapiValue, ValueType}; -use y_octo::{Any, Map, Value}; - -use super::*; - -#[napi] -pub struct YMap { - pub(crate) map: Map, -} - -#[napi] -impl YMap { - pub(crate) fn inner_new(map: Map) -> Self { - Self { map } - } - - #[napi(getter)] - pub fn length(&self) -> i64 { - self.map.len() as i64 - } - - #[napi(getter)] - pub fn is_empty(&self) -> bool { - self.map.is_empty() - } - - #[napi(ts_generic_types = "T = unknown", ts_return_type = "T")] - pub fn get<'a>(&'a self, env: &'a Env, key: String) -> Result> { - if let Some(value) = self.map.get(&key) { - match value { - Value::Any(any) => get_js_unknown_from_any(env, any).map(MixedYType::D), - Value::Array(array) => Ok(MixedYType::A(YArray::inner_new(array))), - Value::Map(map) => Ok(MixedYType::B(YMap::inner_new(map))), - Value::Text(text) => Ok(MixedYType::C(YText::inner_new(text))), - _ => Null.into_unknown(env).map(MixedYType::D), - } - .map_err(anyhow::Error::from) - } else { - Ok(MixedYType::D(Null.into_unknown(env)?)) - } - } - - #[napi( - ts_args_type = "key: string, value: YArray | YMap | YText | boolean | number | string | \ - Record | null | undefined" - )] - pub fn set(&mut self, 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::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) - } - ValueType::Boolean => match unsafe { unknown.cast::() } { - Ok(boolean) => self.map.insert(key, boolean).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), - Err(e) => Err(anyhow::Error::from(e).context("Failed to coerce value to number")), - }, - ValueType::String => { - match unknown - .coerce_to_string() - .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), - 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), - Err(e) => Err(anyhow::Error::from(e).context("Failed to coerce value to object")), - }, - ValueType::BigInt => Err(anyhow::Error::msg("BigInt values are not supported")), - ValueType::Symbol => Err(anyhow::Error::msg("Symbol values are not supported")), - ValueType::Function => Err(anyhow::Error::msg("Function values are not supported")), - ValueType::External => Err(anyhow::Error::msg("External values are not supported")), - ValueType::Unknown => Err(anyhow::Error::msg("Unknown values are not supported")), - }, - Err(e) => Err(anyhow::Error::from(e)), - }, - } - } - - #[napi] - pub fn remove(&mut self, key: String) { - self.map.remove(&key); - } - - #[napi] - pub fn to_json(&self, env: Env) -> Result> { - let mut js_object = Object::new(&env)?; - for (key, value) in self.map.iter() { - js_object.set(key, get_js_unknown_from_value(&env, value))?; - } - Ok(js_object) - } -} - -#[cfg(test)] -mod tests { - - use super::*; - - #[test] - fn test_map_init() { - let doc = Doc::new(None); - let text = doc.get_or_create_map("map".into()).unwrap(); - assert_eq!(text.length(), 0); - } -} diff --git a/packages/common/y-octo/node/src/text.rs b/packages/common/y-octo/node/src/text.rs deleted file mode 100644 index ca6478698a..0000000000 --- a/packages/common/y-octo/node/src/text.rs +++ /dev/null @@ -1,82 +0,0 @@ -use y_octo::Text; - -use super::*; - -#[napi] -pub struct YText { - pub(crate) text: Text, -} - -#[napi] -impl YText { - #[allow(clippy::new_without_default)] - #[napi(constructor)] - pub fn new() -> Self { - unimplemented!() - } - - pub(crate) fn inner_new(text: Text) -> Self { - Self { text } - } - - #[napi(getter)] - pub fn len(&self) -> i64 { - self.text.len() as i64 - } - - #[napi(getter)] - pub fn is_empty(&self) -> bool { - self.text.is_empty() - } - - #[napi] - pub fn insert(&mut self, index: i64, str: String) -> Result<()> { - self - .text - .insert(index as u64, str) - .map_err(anyhow::Error::from) - } - - #[napi] - pub fn remove(&mut self, index: i64, len: i64) -> Result<()> { - self - .text - .remove(index as u64, len as u64) - .map_err(anyhow::Error::from) - } - - #[napi(getter)] - pub fn length(&self) -> i64 { - self.text.len() as i64 - } - - #[allow(clippy::inherent_to_string)] - #[napi] - pub fn to_string(&self) -> String { - self.text.to_string() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_text_init() { - let doc = Doc::new(None); - let text = doc.get_or_create_text("text".into()).unwrap(); - assert_eq!(text.len(), 0); - } - - #[test] - fn test_text_edit() { - let doc = Doc::new(None); - let mut text = doc.get_or_create_text("text".into()).unwrap(); - text.insert(0, "hello".into()).unwrap(); - assert_eq!(text.to_string(), "hello"); - text.insert(5, " world".into()).unwrap(); - assert_eq!(text.to_string(), "hello world"); - text.remove(5, 6).unwrap(); - assert_eq!(text.to_string(), "hello"); - } -} diff --git a/packages/common/y-octo/node/src/utils.rs b/packages/common/y-octo/node/src/utils.rs deleted file mode 100644 index 85d8419d2d..0000000000 --- a/packages/common/y-octo/node/src/utils.rs +++ /dev/null @@ -1,122 +0,0 @@ -use napi::bindgen_prelude::{ - Array, Either4, Env, Error, External, JsObjectValue, JsValue, Null, Object, Result, Status, - ToNapiValue, Unknown, ValueType, -}; -use y_octo::{AHashMap, Any, HashMapExt, Value}; - -use super::*; - -pub type MixedYType<'a> = Either4>; -pub type MixedRefYType<'a> = Either4<&'a YArray, &'a YMap, &'a YText, Unknown<'a>>; - -pub fn get_js_unknown_from_any(env: &Env, any: Any) -> Result> { - match any { - Any::Null | Any::Undefined => Null.into_unknown(env), - Any::True => true.into_unknown(env), - Any::False => false.into_unknown(env), - Any::Integer(number) => number.into_unknown(env), - Any::BigInt64(number) => number.into_unknown(env), - Any::Float32(number) => number.0.into_unknown(env), - Any::Float64(number) => number.0.into_unknown(env), - Any::String(string) => string.into_unknown(env), - Any::Array(array) => { - let js_array = Array::from_vec( - env, - array - .into_iter() - .map(|value| get_js_unknown_from_any(env, value)) - .collect::>>()?, - )?; - Ok(js_array.to_unknown()) - } - _ => Null.into_unknown(env), - } -} - -pub fn get_js_unknown_from_value(env: &Env, value: Value) -> Result> { - match value { - Value::Any(any) => get_js_unknown_from_any(env, any), - Value::Array(array) => { - let external = External::new(YArray::inner_new(array)); - Ok(unsafe { - Unknown::from_raw_unchecked(env.raw(), ToNapiValue::to_napi_value(env.raw(), external)?) - }) - } - Value::Map(map) => { - let external = External::new(YMap::inner_new(map)); - Ok(unsafe { - Unknown::from_raw_unchecked(env.raw(), ToNapiValue::to_napi_value(env.raw(), external)?) - }) - } - Value::Text(text) => { - let external = External::new(YText::inner_new(text)); - external.into_unknown(env) - } - _ => Null.into_unknown(env), - } -} - -pub fn get_any_from_js_object(object: Object) -> Result { - if let Ok(length) = object.get_array_length() { - let mut array = Vec::with_capacity(length as usize); - for i in 0..length { - if let Ok(value) = object.get_element::(i) { - array.push(get_any_from_js_unknown(value)?); - } - } - Ok(Any::Array(array)) - } else { - let mut map = AHashMap::new(); - let keys = object.get_property_names()?; - if let Ok(length) = keys.get_array_length() { - for i in 0..length { - if let Ok(key) = keys.get_element::(i).and_then(|o| { - o.coerce_to_string().and_then(|obj| { - obj - .into_utf8() - .and_then(|s| s.as_str().map(|s| s.to_string())) - }) - }) { - if let Ok(value) = object.get_named_property_unchecked::(&key) { - map.insert(key, get_any_from_js_unknown(value)?); - } - } - } - } - Ok(Any::Object(map)) - } -} - -pub fn get_any_from_js_unknown(js_unknown: Unknown) -> Result { - match js_unknown.get_type()? { - ValueType::Undefined | ValueType::Null => Ok(Any::Null), - ValueType::Boolean => Ok(unsafe { js_unknown.cast::()? }.into()), - ValueType::Number => Ok( - js_unknown - .coerce_to_number() - .and_then(|v| v.get_double()) - .map(|v| v.into())?, - ), - ValueType::String => Ok( - js_unknown - .coerce_to_string() - .and_then(|v| v.into_utf8()) - .and_then(|s| s.as_str().map(|s| s.to_string()))? - .into(), - ), - ValueType::Object => { - if let Ok(object) = js_unknown.coerce_to_object() { - get_any_from_js_object(object) - } else { - Err(Error::new( - Status::InvalidArg, - "Failed to coerce value to object", - )) - } - } - _ => Err(Error::new( - Status::InvalidArg, - "Failed to coerce value to any", - )), - } -} diff --git a/packages/common/y-octo/node/tests/array.spec.mts b/packages/common/y-octo/node/tests/array.spec.mts deleted file mode 100644 index 0b2f57d078..0000000000 --- a/packages/common/y-octo/node/tests/array.spec.mts +++ /dev/null @@ -1,62 +0,0 @@ -import assert, { equal, deepEqual } from 'node:assert'; -import { test } from 'node:test'; - -import { Doc, type YArray } from '../index'; - -test('array test', { concurrency: false }, async t => { - let client_id: number; - let doc: Doc; - t.beforeEach(async () => { - client_id = (Math.random() * 100000) | 0; - doc = new Doc(client_id); - }); - - t.afterEach(async () => { - client_id = -1; - // @ts-expect-error - doc must not null in next range - doc = null; - }); - - await t.test('array should be created', () => { - let arr = doc.getOrCreateArray('arr'); - deepEqual(doc.keys, ['arr']); - equal(arr.length, 0); - }); - - await t.test('array editing', () => { - let arr = doc.getOrCreateArray('arr'); - arr.insert(0, true); - arr.insert(1, false); - arr.insert(2, 1); - arr.insert(3, 'hello world'); - equal(arr.length, 4); - equal(arr.get(0), true); - equal(arr.get(1), false); - equal(arr.get(2), 1); - equal(arr.get(3), 'hello world'); - equal(arr.length, 4); - arr.remove(1, 1); - equal(arr.length, 3); - equal(arr.get(2), 'hello world'); - }); - - await t.test('sub array should can edit', () => { - let map = doc.getOrCreateMap('map'); - let sub = doc.createArray(); - map.set('sub', sub); - - sub.insert(0, true); - sub.insert(1, false); - sub.insert(2, 1); - sub.insert(3, 'hello world'); - equal(sub.length, 4); - - let sub2 = map.get('sub'); - assert(sub2); - equal(sub2.get(0), true); - equal(sub2.get(1), false); - equal(sub2.get(2), 1); - equal(sub2.get(3), 'hello world'); - equal(sub2.length, 4); - }); -}); diff --git a/packages/common/y-octo/node/tests/doc.spec.mts b/packages/common/y-octo/node/tests/doc.spec.mts deleted file mode 100644 index 65f36d5a7c..0000000000 --- a/packages/common/y-octo/node/tests/doc.spec.mts +++ /dev/null @@ -1,99 +0,0 @@ -import { equal } from 'node:assert'; -import { test } from 'node:test'; - -import { Doc } from '../index'; -import * as Y from 'yjs'; - -test('doc test', { concurrency: false }, async t => { - let client_id: number; - let doc: Doc; - t.beforeEach(async () => { - client_id = (Math.random() * 100000) | 0; - doc = new Doc(client_id); - }); - - t.afterEach(async () => { - client_id = -1; - // @ts-expect-error - doc must not null in next range - doc = null; - }); - - await t.test('doc id should be set', () => { - equal(doc.clientId, client_id); - }); - - await t.test('y-octo doc update should be apply', () => { - let array = doc.getOrCreateArray('array'); - let map = doc.getOrCreateMap('map'); - let text = doc.getOrCreateText('text'); - - array.insert(0, true); - array.insert(1, false); - array.insert(2, 1); - array.insert(3, 'hello world'); - map.set('a', true); - map.set('b', false); - map.set('c', 1); - map.set('d', 'hello world'); - text.insert(0, 'a'); - text.insert(1, 'b'); - text.insert(2, 'c'); - - let doc2 = new Doc(client_id); - doc2.applyUpdate(doc.encodeStateAsUpdateV1()); - - let array2 = doc2.getOrCreateArray('array'); - let map2 = doc2.getOrCreateMap('map'); - let text2 = doc2.getOrCreateText('text'); - - equal(doc2.clientId, client_id); - equal(array2.length, 4); - equal(array2.get(0), true); - equal(array2.get(1), false); - equal(array2.get(2), 1); - equal(array2.get(3), 'hello world'); - equal(map2.length, 4); - equal(map2.get('a'), true); - equal(map2.get('b'), false); - equal(map2.get('c'), 1); - equal(map2.get('d'), 'hello world'); - equal(text2.toString(), 'abc'); - }); - - await t.test('yjs doc update should be apply', () => { - let doc2 = new Y.Doc(); - let array2 = doc2.getArray('array'); - let map2 = doc2.getMap('map'); - let text2 = doc2.getText('text'); - - array2.insert(0, [true]); - array2.insert(1, [false]); - array2.insert(2, [1]); - array2.insert(3, ['hello world']); - map2.set('a', true); - map2.set('b', false); - map2.set('c', 1); - map2.set('d', 'hello world'); - text2.insert(0, 'a'); - text2.insert(1, 'b'); - text2.insert(2, 'c'); - - doc.applyUpdate(Buffer.from(Y.encodeStateAsUpdate(doc2))); - - let array = doc.getOrCreateArray('array'); - let map = doc.getOrCreateMap('map'); - let text = doc.getOrCreateText('text'); - - equal(array.length, 4); - equal(array.get(0), true); - equal(array.get(1), false); - equal(array.get(2), 1); - equal(array.get(3), 'hello world'); - equal(map.length, 4); - equal(map.get('a'), true); - equal(map.get('b'), false); - equal(map.get('c'), 1); - equal(map.get('d'), 'hello world'); - equal(text.toString(), 'abc'); - }); -}); diff --git a/packages/common/y-octo/node/tests/map.spec.mts b/packages/common/y-octo/node/tests/map.spec.mts deleted file mode 100644 index c31cf6846c..0000000000 --- a/packages/common/y-octo/node/tests/map.spec.mts +++ /dev/null @@ -1,152 +0,0 @@ -import assert, { equal, deepEqual } from 'node:assert'; -import { test } from 'node:test'; - -import * as Y from 'yjs'; -import { Doc, type YArray, type YMap, type YText } from '../index'; - -test('map test', { concurrency: false }, async t => { - let client_id: number; - let doc: Doc; - t.beforeEach(async () => { - client_id = (Math.random() * 100000) | 0; - doc = new Doc(client_id); - }); - - t.afterEach(async () => { - client_id = -1; - // @ts-expect-error - doc must not null in next range - doc = null; - }); - - await t.test('map should be created', () => { - let map = doc.getOrCreateMap('map'); - deepEqual(doc.keys, ['map']); - equal(map.length, 0); - }); - - await t.test('map editing', () => { - let map = doc.getOrCreateMap('map'); - map.set('a', true); - map.set('b', false); - map.set('c', 1); - map.set('d', 'hello world'); - equal(map.length, 4); - equal(map.get('a'), true); - equal(map.get('b'), false); - equal(map.get('c'), 1); - equal(map.get('d'), 'hello world'); - equal(map.length, 4); - map.remove('b'); - equal(map.length, 3); - equal(map.get('d'), 'hello world'); - }); - - await t.test('map should can be nested', () => { - let map = doc.getOrCreateMap('map'); - let sub = doc.createMap(); - map.set('sub', sub); - - sub.set('a', true); - sub.set('b', false); - sub.set('c', 1); - sub.set('d', 'hello world'); - equal(sub.length, 4); - - let sub2 = map.get('sub'); - assert(sub2); - equal(sub2.get('a'), true); - equal(sub2.get('b'), false); - equal(sub2.get('c'), 1); - equal(sub2.get('d'), 'hello world'); - equal(sub2.length, 4); - }); - - await t.test('y-octo to yjs compatibility test with nested type', () => { - let map = doc.getOrCreateMap('map'); - let sub_array = doc.createArray(); - let sub_map = doc.createMap(); - let sub_text = doc.createText(); - - map.set('array', sub_array); - map.set('map', sub_map); - map.set('text', sub_text); - - sub_array.insert(0, true); - sub_array.insert(1, false); - sub_array.insert(2, 1); - sub_array.insert(3, 'hello world'); - sub_map.set('a', true); - sub_map.set('b', false); - sub_map.set('c', 1); - sub_map.set('d', 'hello world'); - sub_text.insert(0, 'a'); - sub_text.insert(1, 'b'); - sub_text.insert(2, 'c'); - - let doc2 = new Y.Doc(); - Y.applyUpdate(doc2, doc.encodeStateAsUpdateV1()); - - let map2 = doc2.getMap('map'); - let sub_array2 = map2.get('array') as Y.Array; - let sub_map2 = map2.get('map') as Y.Map; - let sub_text2 = map2.get('text') as Y.Text; - - assert(sub_array2); - equal(sub_array2.length, 4); - equal(sub_array2.get(0), true); - equal(sub_array2.get(1), false); - equal(sub_array2.get(2), 1); - equal(sub_array2.get(3), 'hello world'); - assert(sub_map2); - equal(sub_map2.get('a'), true); - equal(sub_map2.get('b'), false); - equal(sub_map2.get('c'), 1); - equal(sub_map2.get('d'), 'hello world'); - assert(sub_text2); - equal(sub_text2.toString(), 'abc'); - }); - - await t.test('yjs to y-octo compatibility test with nested type', () => { - let doc2 = new Y.Doc(); - let map2 = doc2.getMap('map'); - let sub_array2 = new Y.Array(); - let sub_map2 = new Y.Map(); - let sub_text2 = new Y.Text(); - map2.set('array', sub_array2); - map2.set('map', sub_map2); - map2.set('text', sub_text2); - - sub_array2.insert(0, [true]); - sub_array2.insert(1, [false]); - sub_array2.insert(2, [1]); - sub_array2.insert(3, ['hello world']); - sub_map2.set('a', true); - sub_map2.set('b', false); - sub_map2.set('c', 1); - sub_map2.set('d', 'hello world'); - sub_text2.insert(0, 'a'); - sub_text2.insert(1, 'b'); - sub_text2.insert(2, 'c'); - - doc.applyUpdate(Buffer.from(Y.encodeStateAsUpdate(doc2))); - - let map = doc.getOrCreateMap('map'); - let sub_array = map.get('array'); - let sub_map = map.get('map'); - let sub_text = map.get('text'); - - assert(sub_array); - equal(sub_array.length, 4); - equal(sub_array.get(0), true); - equal(sub_array.get(1), false); - equal(sub_array.get(2), 1); - equal(sub_array.get(3), 'hello world'); - assert(sub_map); - equal(sub_map.get('a'), true); - equal(sub_map.get('b'), false); - equal(sub_map.get('c'), 1); - equal(sub_map.get('d'), 'hello world'); - assert(sub_text); - equal(sub_text.toString(), 'abc'); - }); -}); diff --git a/packages/common/y-octo/node/tests/text.spec.mts b/packages/common/y-octo/node/tests/text.spec.mts deleted file mode 100644 index 123440d4f5..0000000000 --- a/packages/common/y-octo/node/tests/text.spec.mts +++ /dev/null @@ -1,54 +0,0 @@ -import assert, { equal, deepEqual } from 'node:assert'; -import { test } from 'node:test'; - -import { Doc, type YText } from '../index'; - -test('text test', { concurrency: false }, async t => { - let client_id: number; - let doc: Doc; - t.beforeEach(async () => { - client_id = (Math.random() * 100000) | 0; - doc = new Doc(client_id); - }); - - t.afterEach(async () => { - client_id = -1; - // @ts-expect-error - doc must not null in next range - doc = null; - }); - - await t.test('text should be created', () => { - let text = doc.getOrCreateText('text'); - deepEqual(doc.keys, ['text']); - equal(text.len, 0); - }); - - await t.test('text editing', () => { - let text = doc.getOrCreateText('text'); - text.insert(0, 'a'); - text.insert(1, 'b'); - text.insert(2, 'c'); - equal(text.toString(), 'abc'); - text.remove(0, 1); - equal(text.toString(), 'bc'); - text.remove(1, 1); - equal(text.toString(), 'b'); - text.remove(0, 1); - equal(text.toString(), ''); - }); - - await t.test('sub text should can edit', () => { - let map = doc.getOrCreateMap('map'); - let sub = doc.createText(); - map.set('sub', sub); - - sub.insert(0, 'a'); - sub.insert(1, 'b'); - sub.insert(2, 'c'); - equal(sub.toString(), 'abc'); - - let sub2 = map.get('sub'); - assert(sub2); - equal(sub2.toString(), 'abc'); - }); -}); diff --git a/packages/common/y-octo/node/tsconfig.json b/packages/common/y-octo/node/tsconfig.json deleted file mode 100644 index fdb88feac5..0000000000 --- a/packages/common/y-octo/node/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../../../../tsconfig.node.json", - "compilerOptions": { - "noEmit": false, - "outDir": "lib", - "composite": true - }, - "include": ["index.d.ts", "tests/**/*.mts"], - "references": [] -} diff --git a/packages/common/y-octo/utils/Cargo.toml b/packages/common/y-octo/utils/Cargo.toml index b1608b8d1e..9dfc7be8ba 100644 --- a/packages/common/y-octo/utils/Cargo.toml +++ b/packages/common/y-octo/utils/Cargo.toml @@ -1,6 +1,6 @@ [package] authors = ["x1a0t <405028157@qq.com>", "DarkSky "] -edition = "2021" +edition = "2024" license = "MIT" name = "y-octo-utils" version = "0.0.1" diff --git a/packages/common/y-octo/utils/benches/apply_benchmarks.rs b/packages/common/y-octo/utils/benches/apply_benchmarks.rs index 050f0f970e..191daac08a 100644 --- a/packages/common/y-octo/utils/benches/apply_benchmarks.rs +++ b/packages/common/y-octo/utils/benches/apply_benchmarks.rs @@ -2,7 +2,7 @@ mod utils; use std::time::Duration; -use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; +use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main}; use path_ext::PathExt; use utils::Files; @@ -19,7 +19,7 @@ fn apply(c: &mut Criterion) { &file.content, |b, content| { b.iter(|| { - use yrs::{updates::decoder::Decode, Doc, Transact, Update}; + use yrs::{Doc, Transact, Update, updates::decoder::Decode}; let update = Update::decode_v1(content).unwrap(); let doc = Doc::new(); doc.transact_mut().apply_update(update).unwrap(); diff --git a/packages/common/y-octo/utils/benches/array_ops_benchmarks.rs b/packages/common/y-octo/utils/benches/array_ops_benchmarks.rs index acaf54e133..6a1c2c5461 100644 --- a/packages/common/y-octo/utils/benches/array_ops_benchmarks.rs +++ b/packages/common/y-octo/utils/benches/array_ops_benchmarks.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use criterion::{criterion_group, criterion_main, Criterion}; +use criterion::{Criterion, criterion_group, criterion_main}; use rand::{Rng, SeedableRng}; fn operations(c: &mut Criterion) { diff --git a/packages/common/y-octo/utils/benches/codec_benchmarks.rs b/packages/common/y-octo/utils/benches/codec_benchmarks.rs index efd1b4597a..6b07a1e0a8 100644 --- a/packages/common/y-octo/utils/benches/codec_benchmarks.rs +++ b/packages/common/y-octo/utils/benches/codec_benchmarks.rs @@ -1,4 +1,4 @@ -use criterion::{criterion_group, criterion_main, Criterion, SamplingMode}; +use criterion::{Criterion, SamplingMode, criterion_group, criterion_main}; use lib0::{ decoding::{Cursor, Read}, encoding::Write, diff --git a/packages/common/y-octo/utils/benches/map_ops_benchmarks.rs b/packages/common/y-octo/utils/benches/map_ops_benchmarks.rs index 7092e29a1f..ce63c82df9 100644 --- a/packages/common/y-octo/utils/benches/map_ops_benchmarks.rs +++ b/packages/common/y-octo/utils/benches/map_ops_benchmarks.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use criterion::{criterion_group, criterion_main, Criterion}; +use criterion::{Criterion, criterion_group, criterion_main}; fn operations(c: &mut Criterion) { let mut group = c.benchmark_group("ops/map"); diff --git a/packages/common/y-octo/utils/benches/text_ops_benchmarks.rs b/packages/common/y-octo/utils/benches/text_ops_benchmarks.rs index 7ed21df1ad..ba8cbf205b 100644 --- a/packages/common/y-octo/utils/benches/text_ops_benchmarks.rs +++ b/packages/common/y-octo/utils/benches/text_ops_benchmarks.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use criterion::{criterion_group, criterion_main, Criterion}; +use criterion::{Criterion, criterion_group, criterion_main}; use rand::{Rng, SeedableRng}; fn operations(c: &mut Criterion) { diff --git a/packages/common/y-octo/utils/benches/update_benchmarks.rs b/packages/common/y-octo/utils/benches/update_benchmarks.rs index 5f553845d2..4a02f987d3 100644 --- a/packages/common/y-octo/utils/benches/update_benchmarks.rs +++ b/packages/common/y-octo/utils/benches/update_benchmarks.rs @@ -2,7 +2,7 @@ mod utils; use std::time::Duration; -use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; +use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main}; use path_ext::PathExt; use utils::Files; @@ -19,7 +19,7 @@ fn update(c: &mut Criterion) { &file.content, |b, content| { b.iter(|| { - use yrs::{updates::decoder::Decode, Update}; + use yrs::{Update, updates::decoder::Decode}; Update::decode_v1(content).unwrap() }); }, diff --git a/packages/common/y-octo/utils/bin/bench_result_render.rs b/packages/common/y-octo/utils/bin/bench_result_render.rs index 054335c05f..5ad2549b9c 100644 --- a/packages/common/y-octo/utils/bin/bench_result_render.rs +++ b/packages/common/y-octo/utils/bin/bench_result_render.rs @@ -87,12 +87,7 @@ fn convert_to_markdown() -> impl Iterator { let diff = -(1.0 - changes_dur_secs / base_dur_secs) * 100.0; difference = format!("{diff:+.2}%"); - if is_significant( - changes_dur_secs, - changes_err_secs, - base_dur_secs, - base_err_secs, - ) { + if is_significant(changes_dur_secs, changes_err_secs, base_dur_secs, base_err_secs) { difference = format!("**{difference}**"); } } @@ -100,16 +95,8 @@ fn convert_to_markdown() -> impl Iterator { Some(format!( "| {} | {} | {} | {} |", name.replace('|', "\\|"), - if base_undefined { - "N/A" - } else { - &base_duration - }, - if changes_undefined { - "N/A" - } else { - &changes_duration - }, + if base_undefined { "N/A" } else { &base_duration }, + if changes_undefined { "N/A" } else { &changes_duration }, difference )) }) diff --git a/packages/common/y-octo/utils/fuzz/Cargo.toml b/packages/common/y-octo/utils/fuzz/Cargo.toml index b0f9effeec..1bdf7e61b9 100644 --- a/packages/common/y-octo/utils/fuzz/Cargo.toml +++ b/packages/common/y-octo/utils/fuzz/Cargo.toml @@ -1,5 +1,5 @@ [package] -edition = "2021" +edition = "2024" name = "y-octo-fuzz" publish = false version = "0.0.0" diff --git a/packages/common/y-octo/utils/src/doc_operation/yrs_op/array.rs b/packages/common/y-octo/utils/src/doc_operation/yrs_op/array.rs index afe59aeb86..9bdb05a764 100644 --- a/packages/common/y-octo/utils/src/doc_operation/yrs_op/array.rs +++ b/packages/common/y-octo/utils/src/doc_operation/yrs_op/array.rs @@ -69,10 +69,7 @@ pub fn yrs_create_array_from_nest_type( let str = text.get_string(&trx); let len = str.chars().fold(0, |acc, _| acc + 1); let index = random_pick_num(len, insert_pos) as usize; - let byte_start_offset = str - .chars() - .take(index) - .fold(0, |acc, ch| acc + ch.len_utf8()); + let byte_start_offset = str.chars().take(index).fold(0, |acc, ch| acc + ch.len_utf8()); Some(text.insert_embed(&mut trx, byte_start_offset as u32, array_prelim)) } @@ -80,10 +77,7 @@ pub fn yrs_create_array_from_nest_type( let str = xml_text.get_string(&trx); let len = str.chars().fold(0, |acc, _| acc + 1); let index = random_pick_num(len, insert_pos) as usize; - let byte_start_offset = str - .chars() - .take(index) - .fold(0, |acc, ch| acc + ch.len_utf8()); + let byte_start_offset = str.chars().take(index).fold(0, |acc, ch| acc + ch.len_utf8()); Some(xml_text.insert_embed(&mut trx, byte_start_offset as u32, array_prelim)) } @@ -114,26 +108,14 @@ mod tests { nest_data_op_type: NestDataOpType::Insert, }; - ops_registry.operate_yrs_nest_type( - &doc, - YrsNestType::ArrayType(array_ref.clone()), - params.clone(), - ); + ops_registry.operate_yrs_nest_type(&doc, YrsNestType::ArrayType(array_ref.clone()), params.clone()); assert_eq!(array_ref.len(&doc.transact()), 1); params.nest_data_op_type = NestDataOpType::Delete; - ops_registry.operate_yrs_nest_type( - &doc, - YrsNestType::ArrayType(array_ref.clone()), - params.clone(), - ); + ops_registry.operate_yrs_nest_type(&doc, YrsNestType::ArrayType(array_ref.clone()), params.clone()); assert_eq!(array_ref.len(&doc.transact()), 0); params.nest_data_op_type = NestDataOpType::Clear; - ops_registry.operate_yrs_nest_type( - &doc, - YrsNestType::ArrayType(array_ref.clone()), - params.clone(), - ); + ops_registry.operate_yrs_nest_type(&doc, YrsNestType::ArrayType(array_ref.clone()), params.clone()); assert_eq!(array_ref.len(&doc.transact()), 0); } diff --git a/packages/common/y-octo/utils/src/doc_operation/yrs_op/map.rs b/packages/common/y-octo/utils/src/doc_operation/yrs_op/map.rs index 754a5e5514..dca9958023 100644 --- a/packages/common/y-octo/utils/src/doc_operation/yrs_op/map.rs +++ b/packages/common/y-octo/utils/src/doc_operation/yrs_op/map.rs @@ -26,9 +26,7 @@ fn remove_op(doc: &yrs::Doc, nest_input: &YrsNestType, params: CRDTParam) { random_pick_num((len - 1) as u32, ¶ms.insert_pos) }; - iter - .nth(skip_step as usize) - .map(|(key, _value)| key.to_string()) + iter.nth(skip_step as usize).map(|(key, _value)| key.to_string()) }; if let Some(key) = rand_key { @@ -77,10 +75,7 @@ pub fn yrs_create_map_from_nest_type( let str = text.get_string(&trx); let len = str.chars().fold(0, |acc, _| acc + 1); let index = random_pick_num(len, insert_pos) as usize; - let byte_start_offset = str - .chars() - .take(index) - .fold(0, |acc, ch| acc + ch.len_utf8()); + let byte_start_offset = str.chars().take(index).fold(0, |acc, ch| acc + ch.len_utf8()); Some(text.insert_embed(&mut trx, byte_start_offset as u32, map_prelim)) } @@ -88,10 +83,7 @@ pub fn yrs_create_map_from_nest_type( let str = xml_text.get_string(&trx); let len = str.chars().fold(0, |acc, _| acc + 1); let index = random_pick_num(len, insert_pos) as usize; - let byte_start_offset = str - .chars() - .take(index) - .fold(0, |acc, ch| acc + ch.len_utf8()); + let byte_start_offset = str.chars().take(index).fold(0, |acc, ch| acc + ch.len_utf8()); Some(xml_text.insert_embed(&mut trx, byte_start_offset as u32, map_prelim)) } diff --git a/packages/common/y-octo/utils/src/doc_operation/yrs_op/mod.rs b/packages/common/y-octo/utils/src/doc_operation/yrs_op/mod.rs index f23d770841..913cd4eb5e 100644 --- a/packages/common/y-octo/utils/src/doc_operation/yrs_op/mod.rs +++ b/packages/common/y-octo/utils/src/doc_operation/yrs_op/mod.rs @@ -14,8 +14,8 @@ use xml_element::*; use xml_fragment::*; use xml_text::*; use yrs::{ - Array, ArrayPrelim, ArrayRef, Doc, GetString, Map, MapPrelim, MapRef, Text, TextPrelim, TextRef, - Transact, XmlFragment, XmlTextPrelim, XmlTextRef, + Array, ArrayPrelim, ArrayRef, Doc, GetString, Map, MapPrelim, MapRef, Text, TextPrelim, TextRef, Transact, + XmlFragment, XmlTextPrelim, XmlTextRef, }; use super::*; @@ -66,12 +66,7 @@ impl OpsRegistry<'_> { } } - pub fn operate_yrs_nest_type( - &self, - doc: &yrs::Doc, - cur_crdt_nest_type: YrsNestType, - crdt_param: CRDTParam, - ) { + pub fn operate_yrs_nest_type(&self, doc: &yrs::Doc, cur_crdt_nest_type: YrsNestType, crdt_param: CRDTParam) { let ops = self.get_ops_from_yrs_nest_type(&cur_crdt_nest_type); ops .get(match &crdt_param.nest_data_op_type { @@ -83,11 +78,7 @@ impl OpsRegistry<'_> { } } -pub fn yrs_create_nest_type_from_root( - doc: &yrs::Doc, - target_type: CRDTNestType, - key: &str, -) -> YrsNestType { +pub fn yrs_create_nest_type_from_root(doc: &yrs::Doc, target_type: CRDTNestType, key: &str) -> YrsNestType { match target_type { CRDTNestType::Array => YrsNestType::ArrayType(doc.get_or_insert_array(key)), CRDTNestType::Map => YrsNestType::MapType(doc.get_or_insert_map(key)), @@ -141,17 +132,13 @@ pub fn gen_nest_type_from_nest_type( nest_type: &mut YrsNestType, ) -> Option { match crdt_param.new_nest_type { - CRDTNestType::Array => { - yrs_create_array_from_nest_type(doc, nest_type, &crdt_param.insert_pos, crdt_param.key) - .map(YrsNestType::ArrayType) - } + CRDTNestType::Array => yrs_create_array_from_nest_type(doc, nest_type, &crdt_param.insert_pos, crdt_param.key) + .map(YrsNestType::ArrayType), CRDTNestType::Map => { - yrs_create_map_from_nest_type(doc, nest_type, &crdt_param.insert_pos, crdt_param.key) - .map(YrsNestType::MapType) + yrs_create_map_from_nest_type(doc, nest_type, &crdt_param.insert_pos, crdt_param.key).map(YrsNestType::MapType) } CRDTNestType::Text => { - yrs_create_text_from_nest_type(doc, nest_type, &crdt_param.insert_pos, crdt_param.key) - .map(YrsNestType::TextType) + yrs_create_text_from_nest_type(doc, nest_type, &crdt_param.insert_pos, crdt_param.key).map(YrsNestType::TextType) } _ => None, } diff --git a/packages/common/y-octo/utils/src/doc_operation/yrs_op/text.rs b/packages/common/y-octo/utils/src/doc_operation/yrs_op/text.rs index 47dddfecd5..e515e6e1d2 100644 --- a/packages/common/y-octo/utils/src/doc_operation/yrs_op/text.rs +++ b/packages/common/y-octo/utils/src/doc_operation/yrs_op/text.rs @@ -12,10 +12,7 @@ fn insert_op(doc: &yrs::Doc, nest_input: &YrsNestType, params: CRDTParam) { let str = text.get_string(&trx); let len = str.chars().fold(0, |acc, _| acc + 1); let index = random_pick_num(len, ¶ms.insert_pos) as usize; - let byte_start_offset = str - .chars() - .take(index) - .fold(0, |acc, ch| acc + ch.len_utf8()); + let byte_start_offset = str.chars().take(index).fold(0, |acc, ch| acc + ch.len_utf8()); text.insert(&mut trx, byte_start_offset as u32, ¶ms.value); } @@ -33,10 +30,7 @@ fn remove_op(doc: &yrs::Doc, nest_input: &YrsNestType, params: CRDTParam) { return; } let index = random_pick_num(len - 1, ¶ms.insert_pos) as usize; - let byte_start_offset = str - .chars() - .take(index) - .fold(0, |acc, ch| acc + ch.len_utf8()); + let byte_start_offset = str.chars().take(index).fold(0, |acc, ch| acc + ch.len_utf8()); let char_byte_len = str.chars().nth(index).unwrap().len_utf8(); text.remove_range(&mut trx, byte_start_offset as u32, char_byte_len as u32); @@ -80,10 +74,7 @@ pub fn yrs_create_text_from_nest_type( let str = text.get_string(&trx); let len = str.chars().fold(0, |acc, _| acc + 1); let index = random_pick_num(len, insert_pos) as usize; - let byte_start_offset = str - .chars() - .take(index) - .fold(0, |acc, ch| acc + ch.len_utf8()); + let byte_start_offset = str.chars().take(index).fold(0, |acc, ch| acc + ch.len_utf8()); Some(text.insert_embed(&mut trx, byte_start_offset as u32, text_prelim)) } @@ -91,10 +82,7 @@ pub fn yrs_create_text_from_nest_type( let str = xml_text.get_string(&trx); let len = str.chars().fold(0, |acc, _| acc + 1); let index = random_pick_num(len, insert_pos) as usize; - let byte_start_offset = str - .chars() - .take(index) - .fold(0, |acc, ch| acc + ch.len_utf8()); + let byte_start_offset = str.chars().take(index).fold(0, |acc, ch| acc + ch.len_utf8()); Some(xml_text.insert_embed(&mut trx, byte_start_offset as u32, text_prelim)) } @@ -122,26 +110,14 @@ mod tests { nest_data_op_type: NestDataOpType::Insert, }; - ops_registry.operate_yrs_nest_type( - &doc, - YrsNestType::TextType(text_ref.clone()), - params.clone(), - ); + ops_registry.operate_yrs_nest_type(&doc, YrsNestType::TextType(text_ref.clone()), params.clone()); assert_eq!(text_ref.len(&doc.transact()), 10); params.nest_data_op_type = NestDataOpType::Delete; - ops_registry.operate_yrs_nest_type( - &doc, - YrsNestType::TextType(text_ref.clone()), - params.clone(), - ); + ops_registry.operate_yrs_nest_type(&doc, YrsNestType::TextType(text_ref.clone()), params.clone()); assert_eq!(text_ref.len(&doc.transact()), 9); params.nest_data_op_type = NestDataOpType::Clear; - ops_registry.operate_yrs_nest_type( - &doc, - YrsNestType::TextType(text_ref.clone()), - params.clone(), - ); + ops_registry.operate_yrs_nest_type(&doc, YrsNestType::TextType(text_ref.clone()), params.clone()); assert_eq!(text_ref.len(&doc.transact()), 0); } diff --git a/packages/common/y-octo/utils/src/doc_operation/yrs_op/xml_text.rs b/packages/common/y-octo/utils/src/doc_operation/yrs_op/xml_text.rs index 4f9b65b1c4..3f083391fb 100644 --- a/packages/common/y-octo/utils/src/doc_operation/yrs_op/xml_text.rs +++ b/packages/common/y-octo/utils/src/doc_operation/yrs_op/xml_text.rs @@ -12,10 +12,7 @@ fn insert_op(doc: &yrs::Doc, nest_input: &YrsNestType, params: CRDTParam) { let str = xml_text.get_string(&trx); let len = str.chars().fold(0, |acc, _| acc + 1); let index = random_pick_num(len, ¶ms.insert_pos) as usize; - let byte_start_offset = str - .chars() - .take(index) - .fold(0, |acc, ch| acc + ch.len_utf8()); + let byte_start_offset = str.chars().take(index).fold(0, |acc, ch| acc + ch.len_utf8()); xml_text.insert(&mut trx, byte_start_offset as u32, ¶ms.value); } @@ -33,10 +30,7 @@ fn remove_op(doc: &yrs::Doc, nest_input: &YrsNestType, params: CRDTParam) { return; } let index = random_pick_num(len - 1, ¶ms.insert_pos) as usize; - let byte_start_offset = str - .chars() - .take(index) - .fold(0, |acc, ch| acc + ch.len_utf8()); + let byte_start_offset = str.chars().take(index).fold(0, |acc, ch| acc + ch.len_utf8()); let char_byte_len = str.chars().nth(index).unwrap().len_utf8(); xml_text.remove_range(&mut trx, byte_start_offset as u32, char_byte_len as u32); diff --git a/packages/frontend/mobile-native/Cargo.toml b/packages/frontend/mobile-native/Cargo.toml index 2ca44b6bed..bbd12cdffe 100644 --- a/packages/frontend/mobile-native/Cargo.toml +++ b/packages/frontend/mobile-native/Cargo.toml @@ -1,5 +1,5 @@ [package] -edition = "2021" +edition = "2024" name = "affine_mobile_native" version = "0.0.0" diff --git a/packages/frontend/mobile-native/src/lib.rs b/packages/frontend/mobile-native/src/lib.rs index 437472db0b..9b4e70be54 100644 --- a/packages/frontend/mobile-native/src/lib.rs +++ b/packages/frontend/mobile-native/src/lib.rs @@ -1,5 +1,5 @@ use affine_common::hashcash::Stamp; -use affine_nbstore::{pool::SqliteDocStoragePool, Data}; +use affine_nbstore::{Data, pool::SqliteDocStoragePool}; #[derive(uniffi::Error, thiserror::Error, Debug)] pub enum UniffiError { @@ -290,22 +290,10 @@ impl DocStoragePool { } pub async fn set_space_id(&self, universal_id: String, space_id: String) -> Result<()> { - Ok( - self - .inner - .get(universal_id) - .await? - .set_space_id(space_id) - .await?, - ) + Ok(self.inner.get(universal_id).await?.set_space_id(space_id).await?) } - pub async fn push_update( - &self, - universal_id: String, - doc_id: String, - update: String, - ) -> Result { + pub async fn push_update(&self, universal_id: String, doc_id: String, update: String) -> Result { Ok( self .inner @@ -323,11 +311,7 @@ impl DocStoragePool { ) } - pub async fn get_doc_snapshot( - &self, - universal_id: String, - doc_id: String, - ) -> Result> { + pub async fn get_doc_snapshot(&self, universal_id: String, doc_id: String) -> Result> { Ok( self .inner @@ -350,11 +334,7 @@ impl DocStoragePool { ) } - pub async fn get_doc_updates( - &self, - universal_id: String, - doc_id: String, - ) -> Result> { + pub async fn get_doc_updates(&self, universal_id: String, doc_id: String) -> Result> { Ok( self .inner @@ -368,12 +348,7 @@ impl DocStoragePool { ) } - pub async fn mark_updates_merged( - &self, - universal_id: String, - doc_id: String, - updates: Vec, - ) -> Result { + pub async fn mark_updates_merged(&self, universal_id: String, doc_id: String, updates: Vec) -> Result { Ok( self .inner @@ -395,21 +370,10 @@ impl DocStoragePool { } pub async fn delete_doc(&self, universal_id: String, doc_id: String) -> Result<()> { - Ok( - self - .inner - .get(universal_id) - .await? - .delete_doc(doc_id) - .await?, - ) + Ok(self.inner.get(universal_id).await?.delete_doc(doc_id).await?) } - pub async fn get_doc_clocks( - &self, - universal_id: String, - after: Option, - ) -> Result> { + pub async fn get_doc_clocks(&self, universal_id: String, after: Option) -> Result> { Ok( self .inner @@ -431,11 +395,7 @@ impl DocStoragePool { ) } - pub async fn get_doc_clock( - &self, - universal_id: String, - doc_id: String, - ) -> Result> { + pub async fn get_doc_clock(&self, universal_id: String, doc_id: String) -> Result> { Ok( self .inner @@ -448,34 +408,14 @@ impl DocStoragePool { } pub async fn get_blob(&self, universal_id: String, key: String) -> Result> { - Ok( - self - .inner - .get(universal_id) - .await? - .get_blob(key) - .await? - .map(Into::into), - ) + Ok(self.inner.get(universal_id).await?.get_blob(key).await?.map(Into::into)) } pub async fn set_blob(&self, universal_id: String, blob: SetBlob) -> Result<()> { - Ok( - self - .inner - .get(universal_id) - .await? - .set_blob(blob.try_into()?) - .await?, - ) + Ok(self.inner.get(universal_id).await?.set_blob(blob.try_into()?).await?) } - pub async fn delete_blob( - &self, - universal_id: String, - key: String, - permanently: bool, - ) -> Result<()> { + pub async fn delete_blob(&self, universal_id: String, key: String, permanently: bool) -> Result<()> { Ok( self .inner @@ -504,11 +444,7 @@ impl DocStoragePool { ) } - pub async fn get_peer_remote_clocks( - &self, - universal_id: String, - peer: String, - ) -> Result> { + pub async fn get_peer_remote_clocks(&self, universal_id: String, peer: String) -> Result> { Ok( self .inner @@ -562,11 +498,7 @@ impl DocStoragePool { ) } - pub async fn get_peer_pulled_remote_clocks( - &self, - universal_id: String, - peer: String, - ) -> Result> { + pub async fn get_peer_pulled_remote_clocks(&self, universal_id: String, peer: String) -> Result> { Ok( self .inner @@ -637,11 +569,7 @@ impl DocStoragePool { ) } - pub async fn get_peer_pushed_clocks( - &self, - universal_id: String, - peer: String, - ) -> Result> { + pub async fn get_peer_pushed_clocks(&self, universal_id: String, peer: String) -> Result> { Ok( self .inner @@ -709,12 +637,7 @@ impl DocStoragePool { ) } - pub async fn get_blob_uploaded_at( - &self, - universal_id: String, - peer: String, - blob_id: String, - ) -> Result> { + pub async fn get_blob_uploaded_at(&self, universal_id: String, peer: String, blob_id: String) -> Result> { Ok( self .inner @@ -753,12 +676,7 @@ impl DocStoragePool { Ok(()) } - pub async fn fts_delete_document( - &self, - universal_id: String, - index_name: String, - doc_id: String, - ) -> Result<()> { + pub async fn fts_delete_document(&self, universal_id: String, index_name: String, doc_id: String) -> Result<()> { self .inner .get(universal_id) @@ -784,12 +702,7 @@ impl DocStoragePool { ) } - pub async fn fts_search( - &self, - universal_id: String, - index_name: String, - query: String, - ) -> Result> { + pub async fn fts_search(&self, universal_id: String, index_name: String, query: String) -> Result> { Ok( self .inner diff --git a/packages/frontend/native/Cargo.toml b/packages/frontend/native/Cargo.toml index ed3eeb08eb..c7bb670896 100644 --- a/packages/frontend/native/Cargo.toml +++ b/packages/frontend/native/Cargo.toml @@ -1,5 +1,5 @@ [package] -edition = "2021" +edition = "2024" name = "affine_native" version = "0.0.0" diff --git a/packages/frontend/native/media_capture/Cargo.toml b/packages/frontend/native/media_capture/Cargo.toml index c5fffb8728..e9e003be83 100644 --- a/packages/frontend/native/media_capture/Cargo.toml +++ b/packages/frontend/native/media_capture/Cargo.toml @@ -1,5 +1,5 @@ [package] -edition = "2021" +edition = "2024" name = "affine_media_capture" version = "0.0.0" diff --git a/packages/frontend/native/media_capture/benches/mix_audio_samples.rs b/packages/frontend/native/media_capture/benches/mix_audio_samples.rs index 35c45742f9..da87f342a2 100644 --- a/packages/frontend/native/media_capture/benches/mix_audio_samples.rs +++ b/packages/frontend/native/media_capture/benches/mix_audio_samples.rs @@ -4,7 +4,7 @@ use std::hint::black_box; #[cfg(target_os = "macos")] use affine_media_capture::macos::audio_buffer::{mix_audio_samples, mix_audio_samples_scalar}; #[cfg(target_os = "macos")] -use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; +use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main}; #[cfg(target_os = "macos")] fn generate_test_samples() -> [f32; 1024] { diff --git a/packages/frontend/native/media_capture/src/audio_decoder.rs b/packages/frontend/native/media_capture/src/audio_decoder.rs index abde907710..3a09afc40a 100644 --- a/packages/frontend/native/media_capture/src/audio_decoder.rs +++ b/packages/frontend/native/media_capture/src/audio_decoder.rs @@ -1,8 +1,8 @@ use std::{io::Cursor, path::Path}; use napi::{ - bindgen_prelude::{AbortSignal, AsyncTask, Float32Array, Result, Status, Uint8Array}, Task, + bindgen_prelude::{AbortSignal, AsyncTask, Float32Array, Result, Status, Uint8Array}, }; use napi_derive::napi; use rubato::{Resampler, SincFixedIn, SincInterpolationParameters, SincInterpolationType}; @@ -26,9 +26,7 @@ fn decode + Send + Sync + 'static>( // Create a probe hint using the file extension let mut hint = Hint::new(); - if let Some(ext) = - filename.and_then(|filename| Path::new(filename).extension().and_then(|ext| ext.to_str())) - { + if let Some(ext) = filename.and_then(|filename| Path::new(filename).extension().and_then(|ext| ext.to_str())) { hint.with_extension(ext); } @@ -106,9 +104,7 @@ fn decode + Send + Sync + 'static>( let mut waves_out = resampler .process(&waves_in, None) .map_err(|_| Error::Unsupported("Failed to run resampler"))?; - output = waves_out - .pop() - .ok_or(Error::Unsupported("No resampled output found"))?; + output = waves_out.pop().ok_or(Error::Unsupported("No resampled output found"))?; } Ok(output) diff --git a/packages/frontend/native/media_capture/src/macos/audio_buffer.rs b/packages/frontend/native/media_capture/src/macos/audio_buffer.rs index c7c13a6745..ba931a844b 100644 --- a/packages/frontend/native/media_capture/src/macos/audio_buffer.rs +++ b/packages/frontend/native/media_capture/src/macos/audio_buffer.rs @@ -170,10 +170,7 @@ pub struct AudioBufferList { } unsafe impl Encode for AudioBufferList { - const ENCODING: Encoding = Encoding::Struct( - "AudioBufferList", - &[::ENCODING, <[AudioBuffer; 1]>::ENCODING], - ); + const ENCODING: Encoding = Encoding::Struct("AudioBufferList", &[::ENCODING, <[AudioBuffer; 1]>::ENCODING]); } unsafe impl RefEncode for AudioBufferList { diff --git a/packages/frontend/native/media_capture/src/macos/audio_stream_basic_desc.rs b/packages/frontend/native/media_capture/src/macos/audio_stream_basic_desc.rs index 1fab8c626f..4fcf065a08 100644 --- a/packages/frontend/native/media_capture/src/macos/audio_stream_basic_desc.rs +++ b/packages/frontend/native/media_capture/src/macos/audio_stream_basic_desc.rs @@ -1,6 +1,6 @@ use std::fmt::Display; -use coreaudio::sys::{kAudioTapPropertyFormat, AudioObjectID}; +use coreaudio::sys::{AudioObjectID, kAudioTapPropertyFormat}; use objc2::{Encode, Encoding, RefEncode}; use crate::{error::CoreAudioError, utils::get_global_main_property}; @@ -226,9 +226,8 @@ impl Display for AudioStreamDescription { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, - "AudioStreamBasicDescription {{ mSampleRate: {}, mFormatID: {:?}, mFormatFlags: {}, \ - mBytesPerPacket: {}, mFramesPerPacket: {}, mBytesPerFrame: {}, mChannelsPerFrame: {}, \ - mBitsPerChannel: {}, mReserved: {} }}", + "AudioStreamBasicDescription {{ mSampleRate: {}, mFormatID: {:?}, mFormatFlags: {}, mBytesPerPacket: {}, \ + mFramesPerPacket: {}, mBytesPerFrame: {}, mChannelsPerFrame: {}, mBitsPerChannel: {}, mReserved: {} }}", self.0.mSampleRate, AudioFormatID::from(self.0.mFormatID), AudioFormatFlags(self.0.mFormatFlags), diff --git a/packages/frontend/native/media_capture/src/macos/av_audio_file.rs b/packages/frontend/native/media_capture/src/macos/av_audio_file.rs index 3f6bdc0502..23754af0c7 100644 --- a/packages/frontend/native/media_capture/src/macos/av_audio_file.rs +++ b/packages/frontend/native/media_capture/src/macos/av_audio_file.rs @@ -1,15 +1,12 @@ use std::ptr; use objc2::{ - msg_send, + AllocAnyThread, msg_send, runtime::{AnyClass, AnyObject}, - AllocAnyThread, }; use objc2_foundation::{NSDictionary, NSError, NSNumber, NSString, NSUInteger, NSURL}; -use crate::{ - av_audio_format::AVAudioFormat, av_audio_pcm_buffer::AVAudioPCMBuffer, error::CoreAudioError, -}; +use crate::{av_audio_format::AVAudioFormat, av_audio_pcm_buffer::AVAudioPCMBuffer, error::CoreAudioError}; #[allow(unused)] pub(crate) struct AVAudioFile { @@ -32,10 +29,7 @@ impl AVAudioFile { &*NSString::from_str("AVNumberOfChannelsKey"), ], &[ - NSNumber::initWithUnsignedInt( - NSNumber::alloc(), - format.audio_stream_basic_description.0.mFormatID, - ), + NSNumber::initWithUnsignedInt(NSNumber::alloc(), format.audio_stream_basic_description.0.mFormatID), NSNumber::initWithDouble(NSNumber::alloc(), format.get_sample_rate()), NSNumber::initWithUnsignedInt(NSNumber::alloc(), format.get_channel_count()), ], @@ -61,8 +55,7 @@ impl AVAudioFile { pub(crate) fn write(&self, buffer: AVAudioPCMBuffer) -> Result<(), CoreAudioError> { let mut error: *mut NSError = ptr::null_mut(); - let success: bool = - unsafe { msg_send![self.inner, writeFromBuffer: buffer.inner, error: &mut error] }; + let success: bool = unsafe { msg_send![self.inner, writeFromBuffer: buffer.inner, error: &mut error] }; if !success { return Err(CoreAudioError::WriteAVAudioFileFailed); } diff --git a/packages/frontend/native/media_capture/src/macos/av_audio_format.rs b/packages/frontend/native/media_capture/src/macos/av_audio_format.rs index 1a126fff05..71cb7a545e 100644 --- a/packages/frontend/native/media_capture/src/macos/av_audio_format.rs +++ b/packages/frontend/native/media_capture/src/macos/av_audio_format.rs @@ -1,7 +1,6 @@ use objc2::{ - msg_send, + Encode, Encoding, RefEncode, msg_send, runtime::{AnyClass, AnyObject}, - Encode, Encoding, RefEncode, }; use crate::{audio_stream_basic_desc::AudioStreamDescription, error::CoreAudioError}; @@ -37,11 +36,7 @@ unsafe impl Encode for AVAudioFormatRef { 1, &Encoding::Struct( "AudioChannelDescription", - &[ - Encoding::UInt, - Encoding::UInt, - Encoding::Array(3, &Encoding::Float), - ], + &[Encoding::UInt, Encoding::UInt, Encoding::Array(3, &Encoding::Float)], ), ), Encoding::UInt, @@ -62,16 +57,13 @@ unsafe impl RefEncode for AVAudioFormatRef { #[allow(unused)] impl AVAudioFormat { - pub fn new( - audio_stream_basic_description: AudioStreamDescription, - ) -> Result { + pub fn new(audio_stream_basic_description: AudioStreamDescription) -> Result { let cls = AnyClass::get(c"AVAudioFormat").ok_or(CoreAudioError::AVAudioFormatClassNotFound)?; let obj: *mut AnyObject = unsafe { msg_send![cls, alloc] }; if obj.is_null() { return Err(CoreAudioError::AllocAVAudioFormatFailed); } - let obj: *mut AnyObject = - unsafe { msg_send![obj, initWithStreamDescription: &audio_stream_basic_description.0] }; + let obj: *mut AnyObject = unsafe { msg_send![obj, initWithStreamDescription: &audio_stream_basic_description.0] }; if obj.is_null() { return Err(CoreAudioError::InitAVAudioFormatFailed); } diff --git a/packages/frontend/native/media_capture/src/macos/av_audio_pcm_buffer.rs b/packages/frontend/native/media_capture/src/macos/av_audio_pcm_buffer.rs index 5c126766b0..77007c5758 100644 --- a/packages/frontend/native/media_capture/src/macos/av_audio_pcm_buffer.rs +++ b/packages/frontend/native/media_capture/src/macos/av_audio_pcm_buffer.rs @@ -13,12 +13,8 @@ pub(crate) struct AVAudioPCMBuffer { #[allow(unused)] impl AVAudioPCMBuffer { - pub(crate) fn new( - audio_format: &AVAudioFormat, - buffer_list: *const AudioBufferList, - ) -> Result { - let cls = - AnyClass::get(c"AVAudioPCMBuffer").ok_or(CoreAudioError::AVAudioPCMBufferClassNotFound)?; + pub(crate) fn new(audio_format: &AVAudioFormat, buffer_list: *const AudioBufferList) -> Result { + let cls = AnyClass::get(c"AVAudioPCMBuffer").ok_or(CoreAudioError::AVAudioPCMBufferClassNotFound)?; let obj: *mut AnyObject = unsafe { msg_send![cls, alloc] }; if obj.is_null() { return Err(CoreAudioError::AllocAVAudioPCMBufferFailed); diff --git a/packages/frontend/native/media_capture/src/macos/ca_tap_description.rs b/packages/frontend/native/media_capture/src/macos/ca_tap_description.rs index f8b767debf..1f4cc384c1 100644 --- a/packages/frontend/native/media_capture/src/macos/ca_tap_description.rs +++ b/packages/frontend/native/media_capture/src/macos/ca_tap_description.rs @@ -4,9 +4,8 @@ use core_foundation::{ }; use coreaudio::sys::AudioObjectID; use objc2::{ - msg_send, + AllocAnyThread, msg_send, runtime::{AnyClass, AnyObject}, - AllocAnyThread, }; use objc2_foundation::{NSArray, NSNumber, NSString, NSUUID}; @@ -17,19 +16,14 @@ pub(crate) struct CATapDescription { } impl CATapDescription { - pub fn init_stereo_mixdown_of_processes( - process: AudioObjectID, - ) -> std::result::Result { - let cls = - AnyClass::get(c"CATapDescription").ok_or(CoreAudioError::CATapDescriptionClassNotFound)?; + pub fn init_stereo_mixdown_of_processes(process: AudioObjectID) -> std::result::Result { + let cls = AnyClass::get(c"CATapDescription").ok_or(CoreAudioError::CATapDescriptionClassNotFound)?; let obj: *mut AnyObject = unsafe { msg_send![cls, alloc] }; if obj.is_null() { return Err(CoreAudioError::AllocCATapDescriptionFailed); } - let processes_array = - NSArray::from_retained_slice(&[NSNumber::initWithUnsignedInt(NSNumber::alloc(), process)]); - let obj: *mut AnyObject = - unsafe { msg_send![obj, initStereoMixdownOfProcesses: &*processes_array] }; + let processes_array = NSArray::from_retained_slice(&[NSNumber::initWithUnsignedInt(NSNumber::alloc(), process)]); + let obj: *mut AnyObject = unsafe { msg_send![obj, initStereoMixdownOfProcesses: &*processes_array] }; if obj.is_null() { return Err(CoreAudioError::InitStereoMixdownOfProcessesFailed); } @@ -44,8 +38,7 @@ impl CATapDescription { pub fn init_stereo_global_tap_but_exclude_processes( processes: &[AudioObjectID], ) -> std::result::Result { - let cls = - AnyClass::get(c"CATapDescription").ok_or(CoreAudioError::CATapDescriptionClassNotFound)?; + let cls = AnyClass::get(c"CATapDescription").ok_or(CoreAudioError::CATapDescriptionClassNotFound)?; let obj: *mut AnyObject = unsafe { msg_send![cls, alloc] }; if obj.is_null() { return Err(CoreAudioError::AllocCATapDescriptionFailed); @@ -57,8 +50,7 @@ impl CATapDescription { .collect::>() .as_slice(), ); - let obj: *mut AnyObject = - unsafe { msg_send![obj, initStereoGlobalTapButExcludeProcesses: &*processes_array] }; + let obj: *mut AnyObject = unsafe { msg_send![obj, initStereoGlobalTapButExcludeProcesses: &*processes_array] }; if obj.is_null() { return Err(CoreAudioError::InitStereoGlobalTapButExcludeProcessesFailed); } diff --git a/packages/frontend/native/media_capture/src/macos/cf_types.rs b/packages/frontend/native/media_capture/src/macos/cf_types.rs index f91da86dae..2ceeb2e2bc 100644 --- a/packages/frontend/native/media_capture/src/macos/cf_types.rs +++ b/packages/frontend/native/media_capture/src/macos/cf_types.rs @@ -60,14 +60,7 @@ impl ToCoreFoundation for CFDictionary { impl ToCoreFoundation for Vec { fn to_cf(&self) -> CFType { - CFArray::from_CFTypes( - self - .iter() - .map(|t| t.to_cf()) - .collect::>() - .as_slice(), - ) - .as_CFType() + CFArray::from_CFTypes(self.iter().map(|t| t.to_cf()).collect::>().as_slice()).as_CFType() } } diff --git a/packages/frontend/native/media_capture/src/macos/device.rs b/packages/frontend/native/media_capture/src/macos/device.rs index 086c8f2926..9d6ba0bb2f 100644 --- a/packages/frontend/native/media_capture/src/macos/device.rs +++ b/packages/frontend/native/media_capture/src/macos/device.rs @@ -2,8 +2,7 @@ use std::ptr; use core_foundation::{base::TCFType, string::CFString}; use coreaudio::sys::{ - kAudioDevicePropertyDeviceUID, kAudioObjectSystemObject, AudioDeviceID, AudioObjectID, - CFStringRef, + AudioDeviceID, AudioObjectID, CFStringRef, kAudioDevicePropertyDeviceUID, kAudioObjectSystemObject, }; use crate::{error::CoreAudioError, utils::get_global_main_property}; @@ -13,20 +12,14 @@ pub(crate) fn get_device_uid( ) -> std::result::Result<(AudioObjectID, CFString), CoreAudioError> { let system_audio_id = get_device_audio_id(device_id)?; let mut output_uid: CFStringRef = ptr::null_mut(); - get_global_main_property( - system_audio_id, - kAudioDevicePropertyDeviceUID, - &mut output_uid, - )?; + get_global_main_property(system_audio_id, kAudioDevicePropertyDeviceUID, &mut output_uid)?; Ok((system_audio_id, unsafe { CFString::wrap_under_create_rule(output_uid.cast()) })) } -pub(crate) fn get_device_audio_id( - device_id: AudioDeviceID, -) -> std::result::Result { +pub(crate) fn get_device_audio_id(device_id: AudioDeviceID) -> std::result::Result { let mut system_output_id: AudioObjectID = 0; get_global_main_property(kAudioObjectSystemObject, device_id, &mut system_output_id)?; diff --git a/packages/frontend/native/media_capture/src/macos/pid.rs b/packages/frontend/native/media_capture/src/macos/pid.rs index caa5cb901d..c6c1d79d4c 100644 --- a/packages/frontend/native/media_capture/src/macos/pid.rs +++ b/packages/frontend/native/media_capture/src/macos/pid.rs @@ -1,10 +1,9 @@ use std::{mem::MaybeUninit, ptr}; use coreaudio::sys::{ - kAudioHardwareNoError, kAudioHardwarePropertyProcessObjectList, kAudioObjectPropertyElementMain, - kAudioObjectPropertyScopeGlobal, kAudioObjectSystemObject, AudioObjectGetPropertyData, - AudioObjectGetPropertyDataSize, AudioObjectID, AudioObjectPropertyAddress, - AudioObjectPropertySelector, + AudioObjectGetPropertyData, AudioObjectGetPropertyDataSize, AudioObjectID, AudioObjectPropertyAddress, + AudioObjectPropertySelector, kAudioHardwareNoError, kAudioHardwarePropertyProcessObjectList, + kAudioObjectPropertyElementMain, kAudioObjectPropertyScopeGlobal, kAudioObjectSystemObject, }; use crate::error::CoreAudioError; @@ -17,15 +16,8 @@ pub fn audio_process_list() -> Result, CoreAudioError> { }; let mut data_size = 0u32; - let status = unsafe { - AudioObjectGetPropertyDataSize( - kAudioObjectSystemObject, - &address, - 0, - ptr::null_mut(), - &mut data_size, - ) - }; + let status = + unsafe { AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &address, 0, ptr::null_mut(), &mut data_size) }; if status != kAudioHardwareNoError as i32 { return Err(CoreAudioError::GetProcessObjectListSizeFailed(status)); @@ -63,9 +55,7 @@ pub fn get_process_property( }; let mut data_size = 0u32; - let status = unsafe { - AudioObjectGetPropertyDataSize(object_id, &address, 0, ptr::null_mut(), &mut data_size) - }; + let status = unsafe { AudioObjectGetPropertyDataSize(object_id, &address, 0, ptr::null_mut(), &mut data_size) }; if status != kAudioHardwareNoError as i32 { return Err(CoreAudioError::AudioObjectGetPropertyDataSizeFailed(status)); diff --git a/packages/frontend/native/media_capture/src/macos/screen_capture_kit.rs b/packages/frontend/native/media_capture/src/macos/screen_capture_kit.rs index a41500dd62..4582f2132d 100644 --- a/packages/frontend/native/media_capture/src/macos/screen_capture_kit.rs +++ b/packages/frontend/native/media_capture/src/macos/screen_capture_kit.rs @@ -3,8 +3,8 @@ use std::{ ffi::c_void, ptr, sync::{ - atomic::{AtomicPtr, Ordering}, Arc, LazyLock, RwLock, + atomic::{AtomicPtr, Ordering}, }, }; @@ -14,11 +14,10 @@ use core_foundation::{ string::{CFString, CFStringRef}, }; use coreaudio::sys::{ - kAudioHardwarePropertyProcessObjectList, kAudioObjectPropertyElementMain, + AudioObjectAddPropertyListenerBlock, AudioObjectID, AudioObjectPropertyAddress, + AudioObjectRemovePropertyListenerBlock, kAudioHardwarePropertyProcessObjectList, kAudioObjectPropertyElementMain, kAudioObjectPropertyScopeGlobal, kAudioObjectSystemObject, kAudioProcessPropertyBundleID, kAudioProcessPropertyIsRunning, kAudioProcessPropertyIsRunningInput, kAudioProcessPropertyPID, - AudioObjectAddPropertyListenerBlock, AudioObjectID, AudioObjectPropertyAddress, - AudioObjectRemovePropertyListenerBlock, }; use libc; use napi::{ @@ -27,9 +26,8 @@ use napi::{ }; use napi_derive::napi; use objc2::{ - msg_send, + Encode, Encoding, msg_send, runtime::{AnyClass, AnyObject}, - Encode, Encoding, }; use objc2_foundation::NSString; use screencapturekit::shareable_content::SCShareableContent; @@ -71,20 +69,16 @@ unsafe impl Encode for CGRect { const ENCODING: Encoding = Encoding::Struct("CGRect", &[::ENCODING, ::ENCODING]); } -static RUNNING_APPLICATIONS: LazyLock< - RwLock, CoreAudioError>>, -> = LazyLock::new(|| RwLock::new(audio_process_list())); +static RUNNING_APPLICATIONS: LazyLock, CoreAudioError>>> = + LazyLock::new(|| RwLock::new(audio_process_list())); -type ApplicationStateChangedSubscriberMap = - HashMap>>>; +type ApplicationStateChangedSubscriberMap = HashMap>>>; -static APPLICATION_STATE_CHANGED_SUBSCRIBERS: LazyLock< - RwLock, -> = LazyLock::new(|| RwLock::new(HashMap::new())); +static APPLICATION_STATE_CHANGED_SUBSCRIBERS: LazyLock> = + LazyLock::new(|| RwLock::new(HashMap::new())); -static APPLICATION_STATE_CHANGED_LISTENER_BLOCKS: LazyLock< - RwLock>>, -> = LazyLock::new(|| RwLock::new(HashMap::new())); +static APPLICATION_STATE_CHANGED_LISTENER_BLOCKS: LazyLock>>> = + LazyLock::new(|| RwLock::new(HashMap::new())); static NSRUNNING_APPLICATION_CLASS: LazyLock> = LazyLock::new(|| AnyClass::get(c"NSRunningApplication")); @@ -155,14 +149,12 @@ impl ApplicationInfo { } // If not available, try to get from the audio process property - if self.object_id > 0 { - if let Ok(bundle_id) = - get_process_property::(&self.object_id, kAudioProcessPropertyBundleID) - { - // Safely convert CFStringRef to Rust String - let cf_string = unsafe { CFString::wrap_under_get_rule(bundle_id) }; - return cf_string.to_string(); - } + if self.object_id > 0 + && let Ok(bundle_id) = get_process_property::(&self.object_id, kAudioProcessPropertyBundleID) + { + // Safely convert CFStringRef to Rust String + let cf_string = unsafe { CFString::wrap_under_get_rule(bundle_id) }; + return cf_string.to_string(); } String::new() @@ -281,8 +273,7 @@ impl ApplicationInfo { let _: () = msg_send![properties, setObject: compression_value, forKey: &*compression_key]; // Get PNG data with properties - let png_data: *mut AnyObject = - msg_send![bitmap, representationUsingType: 4u64, properties: properties]; // 4 = PNG + let png_data: *mut AnyObject = msg_send![bitmap, representationUsingType: 4u64, properties: properties]; // 4 = PNG if png_data.is_null() { return Ok(Buffer::from(Vec::::new())); @@ -338,10 +329,7 @@ impl ApplicationListChangedSubscriber { match result { Ok(status) => { if status != 0 { - return Err(Error::new( - Status::GenericFailure, - "Failed to remove property listener", - )); + return Err(Error::new(Status::GenericFailure, "Failed to remove property listener")); } Ok(()) } @@ -363,31 +351,31 @@ pub struct ApplicationStateChangedSubscriber { impl ApplicationStateChangedSubscriber { #[napi] pub fn unsubscribe(&self) { - if let Ok(mut lock) = APPLICATION_STATE_CHANGED_SUBSCRIBERS.write() { - if let Some(subscribers) = lock.get_mut(&self.object_id) { - subscribers.remove(&self.id); - if subscribers.is_empty() { - lock.remove(&self.object_id); - if let Some(listener_block) = APPLICATION_STATE_CHANGED_LISTENER_BLOCKS - .write() - .ok() - .as_mut() - .and_then(|map| map.remove(&self.object_id)) - { - // Wrap in catch_unwind to prevent crashes during shutdown - let _ = std::panic::catch_unwind(|| unsafe { - AudioObjectRemovePropertyListenerBlock( - self.object_id, - &AudioObjectPropertyAddress { - mSelector: kAudioProcessPropertyIsRunning, - mScope: kAudioObjectPropertyScopeGlobal, - mElement: kAudioObjectPropertyElementMain, - }, - ptr::null_mut(), - listener_block.load(Ordering::Relaxed), - ); - }); - } + if let Ok(mut lock) = APPLICATION_STATE_CHANGED_SUBSCRIBERS.write() + && let Some(subscribers) = lock.get_mut(&self.object_id) + { + subscribers.remove(&self.id); + if subscribers.is_empty() { + lock.remove(&self.object_id); + if let Some(listener_block) = APPLICATION_STATE_CHANGED_LISTENER_BLOCKS + .write() + .ok() + .as_mut() + .and_then(|map| map.remove(&self.object_id)) + { + // Wrap in catch_unwind to prevent crashes during shutdown + let _ = std::panic::catch_unwind(|| unsafe { + AudioObjectRemovePropertyListenerBlock( + self.object_id, + &AudioObjectPropertyAddress { + mSelector: kAudioProcessPropertyIsRunning, + mScope: kAudioObjectPropertyScopeGlobal, + mElement: kAudioObjectPropertyElementMain, + }, + ptr::null_mut(), + listener_block.load(Ordering::Relaxed), + ); + }); } } } @@ -402,9 +390,7 @@ pub struct ShareableContent { #[napi] impl ShareableContent { #[napi] - pub fn on_application_list_changed( - callback: ThreadsafeFunction<(), ()>, - ) -> Result { + pub fn on_application_list_changed(callback: ThreadsafeFunction<(), ()>) -> Result { let callback_arc = Arc::new(callback); let callback_clone = callback_arc.clone(); let callback_block: RcBlock = @@ -442,10 +428,7 @@ impl ShareableContent { ) }; if status != 0 { - return Err(Error::new( - Status::GenericFailure, - "Failed to add property listener", - )); + return Err(Error::new(Status::GenericFailure, "Failed to add property listener")); } Ok(ApplicationListChangedSubscriber { listener_block: callback_block, @@ -480,16 +463,15 @@ impl ShareableContent { ) }; for address in addresses { - if address.mSelector == kAudioProcessPropertyIsRunning { - if let Some(subscribers) = APPLICATION_STATE_CHANGED_SUBSCRIBERS + if address.mSelector == kAudioProcessPropertyIsRunning + && let Some(subscribers) = APPLICATION_STATE_CHANGED_SUBSCRIBERS .read() .ok() .as_ref() .and_then(|map| map.get(&object_id)) - { - for callback in subscribers.values() { - callback.call(Ok(()), ThreadsafeFunctionCallMode::NonBlocking); - } + { + for callback in subscribers.values() { + callback.call(Ok(()), ThreadsafeFunctionCallMode::NonBlocking); } } } @@ -501,18 +483,10 @@ impl ShareableContent { }; let listener_block = &*list_change as *const Block; let status = unsafe { - AudioObjectAddPropertyListenerBlock( - object_id, - &address, - ptr::null_mut(), - listener_block.cast_mut().cast(), - ) + AudioObjectAddPropertyListenerBlock(object_id, &address, ptr::null_mut(), listener_block.cast_mut().cast()) }; if status != 0 { - return Err(Error::new( - Status::GenericFailure, - "Failed to add property listener", - )); + return Err(Error::new(Status::GenericFailure, "Failed to add property listener")); } let subscribers = { let mut map = HashMap::new(); @@ -659,16 +633,16 @@ impl ShareableContent { } // Find the audio object ID for this process - if let Ok(app_list) = RUNNING_APPLICATIONS.read() { - if let Ok(app_list) = app_list.as_ref() { - for object_id in app_list { - let pid = get_process_property(object_id, kAudioProcessPropertyPID).unwrap_or(-1); - if pid == process_id as i32 { - // Check if the process is actively using input (microphone) - match get_process_property(object_id, kAudioProcessPropertyIsRunningInput) { - Ok(is_running) => return Ok(is_running), - Err(_) => continue, - } + if let Ok(app_list) = RUNNING_APPLICATIONS.read() + && let Ok(app_list) = app_list.as_ref() + { + for object_id in app_list { + let pid = get_process_property(object_id, kAudioProcessPropertyPID).unwrap_or(-1); + if pid == process_id as i32 { + // Check if the process is actively using input (microphone) + match get_process_property(object_id, kAudioProcessPropertyIsRunningInput) { + Ok(is_running) => return Ok(is_running), + Err(_) => continue, } } } diff --git a/packages/frontend/native/media_capture/src/macos/tap_audio.rs b/packages/frontend/native/media_capture/src/macos/tap_audio.rs index 06e9ae1258..d302aae9b4 100644 --- a/packages/frontend/native/media_capture/src/macos/tap_audio.rs +++ b/packages/frontend/native/media_capture/src/macos/tap_audio.rs @@ -8,20 +8,17 @@ use core_foundation::{ uuid::CFUUID, }; use coreaudio::sys::{ - kAudioAggregateDeviceClockDeviceKey, kAudioAggregateDeviceIsPrivateKey, - kAudioAggregateDeviceIsStackedKey, kAudioAggregateDeviceMainSubDeviceKey, - kAudioAggregateDeviceNameKey, kAudioAggregateDeviceSubDeviceListKey, - kAudioAggregateDeviceTapAutoStartKey, kAudioAggregateDeviceTapListKey, - kAudioAggregateDeviceUIDKey, kAudioDevicePropertyDeviceIsAlive, - kAudioDevicePropertyNominalSampleRate, kAudioHardwareBadDeviceError, - kAudioHardwareBadStreamError, kAudioHardwareNoError, kAudioHardwarePropertyDefaultInputDevice, - kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyElementMain, - kAudioObjectPropertyScopeGlobal, kAudioObjectSystemObject, kAudioSubDeviceUIDKey, - kAudioSubTapUIDKey, AudioDeviceCreateIOProcIDWithBlock, AudioDeviceDestroyIOProcID, - AudioDeviceIOProcID, AudioDeviceStart, AudioDeviceStop, AudioHardwareCreateAggregateDevice, - AudioHardwareDestroyAggregateDevice, AudioObjectAddPropertyListenerBlock, - AudioObjectGetPropertyDataSize, AudioObjectID, AudioObjectPropertyAddress, - AudioObjectRemovePropertyListenerBlock, AudioTimeStamp, OSStatus, + AudioDeviceCreateIOProcIDWithBlock, AudioDeviceDestroyIOProcID, AudioDeviceIOProcID, AudioDeviceStart, + AudioDeviceStop, AudioHardwareCreateAggregateDevice, AudioHardwareDestroyAggregateDevice, + AudioObjectAddPropertyListenerBlock, AudioObjectGetPropertyDataSize, AudioObjectID, AudioObjectPropertyAddress, + AudioObjectRemovePropertyListenerBlock, AudioTimeStamp, OSStatus, kAudioAggregateDeviceClockDeviceKey, + kAudioAggregateDeviceIsPrivateKey, kAudioAggregateDeviceIsStackedKey, kAudioAggregateDeviceMainSubDeviceKey, + kAudioAggregateDeviceNameKey, kAudioAggregateDeviceSubDeviceListKey, kAudioAggregateDeviceTapAutoStartKey, + kAudioAggregateDeviceTapListKey, kAudioAggregateDeviceUIDKey, kAudioDevicePropertyDeviceIsAlive, + kAudioDevicePropertyNominalSampleRate, kAudioHardwareBadDeviceError, kAudioHardwareBadStreamError, + kAudioHardwareNoError, kAudioHardwarePropertyDefaultInputDevice, kAudioHardwarePropertyDefaultOutputDevice, + kAudioObjectPropertyElementMain, kAudioObjectPropertyScopeGlobal, kAudioObjectSystemObject, kAudioSubDeviceUIDKey, + kAudioSubTapUIDKey, }; use napi::{ bindgen_prelude::{Float32Array, Result, Status}, @@ -41,11 +38,8 @@ use crate::{ utils::{cfstring_from_bytes_with_nul, get_global_main_property}, }; -extern "C" { - fn AudioHardwareCreateProcessTap( - inDescription: *mut AnyObject, - outTapID: *mut AudioObjectID, - ) -> OSStatus; +unsafe extern "C" { + fn AudioHardwareCreateProcessTap(inDescription: *mut AnyObject, outTapID: *mut AudioObjectID) -> OSStatus; fn AudioHardwareDestroyProcessTap(tapID: AudioObjectID) -> OSStatus; } @@ -80,12 +74,10 @@ impl AggregateDevice { return Err(CoreAudioError::CreateProcessTapFailed(status).into()); } - let (input_device_id, default_input_uid) = - get_device_uid(kAudioHardwarePropertyDefaultInputDevice)?; + let (input_device_id, default_input_uid) = get_device_uid(kAudioHardwarePropertyDefaultInputDevice)?; // Get the default output device ID - let (output_device_id, output_device_uid) = - get_device_uid(kAudioHardwarePropertyDefaultOutputDevice)?; + let (output_device_id, output_device_uid) = get_device_uid(kAudioHardwarePropertyDefaultOutputDevice)?; let description_dict = Self::create_aggregate_description( tap_id, tap_description.get_uuid()?, @@ -96,10 +88,7 @@ impl AggregateDevice { let mut aggregate_device_id: AudioObjectID = 0; let status = unsafe { - AudioHardwareCreateAggregateDevice( - description_dict.as_concrete_TypeRef().cast(), - &mut aggregate_device_id, - ) + AudioHardwareCreateAggregateDevice(description_dict.as_concrete_TypeRef().cast(), &mut aggregate_device_id) }; if status != 0 { @@ -119,8 +108,7 @@ impl AggregateDevice { pub fn create_global_tap_but_exclude_processes(processes: &[AudioObjectID]) -> Result { let mut tap_id: AudioObjectID = 0; - let tap_description = - CATapDescription::init_stereo_global_tap_but_exclude_processes(processes)?; + let tap_description = CATapDescription::init_stereo_global_tap_but_exclude_processes(processes)?; let status = unsafe { AudioHardwareCreateProcessTap(tap_description.inner, &mut tap_id) }; if status != 0 { @@ -128,12 +116,10 @@ impl AggregateDevice { } // Get the default input device (microphone) UID and ID - let (input_device_id, default_input_uid) = - get_device_uid(kAudioHardwarePropertyDefaultInputDevice)?; + let (input_device_id, default_input_uid) = get_device_uid(kAudioHardwarePropertyDefaultInputDevice)?; // Get the default output device ID - let (output_device_id, output_device_uid) = - get_device_uid(kAudioHardwarePropertyDefaultOutputDevice)?; + let (output_device_id, output_device_uid) = get_device_uid(kAudioHardwarePropertyDefaultOutputDevice)?; let description_dict = Self::create_aggregate_description( tap_id, @@ -145,10 +131,7 @@ impl AggregateDevice { let mut aggregate_device_id: AudioObjectID = 0; let status = unsafe { - AudioHardwareCreateAggregateDevice( - description_dict.as_concrete_TypeRef().cast(), - &mut aggregate_device_id, - ) + AudioHardwareCreateAggregateDevice(description_dict.as_concrete_TypeRef().cast(), &mut aggregate_device_id) }; // Check the status and return the appropriate result @@ -184,11 +167,7 @@ impl AggregateDevice { fn get_aggregate_device_stats(&self) -> Result { let mut sample_rate: f64 = 0.0; - get_global_main_property( - self.id, - kAudioDevicePropertyNominalSampleRate, - &mut sample_rate, - )?; + get_global_main_property(self.id, kAudioDevicePropertyNominalSampleRate, &mut sample_rate)?; let audio_stats = AudioStats { sample_rate, @@ -270,9 +249,7 @@ impl AggregateDevice { // Use the consistent stats for the stream object returned let audio_stats_for_stream = current_audio_stats; - let in_io_block: RcBlock< - dyn Fn(*mut c_void, *mut c_void, *mut c_void, *mut c_void, *mut c_void) -> i32, - >; + let in_io_block: RcBlock i32>; { in_io_block = RcBlock::new( move |_in_now: *mut c_void, @@ -286,9 +263,7 @@ impl AggregateDevice { if *mSampleTime < 0.0 { return kAudioHardwareNoError as i32; } - let Ok(dua_audio_buffer_list) = - (unsafe { InputAndOutputAudioBufferList::from_raw(in_input_data) }) - else { + let Ok(dua_audio_buffer_list) = (unsafe { InputAndOutputAudioBufferList::from_raw(in_input_data) }) else { return kAudioHardwareBadDeviceError as i32; }; @@ -301,10 +276,7 @@ impl AggregateDevice { }; // Send the processed audio data to JavaScript - audio_stream_callback.call( - Ok(mixed_samples.into()), - ThreadsafeFunctionCallMode::NonBlocking, - ); + audio_stream_callback.call(Ok(mixed_samples.into()), ThreadsafeFunctionCallMode::NonBlocking); kAudioHardwareNoError as i32 }, @@ -316,10 +288,7 @@ impl AggregateDevice { &mut in_proc_id, self.id, dispatch2::DispatchRetained::as_ptr(&queue).as_ptr().cast(), - (&*in_io_block - as *const Block< - dyn Fn(*mut c_void, *mut c_void, *mut c_void, *mut c_void, *mut c_void) -> i32, - >) + (&*in_io_block as *const Block i32>) .cast_mut() .cast(), ) @@ -374,34 +343,16 @@ impl AggregateDevice { let mut cf_dict_builder = CFDictionaryBuilder::new(); cf_dict_builder - .add( - kAudioAggregateDeviceNameKey.as_slice(), - aggregate_device_name, - ) - .add( - kAudioAggregateDeviceUIDKey.as_slice(), - aggregate_device_uid_string, - ) - .add( - kAudioAggregateDeviceMainSubDeviceKey.as_slice(), - &output_device_id, - ) + .add(kAudioAggregateDeviceNameKey.as_slice(), aggregate_device_name) + .add(kAudioAggregateDeviceUIDKey.as_slice(), aggregate_device_uid_string) + .add(kAudioAggregateDeviceMainSubDeviceKey.as_slice(), &output_device_id) .add(kAudioAggregateDeviceIsPrivateKey.as_slice(), true) // can't be stacked because we're using a tap .add(kAudioAggregateDeviceIsStackedKey.as_slice(), false) .add(kAudioAggregateDeviceTapAutoStartKey.as_slice(), true) - .add( - kAudioAggregateDeviceSubDeviceListKey.as_slice(), - capture_device_list, - ) - .add( - kAudioAggregateDeviceClockDeviceKey.as_slice(), - input_device_id, - ) - .add( - kAudioAggregateDeviceTapListKey.as_slice(), - vec![tap_device_dict], - ); + .add(kAudioAggregateDeviceSubDeviceListKey.as_slice(), capture_device_list) + .add(kAudioAggregateDeviceClockDeviceKey.as_slice(), input_device_id) + .add(kAudioAggregateDeviceTapListKey.as_slice(), vec![tap_device_dict]); Ok(cf_dict_builder.build()) } @@ -627,18 +578,13 @@ impl AggregateDeviceManager { // Start the initial stream // Pass the initially determined consistent audio stats - let original_audio_stats = self - .device - .get_aggregate_device_stats() - .unwrap_or(AudioStats { - sample_rate: 48000.0, // Match fallback in setup_device_change_listeners - channels: 2, - }); + let original_audio_stats = self.device.get_aggregate_device_stats().unwrap_or(AudioStats { + sample_rate: 48000.0, // Match fallback in setup_device_change_listeners + channels: 2, + }); self.original_audio_stats = Some(original_audio_stats); // Store for listener use - let initial_audio_tap_stream = self - .device - .start(audio_stream_callback.clone(), original_audio_stats)?; // Pass clone of callback + let initial_audio_tap_stream = self.device.start(audio_stream_callback.clone(), original_audio_stats)?; // Pass clone of callback // Setup device change listeners AFTER getting initial stats and stream self.setup_device_change_listeners()?; @@ -679,70 +625,68 @@ impl AggregateDeviceManager { }; // Create a block that will handle device changes - let device_changed_block = RcBlock::new( - move |_in_number_addresses: u32, _in_addresses: *mut c_void| { - // Skip if we don't have all required information - let Some(stream_mutex) = stream_arc.as_ref() else { - return; - }; - let Some(callback) = callback_arc.as_ref() else { - return; - }; + let device_changed_block = RcBlock::new(move |_in_number_addresses: u32, _in_addresses: *mut c_void| { + // Skip if we don't have all required information + let Some(stream_mutex) = stream_arc.as_ref() else { + return; + }; + let Some(callback) = callback_arc.as_ref() else { + return; + }; - // Try to lock the stream mutex - let Ok(mut stream_guard) = stream_mutex.lock() else { - return; - }; + // Try to lock the stream mutex + let Ok(mut stream_guard) = stream_mutex.lock() else { + return; + }; - // Create a new device with updated default devices - let result: Result = { - if is_app_specific { - if let Some(id) = app_id { - // For device change listener, we need to create a minimal ApplicationInfo - // We don't have the name here, so we'll use an empty string - let app = ApplicationInfo::new(id as i32, String::new(), id); - AggregateDevice::new(&app) - } else { - Err(CoreAudioError::CreateProcessTapFailed(0).into()) - } + // Create a new device with updated default devices + let result: Result = { + if is_app_specific { + if let Some(id) = app_id { + // For device change listener, we need to create a minimal ApplicationInfo + // We don't have the name here, so we'll use an empty string + let app = ApplicationInfo::new(id as i32, String::new(), id); + AggregateDevice::new(&app) } else { - AggregateDevice::create_global_tap_but_exclude_processes(&excluded_processes) + Err(CoreAudioError::CreateProcessTapFailed(0).into()) } - }; + } else { + AggregateDevice::create_global_tap_but_exclude_processes(&excluded_processes) + } + }; - // If we successfully created a new device, stop the old stream and start a new - // one - match result { - Ok(mut new_device) => { - // Stop and drop the old stream if it exists - if let Some(mut old_stream) = stream_guard.take() { - // Explicitly drop the old stream's Box before creating the new device. - // The drop implementation handles cleanup. - // We call stop() directly. - let stop_result = old_stream.stop(); - match stop_result { - Ok(_) => {} - Err(e) => println!("DEBUG: Error stopping old stream (proceeding anyway): {e}"), - }; - drop(old_stream); // Ensure it's dropped now - } - - match new_device.start(callback.clone(), original_audio_stats) { - Ok(new_stream) => { - // Use the existing stream_guard which already holds the lock - *stream_guard = Some(new_stream); - } - Err(e) => { - println!("DEBUG: Failed to start new stream: {e}"); - } - } + // If we successfully created a new device, stop the old stream and start a new + // one + match result { + Ok(mut new_device) => { + // Stop and drop the old stream if it exists + if let Some(mut old_stream) = stream_guard.take() { + // Explicitly drop the old stream's Box before creating the new device. + // The drop implementation handles cleanup. + // We call stop() directly. + let stop_result = old_stream.stop(); + match stop_result { + Ok(_) => {} + Err(e) => println!("DEBUG: Error stopping old stream (proceeding anyway): {e}"), + }; + drop(old_stream); // Ensure it's dropped now } - Err(e) => { - println!("DEBUG: Failed to create new device: {e}"); + + match new_device.start(callback.clone(), original_audio_stats) { + Ok(new_stream) => { + // Use the existing stream_guard which already holds the lock + *stream_guard = Some(new_stream); + } + Err(e) => { + println!("DEBUG: Failed to start new stream: {e}"); + } } } - }, - ); + Err(e) => { + println!("DEBUG: Failed to create new device: {e}"); + } + } + }); // Create pointers to the device_changed_block that can be used in C functions let block_ptr = &*device_changed_block as *const Block; @@ -1026,9 +970,7 @@ impl AudioCaptureSession { if let Some(manager) = &self.manager { manager .get_current_actual_sample_rate()? // Propagate CoreAudioError - .ok_or_else(|| { - napi::Error::from_reason("No active audio stream to get actual sample rate from") - }) + .ok_or_else(|| napi::Error::from_reason("No active audio stream to get actual sample rate from")) } else if let Some(cached_rate) = self.sample_rate { // Return cached sample rate as the best approximation when session is stopped Ok(cached_rate) diff --git a/packages/frontend/native/media_capture/src/macos/utils.rs b/packages/frontend/native/media_capture/src/macos/utils.rs index a638dac063..aa7eaef51c 100644 --- a/packages/frontend/native/media_capture/src/macos/utils.rs +++ b/packages/frontend/native/media_capture/src/macos/utils.rs @@ -2,8 +2,8 @@ use std::{cell::RefCell, collections::HashMap, ffi::c_void, mem::size_of}; use core_foundation::string::CFString; use coreaudio::sys::{ - kAudioObjectPropertyElementMain, kAudioObjectPropertyScopeGlobal, AudioObjectGetPropertyData, - AudioObjectID, AudioObjectPropertyAddress, + AudioObjectGetPropertyData, AudioObjectID, AudioObjectPropertyAddress, kAudioObjectPropertyElementMain, + kAudioObjectPropertyScopeGlobal, }; use rubato::{FastFixedIn, PolynomialDegree, Resampler}; @@ -29,7 +29,7 @@ impl BufferedResampler { let ratio = to_sr / from_sr; let resampler = FastFixedIn::::new( ratio, - 1.0, // max_resample_ratio_relative (must be >= 1.0, use 1.0 for fixed ratio) + 1.0, // max_resample_ratio_relative (must be >= 1.0, use 1.0 for fixed ratio) PolynomialDegree::Linear, // Use Linear interpolation quality RESAMPLER_INPUT_CHUNK, channels, @@ -58,9 +58,7 @@ impl BufferedResampler { // Drain exactly one chunk per channel let mut chunk: Vec> = Vec::with_capacity(self.channels); for ch in 0..self.channels { - let tail = self.fifo[ch] - .drain(..RESAMPLER_INPUT_CHUNK) - .collect::>(); + let tail = self.fifo[ch].drain(..RESAMPLER_INPUT_CHUNK).collect::>(); chunk.push(tail); } @@ -171,20 +169,14 @@ pub fn process_audio_frame( if current_sample_rate != target_sample_rate { // Use (or create) a persistent BufferedResampler - let out_vec = RESAMPLER_CACHE.with(|cache| { + RESAMPLER_CACHE.with(|cache| { let mut map = cache.borrow_mut(); - let key = ( - current_sample_rate as u32, - target_sample_rate as u32, - 2usize, - ); + let key = (current_sample_rate as u32, target_sample_rate as u32, 2usize); let resampler = map .entry(key) .or_insert_with(|| BufferedResampler::new(current_sample_rate, target_sample_rate, 2)); resampler.feed(&[left, right]) - }); - - out_vec + }) } else { // No resampling needed, just interleave existing left/right data let mut interleaved: Vec = Vec::with_capacity(left.len() * 2); @@ -202,11 +194,7 @@ pub fn process_audio_frame( if current_sample_rate != target_sample_rate { let out_vec = RESAMPLER_CACHE.with(|cache| { let mut map = cache.borrow_mut(); - let key = ( - current_sample_rate as u32, - target_sample_rate as u32, - 1usize, - ); + let key = (current_sample_rate as u32, target_sample_rate as u32, 1usize); let resampler = map .entry(key) .or_insert_with(|| BufferedResampler::new(current_sample_rate, target_sample_rate, 1)); diff --git a/packages/frontend/native/media_capture/src/windows/audio_capture.rs b/packages/frontend/native/media_capture/src/windows/audio_capture.rs index a8cc87a7d4..e7252a3ddf 100644 --- a/packages/frontend/native/media_capture/src/windows/audio_capture.rs +++ b/packages/frontend/native/media_capture/src/windows/audio_capture.rs @@ -2,21 +2,21 @@ use std::{ cell::RefCell, collections::HashMap, sync::{ - atomic::{AtomicBool, Ordering}, Arc, + atomic::{AtomicBool, Ordering}, }, thread::JoinHandle, }; use cpal::{ - traits::{DeviceTrait, HostTrait, StreamTrait}, SampleRate, + traits::{DeviceTrait, HostTrait, StreamTrait}, }; use crossbeam_channel::unbounded; use napi::{ + Error, Status, bindgen_prelude::{Float32Array, Result}, threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode}, - Error, Status, }; use napi_derive::napi; use rubato::{FastFixedIn, PolynomialDegree, Resampler}; @@ -95,11 +95,7 @@ thread_local! { static RESAMPLER_CACHE: RefCell> = RefCell::new(HashMap::new()); } -fn process_audio_with_resampler( - samples: Vec, - from_sample_rate: u32, - to_sample_rate: u32, -) -> Vec { +fn process_audio_with_resampler(samples: Vec, from_sample_rate: u32, to_sample_rate: u32) -> Vec { if from_sample_rate == to_sample_rate { return samples; } @@ -220,16 +216,13 @@ impl Drop for AudioCaptureSession { } } -pub fn start_recording( - audio_buffer_callback: ThreadsafeFunction, -) -> Result { +pub fn start_recording(audio_buffer_callback: ThreadsafeFunction) -> Result { let available_hosts = cpal::available_hosts(); let host_id = available_hosts .first() .ok_or_else(|| Error::new(Status::GenericFailure, "No CPAL hosts available"))?; - let host = - cpal::host_from_id(*host_id).map_err(|e| Error::new(Status::GenericFailure, format!("{e}")))?; + let host = cpal::host_from_id(*host_id).map_err(|e| Error::new(Status::GenericFailure, format!("{e}")))?; let mic = host .default_input_device() @@ -267,9 +260,7 @@ pub fn start_recording( .build_input_stream( &mic_stream_config, move |data: &[f32], _| { - let _ = tx_mic.send(AudioBuffer { - data: data.to_vec(), - }); + let _ = tx_mic.send(AudioBuffer { data: data.to_vec() }); }, |err| eprintln!("CPAL mic stream error: {err}"), None, @@ -282,9 +273,7 @@ pub fn start_recording( .build_input_stream( &lb_stream_config, move |data: &[f32], _| { - let _ = tx_lb.send(AudioBuffer { - data: data.to_vec(), - }); + let _ = tx_lb.send(AudioBuffer { data: data.to_vec() }); }, |err| eprintln!("CPAL loopback stream error: {err}"), None, @@ -306,11 +295,7 @@ pub fn start_recording( let mono_samples: Vec = if mic_channels == 1 { buf.data } else { - buf - .data - .chunks(mic_channels as usize) - .map(to_mono) - .collect() + buf.data.chunks(mic_channels as usize).map(to_mono).collect() }; pre_mic.extend_from_slice(&mono_samples); } @@ -347,10 +332,7 @@ pub fn start_recording( let lb_chunk: Vec = post_lb.drain(..TARGET_FRAME_SIZE).collect(); let mixed = mix(&mic_chunk, &lb_chunk); if !mixed.is_empty() { - let _ = audio_buffer_callback.call( - Ok(mixed.clone().into()), - ThreadsafeFunctionCallMode::NonBlocking, - ); + let _ = audio_buffer_callback.call(Ok(mixed.clone().into()), ThreadsafeFunctionCallMode::NonBlocking); } } diff --git a/packages/frontend/native/media_capture/src/windows/microphone_listener.rs b/packages/frontend/native/media_capture/src/windows/microphone_listener.rs index 262b565ab9..f999abe8e1 100644 --- a/packages/frontend/native/media_capture/src/windows/microphone_listener.rs +++ b/packages/frontend/native/media_capture/src/windows/microphone_listener.rs @@ -3,32 +3,31 @@ use std::{ os::windows::ffi::OsStringExt, process, sync::{ - atomic::{AtomicBool, AtomicUsize, Ordering}, Arc, Mutex, + atomic::{AtomicBool, AtomicUsize, Ordering}, }, }; use napi::{ - threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode}, Result, + threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode}, }; use windows::{ - core::Interface, Win32::{ Foundation::CloseHandle, Media::Audio::{ - eCapture, eCommunications, eConsole, AudioSessionState, AudioSessionStateActive, - IAudioSessionControl, IAudioSessionControl2, IAudioSessionEnumerator, IAudioSessionEvents, - IAudioSessionEvents_Impl, IAudioSessionManager2, IAudioSessionNotification, - IAudioSessionNotification_Impl, IMMDevice, IMMDeviceCollection, IMMDeviceEnumerator, - MMDeviceEnumerator, DEVICE_STATE_ACTIVE, + AudioSessionState, AudioSessionStateActive, DEVICE_STATE_ACTIVE, IAudioSessionControl, IAudioSessionControl2, + IAudioSessionEnumerator, IAudioSessionEvents, IAudioSessionEvents_Impl, IAudioSessionManager2, + IAudioSessionNotification, IAudioSessionNotification_Impl, IMMDevice, IMMDeviceCollection, IMMDeviceEnumerator, + MMDeviceEnumerator, eCapture, eCommunications, eConsole, }, System::{ - Com::{CoCreateInstance, CoInitializeEx, CLSCTX_ALL, COINIT_MULTITHREADED}, + Com::{CLSCTX_ALL, COINIT_MULTITHREADED, CoCreateInstance, CoInitializeEx}, ProcessStatus::{GetModuleFileNameExW, GetProcessImageFileNameW}, Threading::{OpenProcess, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ}, }, }, + core::Interface, }; use windows_core::implement; @@ -148,9 +147,7 @@ impl IAudioSessionEvents_Impl for SessionEvents_Impl { let currently_recording = newstate == AudioSessionStateActive; // Atomically swap the flag tracking this particular session - let previously_recording = self - .session_is_active - .swap(currently_recording, Ordering::SeqCst); + let previously_recording = self.session_is_active.swap(currently_recording, Ordering::SeqCst); // Update the global counter accordingly if !previously_recording && currently_recording { @@ -272,12 +269,7 @@ impl SessionNotifier { if should_notify { self.callback.call( - Ok(( - true, - process_name, - self.device_id.clone(), - self.device_name.clone(), - )), + Ok((true, process_name, self.device_id.clone(), self.device_name.clone())), ThreadsafeFunctionCallMode::NonBlocking, ); } @@ -309,8 +301,7 @@ pub fn register_audio_device_status_callback( let enumerator: IMMDeviceEnumerator = CoCreateInstance(&MMDeviceEnumerator, None, CLSCTX_ALL)?; // Get all active capture devices - let device_collection: IMMDeviceCollection = - enumerator.EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE)?; + let device_collection: IMMDeviceCollection = enumerator.EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE)?; let device_count = device_collection.GetCount()?; let mut session_notifiers = Vec::new(); @@ -374,17 +365,14 @@ impl MicrophoneListener { let is_running = Arc::new(AtomicBool::new(false)); let active_sessions = Arc::new(AtomicUsize::new(0)); - let session_notifiers = match register_audio_device_status_callback( - is_running.clone(), - active_sessions.clone(), - Arc::new(callback), - ) { - Ok(notifiers) => notifiers, - Err(_) => { - // If registration fails, create a listener with empty notifiers - Vec::new() - } - }; + let session_notifiers = + match register_audio_device_status_callback(is_running.clone(), active_sessions.clone(), Arc::new(callback)) { + Ok(notifiers) => notifiers, + Err(_) => { + // If registration fails, create a listener with empty notifiers + Vec::new() + } + }; Self { is_running, @@ -401,17 +389,13 @@ impl MicrophoneListener { pub fn is_process_using_microphone(process_id: u32) -> bool { // Use the proven get_all_audio_processes logic match get_all_audio_processes() { - Ok(processes) => processes - .iter() - .any(|p| p.process_id == process_id && p.is_running), + Ok(processes) => processes.iter().any(|p| p.process_id == process_id && p.is_running), Err(_) => false, } } } -fn get_mgr_audio_session_running_status( - mgr: &IAudioSessionManager2, -) -> windows_core::Result<(bool, String)> { +fn get_mgr_audio_session_running_status(mgr: &IAudioSessionManager2) -> windows_core::Result<(bool, String)> { let list: IAudioSessionEnumerator = unsafe { mgr.GetSessionEnumerator()? }; let sessions = unsafe { list.GetCount()? }; for idx in 0..sessions { @@ -445,8 +429,7 @@ fn get_mgr_audio_session_running_status( fn get_process_name(pid: u32) -> Option { unsafe { // Open process with required access rights - let process_handle = - OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid).ok()?; + let process_handle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid).ok()?; // Allocate a buffer large enough to hold extended-length paths (up to ~32K // characters) instead of the legacy MAX_PATH (260) limit. @@ -489,8 +472,8 @@ pub fn list_audio_processes() -> Result> { let _ = CoInitializeEx(None, COINIT_MULTITHREADED); }; - let result = get_all_audio_processes() - .map_err(|err| napi::Error::new(napi::Status::GenericFailure, err.message()))?; + let result = + get_all_audio_processes().map_err(|err| napi::Error::new(napi::Status::GenericFailure, err.message()))?; Ok(result) } @@ -501,8 +484,7 @@ pub fn list_audio_devices() -> Result> { let _ = CoInitializeEx(None, COINIT_MULTITHREADED); }; - let result = get_all_audio_devices() - .map_err(|err| napi::Error::new(napi::Status::GenericFailure, err.message()))?; + let result = get_all_audio_devices().map_err(|err| napi::Error::new(napi::Status::GenericFailure, err.message()))?; Ok(result) } @@ -511,8 +493,7 @@ fn get_all_audio_processes() -> windows_core::Result> { unsafe { let enumerator: IMMDeviceEnumerator = CoCreateInstance(&MMDeviceEnumerator, None, CLSCTX_ALL)?; - let device_collection: IMMDeviceCollection = - enumerator.EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE)?; + let device_collection: IMMDeviceCollection = enumerator.EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE)?; let device_count = device_collection.GetCount()?; let mut all_processes = Vec::new(); @@ -569,8 +550,7 @@ fn get_all_audio_devices() -> windows_core::Result> { unsafe { let enumerator: IMMDeviceEnumerator = CoCreateInstance(&MMDeviceEnumerator, None, CLSCTX_ALL)?; - let device_collection: IMMDeviceCollection = - enumerator.EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE)?; + let device_collection: IMMDeviceCollection = enumerator.EnumAudioEndpoints(eCapture, DEVICE_STATE_ACTIVE)?; let device_count = device_collection.GetCount()?; let mut devices = Vec::new(); @@ -619,8 +599,8 @@ pub fn get_active_audio_processes() -> Result> { let _ = CoInitializeEx(None, COINIT_MULTITHREADED); }; - let result = get_all_audio_processes() - .map_err(|err| napi::Error::new(napi::Status::GenericFailure, err.message()))?; + let result = + get_all_audio_processes().map_err(|err| napi::Error::new(napi::Status::GenericFailure, err.message()))?; // Filter to only return active/running processes let active_processes = result.into_iter().filter(|p| p.is_running).collect(); @@ -633,8 +613,8 @@ pub fn is_process_actively_using_microphone(pid: u32) -> Result { let _ = CoInitializeEx(None, COINIT_MULTITHREADED); }; - let result = get_all_audio_processes() - .map_err(|err| napi::Error::new(napi::Status::GenericFailure, err.message()))?; + let result = + get_all_audio_processes().map_err(|err| napi::Error::new(napi::Status::GenericFailure, err.message()))?; // Check if the PID exists in the list of active processes let is_active = result diff --git a/packages/frontend/native/media_capture/src/windows/screen_capture_kit.rs b/packages/frontend/native/media_capture/src/windows/screen_capture_kit.rs index 0a0fe83525..817c91bc35 100644 --- a/packages/frontend/native/media_capture/src/windows/screen_capture_kit.rs +++ b/packages/frontend/native/media_capture/src/windows/screen_capture_kit.rs @@ -3,8 +3,8 @@ use std::{ ffi::OsString, os::windows::ffi::OsStringExt, sync::{ - atomic::{AtomicBool, AtomicU32, Ordering}, Arc, LazyLock, RwLock, + atomic::{AtomicBool, AtomicU32, Ordering}, }, thread, time::Duration, @@ -18,9 +18,9 @@ use napi_derive::napi; // Windows API imports use windows::Win32::Foundation::{CloseHandle, INVALID_HANDLE_VALUE}; // HWND removed use windows::Win32::System::{ - Com::{CoInitializeEx, COINIT_MULTITHREADED}, + Com::{COINIT_MULTITHREADED, CoInitializeEx}, Diagnostics::ToolHelp::{ - CreateToolhelp32Snapshot, Process32FirstW, Process32NextW, PROCESSENTRY32W, TH32CS_SNAPPROCESS, + CreateToolhelp32Snapshot, PROCESSENTRY32W, Process32FirstW, Process32NextW, TH32CS_SNAPPROCESS, }, ProcessStatus::{GetModuleFileNameExW, GetProcessImageFileNameW}, Threading::{OpenProcess, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ}, @@ -34,20 +34,17 @@ pub type AudioObjectID = u32; // Global storage for running applications (Windows equivalent of macOS audio // process list) -static RUNNING_APPLICATIONS: LazyLock>> = - LazyLock::new(|| RwLock::new(get_running_processes())); +static RUNNING_APPLICATIONS: LazyLock>> = LazyLock::new(|| RwLock::new(get_running_processes())); // Simple counter for generating unique handles static NEXT_HANDLE: AtomicU32 = AtomicU32::new(1); // Global storage for active watchers -static ACTIVE_APP_WATCHERS: LazyLock< - RwLock>, Arc)>>, -> = LazyLock::new(|| RwLock::new(Vec::new())); +static ACTIVE_APP_WATCHERS: LazyLock>, Arc)>>> = + LazyLock::new(|| RwLock::new(Vec::new())); -static ACTIVE_LIST_WATCHERS: LazyLock< - RwLock>, Arc)>>, -> = LazyLock::new(|| RwLock::new(Vec::new())); +static ACTIVE_LIST_WATCHERS: LazyLock>, Arc)>>> = + LazyLock::new(|| RwLock::new(Vec::new())); // Plain struct for efficient transmission via napi-rs #[napi] @@ -103,10 +100,7 @@ impl ApplicationListChangedSubscriber { #[napi] pub fn unsubscribe(&self) -> Result<()> { if let Ok(mut watchers) = ACTIVE_LIST_WATCHERS.write() { - if let Some(pos) = watchers - .iter() - .position(|(handle, _, _)| *handle == self.handle) - { + if let Some(pos) = watchers.iter().position(|(handle, _, _)| *handle == self.handle) { let (_, _, should_stop) = &watchers[pos]; should_stop.store(true, Ordering::Relaxed); watchers.remove(pos); @@ -132,10 +126,7 @@ impl ApplicationStateChangedSubscriber { #[napi] pub fn unsubscribe(&self) { if let Ok(mut watchers) = ACTIVE_APP_WATCHERS.write() { - if let Some(pos) = watchers - .iter() - .position(|(handle, _, _, _)| *handle == self.handle) - { + if let Some(pos) = watchers.iter().position(|(handle, _, _, _)| *handle == self.handle) { let (_, _, _, should_stop) = &watchers[pos]; should_stop.store(true, Ordering::Relaxed); watchers.remove(pos); @@ -152,9 +143,7 @@ pub struct ShareableContent { #[napi] impl ShareableContent { #[napi] - pub fn on_application_list_changed( - callback: ThreadsafeFunction<(), ()>, - ) -> Result { + pub fn on_application_list_changed(callback: ThreadsafeFunction<(), ()>) -> Result { let handle = NEXT_HANDLE.fetch_add(1, Ordering::Relaxed); let callback_arc = Arc::new(callback); @@ -189,25 +178,20 @@ impl ShareableContent { #[napi(constructor)] pub fn new() -> Self { unsafe { - CoInitializeEx(None, COINIT_MULTITHREADED) - .ok() - .unwrap_or_else(|_| { - // COM initialization failed, but we can't return an error from - // constructor This is typically not fatal as COM might - // already be initialized - }); + CoInitializeEx(None, COINIT_MULTITHREADED).ok().unwrap_or_else(|_| { + // COM initialization failed, but we can't return an error from + // constructor This is typically not fatal as COM might + // already be initialized + }); } Self {} } #[napi] pub fn applications() -> Result> { - let processes = RUNNING_APPLICATIONS.read().map_err(|_| { - Error::new( - Status::GenericFailure, - "Failed to read running applications", - ) - })?; + let processes = RUNNING_APPLICATIONS + .read() + .map_err(|_| Error::new(Status::GenericFailure, "Failed to read running applications"))?; let mut apps = Vec::new(); for &process_id in processes.iter() { @@ -326,8 +310,7 @@ fn is_process_running(process_id: u32) -> bool { fn get_process_name(pid: u32) -> Option { unsafe { - let process_handle = - OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid).ok()?; + let process_handle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid).ok()?; // Allocate a buffer large enough to hold extended-length paths (up to ~32K // characters) instead of the legacy MAX_PATH (260) limit. 32 768 is the // maximum length supported by the Win32 APIs when the path is prefixed @@ -352,8 +335,7 @@ fn get_process_name(pid: u32) -> Option { fn get_process_executable_path(pid: u32) -> Option { unsafe { - let process_handle = - OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid).ok()?; + let process_handle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid).ok()?; // Use a buffer that can hold extended-length paths. See rationale above. let mut buffer: Vec = std::iter::repeat(0).take(32_768).collect(); @@ -372,11 +354,7 @@ fn get_process_executable_path(pid: u32) -> Option { } // Helper function to start monitoring a specific process -fn start_process_monitoring( - handle: u32, - process_id: u32, - callback: Arc>, -) { +fn start_process_monitoring(handle: u32, process_id: u32, callback: Arc>) { let should_stop = Arc::new(AtomicBool::new(false)); let should_stop_clone = should_stop.clone(); diff --git a/packages/frontend/native/nbstore/Cargo.toml b/packages/frontend/native/nbstore/Cargo.toml index 08bffcc69d..5ef99e5a94 100644 --- a/packages/frontend/native/nbstore/Cargo.toml +++ b/packages/frontend/native/nbstore/Cargo.toml @@ -1,5 +1,5 @@ [package] -edition = "2021" +edition = "2024" name = "affine_nbstore" version = "0.0.0" diff --git a/packages/frontend/native/nbstore/src/blob.rs b/packages/frontend/native/nbstore/src/blob.rs index be574f2878..5552a24d55 100644 --- a/packages/frontend/native/nbstore/src/blob.rs +++ b/packages/frontend/native/nbstore/src/blob.rs @@ -1,6 +1,6 @@ use std::ops::Deref; -use super::{error::Result, storage::SqliteDocStorage, Blob, ListedBlob, SetBlob}; +use super::{Blob, ListedBlob, SetBlob, error::Result, storage::SqliteDocStorage}; impl SqliteDocStorage { pub async fn get_blob(&self, key: String) -> Result> { @@ -60,8 +60,7 @@ impl SqliteDocStorage { pub async fn list_blobs(&self) -> Result> { let result = sqlx::query_as!( ListedBlob, - "SELECT key, size, mime, created_at FROM blobs WHERE deleted_at IS NULL ORDER BY created_at \ - DESC;" + "SELECT key, size, mime, created_at FROM blobs WHERE deleted_at IS NULL ORDER BY created_at DESC;" ) .fetch_all(&self.pool) .await?; @@ -102,18 +101,12 @@ mod tests { assert!(result.is_some()); - storage - .delete_blob("test_".to_string(), false) - .await - .unwrap(); + storage.delete_blob("test_".to_string(), false).await.unwrap(); let result = storage.get_blob("test".to_string()).await.unwrap(); assert!(result.is_none()); - storage - .delete_blob("test_2".to_string(), true) - .await - .unwrap(); + storage.delete_blob("test_2".to_string(), true).await.unwrap(); let result = storage.get_blob("test".to_string()).await.unwrap(); assert!(result.is_none()); @@ -146,15 +139,9 @@ mod tests { vec!["test_1", "test_2", "test_3", "test_4"] ); - storage - .delete_blob("test_2".to_string(), false) - .await - .unwrap(); + storage.delete_blob("test_2".to_string(), false).await.unwrap(); - storage - .delete_blob("test_3".to_string(), true) - .await - .unwrap(); + storage.delete_blob("test_3".to_string(), true).await.unwrap(); let query = sqlx::query("SELECT COUNT(*) as len FROM blobs;") .fetch_one(&storage.pool) @@ -186,10 +173,7 @@ mod tests { .unwrap(); } - storage - .delete_blob("test_2".to_string(), false) - .await - .unwrap(); + storage.delete_blob("test_2".to_string(), false).await.unwrap(); storage.release_blobs().await.unwrap(); let query = sqlx::query("SELECT COUNT(*) as len FROM blobs;") diff --git a/packages/frontend/native/nbstore/src/blob_sync.rs b/packages/frontend/native/nbstore/src/blob_sync.rs index 32c1bd5586..617e04fa11 100644 --- a/packages/frontend/native/nbstore/src/blob_sync.rs +++ b/packages/frontend/native/nbstore/src/blob_sync.rs @@ -25,11 +25,7 @@ impl SqliteDocStorage { Ok(()) } - pub async fn get_blob_uploaded_at( - &self, - peer: String, - blob_id: String, - ) -> Result> { + pub async fn get_blob_uploaded_at(&self, peer: String, blob_id: String) -> Result> { let result = sqlx::query_scalar!( "SELECT uploaded_at FROM peer_blob_sync WHERE peer = ? AND blob_id = ?", peer, diff --git a/packages/frontend/native/nbstore/src/doc.rs b/packages/frontend/native/nbstore/src/doc.rs index e05dd251eb..123eff7a3f 100644 --- a/packages/frontend/native/nbstore/src/doc.rs +++ b/packages/frontend/native/nbstore/src/doc.rs @@ -3,7 +3,7 @@ use std::ops::Deref; use chrono::{DateTime, NaiveDateTime}; use sqlx::{QueryBuilder, Row}; -use super::{error::Result, storage::SqliteDocStorage, DocClock, DocRecord, DocUpdate}; +use super::{DocClock, DocRecord, DocUpdate, error::Result, storage::SqliteDocStorage}; struct Meta { space_id: String, @@ -65,11 +65,7 @@ impl SqliteDocStorage { Ok(()) } - pub async fn push_update>( - &self, - doc_id: String, - update: Update, - ) -> Result { + pub async fn push_update>(&self, doc_id: String, update: Update) -> Result { let mut timestamp = DateTime::from_timestamp_millis(chrono::Utc::now().timestamp_millis()) .unwrap() .naive_utc(); @@ -171,11 +167,7 @@ impl SqliteDocStorage { Ok(result) } - pub async fn mark_updates_merged( - &self, - doc_id: String, - updates: Vec, - ) -> Result { + pub async fn mark_updates_merged(&self, doc_id: String, updates: Vec) -> Result { let mut qb = QueryBuilder::new("DELETE FROM updates"); qb.push(" WHERE doc_id = "); @@ -297,10 +289,7 @@ mod tests { let storage = get_storage().await; storage.set_space_id("test".to_string()).await.unwrap(); - storage - .push_update("test".to_string(), vec![0, 0]) - .await - .unwrap(); + storage.push_update("test".to_string(), vec![0, 0]).await.unwrap(); storage .set_doc_snapshot(DocRecord { doc_id: "test".to_string(), @@ -311,11 +300,7 @@ mod tests { .unwrap(); storage - .set_peer_pulled_remote_clock( - "remote".to_string(), - "test".to_string(), - Utc::now().naive_utc(), - ) + .set_peer_pulled_remote_clock("remote".to_string(), "test".to_string(), Utc::now().naive_utc()) .await .unwrap(); @@ -344,10 +329,7 @@ mod tests { assert_eq!(updates.len(), 1); - let snapshot = storage - .get_doc_snapshot("new_id".to_string()) - .await - .unwrap(); + let snapshot = storage.get_doc_snapshot("new_id".to_string()).await.unwrap(); assert!(snapshot.is_some()); } @@ -359,19 +341,13 @@ mod tests { let updates = vec![vec![0, 0], vec![0, 1], vec![1, 0], vec![1, 1]]; for update in updates.iter() { - storage - .push_update("test".to_string(), update) - .await - .unwrap(); + storage.push_update("test".to_string(), update).await.unwrap(); } let result = storage.get_doc_updates("test".to_string()).await.unwrap(); assert_eq!(result.len(), 4); - assert_eq!( - result.iter().map(|u| u.bin.to_vec()).collect::>(), - updates - ); + assert_eq!(result.iter().map(|u| u.bin.to_vec()).collect::>(), updates); } #[tokio::test] @@ -439,10 +415,7 @@ mod tests { assert_eq!(clocks.len(), 0); for i in 1..5u32 { - storage - .push_update(format!("test_{i}"), vec![0, 0]) - .await - .unwrap(); + storage.push_update(format!("test_{i}"), vec![0, 0]).await.unwrap(); } let clocks = storage.get_doc_clocks(None).await.unwrap(); @@ -453,10 +426,7 @@ mod tests { vec!["test_1", "test_2", "test_3", "test_4"] ); - let clocks = storage - .get_doc_clocks(Some(Utc::now().naive_utc())) - .await - .unwrap(); + let clocks = storage.get_doc_clocks(Some(Utc::now().naive_utc())).await.unwrap(); assert_eq!(clocks.len(), 0); @@ -473,10 +443,7 @@ mod tests { let updates = [vec![0, 0], vec![0, 1], vec![1, 0], vec![1, 1]]; for update in updates.iter() { - storage - .push_update("test".to_string(), update) - .await - .unwrap(); + storage.push_update("test".to_string(), update).await.unwrap(); } let updates = storage.get_doc_updates("test".to_string()).await.unwrap(); @@ -484,11 +451,7 @@ mod tests { let result = storage .mark_updates_merged( "test".to_string(), - updates - .iter() - .skip(1) - .map(|u| u.timestamp) - .collect::>(), + updates.iter().skip(1).map(|u| u.timestamp).collect::>(), ) .await .unwrap(); diff --git a/packages/frontend/native/nbstore/src/doc_sync.rs b/packages/frontend/native/nbstore/src/doc_sync.rs index 3c12423b4b..1dedbe1c69 100644 --- a/packages/frontend/native/nbstore/src/doc_sync.rs +++ b/packages/frontend/native/nbstore/src/doc_sync.rs @@ -1,6 +1,6 @@ use chrono::NaiveDateTime; -use super::{error::Result, storage::SqliteDocStorage, DocClock}; +use super::{DocClock, error::Result, storage::SqliteDocStorage}; impl SqliteDocStorage { pub async fn get_peer_remote_clocks(&self, peer: String) -> Result> { @@ -15,11 +15,7 @@ impl SqliteDocStorage { Ok(result) } - pub async fn get_peer_remote_clock( - &self, - peer: String, - doc_id: String, - ) -> Result> { + pub async fn get_peer_remote_clock(&self, peer: String, doc_id: String) -> Result> { let result = sqlx::query_as!( DocClock, "SELECT doc_id, remote_clock as timestamp FROM peer_clocks WHERE peer = ? AND doc_id = ?", @@ -32,12 +28,7 @@ impl SqliteDocStorage { Ok(result) } - pub async fn set_peer_remote_clock( - &self, - peer: String, - doc_id: String, - clock: NaiveDateTime, - ) -> Result<()> { + pub async fn set_peer_remote_clock(&self, peer: String, doc_id: String, clock: NaiveDateTime) -> Result<()> { sqlx::query( r#" INSERT INTO peer_clocks (peer, doc_id, remote_clock) @@ -66,11 +57,7 @@ impl SqliteDocStorage { Ok(result) } - pub async fn get_peer_pulled_remote_clock( - &self, - peer: String, - doc_id: String, - ) -> Result> { + pub async fn get_peer_pulled_remote_clock(&self, peer: String, doc_id: String) -> Result> { let result = sqlx::query_as!( DocClock, r#"SELECT doc_id, pulled_remote_clock as timestamp FROM peer_clocks WHERE peer = ? AND doc_id = ?"#, @@ -83,12 +70,7 @@ impl SqliteDocStorage { Ok(result) } - pub async fn set_peer_pulled_remote_clock( - &self, - peer: String, - doc_id: String, - clock: NaiveDateTime, - ) -> Result<()> { + pub async fn set_peer_pulled_remote_clock(&self, peer: String, doc_id: String, clock: NaiveDateTime) -> Result<()> { sqlx::query( r#" INSERT INTO peer_clocks (peer, doc_id, pulled_remote_clock) @@ -117,11 +99,7 @@ impl SqliteDocStorage { Ok(result) } - pub async fn get_peer_pushed_clock( - &self, - peer: String, - doc_id: String, - ) -> Result> { + pub async fn get_peer_pushed_clock(&self, peer: String, doc_id: String) -> Result> { let result = sqlx::query_as!( DocClock, "SELECT doc_id, pushed_clock as timestamp FROM peer_clocks WHERE peer = ? AND doc_id = ?", @@ -134,12 +112,7 @@ impl SqliteDocStorage { Ok(result) } - pub async fn set_peer_pushed_clock( - &self, - peer: String, - doc_id: String, - clock: NaiveDateTime, - ) -> Result<()> { + pub async fn set_peer_pushed_clock(&self, peer: String, doc_id: String, clock: NaiveDateTime) -> Result<()> { sqlx::query( r#" INSERT INTO peer_clocks (peer, doc_id, pushed_clock) @@ -157,9 +130,7 @@ impl SqliteDocStorage { } pub async fn clear_clocks(&self) -> Result<()> { - sqlx::query("DELETE FROM peer_clocks;") - .execute(&self.pool) - .await?; + sqlx::query("DELETE FROM peer_clocks;").execute(&self.pool).await?; Ok(()) } diff --git a/packages/frontend/native/nbstore/src/indexer.rs b/packages/frontend/native/nbstore/src/indexer.rs index bf82813243..a54bb199b9 100644 --- a/packages/frontend/native/nbstore/src/indexer.rs +++ b/packages/frontend/native/nbstore/src/indexer.rs @@ -1,4 +1,4 @@ -use affine_common::doc_parser::{parse_doc_from_binary, BlockInfo, CrawlResult, ParseError}; +use affine_common::doc_parser::{BlockInfo, CrawlResult, ParseError, parse_doc_from_binary}; use memory_indexer::{SearchHit, SnapshotData}; use napi_derive::napi; use serde::Serialize; @@ -94,10 +94,7 @@ impl From<(u32, u32)> for NativeMatch { impl SqliteDocStorage { pub async fn crawl_doc_data(&self, doc_id: &str) -> Result { - let doc_bin = self - .load_doc_binary(doc_id) - .await? - .ok_or(ParseError::DocNotFound)?; + let doc_bin = self.load_doc_binary(doc_id).await?.ok_or(ParseError::DocNotFound)?; let result = parse_doc_from_binary(doc_bin, doc_id.to_string())?; Ok(result.into()) @@ -113,8 +110,7 @@ impl SqliteDocStorage { updates.sort_by(|a, b| a.timestamp.cmp(&b.timestamp)); - let mut segments = - Vec::with_capacity(snapshot.as_ref().map(|_| 1).unwrap_or(0) + updates.len()); + let mut segments = Vec::with_capacity(snapshot.as_ref().map(|_| 1).unwrap_or(0) + updates.len()); if let Some(record) = snapshot { segments.push(record.bin.to_vec()); } @@ -134,12 +130,10 @@ impl SqliteDocStorage { for row in snapshots { let index_name: String = row.get("index_name"); let data: Vec = row.get("data"); - if let Ok(decompressed) = zstd::stream::decode_all(std::io::Cursor::new(&data)) { - if let Ok((snapshot, _)) = - bincode::serde::decode_from_slice::(&decompressed, config) - { - index.load_snapshot(&index_name, snapshot); - } + if let Ok(decompressed) = zstd::stream::decode_all(std::io::Cursor::new(&data)) + && let Ok((snapshot, _)) = bincode::serde::decode_from_slice::(&decompressed, config) + { + index.load_snapshot(&index_name, snapshot); } } } @@ -156,8 +150,8 @@ impl SqliteDocStorage { if let Some(data) = snapshot_data { let blob = bincode::serde::encode_to_vec(&data, bincode::config::standard()) .map_err(|e| Error::Serialization(e.to_string()))?; - let compressed = zstd::stream::encode_all(std::io::Cursor::new(&blob), 4) - .map_err(|e| Error::Serialization(e.to_string()))?; + let compressed = + zstd::stream::encode_all(std::io::Cursor::new(&blob), 4).map_err(|e| Error::Serialization(e.to_string()))?; let mut tx = self.pool.begin().await?; @@ -201,13 +195,7 @@ impl SqliteDocStorage { memory_indexer::InMemoryIndex::snapshot_version() } - pub async fn fts_add( - &self, - index_name: &str, - doc_id: &str, - text: &str, - index: bool, - ) -> Result<()> { + pub async fn fts_add(&self, index_name: &str, doc_id: &str, text: &str, index: bool) -> Result<()> { let mut idx = self.index.write().await; idx.add_doc(index_name, doc_id, text, index); Ok(()) @@ -226,21 +214,10 @@ impl SqliteDocStorage { pub async fn fts_search(&self, index_name: &str, query: &str) -> Result> { let idx = self.index.read().await; - Ok( - idx - .search_hits(index_name, query) - .into_iter() - .map(Into::into) - .collect(), - ) + Ok(idx.search_hits(index_name, query).into_iter().map(Into::into).collect()) } - pub async fn fts_get_matches( - &self, - index_name: &str, - doc_id: &str, - query: &str, - ) -> Result> { + pub async fn fts_get_matches(&self, index_name: &str, doc_id: &str, query: &str) -> Result> { let idx = self.index.read().await; Ok( idx diff --git a/packages/frontend/native/nbstore/src/indexer_sync.rs b/packages/frontend/native/nbstore/src/indexer_sync.rs index ce01dbc44b..42c3bd8ab2 100644 --- a/packages/frontend/native/nbstore/src/indexer_sync.rs +++ b/packages/frontend/native/nbstore/src/indexer_sync.rs @@ -1,6 +1,6 @@ use chrono::NaiveDateTime; -use super::{error::Result, storage::SqliteDocStorage, DocIndexedClock}; +use super::{DocIndexedClock, error::Result, storage::SqliteDocStorage}; impl SqliteDocStorage { pub async fn get_doc_indexed_clock(&self, doc_id: String) -> Result> { @@ -70,10 +70,7 @@ mod tests { let storage = get_storage().await; let ts = Utc::now().naive_utc(); - storage - .set_doc_indexed_clock("doc1".to_string(), ts, 1) - .await - .unwrap(); + storage.set_doc_indexed_clock("doc1".to_string(), ts, 1).await.unwrap(); let clock = storage .get_doc_indexed_clock("doc1".to_string()) @@ -91,20 +88,11 @@ mod tests { let storage = get_storage().await; let ts = Utc::now().naive_utc(); - storage - .set_doc_indexed_clock("doc1".to_string(), ts, 1) - .await - .unwrap(); + storage.set_doc_indexed_clock("doc1".to_string(), ts, 1).await.unwrap(); - storage - .clear_doc_indexed_clock("doc1".to_string()) - .await - .unwrap(); + storage.clear_doc_indexed_clock("doc1".to_string()).await.unwrap(); - let clock = storage - .get_doc_indexed_clock("doc1".to_string()) - .await - .unwrap(); + let clock = storage.get_doc_indexed_clock("doc1".to_string()).await.unwrap(); assert!(clock.is_none()); } diff --git a/packages/frontend/native/nbstore/src/lib.rs b/packages/frontend/native/nbstore/src/lib.rs index 1b9eb67b5b..89fc1f6f60 100644 --- a/packages/frontend/native/nbstore/src/lib.rs +++ b/packages/frontend/native/nbstore/src/lib.rs @@ -128,16 +128,8 @@ impl DocStoragePool { } #[napi] - pub async fn crawl_doc_data( - &self, - universal_id: String, - doc_id: String, - ) -> Result { - let result = self - .get(universal_id) - .await? - .crawl_doc_data(&doc_id) - .await?; + pub async fn crawl_doc_data(&self, universal_id: String, doc_id: String) -> Result { + let result = self.get(universal_id).await?.crawl_doc_data(&doc_id).await?; Ok(result) } @@ -148,60 +140,23 @@ impl DocStoragePool { } #[napi] - pub async fn push_update( - &self, - universal_id: String, - doc_id: String, - update: Uint8Array, - ) -> Result { - Ok( - self - .get(universal_id) - .await? - .push_update(doc_id, update) - .await?, - ) + pub async fn push_update(&self, universal_id: String, doc_id: String, update: Uint8Array) -> Result { + Ok(self.get(universal_id).await?.push_update(doc_id, update).await?) } #[napi] - pub async fn get_doc_snapshot( - &self, - universal_id: String, - doc_id: String, - ) -> Result> { - Ok( - self - .get(universal_id) - .await? - .get_doc_snapshot(doc_id) - .await?, - ) + pub async fn get_doc_snapshot(&self, universal_id: String, doc_id: String) -> Result> { + Ok(self.get(universal_id).await?.get_doc_snapshot(doc_id).await?) } #[napi] pub async fn set_doc_snapshot(&self, universal_id: String, snapshot: DocRecord) -> Result { - Ok( - self - .get(universal_id) - .await? - .set_doc_snapshot(snapshot) - .await?, - ) + Ok(self.get(universal_id).await?.set_doc_snapshot(snapshot).await?) } #[napi] - pub async fn get_doc_updates( - &self, - universal_id: String, - doc_id: String, - ) -> Result> { - Ok( - self - .get(universal_id) - .await? - .get_doc_updates(doc_id) - .await?, - ) + pub async fn get_doc_updates(&self, universal_id: String, doc_id: String) -> Result> { + Ok(self.get(universal_id).await?.get_doc_updates(doc_id).await?) } #[napi] @@ -227,36 +182,18 @@ impl DocStoragePool { } #[napi] - pub async fn get_doc_clocks( - &self, - universal_id: String, - after: Option, - ) -> Result> { + pub async fn get_doc_clocks(&self, universal_id: String, after: Option) -> Result> { Ok(self.get(universal_id).await?.get_doc_clocks(after).await?) } #[napi] - pub async fn get_doc_clock( - &self, - universal_id: String, - doc_id: String, - ) -> Result> { + pub async fn get_doc_clock(&self, universal_id: String, doc_id: String) -> Result> { Ok(self.get(universal_id).await?.get_doc_clock(doc_id).await?) } #[napi] - pub async fn get_doc_indexed_clock( - &self, - universal_id: String, - doc_id: String, - ) -> Result> { - Ok( - self - .get(universal_id) - .await? - .get_doc_indexed_clock(doc_id) - .await?, - ) + pub async fn get_doc_indexed_clock(&self, universal_id: String, doc_id: String) -> Result> { + Ok(self.get(universal_id).await?.get_doc_indexed_clock(doc_id).await?) } #[napi] @@ -277,11 +214,7 @@ impl DocStoragePool { #[napi] pub async fn clear_doc_indexed_clock(&self, universal_id: String, doc_id: String) -> Result<()> { - self - .get(universal_id) - .await? - .clear_doc_indexed_clock(doc_id) - .await?; + self.get(universal_id).await?.clear_doc_indexed_clock(doc_id).await?; Ok(()) } @@ -297,17 +230,8 @@ impl DocStoragePool { } #[napi] - pub async fn delete_blob( - &self, - universal_id: String, - key: String, - permanently: bool, - ) -> Result<()> { - self - .get(universal_id) - .await? - .delete_blob(key, permanently) - .await?; + pub async fn delete_blob(&self, universal_id: String, key: String, permanently: bool) -> Result<()> { + self.get(universal_id).await?.delete_blob(key, permanently).await?; Ok(()) } @@ -323,18 +247,8 @@ impl DocStoragePool { } #[napi] - pub async fn get_peer_remote_clocks( - &self, - universal_id: String, - peer: String, - ) -> Result> { - Ok( - self - .get(universal_id) - .await? - .get_peer_remote_clocks(peer) - .await?, - ) + pub async fn get_peer_remote_clocks(&self, universal_id: String, peer: String) -> Result> { + Ok(self.get(universal_id).await?.get_peer_remote_clocks(peer).await?) } #[napi] @@ -370,11 +284,7 @@ impl DocStoragePool { } #[napi] - pub async fn get_peer_pulled_remote_clocks( - &self, - universal_id: String, - peer: String, - ) -> Result> { + pub async fn get_peer_pulled_remote_clocks(&self, universal_id: String, peer: String) -> Result> { Ok( self .get(universal_id) @@ -417,18 +327,8 @@ impl DocStoragePool { } #[napi] - pub async fn get_peer_pushed_clocks( - &self, - universal_id: String, - peer: String, - ) -> Result> { - Ok( - self - .get(universal_id) - .await? - .get_peer_pushed_clocks(peer) - .await?, - ) + pub async fn get_peer_pushed_clocks(&self, universal_id: String, peer: String) -> Result> { + Ok(self.get(universal_id).await?.get_peer_pushed_clocks(peer).await?) } #[napi] @@ -528,24 +428,14 @@ impl DocStoragePool { } #[napi] - pub async fn fts_delete_document( - &self, - id: String, - index_name: String, - doc_id: String, - ) -> Result<()> { + pub async fn fts_delete_document(&self, id: String, index_name: String, doc_id: String) -> Result<()> { let storage = self.pool.get(id).await?; storage.fts_delete(&index_name, &doc_id).await?; Ok(()) } #[napi] - pub async fn fts_get_document( - &self, - id: String, - index_name: String, - doc_id: String, - ) -> Result> { + pub async fn fts_get_document(&self, id: String, index_name: String, doc_id: String) -> Result> { let storage = self.pool.get(id).await?; Ok(storage.fts_get(&index_name, &doc_id).await?) } @@ -570,11 +460,7 @@ impl DocStoragePool { query: String, ) -> Result> { let storage = self.pool.get(id).await?; - Ok( - storage - .fts_get_matches(&index_name, &doc_id, &query) - .await?, - ) + Ok(storage.fts_get_matches(&index_name, &doc_id, &query).await?) } } diff --git a/packages/frontend/native/nbstore/src/pool.rs b/packages/frontend/native/nbstore/src/pool.rs index d3f5323df7..fed79e227d 100644 --- a/packages/frontend/native/nbstore/src/pool.rs +++ b/packages/frontend/native/nbstore/src/pool.rs @@ -44,18 +44,12 @@ pub struct SqliteDocStoragePool { } impl SqliteDocStoragePool { - async fn get_or_create_storage<'a>( - &'a self, - universal_id: String, - path: &str, - ) -> RefMut<'a, SqliteDocStorage> { - let lock = RwLockWriteGuard::map(self.inner.write().await, |lock| { - match lock.entry(universal_id) { - Entry::Occupied(entry) => entry.into_mut(), - Entry::Vacant(entry) => { - let storage = SqliteDocStorage::new(path.to_string()); - entry.insert(storage) - } + async fn get_or_create_storage<'a>(&'a self, universal_id: String, path: &str) -> RefMut<'a, SqliteDocStorage> { + let lock = RwLockWriteGuard::map(self.inner.write().await, |lock| match lock.entry(universal_id) { + Entry::Occupied(entry) => entry.into_mut(), + Entry::Vacant(entry) => { + let storage = SqliteDocStorage::new(path.to_string()); + entry.insert(storage) } }); @@ -79,9 +73,7 @@ impl SqliteDocStoragePool { /// Initialize the database and run migrations. pub async fn connect(&self, universal_id: String, path: String) -> Result<()> { - let storage = self - .get_or_create_storage(universal_id.to_owned(), &path) - .await; + let storage = self.get_or_create_storage(universal_id.to_owned(), &path).await; storage.connect().await?; Ok(()) diff --git a/packages/frontend/native/nbstore/src/storage.rs b/packages/frontend/native/nbstore/src/storage.rs index 82850199f8..71ad15ef4c 100644 --- a/packages/frontend/native/nbstore/src/storage.rs +++ b/packages/frontend/native/nbstore/src/storage.rs @@ -3,9 +3,9 @@ use std::sync::Arc; use affine_schema::get_migrator; use memory_indexer::InMemoryIndex; use sqlx::{ + Pool, Row, migrate::MigrateDatabase, sqlite::{Sqlite, SqliteConnectOptions, SqlitePoolOptions}, - Pool, Row, }; use tokio::sync::RwLock; @@ -19,9 +19,7 @@ pub struct SqliteDocStorage { impl SqliteDocStorage { pub fn new(path: String) -> Self { - let sqlite_options = SqliteConnectOptions::new() - .filename(&path) - .foreign_keys(false); + let sqlite_options = SqliteConnectOptions::new().filename(&path).foreign_keys(false); let mut pool_options = SqlitePoolOptions::new(); @@ -94,9 +92,7 @@ impl SqliteDocStorage { /// Flush the WAL file to the database file. /// See https://www.sqlite.org/pragma.html#pragma_wal_checkpoint:~:text=PRAGMA%20schema.wal_checkpoint%3B pub async fn checkpoint(&self) -> Result<()> { - sqlx::query("PRAGMA wal_checkpoint(FULL);") - .execute(&self.pool) - .await?; + sqlx::query("PRAGMA wal_checkpoint(FULL);").execute(&self.pool).await?; Ok(()) } diff --git a/packages/frontend/native/schema/Cargo.toml b/packages/frontend/native/schema/Cargo.toml index 2180e23d44..c75fa92d4f 100644 --- a/packages/frontend/native/schema/Cargo.toml +++ b/packages/frontend/native/schema/Cargo.toml @@ -1,5 +1,5 @@ [package] -edition = "2021" +edition = "2024" name = "affine_schema" version = "0.0.0" diff --git a/packages/frontend/native/sqlite_v1/Cargo.toml b/packages/frontend/native/sqlite_v1/Cargo.toml index 9085d7dcbc..03b34f5908 100644 --- a/packages/frontend/native/sqlite_v1/Cargo.toml +++ b/packages/frontend/native/sqlite_v1/Cargo.toml @@ -1,5 +1,5 @@ [package] -edition = "2021" +edition = "2024" name = "affine_sqlite_v1" version = "0.0.0" diff --git a/packages/frontend/native/sqlite_v1/build.rs b/packages/frontend/native/sqlite_v1/build.rs index 67a6d3da8c..3a3fe36cb9 100644 --- a/packages/frontend/native/sqlite_v1/build.rs +++ b/packages/frontend/native/sqlite_v1/build.rs @@ -25,10 +25,7 @@ async fn main() -> Result<(), std::io::Error> { .await .unwrap(); - sqlx::query(affine_schema::v1::SCHEMA) - .execute(&pool) - .await - .unwrap(); + sqlx::query(affine_schema::v1::SCHEMA).execute(&pool).await.unwrap(); println!("cargo::rustc-env=DATABASE_URL=sqlite://{db_path}"); diff --git a/packages/frontend/native/sqlite_v1/src/lib.rs b/packages/frontend/native/sqlite_v1/src/lib.rs index 4bab152271..2aeea46e18 100644 --- a/packages/frontend/native/sqlite_v1/src/lib.rs +++ b/packages/frontend/native/sqlite_v1/src/lib.rs @@ -2,9 +2,9 @@ use chrono::NaiveDateTime; use napi::bindgen_prelude::{Buffer, Uint8Array}; use napi_derive::napi; use sqlx::{ + Pool, Row, migrate::MigrateDatabase, sqlite::{Sqlite, SqliteConnectOptions, SqlitePoolOptions}, - Pool, Row, }; // latest version @@ -69,9 +69,7 @@ impl SqliteConnection { #[napi] pub async fn connect(&self) -> napi::Result<()> { if !Sqlite::database_exists(&self.path).await.unwrap_or(false) { - Sqlite::create_database(&self.path) - .await - .map_err(anyhow::Error::from)?; + Sqlite::create_database(&self.path).await.map_err(anyhow::Error::from)?; }; let mut connection = self.pool.acquire().await.map_err(anyhow::Error::from)?; sqlx::query(affine_schema::v1::SCHEMA) @@ -89,8 +87,7 @@ impl SqliteConnection { let blob = blob.as_ref(); sqlx::query_as!( BlobRow, - "INSERT INTO blobs (key, data) VALUES ($1, $2) ON CONFLICT(key) DO UPDATE SET data = \ - excluded.data", + "INSERT INTO blobs (key, data) VALUES ($1, $2) ON CONFLICT(key) DO UPDATE SET data = excluded.data", key, blob, ) @@ -102,14 +99,10 @@ impl SqliteConnection { #[napi] pub async fn get_blob(&self, key: String) -> Option { - sqlx::query_as!( - BlobRow, - "SELECT key, data, timestamp FROM blobs WHERE key = ?", - key - ) - .fetch_one(&self.pool) - .await - .ok() + sqlx::query_as!(BlobRow, "SELECT key, data, timestamp FROM blobs WHERE key = ?", key) + .fetch_one(&self.pool) + .await + .ok() } #[napi] @@ -190,14 +183,11 @@ impl SqliteConnection { pub async fn get_updates_count(&self, doc_id: Option) -> napi::Result { let count = match doc_id { Some(doc_id) => { - sqlx::query!( - "SELECT COUNT(*) as count FROM updates WHERE doc_id = ?", - doc_id - ) - .fetch_one(&self.pool) - .await - .map_err(anyhow::Error::from)? - .count + sqlx::query!("SELECT COUNT(*) as count FROM updates WHERE doc_id = ?", doc_id) + .fetch_one(&self.pool) + .await + .map_err(anyhow::Error::from)? + .count } None => { sqlx::query!("SELECT COUNT(*) as count FROM updates WHERE doc_id is NULL") @@ -239,11 +229,7 @@ impl SqliteConnection { } #[napi] - pub async fn replace_updates( - &self, - doc_id: Option, - updates: Vec, - ) -> napi::Result<()> { + pub async fn replace_updates(&self, doc_id: Option, updates: Vec) -> napi::Result<()> { let mut transaction = self.pool.begin().await.map_err(anyhow::Error::from)?; match doc_id { @@ -289,8 +275,7 @@ impl SqliteConnection { pub async fn set_server_clock(&self, key: String, data: Uint8Array) -> napi::Result<()> { let data = data.as_ref(); sqlx::query!( - "INSERT INTO server_clock (key, data) VALUES ($1, $2) ON CONFLICT(key) DO UPDATE SET data = \ - excluded.data", + "INSERT INTO server_clock (key, data) VALUES ($1, $2) ON CONFLICT(key) DO UPDATE SET data = excluded.data", key, data, ) @@ -344,8 +329,7 @@ impl SqliteConnection { pub async fn set_sync_metadata(&self, key: String, data: Uint8Array) -> napi::Result<()> { let data = data.as_ref(); sqlx::query!( - "INSERT INTO sync_metadata (key, data) VALUES ($1, $2) ON CONFLICT(key) DO UPDATE SET data \ - = excluded.data", + "INSERT INTO sync_metadata (key, data) VALUES ($1, $2) ON CONFLICT(key) DO UPDATE SET data = excluded.data", key, data, ) @@ -439,11 +423,7 @@ impl SqliteConnection { #[napi] pub async fn validate(path: String) -> ValidationResult { - let pool = match SqlitePoolOptions::new() - .max_connections(1) - .connect(&path) - .await - { + let pool = match SqlitePoolOptions::new().max_connections(1).connect(&path).await { Ok(pool) => pool, Err(_) => return ValidationResult::GeneralError, }; @@ -472,9 +452,7 @@ impl SqliteConnection { Err(_) => return ValidationResult::GeneralError, }; - let columns_res = sqlx::query("PRAGMA table_info(updates)") - .fetch_all(&pool) - .await; + let columns_res = sqlx::query("PRAGMA table_info(updates)").fetch_all(&pool).await; let doc_id_exist = match columns_res { Ok(res) => { diff --git a/packages/frontend/native/src/hashcash.rs b/packages/frontend/native/src/hashcash.rs index b1e32cd61f..bda991081f 100644 --- a/packages/frontend/native/src/hashcash.rs +++ b/packages/frontend/native/src/hashcash.rs @@ -1,7 +1,7 @@ use std::convert::TryFrom; use affine_common::hashcash::Stamp; -use napi::{bindgen_prelude::AsyncTask, Env, Result, Task}; +use napi::{Env, Result, Task, bindgen_prelude::AsyncTask}; use napi_derive::napi; pub struct AsyncVerifyChallengeResponse { @@ -61,9 +61,6 @@ impl Task for AsyncMintChallengeResponse { } #[napi] -pub fn mint_challenge_response( - resource: String, - bits: Option, -) -> AsyncTask { +pub fn mint_challenge_response(resource: String, bits: Option) -> AsyncTask { AsyncTask::new(AsyncMintChallengeResponse { bits, resource }) } diff --git a/rustfmt.toml b/rustfmt.toml index 543f822040..bf7df1cb63 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,10 +1,13 @@ -max_width = 100 +edition = "2024" +max_width = 120 +style_edition = "2024" hard_tabs = false tab_spaces = 2 -format_strings = true -wrap_comments = true +format_strings = true +unstable_features = true +wrap_comments = true group_imports = "StdExternalCrate" imports_granularity = "Crate" diff --git a/tools/utils/src/workspace.gen.ts b/tools/utils/src/workspace.gen.ts index 15c99b4f05..3b5c6eb94d 100644 --- a/tools/utils/src/workspace.gen.ts +++ b/tools/utils/src/workspace.gen.ts @@ -1222,11 +1222,6 @@ export const PackageList = [ name: '@affine/reader', workspaceDependencies: ['blocksuite/affine/all'], }, - { - location: 'packages/common/y-octo/node', - name: '@y-octo/node', - workspaceDependencies: [], - }, { location: 'packages/frontend/admin', name: '@affine/admin', @@ -1572,7 +1567,6 @@ export type PackageName = | '@toeverything/infra' | '@affine/nbstore' | '@affine/reader' - | '@y-octo/node' | '@affine/admin' | '@affine/android' | '@affine/electron' diff --git a/tsconfig.json b/tsconfig.json index d72f3e5412..1a43f6aa29 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -132,7 +132,6 @@ { "path": "./packages/common/infra" }, { "path": "./packages/common/nbstore" }, { "path": "./packages/common/reader" }, - { "path": "./packages/common/y-octo/node" }, { "path": "./packages/frontend/admin" }, { "path": "./packages/frontend/apps/android" }, { "path": "./packages/frontend/apps/electron" }, diff --git a/yarn.lock b/yarn.lock index e1bed10ece..7e575db249 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8104,7 +8104,7 @@ __metadata: languageName: node linkType: hard -"@inquirer/prompts@npm:^7.4.0, @inquirer/prompts@npm:^7.5.1": +"@inquirer/prompts@npm:^7.5.1": version: 7.5.1 resolution: "@inquirer/prompts@npm:7.5.1" dependencies: @@ -9017,39 +9017,6 @@ __metadata: languageName: node linkType: hard -"@napi-rs/cli@npm:3.0.0": - version: 3.0.0 - resolution: "@napi-rs/cli@npm:3.0.0" - dependencies: - "@inquirer/prompts": "npm:^7.4.0" - "@napi-rs/cross-toolchain": "npm:^0.0.19" - "@napi-rs/wasm-tools": "npm:^0.0.3" - "@octokit/rest": "npm:^22.0.0" - clipanion: "npm:^4.0.0-rc.4" - colorette: "npm:^2.0.20" - debug: "npm:^4.4.0" - emnapi: "npm:^1.4.0" - find-up: "npm:^7.0.0" - js-yaml: "npm:^4.1.0" - lodash-es: "npm:^4.17.21" - semver: "npm:^7.7.1" - typanion: "npm:^3.14.0" - wasm-sjlj: "npm:^1.0.6" - peerDependencies: - "@emnapi/runtime": ^1.1.0 - emnapi: ^1.1.0 - peerDependenciesMeta: - "@emnapi/runtime": - optional: true - emnapi: - optional: true - bin: - napi: ./dist/cli.js - napi-raw: ./cli.mjs - checksum: 10/d41944bab77f3b56173e45728206cd2654f438cc0fa290d0a88ec58a5b20dbd49bd5b12de949084e42dcb7b6ae90817510bfcfa89368b39e3129dbc8155ff0d1 - languageName: node - linkType: hard - "@napi-rs/cli@npm:3.5.0": version: 3.5.0 resolution: "@napi-rs/cli@npm:3.5.0" @@ -9078,37 +9045,6 @@ __metadata: languageName: node linkType: hard -"@napi-rs/cross-toolchain@npm:^0.0.19": - version: 0.0.19 - resolution: "@napi-rs/cross-toolchain@npm:0.0.19" - dependencies: - "@napi-rs/lzma": "npm:^1.4.1" - "@napi-rs/tar": "npm:^0.1.4" - debug: "npm:^4.4.0" - peerDependencies: - "@napi-rs/cross-toolchain-arm64-target-aarch64": ^0.0.19 - "@napi-rs/cross-toolchain-arm64-target-armv7": ^0.0.19 - "@napi-rs/cross-toolchain-arm64-target-x86_64": ^0.0.19 - "@napi-rs/cross-toolchain-x64-target-aarch64": ^0.0.19 - "@napi-rs/cross-toolchain-x64-target-armv7": ^0.0.19 - "@napi-rs/cross-toolchain-x64-target-x86_64": ^0.0.19 - peerDependenciesMeta: - "@napi-rs/cross-toolchain-arm64-target-aarch64": - optional: true - "@napi-rs/cross-toolchain-arm64-target-armv7": - optional: true - "@napi-rs/cross-toolchain-arm64-target-x86_64": - optional: true - "@napi-rs/cross-toolchain-x64-target-aarch64": - optional: true - "@napi-rs/cross-toolchain-x64-target-armv7": - optional: true - "@napi-rs/cross-toolchain-x64-target-x86_64": - optional: true - checksum: 10/80c58c1768272b5c5ecafd0a37d5bbe1c360de120fc5cb0f354ea668998bdb116436ffd1ba5f33086b1e27fdb36fdd6553b661e618d00438cfedd6a36a12339c - languageName: node - linkType: hard - "@napi-rs/cross-toolchain@npm:^1.0.3": version: 1.0.3 resolution: "@napi-rs/cross-toolchain@npm:1.0.3" @@ -9273,7 +9209,7 @@ __metadata: languageName: node linkType: hard -"@napi-rs/lzma@npm:^1.4.1, @napi-rs/lzma@npm:^1.4.5": +"@napi-rs/lzma@npm:^1.4.5": version: 1.4.5 resolution: "@napi-rs/lzma@npm:1.4.5" dependencies: @@ -9678,13 +9614,6 @@ __metadata: languageName: node linkType: hard -"@napi-rs/tar-android-arm-eabi@npm:0.1.5": - version: 0.1.5 - resolution: "@napi-rs/tar-android-arm-eabi@npm:0.1.5" - conditions: os=android & cpu=arm - languageName: node - linkType: hard - "@napi-rs/tar-android-arm-eabi@npm:1.1.0": version: 1.1.0 resolution: "@napi-rs/tar-android-arm-eabi@npm:1.1.0" @@ -9692,13 +9621,6 @@ __metadata: languageName: node linkType: hard -"@napi-rs/tar-android-arm64@npm:0.1.5": - version: 0.1.5 - resolution: "@napi-rs/tar-android-arm64@npm:0.1.5" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - "@napi-rs/tar-android-arm64@npm:1.1.0": version: 1.1.0 resolution: "@napi-rs/tar-android-arm64@npm:1.1.0" @@ -9706,13 +9628,6 @@ __metadata: languageName: node linkType: hard -"@napi-rs/tar-darwin-arm64@npm:0.1.5": - version: 0.1.5 - resolution: "@napi-rs/tar-darwin-arm64@npm:0.1.5" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - "@napi-rs/tar-darwin-arm64@npm:1.1.0": version: 1.1.0 resolution: "@napi-rs/tar-darwin-arm64@npm:1.1.0" @@ -9720,13 +9635,6 @@ __metadata: languageName: node linkType: hard -"@napi-rs/tar-darwin-x64@npm:0.1.5": - version: 0.1.5 - resolution: "@napi-rs/tar-darwin-x64@npm:0.1.5" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - "@napi-rs/tar-darwin-x64@npm:1.1.0": version: 1.1.0 resolution: "@napi-rs/tar-darwin-x64@npm:1.1.0" @@ -9734,13 +9642,6 @@ __metadata: languageName: node linkType: hard -"@napi-rs/tar-freebsd-x64@npm:0.1.5": - version: 0.1.5 - resolution: "@napi-rs/tar-freebsd-x64@npm:0.1.5" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - "@napi-rs/tar-freebsd-x64@npm:1.1.0": version: 1.1.0 resolution: "@napi-rs/tar-freebsd-x64@npm:1.1.0" @@ -9748,13 +9649,6 @@ __metadata: languageName: node linkType: hard -"@napi-rs/tar-linux-arm-gnueabihf@npm:0.1.5": - version: 0.1.5 - resolution: "@napi-rs/tar-linux-arm-gnueabihf@npm:0.1.5" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - "@napi-rs/tar-linux-arm-gnueabihf@npm:1.1.0": version: 1.1.0 resolution: "@napi-rs/tar-linux-arm-gnueabihf@npm:1.1.0" @@ -9762,13 +9656,6 @@ __metadata: languageName: node linkType: hard -"@napi-rs/tar-linux-arm64-gnu@npm:0.1.5": - version: 0.1.5 - resolution: "@napi-rs/tar-linux-arm64-gnu@npm:0.1.5" - conditions: os=linux & cpu=arm64 & libc=glibc - languageName: node - linkType: hard - "@napi-rs/tar-linux-arm64-gnu@npm:1.1.0": version: 1.1.0 resolution: "@napi-rs/tar-linux-arm64-gnu@npm:1.1.0" @@ -9776,13 +9663,6 @@ __metadata: languageName: node linkType: hard -"@napi-rs/tar-linux-arm64-musl@npm:0.1.5": - version: 0.1.5 - resolution: "@napi-rs/tar-linux-arm64-musl@npm:0.1.5" - conditions: os=linux & cpu=arm64 & libc=musl - languageName: node - linkType: hard - "@napi-rs/tar-linux-arm64-musl@npm:1.1.0": version: 1.1.0 resolution: "@napi-rs/tar-linux-arm64-musl@npm:1.1.0" @@ -9790,13 +9670,6 @@ __metadata: languageName: node linkType: hard -"@napi-rs/tar-linux-ppc64-gnu@npm:0.1.5": - version: 0.1.5 - resolution: "@napi-rs/tar-linux-ppc64-gnu@npm:0.1.5" - conditions: os=linux & cpu=ppc64 & libc=glibc - languageName: node - linkType: hard - "@napi-rs/tar-linux-ppc64-gnu@npm:1.1.0": version: 1.1.0 resolution: "@napi-rs/tar-linux-ppc64-gnu@npm:1.1.0" @@ -9804,13 +9677,6 @@ __metadata: languageName: node linkType: hard -"@napi-rs/tar-linux-s390x-gnu@npm:0.1.5": - version: 0.1.5 - resolution: "@napi-rs/tar-linux-s390x-gnu@npm:0.1.5" - conditions: os=linux & cpu=s390x & libc=glibc - languageName: node - linkType: hard - "@napi-rs/tar-linux-s390x-gnu@npm:1.1.0": version: 1.1.0 resolution: "@napi-rs/tar-linux-s390x-gnu@npm:1.1.0" @@ -9818,13 +9684,6 @@ __metadata: languageName: node linkType: hard -"@napi-rs/tar-linux-x64-gnu@npm:0.1.5": - version: 0.1.5 - resolution: "@napi-rs/tar-linux-x64-gnu@npm:0.1.5" - conditions: os=linux & cpu=x64 & libc=glibc - languageName: node - linkType: hard - "@napi-rs/tar-linux-x64-gnu@npm:1.1.0": version: 1.1.0 resolution: "@napi-rs/tar-linux-x64-gnu@npm:1.1.0" @@ -9832,13 +9691,6 @@ __metadata: languageName: node linkType: hard -"@napi-rs/tar-linux-x64-musl@npm:0.1.5": - version: 0.1.5 - resolution: "@napi-rs/tar-linux-x64-musl@npm:0.1.5" - conditions: os=linux & cpu=x64 & libc=musl - languageName: node - linkType: hard - "@napi-rs/tar-linux-x64-musl@npm:1.1.0": version: 1.1.0 resolution: "@napi-rs/tar-linux-x64-musl@npm:1.1.0" @@ -9846,15 +9698,6 @@ __metadata: languageName: node linkType: hard -"@napi-rs/tar-wasm32-wasi@npm:0.1.5": - version: 0.1.5 - resolution: "@napi-rs/tar-wasm32-wasi@npm:0.1.5" - dependencies: - "@napi-rs/wasm-runtime": "npm:^0.2.9" - conditions: cpu=wasm32 - languageName: node - linkType: hard - "@napi-rs/tar-wasm32-wasi@npm:1.1.0": version: 1.1.0 resolution: "@napi-rs/tar-wasm32-wasi@npm:1.1.0" @@ -9864,13 +9707,6 @@ __metadata: languageName: node linkType: hard -"@napi-rs/tar-win32-arm64-msvc@npm:0.1.5": - version: 0.1.5 - resolution: "@napi-rs/tar-win32-arm64-msvc@npm:0.1.5" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - "@napi-rs/tar-win32-arm64-msvc@npm:1.1.0": version: 1.1.0 resolution: "@napi-rs/tar-win32-arm64-msvc@npm:1.1.0" @@ -9878,13 +9714,6 @@ __metadata: languageName: node linkType: hard -"@napi-rs/tar-win32-ia32-msvc@npm:0.1.5": - version: 0.1.5 - resolution: "@napi-rs/tar-win32-ia32-msvc@npm:0.1.5" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - "@napi-rs/tar-win32-ia32-msvc@npm:1.1.0": version: 1.1.0 resolution: "@napi-rs/tar-win32-ia32-msvc@npm:1.1.0" @@ -9892,13 +9721,6 @@ __metadata: languageName: node linkType: hard -"@napi-rs/tar-win32-x64-msvc@npm:0.1.5": - version: 0.1.5 - resolution: "@napi-rs/tar-win32-x64-msvc@npm:0.1.5" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - "@napi-rs/tar-win32-x64-msvc@npm:1.1.0": version: 1.1.0 resolution: "@napi-rs/tar-win32-x64-msvc@npm:1.1.0" @@ -9906,63 +9728,6 @@ __metadata: languageName: node linkType: hard -"@napi-rs/tar@npm:^0.1.4": - version: 0.1.5 - resolution: "@napi-rs/tar@npm:0.1.5" - dependencies: - "@napi-rs/tar-android-arm-eabi": "npm:0.1.5" - "@napi-rs/tar-android-arm64": "npm:0.1.5" - "@napi-rs/tar-darwin-arm64": "npm:0.1.5" - "@napi-rs/tar-darwin-x64": "npm:0.1.5" - "@napi-rs/tar-freebsd-x64": "npm:0.1.5" - "@napi-rs/tar-linux-arm-gnueabihf": "npm:0.1.5" - "@napi-rs/tar-linux-arm64-gnu": "npm:0.1.5" - "@napi-rs/tar-linux-arm64-musl": "npm:0.1.5" - "@napi-rs/tar-linux-ppc64-gnu": "npm:0.1.5" - "@napi-rs/tar-linux-s390x-gnu": "npm:0.1.5" - "@napi-rs/tar-linux-x64-gnu": "npm:0.1.5" - "@napi-rs/tar-linux-x64-musl": "npm:0.1.5" - "@napi-rs/tar-wasm32-wasi": "npm:0.1.5" - "@napi-rs/tar-win32-arm64-msvc": "npm:0.1.5" - "@napi-rs/tar-win32-ia32-msvc": "npm:0.1.5" - "@napi-rs/tar-win32-x64-msvc": "npm:0.1.5" - dependenciesMeta: - "@napi-rs/tar-android-arm-eabi": - optional: true - "@napi-rs/tar-android-arm64": - optional: true - "@napi-rs/tar-darwin-arm64": - optional: true - "@napi-rs/tar-darwin-x64": - optional: true - "@napi-rs/tar-freebsd-x64": - optional: true - "@napi-rs/tar-linux-arm-gnueabihf": - optional: true - "@napi-rs/tar-linux-arm64-gnu": - optional: true - "@napi-rs/tar-linux-arm64-musl": - optional: true - "@napi-rs/tar-linux-ppc64-gnu": - optional: true - "@napi-rs/tar-linux-s390x-gnu": - optional: true - "@napi-rs/tar-linux-x64-gnu": - optional: true - "@napi-rs/tar-linux-x64-musl": - optional: true - "@napi-rs/tar-wasm32-wasi": - optional: true - "@napi-rs/tar-win32-arm64-msvc": - optional: true - "@napi-rs/tar-win32-ia32-msvc": - optional: true - "@napi-rs/tar-win32-x64-msvc": - optional: true - checksum: 10/f9d8983951d6b18e54c0d4123efac4c052412345de929768c6a751a7e04406199810ad01250c459fcb04cd6fe2069e0be66e5ebc2f5fdce13eed64f681f5df6d - languageName: node - linkType: hard - "@napi-rs/tar@npm:^1.1.0": version: 1.1.0 resolution: "@napi-rs/tar@npm:1.1.0" @@ -10020,7 +9785,7 @@ __metadata: languageName: node linkType: hard -"@napi-rs/wasm-runtime@npm:^0.2.5, @napi-rs/wasm-runtime@npm:^0.2.7, @napi-rs/wasm-runtime@npm:^0.2.9": +"@napi-rs/wasm-runtime@npm:^0.2.5, @napi-rs/wasm-runtime@npm:^0.2.9": version: 0.2.9 resolution: "@napi-rs/wasm-runtime@npm:0.2.9" dependencies: @@ -10042,13 +9807,6 @@ __metadata: languageName: node linkType: hard -"@napi-rs/wasm-tools-android-arm-eabi@npm:0.0.3": - version: 0.0.3 - resolution: "@napi-rs/wasm-tools-android-arm-eabi@npm:0.0.3" - conditions: os=android & cpu=arm - languageName: node - linkType: hard - "@napi-rs/wasm-tools-android-arm-eabi@npm:1.0.1": version: 1.0.1 resolution: "@napi-rs/wasm-tools-android-arm-eabi@npm:1.0.1" @@ -10056,13 +9814,6 @@ __metadata: languageName: node linkType: hard -"@napi-rs/wasm-tools-android-arm64@npm:0.0.3": - version: 0.0.3 - resolution: "@napi-rs/wasm-tools-android-arm64@npm:0.0.3" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - "@napi-rs/wasm-tools-android-arm64@npm:1.0.1": version: 1.0.1 resolution: "@napi-rs/wasm-tools-android-arm64@npm:1.0.1" @@ -10070,13 +9821,6 @@ __metadata: languageName: node linkType: hard -"@napi-rs/wasm-tools-darwin-arm64@npm:0.0.3": - version: 0.0.3 - resolution: "@napi-rs/wasm-tools-darwin-arm64@npm:0.0.3" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - "@napi-rs/wasm-tools-darwin-arm64@npm:1.0.1": version: 1.0.1 resolution: "@napi-rs/wasm-tools-darwin-arm64@npm:1.0.1" @@ -10084,13 +9828,6 @@ __metadata: languageName: node linkType: hard -"@napi-rs/wasm-tools-darwin-x64@npm:0.0.3": - version: 0.0.3 - resolution: "@napi-rs/wasm-tools-darwin-x64@npm:0.0.3" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - "@napi-rs/wasm-tools-darwin-x64@npm:1.0.1": version: 1.0.1 resolution: "@napi-rs/wasm-tools-darwin-x64@npm:1.0.1" @@ -10098,13 +9835,6 @@ __metadata: languageName: node linkType: hard -"@napi-rs/wasm-tools-freebsd-x64@npm:0.0.3": - version: 0.0.3 - resolution: "@napi-rs/wasm-tools-freebsd-x64@npm:0.0.3" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - "@napi-rs/wasm-tools-freebsd-x64@npm:1.0.1": version: 1.0.1 resolution: "@napi-rs/wasm-tools-freebsd-x64@npm:1.0.1" @@ -10112,13 +9842,6 @@ __metadata: languageName: node linkType: hard -"@napi-rs/wasm-tools-linux-arm64-gnu@npm:0.0.3": - version: 0.0.3 - resolution: "@napi-rs/wasm-tools-linux-arm64-gnu@npm:0.0.3" - conditions: os=linux & cpu=arm64 & libc=glibc - languageName: node - linkType: hard - "@napi-rs/wasm-tools-linux-arm64-gnu@npm:1.0.1": version: 1.0.1 resolution: "@napi-rs/wasm-tools-linux-arm64-gnu@npm:1.0.1" @@ -10126,13 +9849,6 @@ __metadata: languageName: node linkType: hard -"@napi-rs/wasm-tools-linux-arm64-musl@npm:0.0.3": - version: 0.0.3 - resolution: "@napi-rs/wasm-tools-linux-arm64-musl@npm:0.0.3" - conditions: os=linux & cpu=arm64 & libc=musl - languageName: node - linkType: hard - "@napi-rs/wasm-tools-linux-arm64-musl@npm:1.0.1": version: 1.0.1 resolution: "@napi-rs/wasm-tools-linux-arm64-musl@npm:1.0.1" @@ -10140,13 +9856,6 @@ __metadata: languageName: node linkType: hard -"@napi-rs/wasm-tools-linux-x64-gnu@npm:0.0.3": - version: 0.0.3 - resolution: "@napi-rs/wasm-tools-linux-x64-gnu@npm:0.0.3" - conditions: os=linux & cpu=x64 & libc=glibc - languageName: node - linkType: hard - "@napi-rs/wasm-tools-linux-x64-gnu@npm:1.0.1": version: 1.0.1 resolution: "@napi-rs/wasm-tools-linux-x64-gnu@npm:1.0.1" @@ -10154,13 +9863,6 @@ __metadata: languageName: node linkType: hard -"@napi-rs/wasm-tools-linux-x64-musl@npm:0.0.3": - version: 0.0.3 - resolution: "@napi-rs/wasm-tools-linux-x64-musl@npm:0.0.3" - conditions: os=linux & cpu=x64 & libc=musl - languageName: node - linkType: hard - "@napi-rs/wasm-tools-linux-x64-musl@npm:1.0.1": version: 1.0.1 resolution: "@napi-rs/wasm-tools-linux-x64-musl@npm:1.0.1" @@ -10168,15 +9870,6 @@ __metadata: languageName: node linkType: hard -"@napi-rs/wasm-tools-wasm32-wasi@npm:0.0.3": - version: 0.0.3 - resolution: "@napi-rs/wasm-tools-wasm32-wasi@npm:0.0.3" - dependencies: - "@napi-rs/wasm-runtime": "npm:^0.2.7" - conditions: cpu=wasm32 - languageName: node - linkType: hard - "@napi-rs/wasm-tools-wasm32-wasi@npm:1.0.1": version: 1.0.1 resolution: "@napi-rs/wasm-tools-wasm32-wasi@npm:1.0.1" @@ -10186,13 +9879,6 @@ __metadata: languageName: node linkType: hard -"@napi-rs/wasm-tools-win32-arm64-msvc@npm:0.0.3": - version: 0.0.3 - resolution: "@napi-rs/wasm-tools-win32-arm64-msvc@npm:0.0.3" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - "@napi-rs/wasm-tools-win32-arm64-msvc@npm:1.0.1": version: 1.0.1 resolution: "@napi-rs/wasm-tools-win32-arm64-msvc@npm:1.0.1" @@ -10200,13 +9886,6 @@ __metadata: languageName: node linkType: hard -"@napi-rs/wasm-tools-win32-ia32-msvc@npm:0.0.3": - version: 0.0.3 - resolution: "@napi-rs/wasm-tools-win32-ia32-msvc@npm:0.0.3" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - "@napi-rs/wasm-tools-win32-ia32-msvc@npm:1.0.1": version: 1.0.1 resolution: "@napi-rs/wasm-tools-win32-ia32-msvc@npm:1.0.1" @@ -10214,13 +9893,6 @@ __metadata: languageName: node linkType: hard -"@napi-rs/wasm-tools-win32-x64-msvc@npm:0.0.3": - version: 0.0.3 - resolution: "@napi-rs/wasm-tools-win32-x64-msvc@npm:0.0.3" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - "@napi-rs/wasm-tools-win32-x64-msvc@npm:1.0.1": version: 1.0.1 resolution: "@napi-rs/wasm-tools-win32-x64-msvc@npm:1.0.1" @@ -10228,54 +9900,6 @@ __metadata: languageName: node linkType: hard -"@napi-rs/wasm-tools@npm:^0.0.3": - version: 0.0.3 - resolution: "@napi-rs/wasm-tools@npm:0.0.3" - dependencies: - "@napi-rs/wasm-tools-android-arm-eabi": "npm:0.0.3" - "@napi-rs/wasm-tools-android-arm64": "npm:0.0.3" - "@napi-rs/wasm-tools-darwin-arm64": "npm:0.0.3" - "@napi-rs/wasm-tools-darwin-x64": "npm:0.0.3" - "@napi-rs/wasm-tools-freebsd-x64": "npm:0.0.3" - "@napi-rs/wasm-tools-linux-arm64-gnu": "npm:0.0.3" - "@napi-rs/wasm-tools-linux-arm64-musl": "npm:0.0.3" - "@napi-rs/wasm-tools-linux-x64-gnu": "npm:0.0.3" - "@napi-rs/wasm-tools-linux-x64-musl": "npm:0.0.3" - "@napi-rs/wasm-tools-wasm32-wasi": "npm:0.0.3" - "@napi-rs/wasm-tools-win32-arm64-msvc": "npm:0.0.3" - "@napi-rs/wasm-tools-win32-ia32-msvc": "npm:0.0.3" - "@napi-rs/wasm-tools-win32-x64-msvc": "npm:0.0.3" - dependenciesMeta: - "@napi-rs/wasm-tools-android-arm-eabi": - optional: true - "@napi-rs/wasm-tools-android-arm64": - optional: true - "@napi-rs/wasm-tools-darwin-arm64": - optional: true - "@napi-rs/wasm-tools-darwin-x64": - optional: true - "@napi-rs/wasm-tools-freebsd-x64": - optional: true - "@napi-rs/wasm-tools-linux-arm64-gnu": - optional: true - "@napi-rs/wasm-tools-linux-arm64-musl": - optional: true - "@napi-rs/wasm-tools-linux-x64-gnu": - optional: true - "@napi-rs/wasm-tools-linux-x64-musl": - optional: true - "@napi-rs/wasm-tools-wasm32-wasi": - optional: true - "@napi-rs/wasm-tools-win32-arm64-msvc": - optional: true - "@napi-rs/wasm-tools-win32-ia32-msvc": - optional: true - "@napi-rs/wasm-tools-win32-x64-msvc": - optional: true - checksum: 10/a7d76c269d2070d8e42d971df7fe2f8f58b0ea87bb310f19f11a23710ca6a87ffb4eccfb5f43cf560e53a91cd53cfdc58dac81a9f2683cb0d9ce1f7c65089bbe - languageName: node - linkType: hard - "@napi-rs/wasm-tools@npm:^1.0.1": version: 1.0.1 resolution: "@napi-rs/wasm-tools@npm:1.0.1" @@ -11336,7 +10960,7 @@ __metadata: languageName: node linkType: hard -"@octokit/rest@npm:^22.0.0, @octokit/rest@npm:^22.0.1": +"@octokit/rest@npm:^22.0.1": version: 22.0.1 resolution: "@octokit/rest@npm:22.0.1" dependencies: @@ -19016,7 +18640,7 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:*, @types/node@npm:>=10.0.0, @types/node@npm:>=13.7.0, @types/node@npm:>=18.0.0, @types/node@npm:>=8.1.0, @types/node@npm:^22.0.0, @types/node@npm:^22.14.1, @types/node@npm:^22.5.5, @types/node@npm:^22.7.7": +"@types/node@npm:*, @types/node@npm:>=10.0.0, @types/node@npm:>=13.7.0, @types/node@npm:>=18.0.0, @types/node@npm:>=8.1.0, @types/node@npm:^22.0.0, @types/node@npm:^22.5.5, @types/node@npm:^22.7.7": version: 22.19.1 resolution: "@types/node@npm:22.19.1" dependencies: @@ -19112,16 +18736,6 @@ __metadata: languageName: node linkType: hard -"@types/prompts@npm:^2.4.9": - version: 2.4.9 - resolution: "@types/prompts@npm:2.4.9" - dependencies: - "@types/node": "npm:*" - kleur: "npm:^3.0.3" - checksum: 10/69b8372f4c790b45fea16a46ff8d1bcc71b14579481776b67bd6263637118a7ecb1f12e1311506c29fadc81bf618dc64f1a91f903cfd5be67a0455a227b3e462 - languageName: node - linkType: hard - "@types/qs@npm:*": version: 6.9.18 resolution: "@types/qs@npm:6.9.18" @@ -20158,21 +19772,6 @@ __metadata: languageName: node linkType: hard -"@y-octo/node@workspace:packages/common/y-octo/node": - version: 0.0.0-use.local - resolution: "@y-octo/node@workspace:packages/common/y-octo/node" - dependencies: - "@napi-rs/cli": "npm:3.0.0" - "@types/node": "npm:^22.14.1" - "@types/prompts": "npm:^2.4.9" - c8: "npm:^10.1.3" - prompts: "npm:^2.4.2" - ts-node: "npm:^10.9.2" - typescript: "npm:^5.8.3" - yjs: "npm:^13.6.27" - languageName: unknown - linkType: soft - "@zeit/schemas@npm:2.36.0": version: 2.36.0 resolution: "@zeit/schemas@npm:2.36.0" @@ -24631,7 +24230,7 @@ __metadata: languageName: node linkType: hard -"emnapi@npm:^1.4.0, emnapi@npm:^1.7.1": +"emnapi@npm:^1.7.1": version: 1.7.1 resolution: "emnapi@npm:1.7.1" peerDependencies: @@ -37977,7 +37576,7 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^5, typescript@npm:^5.3.3, typescript@npm:^5.5.4, typescript@npm:^5.7.2, typescript@npm:^5.8.3": +"typescript@npm:^5, typescript@npm:^5.3.3, typescript@npm:^5.5.4, typescript@npm:^5.7.2": version: 5.8.3 resolution: "typescript@npm:5.8.3" bin: @@ -37997,7 +37596,7 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A^5#optional!builtin, typescript@patch:typescript@npm%3A^5.3.3#optional!builtin, typescript@patch:typescript@npm%3A^5.5.4#optional!builtin, typescript@patch:typescript@npm%3A^5.7.2#optional!builtin, typescript@patch:typescript@npm%3A^5.8.3#optional!builtin": +"typescript@patch:typescript@npm%3A^5#optional!builtin, typescript@patch:typescript@npm%3A^5.3.3#optional!builtin, typescript@patch:typescript@npm%3A^5.5.4#optional!builtin, typescript@patch:typescript@npm%3A^5.7.2#optional!builtin": version: 5.8.3 resolution: "typescript@patch:typescript@npm%3A5.8.3#optional!builtin::version=5.8.3&hash=5786d5" bin: @@ -38918,13 +38517,6 @@ __metadata: languageName: node linkType: hard -"wasm-sjlj@npm:^1.0.6": - version: 1.0.6 - resolution: "wasm-sjlj@npm:1.0.6" - checksum: 10/36b730eb7587c019d35192653de6f23e5c2782bf4cc823dc7bf1254d578c3f7dc4469b4ec1a8f6c0cd19eea0c2b15e5890fe0c1b6c772d2b7efcc55bbbf6e251 - languageName: node - linkType: hard - "watchpack@npm:^2.4.4": version: 2.4.4 resolution: "watchpack@npm:2.4.4"