# Supabase PostgREST Client for Rust
A Rust client library for Supabase PostgreSQL REST API access.
## Features
- Basic CRUD operations (`select`, `insert`, `update`, `delete`)
- Filtering (`eq`, `gt`, `lt`, `like`, `ilike`, `in_list`, `not`, `contains`, `contained_by`, `text_search`, etc.)
- Ordering (`order`) and pagination (`limit`, `offset`)
- Joins (`inner_join`, `left_join`, `include`, `referenced_by`)
- Transactions (`begin_transaction`, `commit`, `rollback`, `savepoint`)
- RPC function calls (`rpc`)
- CSV export (`export_csv`)
- TypeScript to Rust type conversion infrastructure (via `schema-convert` feature, conversion logic is currently a placeholder)
- Basic type-safe operation helpers (requires `schema-convert` feature, experimental)
## Project Status & Roadmap
**Current Status:** Alpha (v0.1.3) - Core API Implemented, Type Safety Experimental
This crate provides core PostgREST functionality, transaction support, and initial infrastructure for type generation. It is under active development.
**Roadmap:**
* [x] Basic CRUD, Filtering, Ordering, Pagination
* [x] RPC Function Calls
* [x] Transactions & Savepoints
* [x] CSV Export
* [x] Full-text search (`text_search`)
* [x] Basic JSONB operations (`contains`, `contained_by`)
* [ ] Support for more complex `select` queries (e.g., advanced nested resources/embedding)
* [ ] Complete the `schema-convert` feature for robust TypeScript -> Rust type generation.
* [ ] Enhance type-safe operation helpers based on generated types.
* [ ] Improved error reporting and handling details.
* [ ] Comprehensive integration tests against a live Supabase instance.
* [ ] Explore potential state machine usage for request building/transaction management.
## Installation
Add the dependency to your Cargo.toml:
```toml
[dependencies]
supabase-rust-postgrest = "0.1.3"
```
To use the TypeScript to Rust type conversion feature:
```toml
[dependencies]
supabase-rust-postgrest = { version = "0.1.3", features = ["schema-convert"] }
```
## Basic Usage
```rust
use supabase_rust_postgrest::PostgrestClient;
use reqwest::Client;
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let http_client = Client::new();
let db = PostgrestClient::new(
"https://your-project.supabase.co",
"your-anon-key",
"your_table",
http_client
);
// Fetch data
let response: Vec<serde_json::Value> = db
.select("*")
.eq("column", "value")
.execute()
.await?;
// Insert data
let data = serde_json::json!({
"name": "John Doe",
"email": "john@example.com"
});
let inserted = db
.insert(&data)
.await?;
// Update data
let update_data = serde_json::json!({
"name": "Jane Doe"
});
let updated = db
.eq("id", "1")
.update(&update_data)
.await?;
// Delete data
let deleted = db
.eq("id", "1")
.delete()
.await?;
Ok(())
}
```
## TypeScript to Rust Type Conversion
This crate provides functionality to convert TypeScript type definitions generated by Supabase's `supabase gen types typescript` command into Rust types. To use this feature, you must enable the `schema-convert` feature.
**Note:** While the infrastructure and CLI tools for schema conversion are present, the core logic (`convert_typescript_to_rust` function) that performs the detailed type mapping from TypeScript interfaces/types to Rust structs/enums is currently a placeholder and requires further implementation based on specific project needs or more sophisticated parsing. The current implementation primarily sets up the file structure and basic generation.
### Converting Using Make (Recommended)
The simplest way to generate Rust types from your Supabase schema is using the provided Makefile:
```bash
# Copy the Makefile from this repository to your project root
# Then run:
make gen-types-rust
```
This will:
1. Generate TypeScript types using `supabase gen types typescript`
2. Convert them to Rust types
3. Place the generated Rust file in `src/generated/schema.rs`
You can customize the output location and module name:
```bash
make gen-types-rust TYPES_OUTPUT_DIR=src/models MODULE_NAME=database
```
For more options, run:
```bash
make help
```
### Converting from Command Line
```bash
# Run in your repository root directory
supabase gen types typescript > types.ts
# Generate Rust types from TypeScript definitions
cargo run --features schema-convert --bin supabase-gen-rust -- \
--input-file ./types.ts \
--output-dir ./src/generated \
--module-name schema
```
### Converting Programmatically
```rust
// NOTE: Requires the 'schema-convert' feature to be enabled.
// The core conversion logic is currently a placeholder.
# #[cfg(feature = "schema-convert")]
# {
use std::path::Path;
use supabase_rust_postgrest::{
convert_typescript_to_rust,
SchemaConvertOptions,
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let input_file = Path::new("./types.ts"); // Ensure this file exists
let options = SchemaConvertOptions::default();
// This function call might only perform basic setup in the current version.
let output_path = convert_typescript_to_rust(input_file, options)?;
println!("Generated Rust types structure at: {:?}", output_path);
Ok(())
}
# }
```
## Type-Safe Database Operations
**Note:** The type-safe operations shown below rely on the `schema-convert` feature and the associated generated types. This feature is currently **experimental**, and the underlying schema conversion logic is incomplete. Use with caution and expect potential limitations or required manual adjustments.
```rust
// NOTE: Requires the 'schema-convert' feature and generated types.
# #[cfg(feature = "schema-convert")]
# {
use serde::{Deserialize, Serialize};
use supabase_rust_postgrest::{
PostgrestClient, Table, PostgrestClientTypeExtension
};
use reqwest::Client; // Added missing import
// Assuming types are generated in src/generated/schema.rs
// mod generated { include!(\"../src/generated/schema.rs\"); }
// use generated::schema::*;
// Or define manually for demonstration:
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] // Added PartialEq for test assertion
struct User {
// Make fields public if accessed directly outside the module
pub id: Option<i32>,
pub name: String,
pub email: String,
}
impl Table for User {
fn table_name() -> &\'static str {
"users" // Ensure this matches your actual table name
}
}
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let http_client = reqwest::Client::new(); // Use the imported Client
// Ensure base_url and api_key are correct
let client = PostgrestClient::new(
"http://localhost:54321", // Example: Replace with your Supabase URL
"your-anon-key", // Example: Replace with your Supabase Anon Key
"", // Table name set via Table trait
http_client,
);
// Example: Type-safe query (assuming User with id=1 exists)
// let users: Vec<User> = client
// .query_typed::<User>()? // Use query_typed
// .eq("name", "John") // Filter example
// .execute()
// .await?;
// Example: Type-safe insert
let new_user = User {
id: None, // ID is usually generated by the database
name: "Alice".to_string(),
email: "alice@example.com".to_string(),
};
// The insert_typed method seems missing or part of the experimental feature.
// Using standard insert for now:
// let inserted_raw = client
// .from(User::table_name()) // Use from() with table name
// .insert(&new_user) // Standard insert takes serializable data
// .execute::<Vec<User>>() // Expecting a Vec<User> back if RETURNING data
// .await?;
// let inserted: User = inserted_raw.into_iter().next().ok_or("Insert failed")?;
// Example: Type-safe update (assuming a user exists)
// let mut user_to_update = users.get(0).cloned().ok_or("No user to update")?;
// user_to_update.name = "Bob".to_string();
// Update requires filter + data. update_typed seems missing/experimental.
// Using standard update:
// let updated_raw = client
// .from(User::table_name())
// .eq("id", &user_to_update.id.unwrap().to_string())
// .update(&serde_json::json!({ "name": user_to_update.name })) // Pass update data
// .execute::<Vec<User>>() // Expecting updated user back
// .await?;
// let updated: User = updated_raw.into_iter().next().ok_or("Update failed")?;
// Example: Type-safe delete (assuming a user exists)
// let user_to_delete = users.get(0).ok_or("No user to delete")?;
// delete_typed seems missing/experimental. Using standard delete:
// client
// .from(User::table_name())
// .eq("id", &user_to_delete.id.unwrap().to_string())
// .delete()
// .execute::<serde_json::Value>() // Delete often returns minimal info
// .await?;
println!("Example operations completed (actual execution depends on setup and uncommenting)");
Ok(())
}
# }
```
## Testing
To run the tests for this crate, including feature-specific tests:
```bash
cargo test --all-features
```
We aim for high test coverage, particularly for core CRUD and filtering operations. Integration tests using `wiremock` simulate responses from the PostgREST API.
## Security Considerations
* **API Keys:** Ensure your Supabase URL and `anon` key (or `service_role` key if used) are stored and handled securely. Avoid hardcoding them directly in your source code. Consider using environment variables or a secrets management solution.
* **Input Validation:** While this library promotes type safety, always validate user-provided input before constructing database queries, especially for filter values, to prevent potential injection issues or unintended data access.
## Contributing
Contributions are welcome! Please feel free to open an issue or submit a pull request. For major changes, please open an issue first to discuss what you would like to change.
Ensure that your contributions pass all tests (`cargo test --all-features`) and adhere to the project's coding style (run `cargo fmt`).
## License
MIT