use std::time::Duration;
#[non_exhaustive]
#[derive(Clone, Debug)]
pub struct ReplicationConfig {
pub replica_path: String,
pub sync_url: Option<String>,
pub auth_token: Option<String>,
pub sync_interval_secs: u64,
}
impl ReplicationConfig {
pub fn new(replica_path: impl Into<String>) -> Self {
Self {
replica_path: replica_path.into(),
sync_url: None,
auth_token: None,
sync_interval_secs: 10,
}
}
pub fn with_sync_url(mut self, sync_url: impl Into<String>) -> Self {
self.sync_url = Some(sync_url.into());
self
}
pub fn with_auth_token(mut self, auth_token: impl Into<String>) -> Self {
self.auth_token = Some(auth_token.into());
self
}
pub fn with_sync_interval(mut self, secs: u64) -> Self {
self.sync_interval_secs = secs;
self
}
}
pub struct ReplicationManager;
impl ReplicationManager {
#[cfg_attr(mutants, mutants::skip)]
pub fn start(config: ReplicationConfig) {
if config.sync_url.is_some() {
println!(
"🔄 Zero-Config SQLite replication initialized: syncing local replica {} with master...",
config.replica_path
);
crate::edge::spawn(async move {
let interval = Duration::from_secs(config.sync_interval_secs);
loop {
#[cfg(not(target_arch = "wasm32"))]
{
tokio::time::sleep(interval).await;
}
#[cfg(target_arch = "wasm32")]
{
let mut ticks = 0;
while ticks < config.sync_interval_secs {
wasm_bindgen_futures::JsFuture::from(js_sys::Promise::resolve(
&wasm_bindgen::JsValue::NULL,
))
.await
.ok();
ticks += 1;
}
}
println!(
"🔄 [Replication] Synchronizing local SQLite replica at '{}' with remote node...",
config.replica_path
);
}
});
}
}
}
#[cfg(not(target_arch = "wasm32"))]
pub use rullst_orm::{Orm, RullstModel, async_trait, schema};
#[cfg(not(target_arch = "wasm32"))]
pub use sqlx;
#[cfg(not(target_arch = "wasm32"))]
pub use sqlx::FromRow;
#[cfg(not(target_arch = "wasm32"))]
#[cfg_attr(mutants, mutants::skip)]
pub fn safe_pool() -> Option<&'static rullst_orm::RullstPool> {
std::panic::catch_unwind(std::panic::AssertUnwindSafe(rullst_orm::Orm::pool)).ok()
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg_attr(mutants, mutants::skip)]
pub fn safe_driver() -> Option<&'static str> {
std::panic::catch_unwind(std::panic::AssertUnwindSafe(rullst_orm::Orm::driver)).ok()
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[tokio::test]
async fn test_db_get_pool() {
let config = ReplicationConfig::new("test.db")
.with_sync_interval(20)
.with_auth_token("secret");
assert_eq!(config.replica_path, "test.db");
assert_eq!(config.sync_interval_secs, 20);
assert_eq!(config.auth_token, Some("secret".to_string()));
}
#[test]
fn test_replication_config_with_sync_url() {
let config = ReplicationConfig::new("test.db").with_sync_url("http://sync");
assert_eq!(config.sync_url, Some("http://sync".to_string()));
}
#[test]
fn test_replication_config_with_auth_token() {
let config = ReplicationConfig::new("test.db").with_auth_token("token123");
assert_eq!(config.auth_token, Some("token123".to_string()));
}
#[test]
#[cfg(not(target_arch = "wasm32"))]
fn test_safe_pool_uninitialized() {
let pool = safe_pool();
assert!(pool.is_none());
}
#[test]
#[cfg(not(target_arch = "wasm32"))]
fn test_safe_driver_uninitialized() {
let driver = safe_driver();
assert!(driver.is_none());
}
#[tokio::test]
async fn test_replication_manager_start() {
let config = ReplicationConfig::new("test.db")
.with_sync_url("https://sync.rullst.dev")
.with_sync_interval(1);
ReplicationManager::start(config);
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
}
#[tokio::test]
async fn test_replication_manager_start_no_url() {
let config = ReplicationConfig::new("test.db");
ReplicationManager::start(config);
}
}