tauri-plugin-sync-state 0.1.0

Bidirectional, type-keyed state sync between a Tauri backend and a JS/React frontend.
Documentation
use crate::{
    commands::event_name,
    error::Error,
    slice::{StateRegistry, SyncState},
};
use std::ops::Deref;
use tauri::{
    ipc::{CommandArg, CommandItem, InvokeError},
    AppHandle, Emitter, Manager, Runtime, Wry,
};

pub struct Mutator<S: SyncState, R: Runtime = Wry> {
    app: AppHandle<R>,
    snapshot: S,
}

impl<S: SyncState, R: Runtime> Deref for Mutator<S, R> {
    type Target = S;
    fn deref(&self) -> &S {
        &self.snapshot
    }
}

impl<S: SyncState, R: Runtime> Mutator<S, R> {
    pub fn current(&self) -> &S {
        &self.snapshot
    }

    pub fn reload(&mut self) -> Result<&S, Error> {
        let registry = self.app.state::<StateRegistry>();
        self.snapshot = registry.read::<S>()?;
        Ok(&self.snapshot)
    }

    pub fn mutate(&mut self, f: impl FnOnce(&mut S)) {
        if let Err(e) = self.try_mutate(f) {
            log::error!("[sync-state] mutate<{}> failed: {e}", S::NAME);
        }
    }

    pub fn try_mutate(&mut self, f: impl FnOnce(&mut S)) -> Result<(), Error> {
        let registry = self.app.state::<StateRegistry>();
        let next = registry.mutate::<S>(f)?;
        self.snapshot = next.clone();
        self.app
            .emit(&event_name(S::NAME), next)
            .map_err(|e| Error::Emit(e.to_string()))
    }

    pub async fn mutate_async<F, Fut>(&mut self, f: F)
    where
        F: FnOnce(S) -> Fut,
        Fut: std::future::Future<Output = S>,
    {
        if let Err(e) = self.try_mutate_async(f).await {
            log::error!("[sync-state] mutate_async<{}> failed: {e}", S::NAME);
        }
    }

    pub async fn try_mutate_async<F, Fut>(&mut self, f: F) -> Result<(), Error>
    where
        F: FnOnce(S) -> Fut,
        Fut: std::future::Future<Output = S>,
    {
        let current = {
            let registry = self.app.state::<StateRegistry>();
            registry.read::<S>()?
        };
        let updated = f(current).await;
        let registry = self.app.state::<StateRegistry>();
        registry.mutate::<S>(|s| *s = updated.clone())?;
        self.snapshot = updated.clone();
        self.app
            .emit(&event_name(S::NAME), updated)
            .map_err(|e| Error::Emit(e.to_string()))
    }
}

impl<'de, S: SyncState, R: Runtime> CommandArg<'de, R> for Mutator<S, R> {
    fn from_command(command: CommandItem<'de, R>) -> Result<Self, InvokeError> {
        let app: AppHandle<R> = command.message.webview().app_handle().clone();

        let registry = app
            .try_state::<StateRegistry>()
            .ok_or_else(|| InvokeError::from(Error::PluginNotInitialized.to_string()))?;

        let snapshot = registry
            .read::<S>()
            .map_err(|e| InvokeError::from(e.to_string()))?;

        Ok(Mutator { app, snapshot })
    }
}