tinyflow-api 0.1.1

Core traits, types, and abstractions for Tinyflow streaming framework
Documentation
use std::collections::HashMap;
use std::path::Path;
use std::sync::{Arc, Mutex};

use async_trait::async_trait;

use crate::error::{StreamResult, task_failed};

#[async_trait]
pub trait StateStore: Send + Sync {
    async fn put(&self, namespace: &str, key: &[u8], value: &[u8]) -> StreamResult<()>;
    async fn get(&self, namespace: &str, key: &[u8]) -> StreamResult<Option<Vec<u8>>>;
    async fn delete(&self, namespace: &str, key: &[u8]) -> StreamResult<()>;
    async fn delete_prefix(&self, namespace: &str, key_prefix: &[u8]) -> StreamResult<()>;
    async fn list_prefix(
        &self,
        namespace: &str,
        key_prefix: &[u8],
    ) -> StreamResult<Vec<(Vec<u8>, Vec<u8>)>>;

    /// Persistent backends (e.g., SQLite) return the database file path; in-memory stores return `None`.
    fn persistent_db_path(&self) -> Option<&Path> {
        None
    }
}

pub fn state_db_err(reason: impl Into<String>) -> crate::error::StreamError {
    task_failed(0, reason)
}

pub fn in_memory_job_state() -> Arc<dyn StateStore> {
    Arc::new(InMemoryStateStore::default())
}

type InMemoryStoreMap = HashMap<(String, Vec<u8>), Vec<u8>>;

#[derive(Default)]
pub struct InMemoryStateStore {
    inner: Mutex<InMemoryStoreMap>,
}

#[async_trait]
impl StateStore for InMemoryStateStore {
    async fn put(&self, namespace: &str, key: &[u8], value: &[u8]) -> StreamResult<()> {
        self.inner
            .lock()
            .unwrap()
            .insert((namespace.to_string(), key.to_vec()), value.to_vec());
        Ok(())
    }

    async fn get(&self, namespace: &str, key: &[u8]) -> StreamResult<Option<Vec<u8>>> {
        Ok(self
            .inner
            .lock()
            .unwrap()
            .get(&(namespace.to_string(), key.to_vec()))
            .cloned())
    }

    async fn delete(&self, namespace: &str, key: &[u8]) -> StreamResult<()> {
        self.inner
            .lock()
            .unwrap()
            .remove(&(namespace.to_string(), key.to_vec()));
        Ok(())
    }

    async fn delete_prefix(&self, namespace: &str, key_prefix: &[u8]) -> StreamResult<()> {
        let ns = namespace.to_string();
        let mut guard = self.inner.lock().unwrap();
        guard.retain(|(n, k), _| !(n == &ns && k.starts_with(key_prefix)));
        Ok(())
    }

    async fn list_prefix(
        &self,
        namespace: &str,
        key_prefix: &[u8],
    ) -> StreamResult<Vec<(Vec<u8>, Vec<u8>)>> {
        let ns = namespace.to_string();
        let guard = self.inner.lock().unwrap();
        let mut out: Vec<(Vec<u8>, Vec<u8>)> = guard
            .iter()
            .filter(|((n, k), _)| n == &ns && k.starts_with(key_prefix))
            .map(|((_, k), v)| (k.clone(), v.clone()))
            .collect();
        out.sort_by(|a, b| a.0.cmp(&b.0));
        Ok(out)
    }
}