@@ -139,10 +139,20 @@ fn sentence_case(value: &str) -> String {
139139/// Wraps a Turso connection with a tokio current-thread runtime so callers can
140140/// use synchronous `execute`/`query` methods while the underlying Turso API
141141/// remains async.
142+ ///
143+ /// Supports two modes:
144+ /// - **Local mode** (default): opens a plain Turso file database.
145+ /// - **Sync mode**: opens a Turso Cloud synced database when both
146+ /// `SCE_SYNC_URL` and `SCE_SYNC_TOKEN` environment variables are set.
147+ /// Sync operations (`push`, `pull`, `checkpoint`, `stats`) are available
148+ /// through explicit methods or the `sce sync` CLI command.
142149#[ allow( dead_code) ]
143150pub struct TursoDb < M : DbSpec > {
144151 conn : turso:: Connection ,
145152 runtime : tokio:: runtime:: Runtime ,
153+ /// Optional sync database handle for Turso Cloud operations (push, pull,
154+ /// checkpoint, stats). `None` when the database is in local-only mode.
155+ sync_db : Option < turso:: sync:: Database > ,
146156 spec : PhantomData < fn ( ) -> M > ,
147157}
148158
@@ -152,6 +162,9 @@ impl<M: DbSpec> TursoDb<M> {
152162 ///
153163 /// Parent directories are created automatically. Migrations are run after
154164 /// the database connection is established.
165+ ///
166+ /// When both `SCE_SYNC_URL` and `SCE_SYNC_TOKEN` are set, the database opens
167+ /// in sync (Turso Cloud) mode. Otherwise, local-only mode is used.
155168 pub fn new ( ) -> Result < Self > {
156169 let db_name = M :: db_name ( ) ;
157170 let db_path = M :: db_path ( ) . with_context ( || format ! ( "failed to resolve {db_name} path" ) ) ?;
@@ -173,33 +186,70 @@ impl<M: DbSpec> TursoDb<M> {
173186 format ! ( "failed to create {db_name} tokio runtime. Try: rerun the command; if the issue persists, verify the local Tokio runtime environment." )
174187 } ) ?;
175188
176- let conn = runtime. block_on ( async {
177- let path_str = db_path. to_str ( ) . ok_or_else ( || {
178- anyhow:: anyhow!( "invalid UTF-8 in database path: {}" , db_path. display( ) )
179- } ) ?;
180- let db = turso:: Builder :: new_local ( path_str)
181- . build ( )
182- . await
183- . map_err ( |e| {
184- anyhow:: anyhow!(
185- "failed to open {db_name} database at {}: {e}" ,
186- db_path. display( )
187- )
188- } ) ?;
189- db. connect ( )
190- . map_err ( |e| anyhow:: anyhow!( "failed to connect to {db_name} database: {e}" ) )
189+ let sync_url = std:: env:: var ( crate :: services:: config:: SYNC_URL_ENV_KEY ) . ok ( ) ;
190+ let sync_token = std:: env:: var ( crate :: services:: config:: SYNC_TOKEN_ENV_KEY ) . ok ( ) ;
191+
192+ let path_str = db_path. to_str ( ) . ok_or_else ( || {
193+ anyhow:: anyhow!( "invalid UTF-8 in database path: {}" , db_path. display( ) )
191194 } ) ?;
192195
193- let db = Self {
194- conn,
195- runtime,
196- spec : PhantomData ,
197- } ;
196+ if let ( Some ( url) , Some ( token) ) = ( sync_url, sync_token) {
197+ let ( conn, sync_db) = runtime. block_on ( async {
198+ let sync_db = turso:: sync:: Builder :: new_remote ( path_str)
199+ . with_remote_url ( url)
200+ . with_auth_token ( token)
201+ . build ( )
202+ . await
203+ . map_err ( |e| {
204+ anyhow:: anyhow!(
205+ "failed to open {db_name} synced database at {}: {e}" ,
206+ db_path. display( )
207+ )
208+ } ) ?;
209+ let conn = sync_db. connect ( ) . await . map_err ( |e| {
210+ anyhow:: anyhow!( "failed to connect to {db_name} synced database: {e}" )
211+ } ) ?;
212+ Ok :: < _ , anyhow:: Error > ( ( conn, sync_db) )
213+ } ) ?;
214+
215+ let db = Self {
216+ conn,
217+ runtime,
218+ sync_db : Some ( sync_db) ,
219+ spec : PhantomData ,
220+ } ;
221+
222+ db. run_migrations ( )
223+ . with_context ( || format ! ( "failed to run {db_name} migrations" ) ) ?;
224+
225+ Ok ( db)
226+ } else {
227+ let conn = runtime. block_on ( async {
228+ let db = turso:: Builder :: new_local ( path_str)
229+ . build ( )
230+ . await
231+ . map_err ( |e| {
232+ anyhow:: anyhow!(
233+ "failed to open {db_name} database at {}: {e}" ,
234+ db_path. display( )
235+ )
236+ } ) ?;
237+ db. connect ( )
238+ . map_err ( |e| anyhow:: anyhow!( "failed to connect to {db_name} database: {e}" ) )
239+ } ) ?;
198240
199- db. run_migrations ( )
200- . with_context ( || format ! ( "failed to run {db_name} migrations" ) ) ?;
241+ let db = Self {
242+ conn,
243+ runtime,
244+ sync_db : None ,
245+ spec : PhantomData ,
246+ } ;
201247
202- Ok ( db)
248+ db. run_migrations ( )
249+ . with_context ( || format ! ( "failed to run {db_name} migrations" ) ) ?;
250+
251+ Ok ( db)
252+ }
203253 }
204254
205255 /// Execute a SQL statement that does not return rows.
@@ -210,6 +260,10 @@ impl<M: DbSpec> TursoDb<M> {
210260 ///
211261 /// # Returns
212262 /// Number of rows affected.
263+ ///
264+ /// Sync is not triggered automatically — callers use the explicit `push()`
265+ /// or `pull()` methods (or the `sce sync` CLI command) to sync with the
266+ /// remote.
213267 pub fn execute ( & self , sql : & str , params : impl turso:: params:: IntoParams ) -> Result < u64 > {
214268 self . runtime . block_on ( async {
215269 self . conn
@@ -219,6 +273,65 @@ impl<M: DbSpec> TursoDb<M> {
219273 } )
220274 }
221275
276+ /// Push local changes to the remote (sync mode only).
277+ ///
278+ /// In local mode this is a no-op that returns `Ok(())`.
279+ pub fn push ( & self ) -> Result < ( ) > {
280+ match self . sync_db {
281+ Some ( ref sync_db) => self
282+ . runtime
283+ . block_on ( sync_db. push ( ) )
284+ . map_err ( |e| anyhow:: anyhow!( "{} sync push failed: {e}" , M :: db_name( ) ) ) ,
285+ None => Ok ( ( ) ) ,
286+ }
287+ }
288+
289+ /// Pull remote changes (sync mode only).
290+ ///
291+ /// Returns `true` if any changes were applied to the local database.
292+ /// In local mode this is a no-op that returns `Ok(false)`.
293+ pub fn pull ( & self ) -> Result < bool > {
294+ match self . sync_db {
295+ Some ( ref sync_db) => self
296+ . runtime
297+ . block_on ( sync_db. pull ( ) )
298+ . map_err ( |e| anyhow:: anyhow!( "{} sync pull failed: {e}" , M :: db_name( ) ) ) ,
299+ None => Ok ( false ) ,
300+ }
301+ }
302+
303+ /// Force a WAL checkpoint (sync mode only).
304+ ///
305+ /// In local mode this is a no-op that returns `Ok(())`.
306+ pub fn checkpoint ( & self ) -> Result < ( ) > {
307+ match self . sync_db {
308+ Some ( ref sync_db) => self
309+ . runtime
310+ . block_on ( sync_db. checkpoint ( ) )
311+ . map_err ( |e| anyhow:: anyhow!( "{} sync checkpoint failed: {e}" , M :: db_name( ) ) ) ,
312+ None => Ok ( ( ) ) ,
313+ }
314+ }
315+
316+ /// Retrieve sync statistics (sync mode only).
317+ ///
318+ /// Returns `None` in local mode.
319+ pub fn stats ( & self ) -> Result < Option < turso:: sync:: DatabaseSyncStats > > {
320+ match self . sync_db {
321+ Some ( ref sync_db) => self
322+ . runtime
323+ . block_on ( sync_db. stats ( ) )
324+ . map ( Some )
325+ . map_err ( |e| anyhow:: anyhow!( "{} sync stats failed: {e}" , M :: db_name( ) ) ) ,
326+ None => Ok ( None ) ,
327+ }
328+ }
329+
330+ /// Returns `true` if the database is in sync (Turso Cloud) mode.
331+ pub fn is_sync_mode ( & self ) -> bool {
332+ self . sync_db . is_some ( )
333+ }
334+
222335 /// Execute a SQL query that returns rows.
223336 ///
224337 /// # Arguments
0 commit comments