surreal-client 0.5.0

CBOR-based SurrealDB client for the Vantage data framework
Documentation
# Surreal Client

A SurrealDB client library for Rust speaking native CBOR over WebSocket.

`ws://`, `wss://`, and `cbor://` URLs all route through the same CBOR engine — `ws://` is the canonical scheme, `cbor://` is accepted as an alias for backward compatibility.

## Features

- **CBOR WebSocket engine**: Native CBOR wire format. Preserves SurrealDB types (Datetime, Duration, RecordId, Bytes, Decimal) that JSON cannot carry faithfully.
- **JSON convenience methods**: `create`/`select`/`update`/`merge`/`patch`/`delete`/`query` accept and return `serde_json::Value`, transcoded to/from CBOR on the wire. Use `query_cbor` when fidelity matters.
- **Immutable Client Design**: Thread-safe, cloneable client with unique sessions.
- **Builder Pattern Connection**: Intuitive connection configuration.
- **Debug mode support**: Outputs requests/responses for debugging.
- **Multiple Authentication Methods**: Root, namespace, database, scope, and JWT token auth.
- **Query Interface**: Execute raw SurrealQL with parameter binding.
- **Session Management**: Variables and state management per client.
- **Relation Support**: Create and query record relationships.
- **Transaction Support**: Execute multi-statement transactions.

## Quick Start

### Basic Connection

```rust
use surreal_client::SurrealConnection;
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Connect using builder pattern
    let client = SurrealConnection::new()
        .url("ws://localhost:8000")
        .namespace("my_namespace")
        .database("my_database")
        .auth_root("root", "root")
        .connect()
        .await?;

    // Client is now immutable and ready to use
    let version = client.version().await?;
    println!("Connected to SurrealDB {}", version);

    // Perform CRUD operations
    let user = client.create("users:john", Some(json!({
        "name": "John Doe",
        "email": "john@example.com",
        "age": 30
    }))).await?;

    println!("Created user: {:?}", user);
    Ok(())
}
```

### DSN Connection

```rust
let client = SurrealConnection::dsn("ws://root:root@localhost:8000/my_ns/my_db")?
    .connect()
    .await?;
```

### Multiple Authentication Methods

```rust
// Root authentication
let client = SurrealConnection::new()
    .url("ws://localhost:8000")
    .auth_root("admin", "password")
    .connect().await?;

// Namespace authentication
let client = SurrealConnection::new()
    .url("ws://localhost:8000")
    .namespace("my_ns")
    .auth_namespace("ns_user", "ns_pass")
    .connect().await?;

// Database authentication
let client = SurrealConnection::new()
    .url("ws://localhost:8000")
    .namespace("my_ns")
    .database("my_db")
    .auth_database("db_user", "db_pass")
    .connect().await?;

// Scope authentication
let client = SurrealConnection::new()
    .url("ws://localhost:8000")
    .auth_scope("my_ns", "my_db", "user_scope", json!({
        "email": "user@example.com",
        "password": "user_password"
    }))
    .connect().await?;

// JWT token authentication
let client = SurrealConnection::new()
    .url("ws://localhost:8000")
    .auth_token("your_jwt_token_here")
    .connect().await?;
```

## CRUD Operations

```rust
// Create
let user = client.create("users:alice", Some(json!({
    "name": "Alice",
    "email": "alice@example.com"
}))).await?;

// Read
let users = client.select("users").await?;
let alice = client.select("users:alice").await?;

// Update
let updated = client.update("users:alice", Some(json!({
    "age": 25
}))).await?;

// Delete
let deleted = client.delete("users:alice").await?;

// Insert (bulk)
let products = client.insert("products", json!([
    {"name": "Laptop", "price": 999.99},
    {"name": "Mouse", "price": 29.99}
])).await?;

// Merge
let merged = client.merge("users:alice", json!({
    "last_login": "2024-01-15T10:00:00Z"
})).await?;

// Upsert
let upserted = client.upsert("users:bob", Some(json!({
    "name": "Bob",
    "email": "bob@example.com"
}))).await?;
```

## Query Interface

```rust
// Simple query
let results = client.query("SELECT * FROM users WHERE age > 18", None).await?;

// Parameterized query
let results = client.query(
    "SELECT * FROM users WHERE age > $min_age AND city = $city",
    Some(json!({
        "min_age": 21,
        "city": "New York"
    }))
).await?;

// Session variables
client.let_var("user_id", json!("user123")).await?;
let results = client.query(
    "SELECT * FROM posts WHERE author = $user_id",
    None
).await?;
client.unset("user_id").await?;
```

## Relations

```rust
// Create relation
let like = client.relate(
    "users:alice",
    "likes",
    "posts:post1",
    Some(json!({"timestamp": "2024-01-15T10:00:00Z"}))
).await?;

// Query relations
let user_likes = client.query("SELECT * FROM users:alice->likes->posts", None).await?;
let post_likes = client.query("SELECT * FROM posts:post1<-likes<-users", None).await?;
```

## Client Cloning

Each cloned client has its own session state:

```rust
let client1 = connection.connect().await?;
let client2 = client1.clone(); // Independent session

// Each client can have different session variables
client1.let_var("role", json!("admin")).await?;
client2.let_var("role", json!("user")).await?;

// Both clients share the same connection but have separate sessions
```

## Pooling

Normally you have one client and one engine. You can clone your client,
but the engine remains the same. As a result, some queries may block
other queries.

To avoid this, you can use `SurrealPool`:

```rust
// DSN="ws://root:secret@localhost:8000/bakery/v1?param=X"
let pool = SurrealConnection::dsn(dsn).pool(10);

let db1 = pool.connect().await?;
let db2 = pool.connect().await?;

// Execute both queries simultaniously
tokio::try_join!(
    db1.query("sleep 1s", None),
    db2.create("user", json!({"name": "John", "age": 25}))
)?
```

## Vantage integration

This crate is designed to work with the Vantage query builders:

```rust
let db = SurrealConnection::dsn(dsn).connect().await?;
let select = SurrealSelect::new()
    .with_source("client")
    .with_condition(expr!("bakery = {}", Thing::new("bakery", "hill_valley")))
    .with_order_by("name", true);
// Second query: SELECT * FROM client WHERE bakery = bakery:hill_valley order by name

// Create delayed query - can be used inside another query
let associated_query = db.defer(&select).await?;

// Execute and get data right away
let data = db.get(&select).await?;

// Also - can be executed directly
let same_data = associated_query.get().await?;
```

## Error Handling

All operations return `Result<T, SurrealError>` with comprehensive error types:

```rust
use surreal_client::SurrealError;

match client.query("INVALID SQL", None).await {
    Ok(result) => println!("Success: {:?}", result),
    Err(SurrealError::Protocol(msg)) => println!("Protocol error: {}", msg),
    Err(SurrealError::Connection(msg)) => println!("Connection error: {}", msg),
    Err(SurrealError::Auth(msg)) => println!("Authentication error: {}", msg),
    Err(err) => println!("Other error: {}", err),
}
```

TODO: Integration with Vantage models and DataSets.