> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/trailbaseio/trailbase/llms.txt
> Use this file to discover all available pages before exploring further.

# Rust SDK

> Official Rust client for TrailBase

The official Rust SDK for TrailBase provides a type-safe, async client for accessing your TrailBase backend from Rust applications.

## Installation

Add `trailbase-client` to your `Cargo.toml`:

```toml Cargo.toml theme={null}
[dependencies]
trailbase-client = "0.5.1"
```

### Features

* `ws` - Enable WebSocket support for subscriptions (optional)

```toml Cargo.toml theme={null}
[dependencies]
trailbase-client = { version = "0.5.1", features = ["ws"] }
```

## Initialization

### Basic Client

```rust theme={null}
use trailbase_client::Client;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new("https://your-server.trailbase.io").await?;
    Ok(())
}
```

### Client with Tokens

```rust theme={null}
use trailbase_client::{Client, Tokens};

let tokens = Tokens {
    auth_token: "your-auth-token".to_string(),
    refresh_token: Some("your-refresh-token".to_string()),
    csrf_token: Some("your-csrf-token".to_string()),
};

let client = Client::with_tokens(
    "https://your-server.trailbase.io",
    tokens
).await?;
```

## Authentication

### Login

```rust theme={null}
let tokens = client.login("user@example.com", "password").await?;
println!("Auth token: {}", tokens.auth_token);

let user = client.user();
if let Some(user) = user {
    println!("Logged in as: {}", user.email);
}
```

### Logout

```rust theme={null}
client.logout().await?;
```

### Current User

```rust theme={null}
if let Some(user) = client.user() {
    println!("User ID: {}", user.sub);
    println!("Email: {}", user.email);
}
```

### Access Tokens

```rust theme={null}
if let Some(tokens) = client.tokens() {
    // Persist tokens for later use
    let json = serde_json::to_string(&tokens)?;
    std::fs::write("tokens.json", json)?;
}
```

## Record API

### Define Your Record Types

```rust theme={null}
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct Post {
    id: String,
    title: String,
    content: String,
    author_id: String,
    created_at: i64,
}

#[derive(Debug, Serialize, Deserialize)]
struct NewPost {
    title: String,
    content: String,
}
```

### List Records

```rust theme={null}
use trailbase_client::{ListArguments, Pagination};

let posts = client.records::<Post>("posts");

let args = ListArguments::new()
    .with_pagination(Pagination::new().with_limit(10))
    .with_order(&["-created_at"])
    .with_count(true);

let response = posts.list(args).await?;

println!("Records: {}", response.records.len());
println!("Total count: {:?}", response.total_count);
println!("Next cursor: {:?}", response.cursor);
```

### Read a Record

```rust theme={null}
// Simple read with string ID
let post: Post = posts.read("post-id").await?;

// Read with integer ID
let post: Post = posts.read(123i64).await?;

// With expanded relationships
use trailbase_client::ReadArguments;

let args = ReadArguments::new("post-id")
    .with_expand(&["author"]);
let post: Post = posts.read(args).await?;

println!("Title: {}", post.title);
```

### Create a Record

```rust theme={null}
let new_post = NewPost {
    title: "Hello World".to_string(),
    content: "My first post from Rust".to_string(),
};

let post_id = posts.create(&new_post).await?;
println!("Created post with ID: {}", post_id);
```

### Update a Record

```rust theme={null}
use serde_json::json;

let update = json!({
    "title": "Updated Title"
});

posts.update("post-id", &update).await?;
```

### Delete a Record

```rust theme={null}
posts.delete("post-id").await?;
```

## Filtering

```rust theme={null}
use trailbase_client::{Filter, CompareOp, ValueOrFilterGroup};

// Simple equality filter
let args = ListArguments::new()
    .with_filter(Filter::new("author_id", "user-123"));

let response = posts.list(args).await?;

// With comparison operators
let args = ListArguments::new()
    .with_filter(
        Filter::new("created_at", &week_ago.to_string())
            .with_op(CompareOp::GreaterThan)
    );

// LIKE operator for text search
let args = ListArguments::new()
    .with_filter(
        Filter::new("title", "%search%")
            .with_op(CompareOp::Like)
    );

// AND composite filter
use trailbase_client::FilterGroup;

let filters = FilterGroup::And(vec![
    ValueOrFilterGroup::Value(Filter::new("status", "published")),
    ValueOrFilterGroup::Value(Filter::new("author_id", user_id)),
]);

let args = ListArguments::new()
    .with_filters(Some(filters));
```

### Available Comparison Operators

```rust theme={null}
pub enum CompareOp {
    Equal,
    NotEqual,
    GreaterThanEqual,
    GreaterThan,
    LessThanEqual,
    LessThan,
    Like,
    Regexp,
    StWithin,      // Geospatial
    StIntersects,  // Geospatial
    StContains,    // Geospatial
}
```

## Real-time Subscriptions

### Subscribe to Record Changes

