supabase-rust-postgrest 0.4.0

PostgreSQL REST client for Supabase
Documentation
# 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