Skip to main content

tauri_plugin_powersync/
lib.rs

1use powersync::error::PowerSyncError;
2use powersync::schema::SchemaOrCustom;
3use powersync::{env::PowerSyncEnvironment, ConnectionPool, PowerSyncDatabase};
4use rusqlite::Connection;
5use std::collections::hash_map::Entry;
6use std::marker::PhantomData;
7use std::sync::{Arc, Weak};
8use std::{collections::HashMap, sync::Mutex};
9use tauri::{
10    plugin::{Builder, TauriPlugin},
11    AppHandle, Manager, Runtime,
12};
13
14mod commands;
15mod database;
16mod error;
17mod handle;
18
19use crate::database::TauriDatabaseState;
20use crate::handle::JavaScriptHandles;
21pub use error::Result;
22
23/// Extensions to [`tauri::App`], [`tauri::AppHandle`] and [`tauri::Window`] to access the PowerSync
24/// plugin.
25pub trait PowerSyncExt<R: Runtime> {
26    fn powersync(&self) -> &PowerSync<R>;
27}
28
29impl<R: Runtime, T: Manager<R>> PowerSyncExt<R> for T {
30    fn powersync(&self) -> &PowerSync<R> {
31        self.state::<PowerSync<R>>().inner()
32    }
33}
34
35pub struct PowerSync<R: Runtime> {
36    app: PhantomData<AppHandle<R>>,
37    databases: Mutex<HashMap<String, Weak<TauriDatabaseState>>>,
38    pub(crate) handles: JavaScriptHandles,
39}
40
41impl<R: Runtime> PowerSync<R> {
42    pub(crate) fn open_database(
43        &self,
44        app: AppHandle<R>,
45        name: &str,
46        schema: SchemaOrCustom,
47    ) -> Result<Arc<TauriDatabaseState>> {
48        let mut map = self.databases.lock().unwrap();
49        let mut entry = map.entry(name.to_owned());
50
51        if let Entry::Occupied(entry) = &mut entry {
52            if let Some(existing) = entry.get().upgrade() {
53                return Ok(existing);
54            }
55        };
56
57        PowerSyncEnvironment::powersync_auto_extension()?;
58        let pool = if name == ":memory:" {
59            ConnectionPool::single_connection(
60                Connection::open_in_memory().map_err(PowerSyncError::from)?,
61            )
62        } else {
63            ConnectionPool::open(name)?
64        };
65
66        let env = PowerSyncEnvironment::custom(
67            reqwest::Client::new(),
68            pool,
69            PowerSyncEnvironment::tokio_timer(),
70        );
71
72        let database = PowerSyncDatabase::new(env, schema);
73        database.async_tasks().spawn_with_tokio();
74
75        let db = Arc::new(TauriDatabaseState::new(app, name, database));
76        entry.insert_entry(Arc::downgrade(&db));
77
78        Ok(db)
79    }
80
81    /// Resolves a [PowerSyncDatabase] from the handle obtained from
82    /// `PowerSyncTauriDatabase.rustHandle` in JavaScript.
83    pub fn database_from_javascript_handle(&self, handle: usize) -> Result<PowerSyncDatabase> {
84        let handle = self.handles.lookup(handle)?;
85        Ok(handle.as_database()?.clone())
86    }
87}
88
89/// Initializes the plugin.
90pub fn init<R: Runtime>() -> TauriPlugin<R> {
91    Builder::new("powersync")
92        .invoke_handler(tauri::generate_handler![commands::powersync])
93        .setup(|app, _api| {
94            let powersync = PowerSync {
95                app: PhantomData::<AppHandle<R>>,
96                databases: Default::default(),
97                handles: Default::default(),
98            };
99            app.manage(powersync);
100            Ok(())
101        })
102        .build()
103}