```rust theme={null}
use futures_lite::StreamExt;
use trailbase_client::DbEvent;

// Subscribe to a single record
let mut stream = posts.subscribe("post-id").await?;

while let Some(event) = stream.next().await {
    match event {
        DbEvent::Insert(value) => {
            println!("Record inserted: {:?}", value);
        }
        DbEvent::Update(value) => {
            println!("Record updated: {:?}", value);
        }
        DbEvent::Delete(value) => {
            println!("Record deleted: {:?}", value);
        }
        DbEvent::Error(msg) => {
            eprintln!("Subscription error: {}", msg);
        }
    }
}
```

### Subscribe to All Records

```rust theme={null}
let filters = Some(Filter::new("author_id", user_id));
let mut stream = posts.subscribe_all(filters).await?;

while let Some(event) = stream.next().await {
    println!("Change event: {:?}", event);
}
```

### WebSocket Subscriptions

With the `ws` feature enabled:

```rust theme={null}
#[cfg(feature = "ws")]
{
    let mut stream = posts.subscribe_ws("post-id", None).await?;
    
    while let Some(event) = stream.next().await {
        println!("WebSocket event: {:?}", event);
    }
}
```

## Error Handling

```rust theme={null}
use trailbase_client::Error;

match posts.read("post-id").await {
    Ok(post) => println!("Post: {:?}", post),
    Err(Error::HttpStatus(status)) => {
        eprintln!("HTTP error: {}", status);
    }
    Err(Error::MissingRefreshToken) => {
        eprintln!("No refresh token available");
    }
    Err(e) => {
        eprintln!("Error: {}", e);
    }
}
```

## Type Definitions

### User

```rust theme={null}
pub struct User {
    pub sub: String,
    pub email: String,
}
```

### Tokens

```rust theme={null}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct Tokens {
    pub auth_token: String,
    pub refresh_token: Option<String>,
    pub csrf_token: Option<String>,
}
```

### ListResponse

```rust theme={null}
#[derive(Clone, Debug, Deserialize)]
pub struct ListResponse<T> {
    pub cursor: Option<String>,
    pub total_count: Option<usize>,
    pub records: Vec<T>,
}
```

### Pagination

```rust theme={null}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Pagination {
    // Private fields
}

impl Pagination {
    pub fn new() -> Self;
    pub fn with_limit(self, limit: impl Into<Option<usize>>) -> Pagination;
    pub fn with_cursor(self, cursor: impl Into<Option<String>>) -> Pagination;
    pub fn with_offset(self, offset: impl Into<Option<usize>>) -> Pagination;
}
```

### DbEvent

```rust theme={null}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum DbEvent {
    Update(Option<serde_json::Value>),
    Insert(Option<serde_json::Value>),
    Delete(Option<serde_json::Value>),
    Error(String),
}
```

## RecordId Trait

The `RecordId` trait allows using different types as record identifiers:

```rust theme={null}
use trailbase_client::RecordId;

// String
let post: Post = posts.read("post-id").await?;

// &str
let post: Post = posts.read("post-id").await?;

// i64
let post: Post = posts.read(123i64).await?;

// String (owned)
let id = String::from("post-id");
let post: Post = posts.read(id).await?;
```

## Best Practices

<Note>
  Use the `#[derive(Serialize, Deserialize)]` macros on your record types for seamless serialization.
</Note>

<Warning>
  Store tokens securely and never commit them to version control. Consider using environment variables or secure credential storage.
</Warning>

<Info>
  The client automatically refreshes auth tokens before they expire. Manual refresh is rarely needed.
</Info>

## Example Application

```rust theme={null}
use trailbase_client::{Client, Filter, ListArguments, Pagination};
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct Post {
    id: String,
    title: String,
    content: String,
    published: bool,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize client
    let client = Client::new(
        std::env::var("TRAILBASE_URL")
            .unwrap_or_else(|_| "http://localhost:4000".to_string())
    ).await?;
    
    // Login
    client.login(
        &std::env::var("TRAILBASE_EMAIL")?,
        &std::env::var("TRAILBASE_PASSWORD")?,
    ).await?;
    
    if let Some(user) = client.user() {
        println!("Logged in as: {}", user.email);
    }
    
    // List posts
    let posts = client.records::<Post>("posts");
    
    let args = ListArguments::new()
        .with_pagination(Pagination::new().with_limit(10))
        .with_order(&["-created_at"])
        .with_filter(Filter::new("published", "true"));
    
    let response = posts.list(args).await?;
    
    println!("\nFound {} posts:", response.records.len());
    for post in &response.records {
        println!("- {}", post.title);
    }
    
    // Create a new post
    let new_post = serde_json::json!({
        "title": "Hello from Rust",
        "content": "This post was created using the TrailBase Rust SDK",
        "published": true
    });
    
    let new_post_id = posts.create(&new_post).await?;
    println!("\nCreated new post with ID: {}", new_post_id);
    
    // Read the post
    let post: Post = posts.read(&new_post_id).await?;
    println!("Post title: {}", post.title);
    
    // Logout
    client.logout().await?;
    println!("\nLogged out");
    
    Ok(())
}
```
