diff --git a/packages/turso-sync/src/database.rs b/packages/turso-sync/src/database.rs new file mode 100644 index 000000000..73a67834f --- /dev/null +++ b/packages/turso-sync/src/database.rs @@ -0,0 +1,99 @@ +use std::path::PathBuf; + +use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder}; +use hyper_util::{client::legacy::connect::HttpConnector, rt::TokioExecutor}; + +use crate::{ + database_inner::{DatabaseInner, Rows}, + errors::Error, + filesystem::tokio::TokioFilesystem, + sync_server::turso::{TursoSyncServer, TursoSyncServerOpts}, + Result, +}; + +/// [Database] expose public interface for synced database from [DatabaseInner] private implementation +/// +/// This layer also serves a purpose of "gluing" together all component for real use, +/// because [DatabaseInner] abstracts things away in order to simplify testing +pub struct Database(DatabaseInner); + +pub struct Builder { + path: String, + sync_url: String, + auth_token: Option, + encryption_key: Option, + connector: Option>, +} + +impl Builder { + pub fn new_synced(path: &str, sync_url: &str, auth_token: Option) -> Self { + Self { + path: path.to_string(), + sync_url: sync_url.to_string(), + auth_token: auth_token, + encryption_key: None, + connector: None, + } + } + pub fn with_encryption_key(self, encryption_key: &str) -> Self { + Self { + encryption_key: Some(encryption_key.to_string()), + ..self + } + } + pub fn with_connector(self, connector: HttpsConnector) -> Self { + Self { + connector: Some(connector), + ..self + } + } + pub async fn build(self) -> Result { + let path = PathBuf::try_from(self.path) + .map_err(|e| Error::DatabaseSyncError(format!("invalid synced database path: {e}")))?; + let connector = self + .connector + .map(Ok) + .unwrap_or_else(|| default_connector())?; + let executor = TokioExecutor::new(); + let client = hyper_util::client::legacy::Builder::new(executor).build(connector); + let sync_server = TursoSyncServer::new( + client, + TursoSyncServerOpts { + sync_url: self.sync_url, + auth_token: self.auth_token, + encryption_key: self.encryption_key, + pull_batch_size: None, + }, + )?; + let filesystem = TokioFilesystem(); + let inner = DatabaseInner::new(filesystem, sync_server, &path).await?; + Ok(Database(inner)) + } +} + +impl Database { + pub async fn sync_full(&mut self) -> Result<()> { + self.0.sync_full().await + } + pub async fn sync_from_remote(&mut self) -> Result<()> { + self.0.sync_from_remote().await + } + pub async fn sync_to_remote(&mut self) -> Result<()> { + self.0.sync_to_remote().await + } + pub async fn execute(&self, sql: &str, params: impl turso::IntoParams) -> Result { + self.0.execute(sql, params).await + } + pub async fn query(&self, sql: &str, params: impl turso::IntoParams) -> Result { + self.0.query(sql, params).await + } +} + +pub fn default_connector() -> Result> { + Ok(HttpsConnectorBuilder::new() + .with_native_roots() + .map_err(|e| Error::DatabaseSyncError(format!("unable to configure CA roots: {}", e)))? + .https_or_http() + .enable_http1() + .build()) +}