diff --git a/.github/workflows/rust-tui-ci.yml b/.github/workflows/rust-tui-ci.yml index de6e3a857..d684ac1f7 100644 --- a/.github/workflows/rust-tui-ci.yml +++ b/.github/workflows/rust-tui-ci.yml @@ -22,6 +22,7 @@ jobs: - name: Install Rust uses: actions-rs/toolchain@v1 with: + toolchain: 1.91.1 override: true components: rustfmt, clippy @@ -54,6 +55,7 @@ jobs: - name: Install Rust uses: actions-rs/toolchain@v1 with: + toolchain: 1.91.1 override: true components: rustfmt, clippy @@ -78,6 +80,7 @@ jobs: - name: Install Rust uses: actions-rs/toolchain@v1 with: + toolchain: 1.91.1 override: true components: rustfmt, clippy @@ -98,6 +101,7 @@ jobs: - name: Install Rust uses: actions-rs/toolchain@v1 with: + toolchain: 1.91.1 override: true components: rustfmt, clippy diff --git a/rust-tui/Cargo.lock b/rust-tui/Cargo.lock index f5a306bae..be515bac3 100644 --- a/rust-tui/Cargo.lock +++ b/rust-tui/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "ahash" version = "0.8.12" @@ -304,6 +310,15 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -472,9 +487,9 @@ dependencies = [ [[package]] name = "dittolive-ditto" -version = "4.13.1" +version = "5.0.0-preview.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf555e9ddb7eb974ed842bd27cfc15f5448b3cf028b1b521745c693acffe517" +checksum = "20ea1be96fb7f214d78102c496da7ae5e70a34c3c338340ee19e167e65dd3df7" dependencies = [ "async-trait", "async_fn_traits", @@ -491,7 +506,7 @@ dependencies = [ "hashbrown 0.14.5", "macro_rules_attribute", "never-say-never", - "rand", + "rand 0.9.2", "safer-ffi", "serde", "serde-transcode", @@ -509,14 +524,17 @@ dependencies = [ [[package]] name = "dittolive-ditto-sys" -version = "4.13.1" +version = "5.0.0-preview.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0626a0f119eaf498041b86f2565742d25b8ff1bba8bb3240723a963edc4ed8e" +checksum = "fab39a2d09e6cecf184fbf87e83aee9455418d44a47b0de32d72cc6ae3109303" dependencies = [ "macro_rules_attribute", + "object", "paste", "safer-ffi", + "thiserror", "tokio", + "toml", ] [[package]] @@ -606,6 +624,16 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1151,6 +1179,16 @@ version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mio" version = "1.1.0" @@ -1193,6 +1231,17 @@ dependencies = [ "autocfg", ] +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "flate2", + "memchr", + "ruzstd", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -1352,8 +1401,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", ] [[package]] @@ -1363,7 +1422,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", ] [[package]] @@ -1375,6 +1444,15 @@ dependencies = [ "getrandom 0.2.16", ] +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + [[package]] name = "ratatui" version = "0.29.0" @@ -1470,6 +1548,15 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "ruzstd" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ff0cc5e135c8870a775d3320910cd9b564ec036b4dc0b8741629020be63f01" +dependencies = [ + "twox-hash", +] + [[package]] name = "ryu" version = "1.0.20" @@ -1646,6 +1733,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_spanned" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +dependencies = [ + "serde_core", +] + [[package]] name = "serde_with" version = "3.16.1" @@ -1728,6 +1824,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + [[package]] name = "slab" version = "0.4.11" @@ -1787,7 +1889,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "rand", + "rand 0.8.5", "syn 1.0.109", ] @@ -1993,11 +2095,26 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.9.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +dependencies = [ + "indexmap 2.12.1", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + [[package]] name = "toml_datetime" -version = "0.7.3" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] @@ -2016,13 +2133,19 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.4" +version = "1.0.9+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" dependencies = [ "winnow", ] +[[package]] +name = "toml_writer" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" + [[package]] name = "tracing" version = "0.1.43" @@ -2084,6 +2207,12 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "twox-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" + [[package]] name = "unicode-ident" version = "1.0.22" diff --git a/rust-tui/Cargo.toml b/rust-tui/Cargo.toml index 2836f5b25..c12c43f16 100644 --- a/rust-tui/Cargo.toml +++ b/rust-tui/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "ditto-quickstart" version = "0.0.0" -edition = "2021" +edition = "2024" default-run = "ditto-quickstart" [lib] @@ -18,7 +18,7 @@ path = "src/bin/integration_test.rs" [dependencies] # Ditto dependenceis -dittolive-ditto = "4.13.1" +dittolive-ditto = "=5.0.0-preview.4" # External dependencies anyhow = "1" diff --git a/rust-tui/README.md b/rust-tui/README.md index 250b586fd..3e8376a53 100644 --- a/rust-tui/README.md +++ b/rust-tui/README.md @@ -1,19 +1,15 @@ # Ditto Rust Quickstart App 🚀 -This directory contains Ditto's quickstart app for the Rust SDK. -This app is a Terminal User Interface (TUI) that allows for creating -a todo list that syncs between multiple peers. +This directory contains Ditto's quickstart app for the Rust SDK. This app is a Terminal User Interface (TUI) that allows for creating a todo list that syncs between multiple peers. ## Getting Started -To get started, you'll first need to create an app in the [Ditto Portal][0] -with the "Online Playground" authentication type. You'll need to find your +To get started, you'll first need to create an app in the [Ditto Portal][0] with the "Online Playground" authentication type. You'll need to find your AppID and Online Playground Token, Auth URL, and Websocket URL in order to use this quickstart. [0]: https://portal.ditto.live -From the repo root, copy the `.env.sample` file to `.env`, and fill in the -fields with your AppID, Online Playground Token, Auth URL, and Websocket URL: +From the repo root, copy the `.env.sample` file to `.env`, and fill in the fields with your AppID, Online Playground Token, Auth URL, and Websocket URL: ``` cp .sample.env .env diff --git a/rust-tui/src/bin/integration_test.rs b/rust-tui/src/bin/integration_test.rs index 977b875df..2ef608c4a 100644 --- a/rust-tui/src/bin/integration_test.rs +++ b/rust-tui/src/bin/integration_test.rs @@ -1,6 +1,7 @@ use anyhow::{Context, Result}; use ditto_quickstart::tui::Todolist; -use dittolive_ditto::{fs::TempRoot, identity::OnlinePlayground, AppId, Ditto}; +use dittolive_ditto::prelude::*; +use dittolive_ditto::{Ditto, fs::TempRoot}; use std::time::Duration; use std::{env, sync::Arc}; use tokio::time::sleep; @@ -12,14 +13,12 @@ async fn main() -> Result<()> { // Load environment variables dotenvy::dotenv().ok(); - let app_id: AppId = env::var("DITTO_APP_ID") - .context("DITTO_APP_ID not found")? - .parse() - .context("Invalid DITTO_APP_ID format")?; - let playground_token = - env::var("DITTO_PLAYGROUND_TOKEN").context("DITTO_PLAYGROUND_TOKEN not found")?; + let database_id = env::var("DITTO_APP_ID").context("DITTO_APP_ID not found")?; + + let token = env::var("DITTO_PLAYGROUND_TOKEN").context("DITTO_PLAYGROUND_TOKEN not found")?; let custom_auth_url = env::var("DITTO_AUTH_URL").unwrap_or_else(|_| "https://auth.cloud.ditto.live".to_string()); + let websocket_url = env::var("DITTO_WEBSOCKET_URL").unwrap_or_else(|_| "wss://cloud.ditto.live".to_string()); @@ -29,34 +28,28 @@ async fn main() -> Result<()> { println!("🔍 Looking for task: {}", task_to_find); + let connect_config = DittoConfigConnect::Server { + url: custom_auth_url.parse().unwrap(), + }; + + let config = DittoConfig::new(database_id, connect_config) + .with_persistence_directory(Arc::new(TempRoot::new()).root_path()); + // Create Ditto instance (using same pattern as main.rs) - let ditto = Ditto::builder() - .with_root(Arc::new(TempRoot::new())) - .with_identity(|root| { - OnlinePlayground::new( - root, - app_id.clone(), - playground_token, - false, - Some(custom_auth_url.as_str()), - ) - })? - .build()?; + let ditto = Ditto::open_sync(config)?; + + ditto.auth().unwrap().set_expiration_handler(TokenHandler { + token: token.clone(), + }); ditto.update_transport_config(|config| { config.enable_all_peer_to_peer(); - config.connect.websocket_urls.insert(websocket_url.clone()); }); // Disable sync with v3 peers and DQL strict mode - let _ = ditto.disable_sync_with_v3(); - let _ = ditto - .store() - .execute_v2("ALTER SYSTEM SET DQL_STRICT_MODE = false") - .await?; // Start sync - let _ = ditto.start_sync(); + let _ = ditto.sync().start(); println!("✅ Created Ditto instance and started sync"); // Create todolist instance (loads the app) @@ -100,9 +93,26 @@ async fn main() -> Result<()> { anyhow::bail!("Integration test failed - seeded task not found"); } - todolist.ditto.stop_sync(); + todolist.ditto.sync().stop(); println!("🛑 Stopped sync"); println!("🎉 Integration test passed! App loads and syncs with Ditto Cloud successfully."); Ok(()) } + +struct TokenHandler { + token: String, +} + +impl DittoAuthExpirationHandler for TokenHandler { + async fn on_expiration(&self, ditto: &Ditto, _duration_remaining: Duration) { + match ditto + .auth() + .unwrap() + .login(self.token.as_str(), &identity::get_development_provider()) + { + Ok(_) => println!("Authentication successful"), + Err(e) => println!("Authentication failed: {}", e), + } + } +} diff --git a/rust-tui/src/bin/main.rs b/rust-tui/src/bin/main.rs index d75659af3..9954fdd48 100644 --- a/rust-tui/src/bin/main.rs +++ b/rust-tui/src/bin/main.rs @@ -1,16 +1,17 @@ use std::{path::PathBuf, sync::Arc, time::Duration}; -use anyhow::{anyhow, Context, Result}; +use anyhow::{Context, Result, anyhow}; use clap::Parser; -use ditto_quickstart::{term, tui::TuiTask, Shutdown}; -use dittolive_ditto::{fs::TempRoot, identity::OnlinePlayground, AppId, Ditto}; +use ditto_quickstart::{Shutdown, term, tui::TuiTask}; +use dittolive_ditto::prelude::*; +use dittolive_ditto::{Ditto, fs::TempRoot}; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; #[derive(Debug, Parser)] pub struct Cli { /// The Ditto App ID this app will use to initialize Ditto #[clap(long, env = "DITTO_APP_ID")] - app_id: AppId, + database_id: String, /// The Online Playground token this app should use for authentication #[clap(long, env = "DITTO_PLAYGROUND_TOKEN")] @@ -61,7 +62,7 @@ async fn main() -> Result<()> { // Initialize and launch app let ditto = try_init_ditto( - cli.app_id, + cli.database_id, cli.token, cli.custom_auth_url, cli.websocket_url.clone(), @@ -105,13 +106,35 @@ async fn main() -> Result<()> { Ok(()) } +struct TokenHandler { + token: String, +} + +impl DittoAuthExpirationHandler for TokenHandler { + async fn on_expiration(&self, ditto: &Ditto, _duration_remaining: Duration) { + match ditto + .auth() + .unwrap() + .login(self.token.as_str(), &identity::get_development_provider()) + { + Ok(_) => println!("Authentication successful"), + Err(e) => println!("Authentication failed: {}", e), + } + } +} + async fn try_init_ditto( - app_id: AppId, + database_id: String, token: String, custom_auth_url: String, websocket_url: String, p2p_enabled: bool, ) -> Result { + //create connect https://software.ditto.live/rust/Ditto/5.0.0-preview.4/x86_64-unknown-linux-gnu/docs/dittolive_ditto/enum.DittoConfigConnect.html + let connect_config = DittoConfigConnect::Server { + url: custom_auth_url.parse().unwrap(), + }; + // We use a temporary directory to store Ditto's local database. // This means that data will not be persistent between runs of the // application, but it allows us to run multiple instances of the @@ -119,18 +142,15 @@ async fn try_init_ditto( // application, we would want to store the database in a more permanent // location, and if multiple instances are needed, ensure that each // instance has its own persistence directory. - let ditto = Ditto::builder() - .with_root(Arc::new(TempRoot::new())) - .with_identity(|root| { - OnlinePlayground::new( - root, - app_id.clone(), - token, - false, // This is required to be set to false to use the correct URLs - Some(custom_auth_url.as_str()), - ) - })? - .build()?; + let config = DittoConfig::new(database_id.clone(), connect_config) + .with_persistence_directory(Arc::new(TempRoot::new()).root_path()); + + // https://software.ditto.live/rust/Ditto/5.0.0-preview.4/x86_64-unknown-linux-gnu/docs/dittolive_ditto/index.html#playground-quickstart + let ditto = Ditto::open_sync(config)?; + + ditto.auth().unwrap().set_expiration_handler(TokenHandler { + token: token.clone(), + }); ditto.update_transport_config(|config| { if p2p_enabled { @@ -146,20 +166,10 @@ async fn try_init_ditto( config.connect.websocket_urls.insert(websocket_url); }); - // disable sync with v3 peers, required for DQL - _ = ditto.disable_sync_with_v3(); - - // disable DQL strict mode - // https://docs.ditto.live/dql/strict-mode - _ = ditto - .store() - .execute_v2("ALTER SYSTEM SET DQL_STRICT_MODE = false") - .await?; - // Start sync - _ = ditto.start_sync(); + ditto.sync().start()?; - tracing::info!(%app_id, "Started Ditto!"); + tracing::info!(database_id = %&database_id, "Started Ditto!"); Ok(ditto) } diff --git a/rust-tui/src/term.rs b/rust-tui/src/term.rs index e88a29134..9447279cb 100644 --- a/rust-tui/src/term.rs +++ b/rust-tui/src/term.rs @@ -5,7 +5,7 @@ //! [0]: https://github.com/tokio-rs/console/blob/cbf6f56a16036ecf13548c4209fcc62f8a84bae2/tokio-console/src/term.rs use anyhow::{Context, Result}; -pub use ratatui::{backend::CrosstermBackend, Terminal}; +pub use ratatui::{Terminal, backend::CrosstermBackend}; use std::io; pub fn init_crossterm() -> Result<(Terminal>, OnShutdown)> { diff --git a/rust-tui/src/tui/mod.rs b/rust-tui/src/tui/mod.rs index 60037326f..33aee338a 100644 --- a/rust-tui/src/tui/mod.rs +++ b/rust-tui/src/tui/mod.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, Context, Result}; +use anyhow::{Context, Result, anyhow}; use crossterm::event::{Event, EventStream}; use dittolive_ditto::prelude::*; use futures::{FutureExt, Stream, StreamExt}; @@ -6,7 +6,7 @@ use ratatui::prelude::*; use std::{io::Stdout, ops::ControlFlow, time::Duration}; use tokio::task::JoinHandle; -use crate::{should_quit, Shutdown}; +use crate::{Shutdown, should_quit}; pub mod todolist; diff --git a/rust-tui/src/tui/todolist.rs b/rust-tui/src/tui/todolist.rs index ae4c4cee8..9037efd7c 100644 --- a/rust-tui/src/tui/todolist.rs +++ b/rust-tui/src/tui/todolist.rs @@ -1,9 +1,9 @@ use anyhow::Context; use anyhow::Result; use crossterm::event::Event; +use dittolive_ditto::Ditto; use dittolive_ditto::store::StoreObserver; use dittolive_ditto::sync::SyncSubscription; -use dittolive_ditto::Ditto; use ratatui::prelude::*; use ratatui::widgets::Block; use ratatui::widgets::BorderType; @@ -94,13 +94,11 @@ impl Todolist { // Register a subscription, which determines what data syncs to this peer // https://docs.ditto.live/sdk/latest/sync/syncing-data#creating-subscriptions - let tasks_subscription = ditto - .sync() - .register_subscription_v2("SELECT * FROM tasks")?; + let tasks_subscription = ditto.sync().register_subscription("SELECT * FROM tasks")?; // register observer for live query // Register observer, which runs against the local database on this peer - let tasks_observer = ditto.store().register_observer_v2( + let tasks_observer = ditto.store().register_observer( "SELECT * FROM tasks WHERE deleted=false ORDER BY title ASC", move |query_result| { let docs = query_result @@ -155,7 +153,7 @@ impl Todolist { }) .collect::>(); - let sync_state = if self.ditto.is_sync_active() { + let sync_state = if self.ditto.sync().is_active() { " 🟢 Sync Active ".green() } else { " 🔴 Sync Inactive ".red() @@ -305,10 +303,10 @@ impl Todolist { } fn toggle_sync(&mut self) -> Result<()> { - if self.ditto.is_sync_active() { - self.ditto.stop_sync(); + if self.ditto.sync().is_active() { + self.ditto.sync().stop(); } else { - self.ditto.start_sync()?; + self.ditto.sync().start()?; } Ok(()) } @@ -329,7 +327,7 @@ impl Todolist { let done = selected_task.done; self.ditto .store() - .execute_v2(( + .execute(( "UPDATE tasks SET done=:done WHERE _id=:id", serde_json::json!({ "id": id, @@ -356,7 +354,7 @@ impl Todolist { let id = selected_task.id; self.ditto .store() - .execute_v2(( + .execute(( "UPDATE tasks SET deleted=true WHERE _id=:id", serde_json::json!({ "id": id @@ -372,7 +370,7 @@ impl Todolist { let task = TodoItem::new(title); self.ditto .store() - .execute_v2(( + .execute(( "INSERT INTO tasks DOCUMENTS (:task)", serde_json::json!({ "task": task @@ -386,7 +384,7 @@ impl Todolist { pub async fn try_edit_todo(&mut self, id: &str, title: &str) -> Result<()> { self.ditto .store() - .execute_v2(( + .execute(( "UPDATE tasks SET title=:title WHERE _id=:id", serde_json::json!({ "title": title,