Skip to main content

supabase_client_core/
client.rs

1use std::sync::Arc;
2
3#[cfg(feature = "direct-sql")]
4use sqlx::postgres::PgPoolOptions;
5#[cfg(feature = "direct-sql")]
6use sqlx::PgPool;
7
8use crate::config::SupabaseConfig;
9use crate::error::SupabaseResult;
10
11/// The main client for interacting with Supabase.
12///
13/// By default, uses the PostgREST REST API. Enable the `direct-sql` feature
14/// and call `with_database()` to also get a direct PostgreSQL connection pool.
15#[derive(Debug, Clone)]
16pub struct SupabaseClient {
17    config: Arc<SupabaseConfig>,
18    http: reqwest::Client,
19    #[cfg(feature = "direct-sql")]
20    pool: Option<Arc<PgPool>>,
21}
22
23impl SupabaseClient {
24    /// Create a new REST-only client (no database connection needed).
25    ///
26    /// This is the primary constructor. Queries go through PostgREST.
27    pub fn new(config: SupabaseConfig) -> SupabaseResult<Self> {
28        let http = reqwest::Client::new();
29        Ok(Self {
30            config: Arc::new(config),
31            http,
32            #[cfg(feature = "direct-sql")]
33            pool: None,
34        })
35    }
36
37    /// Create a client with a direct database connection pool.
38    ///
39    /// Requires the `direct-sql` feature and a `database_url` in config.
40    #[cfg(feature = "direct-sql")]
41    pub async fn with_database(config: SupabaseConfig) -> SupabaseResult<Self> {
42        let db_url = config
43            .database_url
44            .as_ref()
45            .ok_or_else(|| crate::error::SupabaseError::Config(
46                "database_url is required for direct-sql mode".into(),
47            ))?;
48
49        let pool = PgPoolOptions::new()
50            .max_connections(config.pool.max_connections)
51            .min_connections(config.pool.min_connections)
52            .acquire_timeout(config.pool.acquire_timeout)
53            .idle_timeout(config.pool.idle_timeout)
54            .max_lifetime(config.pool.max_lifetime)
55            .connect(db_url)
56            .await?;
57
58        let http = reqwest::Client::new();
59
60        Ok(Self {
61            config: Arc::new(config),
62            http,
63            pool: Some(Arc::new(pool)),
64        })
65    }
66
67    /// Create a client from an existing pool (direct-sql mode).
68    #[cfg(feature = "direct-sql")]
69    pub fn from_pool(pool: PgPool, config: SupabaseConfig) -> Self {
70        Self {
71            config: Arc::new(config),
72            http: reqwest::Client::new(),
73            pool: Some(Arc::new(pool)),
74        }
75    }
76
77    /// Get a reference to the HTTP client.
78    pub fn http(&self) -> &reqwest::Client {
79        &self.http
80    }
81
82    /// Get the Supabase project URL.
83    pub fn supabase_url(&self) -> &str {
84        &self.config.supabase_url
85    }
86
87    /// Get the Supabase API key.
88    pub fn api_key(&self) -> &str {
89        &self.config.supabase_key
90    }
91
92    /// Get the default schema.
93    pub fn schema(&self) -> &str {
94        &self.config.schema
95    }
96
97    /// Get the full config.
98    pub fn config(&self) -> &SupabaseConfig {
99        &self.config
100    }
101
102    /// Get a reference to the underlying connection pool (if available).
103    #[cfg(feature = "direct-sql")]
104    pub fn pool(&self) -> Option<&PgPool> {
105        self.pool.as_deref()
106    }
107
108    /// Get an Arc to the pool (for passing to builders).
109    #[cfg(feature = "direct-sql")]
110    pub fn pool_arc(&self) -> Option<Arc<PgPool>> {
111        self.pool.clone()
112    }
113
114    /// Check if direct-sql pool is available.
115    #[cfg(feature = "direct-sql")]
116    pub fn has_pool(&self) -> bool {
117        self.pool.is_some()
118    }
119
120    /// Close the connection pool gracefully (if available).
121    #[cfg(feature = "direct-sql")]
122    pub async fn close(&self) {
123        if let Some(pool) = &self.pool {
124            pool.close().await;
125        }
126    }
127
128    /// Check if the pool is closed (if available).
129    #[cfg(feature = "direct-sql")]
130    pub fn is_closed(&self) -> bool {
131        self.pool.as_ref().map_or(true, |p| p.is_closed())
132    }
133}