pixeluvw_supabase 0.1.0

A production-ready, high-performance Supabase SDK for Rust with middleware, retry logic, and Arc<Inner> architecture
Documentation
use crate::config::ClientConfig;
use crate::core::{Middleware, SupabaseClient, SupabaseInner};
use crate::error::{Result, SupaError};
use reqwest::Client;
use std::sync::Arc;
use url::Url;

/// Builder for `SupabaseClient`.
pub struct SupabaseClientBuilder {
    pub(crate) url: Option<String>,
    pub(crate) key: Option<String>,
    pub(crate) config: ClientConfig,
    pub(crate) middlewares: Vec<Arc<dyn Middleware>>,
    pub(crate) is_service_role: bool,
    #[cfg(feature = "auth")]
    pub(crate) session_store: Option<Arc<dyn crate::auth_store::SessionStore>>,
}

impl Default for SupabaseClientBuilder {
    fn default() -> Self {
        Self::new()
    }
}

impl SupabaseClientBuilder {
    pub fn new() -> Self {
        Self {
            url: None,
            key: None,
            config: ClientConfig::default(),
            middlewares: Vec::new(),
            is_service_role: false,
            #[cfg(feature = "auth")]
            session_store: None,
        }
    }

    /// Set the Supabase project URL.
    pub fn url(mut self, url: impl Into<String>) -> Self {
        self.url = Some(url.into());
        self
    }

    /// Set the Supabase anon/service key.
    pub fn key(mut self, key: impl Into<String>) -> Self {
        self.key = Some(key.into());
        self
    }

    /// Set the request timeout in seconds.
    pub fn timeout(mut self, secs: u64) -> Self {
        self.config.timeout_secs = secs;
        self
    }

    /// Set the maximum number of retries.
    pub fn max_retries(mut self, retries: u32) -> Self {
        self.config.max_retries = retries;
        self
    }

    /// Set the session store for authentication.
    #[cfg(feature = "auth")]
    pub fn with_session_store<S: crate::auth_store::SessionStore + 'static>(
        mut self,
        store: S,
    ) -> Self {
        self.session_store = Some(Arc::new(store));
        self
    }

    /// Add a middleware to the client.
    pub fn middleware<M: Middleware + 'static>(mut self, middleware: M) -> Self {
        self.middlewares.push(Arc::new(middleware));
        self
    }

    /// Mark this client as using a service_role key.
    ///
    /// This enables admin API methods. If you're using an anon key,
    /// admin methods will fail at runtime if this is not set.
    ///
    /// # Example
    /// ```rust,no_run
    /// let client = SupabaseClient::builder()
    ///     .url("https://xxx.supabase.co")
    ///     .key("service_role_key_here")
    ///     .service_role(true)
    ///     .build()?;
    /// # Ok::<(), pixeluvw_supabase::SupaError>(())
    /// ```
    pub fn service_role(mut self, is_service_role: bool) -> Self {
        self.is_service_role = is_service_role;
        self
    }

    /// Build the SupabaseClient.
    pub fn build(self) -> Result<SupabaseClient> {
        let url_str = self.url.ok_or_else(|| SupaError::ClientError {
            message: "Supabase URL is required".to_string(),
        })?;
        let key = self.key.ok_or_else(|| SupaError::ClientError {
            message: "Supabase Key is required".to_string(),
        })?;

        let http = Client::builder()
            .timeout(std::time::Duration::from_secs(self.config.timeout_secs))
            .build()
            .map_err(|e| SupaError::ClientError {
                message: format!("Failed to build HTTP client: {}", e),
            })?;

        let inner = SupabaseInner {
            url: Url::parse(&url_str)?,
            key,
            http,
            config: self.config,
            middlewares: self.middlewares,
            session: std::sync::RwLock::new(None),
            is_service_role: self.is_service_role,
            #[cfg(feature = "auth")]
            session_store: std::sync::RwLock::new(self.session_store),
            schema: std::sync::RwLock::new(None),
        };

        let client = SupabaseClient {
            inner: Arc::new(inner),
        };

        // Try load session from store if available
        #[cfg(feature = "auth")]
        {
            // We can't easily wait here as build is sync.
            // But session load is sync in trait? Yes Result<Option<Session>>.
            let store_lock = client.inner.session_store.read().unwrap();
            if let Some(store) = store_lock.as_ref() {
                if let Ok(Some(session)) = store.load() {
                    // Manually set without triggering save (avoid loop or double write not needed)
                    // But set_session does save, which is fine (idempotent-ish)
                    // Or manual write to lock
                    if let Ok(mut sess_lock) = client.inner.session.write() {
                        *sess_lock = Some(session);
                    }
                }
            }
        }

        Ok(client)
    }
}