Skip to main content

supabase_client_wasm/
lib.rs

1//! WASM/TypeScript bindings for the Supabase client SDK.
2//!
3//! Build with `make wasm` (both targets) or individually:
4//! - `make wasm-web`  → `pkg/web/`  (ES modules for browsers)
5//! - `make wasm-node` → `pkg/node/` (CommonJS for Node.js)
6
7use wasm_bindgen::prelude::*;
8use serde_json::Value as JsonValue;
9
10use supabase_client_sdk::prelude::*;
11
12// ── Error conversion ─────────────────────────────────────────────────────────
13
14fn to_js_err(e: impl std::fmt::Display) -> JsValue {
15    JsValue::from_str(&e.to_string())
16}
17
18fn to_js_value(val: &impl serde::Serialize) -> Result<JsValue, JsValue> {
19    serde_wasm_bindgen::to_value(val).map_err(to_js_err)
20}
21
22/// Convert a JSON value (expected to be an object) into a Row.
23fn json_to_row(json: JsonValue) -> Result<Row, JsValue> {
24    match json {
25        JsonValue::Object(map) => {
26            let mut row = Row::new();
27            for (k, v) in map {
28                row.set(k, v);
29            }
30            Ok(row)
31        }
32        _ => Err(JsValue::from_str("expected a JSON object")),
33    }
34}
35
36// ── WasmSupabaseClient ───────────────────────────────────────────────────────
37
38/// Main Supabase client for WASM/JavaScript usage.
39#[wasm_bindgen]
40pub struct WasmSupabaseClient {
41    inner: SupabaseClient,
42    url: String,
43    key: String,
44}
45
46#[wasm_bindgen]
47impl WasmSupabaseClient {
48    /// Create a new Supabase client.
49    ///
50    /// @param url - The Supabase project URL (e.g., `https://your-project.supabase.co`)
51    /// @param key - The Supabase anon key
52    #[wasm_bindgen(constructor)]
53    pub fn new(url: &str, key: &str) -> Result<WasmSupabaseClient, JsValue> {
54        let config = SupabaseConfig::new(url, key);
55        let inner = SupabaseClient::new(config).map_err(to_js_err)?;
56        Ok(WasmSupabaseClient {
57            inner,
58            url: url.to_string(),
59            key: key.to_string(),
60        })
61    }
62
63    /// Execute a SELECT query on a table. Returns JSON results.
64    pub async fn from_select(&self, table: &str, columns: &str) -> Result<JsValue, JsValue> {
65        let response = self.inner.from(table).select(columns).execute().await;
66        let rows = response.into_result().map_err(to_js_err)?;
67        to_js_value(&rows)
68    }
69
70    /// Execute an INSERT query. `data` should be a JSON object (single row).
71    pub async fn from_insert(&self, table: &str, data: JsValue) -> Result<JsValue, JsValue> {
72        let json: JsonValue = serde_wasm_bindgen::from_value(data)?;
73        let row = json_to_row(json)?;
74        let response = self.inner.from(table).insert(row).execute().await;
75        let result = response.into_result().map_err(to_js_err)?;
76        to_js_value(&result)
77    }
78
79    /// Execute an UPDATE query with eq filter. `data` should be a JSON object.
80    pub async fn from_update(
81        &self,
82        table: &str,
83        data: JsValue,
84        column: &str,
85        value: &str,
86    ) -> Result<JsValue, JsValue> {
87        let json: JsonValue = serde_wasm_bindgen::from_value(data)?;
88        let row = json_to_row(json)?;
89        let response = self
90            .inner
91            .from(table)
92            .update(row)
93            .eq(column, value)
94            .execute()
95            .await;
96        let result = response.into_result().map_err(to_js_err)?;
97        to_js_value(&result)
98    }
99
100    /// Execute a DELETE query with eq filter.
101    pub async fn from_delete(
102        &self,
103        table: &str,
104        column: &str,
105        value: &str,
106    ) -> Result<JsValue, JsValue> {
107        let response = self
108            .inner
109            .from(table)
110            .delete()
111            .eq(column, value)
112            .execute()
113            .await;
114        let result = response.into_result().map_err(to_js_err)?;
115        to_js_value(&result)
116    }
117
118    /// Create an auth client.
119    pub fn auth(&self) -> Result<WasmAuthClient, JsValue> {
120        let auth = AuthClient::new(&self.url, &self.key).map_err(to_js_err)?;
121        Ok(WasmAuthClient { inner: auth })
122    }
123
124    /// Create a realtime client.
125    pub fn realtime(&self) -> Result<WasmRealtimeClient, JsValue> {
126        let rt = RealtimeClient::new(&self.url, &self.key).map_err(to_js_err)?;
127        Ok(WasmRealtimeClient { inner: rt })
128    }
129
130    /// Create a storage client.
131    pub fn storage(&self) -> Result<WasmStorageClient, JsValue> {
132        let storage = supabase_client_sdk::supabase_client_storage::StorageClient::new(
133            &self.url,
134            &self.key,
135        ).map_err(to_js_err)?;
136        Ok(WasmStorageClient { inner: storage })
137    }
138
139    /// Create a functions client.
140    pub fn functions(&self) -> Result<WasmFunctionsClient, JsValue> {
141        let functions = supabase_client_sdk::supabase_client_functions::FunctionsClient::new(
142            &self.url,
143            &self.key,
144        ).map_err(to_js_err)?;
145        Ok(WasmFunctionsClient { inner: functions })
146    }
147
148    /// Create a GraphQL client.
149    pub fn graphql(&self) -> Result<WasmGraphqlClient, JsValue> {
150        let graphql = supabase_client_sdk::supabase_client_graphql::GraphqlClient::new(
151            &self.url,
152            &self.key,
153        ).map_err(to_js_err)?;
154        Ok(WasmGraphqlClient { inner: graphql })
155    }
156}
157
158// ── WasmAuthClient ───────────────────────────────────────────────────────────
159
160/// Auth client for WASM/JavaScript usage.
161#[wasm_bindgen]
162pub struct WasmAuthClient {
163    inner: AuthClient,
164}
165
166#[wasm_bindgen]
167impl WasmAuthClient {
168    /// Sign up with email and password. Returns the auth response as JSON.
169    pub async fn sign_up(&self, email: &str, password: &str) -> Result<JsValue, JsValue> {
170        let resp = self.inner.sign_up_with_email(email, password).await.map_err(to_js_err)?;
171        to_js_value(&resp)
172    }
173
174    /// Sign in with email and password. Returns the session as JSON.
175    pub async fn sign_in_with_password(&self, email: &str, password: &str) -> Result<JsValue, JsValue> {
176        let session = self.inner.sign_in_with_password_email(email, password).await.map_err(to_js_err)?;
177        to_js_value(&session)
178    }
179
180    /// Sign in anonymously. Returns the session as JSON.
181    pub async fn sign_in_anonymous(&self) -> Result<JsValue, JsValue> {
182        let session = self.inner.sign_in_anonymous().await.map_err(to_js_err)?;
183        to_js_value(&session)
184    }
185
186    /// Send a magic link / OTP to an email address.
187    pub async fn sign_in_with_otp(&self, email: &str) -> Result<(), JsValue> {
188        self.inner.sign_in_with_otp_email(email).await.map_err(to_js_err)
189    }
190
191    /// Get the current session as JSON (or null).
192    pub async fn get_session(&self) -> Result<JsValue, JsValue> {
193        match self.inner.get_session().await {
194            Some(session) => to_js_value(&session),
195            None => Ok(JsValue::NULL),
196        }
197    }
198
199    /// Refresh the current session. Returns new session as JSON.
200    pub async fn refresh_session(&self) -> Result<JsValue, JsValue> {
201        let session = self.inner.refresh_current_session().await.map_err(to_js_err)?;
202        to_js_value(&session)
203    }
204
205    /// Sign out the current user.
206    pub async fn sign_out(&self) -> Result<(), JsValue> {
207        self.inner.sign_out_current().await.map_err(to_js_err)
208    }
209
210    /// Get the user for a given access token. Returns user as JSON.
211    pub async fn get_user(&self, access_token: &str) -> Result<JsValue, JsValue> {
212        let user = self.inner.get_user(access_token).await.map_err(to_js_err)?;
213        to_js_value(&user)
214    }
215
216    /// Send a password reset email.
217    pub async fn reset_password_for_email(&self, email: &str) -> Result<(), JsValue> {
218        self.inner.reset_password_for_email(email, None).await.map_err(to_js_err)
219    }
220
221    /// Get an OAuth sign-in URL for a given provider.
222    pub fn get_oauth_url(&self, provider: &str) -> Result<String, JsValue> {
223        let provider = match provider {
224            "google" => supabase_client_sdk::supabase_client_auth::OAuthProvider::Google,
225            "github" => supabase_client_sdk::supabase_client_auth::OAuthProvider::GitHub,
226            "apple" => supabase_client_sdk::supabase_client_auth::OAuthProvider::Apple,
227            "facebook" => supabase_client_sdk::supabase_client_auth::OAuthProvider::Facebook,
228            "twitter" => supabase_client_sdk::supabase_client_auth::OAuthProvider::Twitter,
229            "discord" => supabase_client_sdk::supabase_client_auth::OAuthProvider::Discord,
230            other => supabase_client_sdk::supabase_client_auth::OAuthProvider::Custom(other.to_string()),
231        };
232        self.inner.get_oauth_sign_in_url(provider, None, None).map_err(to_js_err)
233    }
234}
235
236// ── WasmRealtimeClient ───────────────────────────────────────────────────────
237
238/// Realtime client for WASM/JavaScript usage.
239#[wasm_bindgen]
240pub struct WasmRealtimeClient {
241    inner: RealtimeClient,
242}
243
244#[wasm_bindgen]
245impl WasmRealtimeClient {
246    /// Connect to the Supabase Realtime server.
247    pub async fn connect(&self) -> Result<(), JsValue> {
248        self.inner.connect().await.map_err(to_js_err)
249    }
250
251    /// Disconnect from the Realtime server.
252    pub async fn disconnect(&self) -> Result<(), JsValue> {
253        self.inner.disconnect().await.map_err(to_js_err)
254    }
255
256    /// Check if connected.
257    pub fn is_connected(&self) -> bool {
258        self.inner.is_connected()
259    }
260}
261
262// ── WasmStorageClient ────────────────────────────────────────────────────────
263
264/// Storage client for WASM/JavaScript usage.
265#[wasm_bindgen]
266pub struct WasmStorageClient {
267    inner: supabase_client_sdk::supabase_client_storage::StorageClient,
268}
269
270#[wasm_bindgen]
271impl WasmStorageClient {
272    /// List all storage buckets. Returns JSON array.
273    pub async fn list_buckets(&self) -> Result<JsValue, JsValue> {
274        let buckets = self.inner.list_buckets().await.map_err(to_js_err)?;
275        to_js_value(&buckets)
276    }
277
278    /// Get a bucket by ID. Returns JSON object.
279    pub async fn get_bucket(&self, id: &str) -> Result<JsValue, JsValue> {
280        let bucket = self.inner.get_bucket(id).await.map_err(to_js_err)?;
281        to_js_value(&bucket)
282    }
283}
284
285// ── WasmFunctionsClient ──────────────────────────────────────────────────────
286
287/// Edge Functions client for WASM/JavaScript usage.
288#[wasm_bindgen]
289pub struct WasmFunctionsClient {
290    inner: supabase_client_sdk::supabase_client_functions::FunctionsClient,
291}
292
293#[wasm_bindgen]
294impl WasmFunctionsClient {
295    /// Invoke an edge function with a JSON body. Returns the response body as JSON.
296    pub async fn invoke(&self, function_name: &str, body: JsValue) -> Result<JsValue, JsValue> {
297        let json: JsonValue = serde_wasm_bindgen::from_value(body)?;
298        let options = InvokeOptions::default().body(json);
299        let response = self.inner
300            .invoke(function_name, options)
301            .await
302            .map_err(to_js_err)?;
303        let result: JsonValue = response.json().map_err(to_js_err)?;
304        to_js_value(&result)
305    }
306}
307
308// ── WasmGraphqlClient ────────────────────────────────────────────────────────
309
310/// GraphQL client for WASM/JavaScript usage.
311#[wasm_bindgen]
312pub struct WasmGraphqlClient {
313    inner: supabase_client_sdk::supabase_client_graphql::GraphqlClient,
314}
315
316#[wasm_bindgen]
317impl WasmGraphqlClient {
318    /// Execute a raw GraphQL query. Returns the response data as JSON.
319    ///
320    /// @param query - The GraphQL query string
321    /// @param variables - Optional JSON variables object
322    pub async fn execute(
323        &self,
324        query: &str,
325        variables: JsValue,
326    ) -> Result<JsValue, JsValue> {
327        let vars: Option<JsonValue> = if variables.is_null() || variables.is_undefined() {
328            None
329        } else {
330            Some(serde_wasm_bindgen::from_value(variables)?)
331        };
332
333        let response = self.inner
334            .execute_raw(query, vars, None)
335            .await
336            .map_err(to_js_err)?;
337
338        to_js_value(&response.data)
339    }
340
341    /// Set a custom auth token for subsequent requests.
342    pub fn set_auth(&self, token: &str) {
343        self.inner.set_auth(token);
344    }
345}