tauri-plugin-rpstate 0.4.2

Tauri v2 plugin that exposes rpstate reactive persistent state to the frontend, with TypeScript codegen
Documentation
use rpstate::Store;
use rpstate::store::SubscriptionId;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use tauri::{AppHandle, Emitter, Runtime, State};

pub struct PluginState {
    pub subscriptions: Mutex<HashMap<String, SubscriptionId>>,
}

#[tauri::command]
pub async fn rpstate_get(
    store: State<'_, Arc<rpstate::DefaultStore>>,
    key: String,
) -> Result<Option<serde_json::Value>, String> {
    store.get(&key).map_err(|e| e.to_string())
}

#[tauri::command]
pub async fn rpstate_set(
    store: State<'_, Arc<rpstate::DefaultStore>>,
    key: String,
    value: serde_json::Value,
) -> Result<(), String> {
    store.set(&key, &value).map_err(|e| e.to_string())
}

#[tauri::command]
pub async fn rpstate_get_prefix(
    store: State<'_, Arc<rpstate::DefaultStore>>,
    prefix: String,
) -> Result<HashMap<String, serde_json::Value>, String> {
    let raw =
        rpstate::Store::scan_prefix(store.inner().as_ref(), &prefix).map_err(|e| e.to_string())?;

    let mut map = HashMap::new();
    for (path, bytes) in raw {
        if let Ok(val) = store.inner().decode::<serde_json::Value>(&bytes) {
            map.insert(path, val);
        }
    }
    Ok(map)
}

#[tauri::command]
pub async fn rpstate_flush(
    store: State<'_, Arc<rpstate::DefaultStore>>,
    prefix: String,
) -> Result<(), String> {
    rpstate::Store::flush_prefix(store.inner().as_ref(), &prefix).map_err(|e| e.to_string())
}

#[tauri::command]
pub async fn rpstate_subscribe<R: Runtime>(
    store: State<'_, Arc<rpstate::DefaultStore>>,
    app: AppHandle<R>,
    state: State<'_, PluginState>,
    key: String,
) -> Result<(), String> {
    let mut subs = state.subscriptions.lock().map_err(|e| e.to_string())?;
    if subs.contains_key(&key) {
        return Ok(());
    }

    let app_handle = app.clone();
    let key_clone = key.clone();
    let store_clone = store.inner().clone();

    let sub_id = store.subscribe(
        rpstate::SubscriptionKind::Prefix(Arc::from(key.as_str())),
        Arc::new(move |event| {
            let event_name = format!("rpstate://{}", key_clone.replace('.', ":"));
            let store_c = store_clone.clone();

            let prefix_dot = format!("{}.", key_clone);
            if let Some(subkey) = event.path.strip_prefix(&prefix_dot) {
                let old_val = event
                    .old
                    .as_ref()
                    .and_then(|b| store_c.decode::<serde_json::Value>(b).ok());
                let new_val = event
                    .new
                    .as_ref()
                    .and_then(|b| store_c.decode::<serde_json::Value>(b).ok());

                let payload = match event.op {
                    rpstate::StoreOp::Set | rpstate::StoreOp::Patch => {
                        if let Some(old) = old_val {
                            serde_json::json!({
                                "type": "Update",
                                "key": subkey,
                                "oldValue": old,
                                "newValue": new_val.unwrap_or(serde_json::Value::Null),
                            })
                        } else {
                            serde_json::json!({
                                "type": "Insert",
                                "key": subkey,
                                "value": new_val.unwrap_or(serde_json::Value::Null),
                            })
                        }
                    }
                    rpstate::StoreOp::Delete => serde_json::json!({
                        "type": "Remove",
                        "key": subkey,
                        "oldValue": old_val.unwrap_or(serde_json::Value::Null),
                    }),
                };
                let _ = app_handle.emit(&event_name, payload);
            } else if *event.path == *key_clone
                && let Some(new_bytes) = &event.new
                && let Ok(val) = store_c.decode::<serde_json::Value>(new_bytes)
            {
                let _ = app_handle.emit(&event_name, val);
            }
        }),
    );

    subs.insert(key, sub_id);
    Ok(())
}

#[tauri::command]
pub async fn rpstate_unsubscribe(
    store: State<'_, Arc<rpstate::DefaultStore>>,
    state: State<'_, PluginState>,
    key: String,
) -> Result<(), String> {
    let mut subs = state.subscriptions.lock().map_err(|e| e.to_string())?;
    if let Some(sub_id) = subs.remove(&key) {
        store.unsubscribe(sub_id);
    }
    Ok(())
}