6 releases
new 0.4.0 | Apr 26, 2025 |
---|---|
0.2.0 | Apr 25, 2025 |
0.1.4 | Apr 23, 2025 |
#2093 in Database interfaces
500 downloads per month
Used in 2 crates
94KB
2K
SLoC
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:
- Basic CRUD, Filtering, Ordering, Pagination
- RPC Function Calls
- Transactions & Savepoints
- CSV Export
- Full-text search (
text_search
) - 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:
[dependencies]
supabase-rust-postgrest = "0.1.3"
To use the TypeScript to Rust type conversion feature:
[dependencies]
supabase-rust-postgrest = { version = "0.1.3", features = ["schema-convert"] }
Basic Usage
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:
# Copy the Makefile from this repository to your project root
# Then run:
make gen-types-rust
This will:
- Generate TypeScript types using
supabase gen types typescript
- Convert them to Rust types
- Place the generated Rust file in
src/generated/schema.rs
You can customize the output location and module name:
make gen-types-rust TYPES_OUTPUT_DIR=src/models MODULE_NAME=database
For more options, run:
make help
Converting from Command Line
# 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
// 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.
// 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:
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 (orservice_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
Dependencies
~6–18MB
~253K SLoC