heroindex_client 0.1.0

Client library for HeroIndex search server
Documentation
# HeroIndex Client

[![Crates.io](https://img.shields.io/crates/v/heroindex_client.svg)](https://crates.io/crates/heroindex_client)
[![Documentation](https://docs.rs/heroindex_client/badge.svg)](https://docs.rs/heroindex_client)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

A Rust client library for [HeroIndex](https://crates.io/crates/heroindex), a high-performance full-text search server built on Tantivy.

## Features

- **Async/Await** - Built on Tokio for async operations
- **Type-Safe** - Strongly typed responses
- **Simple API** - Intuitive method names matching RPC calls
- **Connection Pooling Ready** - Each client maintains its own connection state

## Installation

Add to your `Cargo.toml`:

```toml
[dependencies]
heroindex_client = "0.1"
tokio = { version = "1", features = ["full"] }
serde_json = "1"
```

## Quick Start

```rust
use heroindex_client::HeroIndexClient;
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), heroindex_client::Error> {
    // Connect to HeroIndex server
    let mut client = HeroIndexClient::connect("/tmp/heroindex.sock").await?;
    
    // Check server health
    let ping = client.ping().await?;
    println!("Server status: {} (v{})", ping.status, ping.version);
    
    Ok(())
}
```

## Complete Example

```rust
use heroindex_client::HeroIndexClient;
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), heroindex_client::Error> {
    let mut client = HeroIndexClient::connect("/tmp/heroindex.sock").await?;
    
    // Create a database with schema
    client.db_create("products", json!({
        "fields": [
            {"name": "id", "type": "str", "stored": true, "indexed": true},
            {"name": "name", "type": "text", "stored": true, "indexed": true},
            {"name": "description", "type": "text", "stored": true, "indexed": true},
            {"name": "price", "type": "f64", "stored": true, "indexed": true, "fast": true},
            {"name": "in_stock", "type": "bool", "stored": true, "indexed": true}
        ]
    })).await?;
    
    // Select the database
    client.db_select("products").await?;
    
    // Add documents one by one
    client.doc_add(json!({
        "id": "prod-001",
        "name": "Wireless Mouse",
        "description": "Ergonomic wireless mouse with USB receiver",
        "price": 29.99,
        "in_stock": true
    })).await?;
    
    // Or add in batch (more efficient)
    client.doc_add_batch(vec![
        json!({"id": "prod-002", "name": "Mechanical Keyboard", "description": "RGB mechanical keyboard with Cherry MX switches", "price": 149.99, "in_stock": true}),
        json!({"id": "prod-003", "name": "USB-C Hub", "description": "7-port USB-C hub with HDMI output", "price": 49.99, "in_stock": false}),
        json!({"id": "prod-004", "name": "Webcam HD", "description": "1080p HD webcam with microphone", "price": 79.99, "in_stock": true}),
    ]).await?;
    
    // Commit and reload to make documents searchable
    client.commit().await?;
    client.reload().await?;
    
    // Search for products
    let results = client.search(
        json!({"type": "match", "field": "description", "value": "wireless"}),
        10, 0
    ).await?;
    
    println!("Found {} products:", results.total_hits);
    for hit in &results.hits {
        println!("  - {} (score: {:.2})", 
            hit.doc["name"].as_str().unwrap_or("?"),
            hit.score
        );
    }
    
    Ok(())
}
```

## API Reference

### Connection

```rust
// Connect to server
let mut client = HeroIndexClient::connect("/tmp/heroindex.sock").await?;
```

### Server Operations

```rust
// Health check
let ping = client.ping().await?;
println!("Status: {}, Version: {}", ping.status, ping.version);

// Server statistics
let stats = client.stats().await?;
println!("Databases: {}, Total docs: {}", stats.databases, stats.total_docs);

// Get OpenRPC discovery document
let schema = client.discover().await?;
```

### Database Operations

```rust
// List all databases
let list = client.db_list().await?;
for db in &list.databases {
    println!("{}: {} docs", db.name, db.doc_count);
}

// Create database
client.db_create("mydb", json!({
    "fields": [
        {"name": "title", "type": "text", "stored": true, "indexed": true}
    ]
})).await?;

// Select database (required before document operations)
client.db_select("mydb").await?;

// Get current database info
let info = client.db_info().await?;
println!("Doc count: {}, Segments: {}", info.doc_count, info.segment_count);

// Delete database
client.db_delete("mydb").await?;
```

### Schema Operations

```rust
// Get schema of selected database
let schema = client.schema().await?;
for field in &schema.fields {
    println!("{}: {} (stored: {})", field.name, field.field_type, field.stored);
}
```

### Document Operations

```rust
// Add single document
client.doc_add(json!({"title": "Hello World"})).await?;

// Add batch of documents (more efficient)
client.doc_add_batch(vec![
    json!({"title": "Doc 1"}),
    json!({"title": "Doc 2"}),
    json!({"title": "Doc 3"}),
]).await?;

// Delete documents by field value
client.doc_delete("id", json!("doc-to-delete")).await?;

// Commit changes (persist to disk)
client.commit().await?;

// Reload index (make changes searchable)
client.reload().await?;
```

### Search Operations

```rust
// Full-text search
let results = client.search(
    json!({"type": "match", "field": "content", "value": "search terms"}),
    10,  // limit
    0    // offset
).await?;

println!("Found {} results in {}ms", results.total_hits, results.took_ms);
for hit in &results.hits {
    println!("Score: {:.2}, Doc: {:?}", hit.score, hit.doc);
}

// Count matching documents
let count = client.count(json!({"type": "all"})).await?;
println!("Total documents: {}", count.count);
```

## Query Examples

### Match Query (Full-Text Search)
```rust
client.search(json!({
    "type": "match", 
    "field": "content", 
    "value": "rust programming"
}), 10, 0).await?;
```

### Fuzzy Query (Typo-Tolerant)
```rust
// Finds "hello" even when searching for "helo"
client.search(json!({
    "type": "fuzzy",
    "field": "title",
    "value": "helo",
    "distance": 1  // Allow 1 character difference
}), 10, 0).await?;
```

### Phrase Query (Exact Phrase)
```rust
client.search(json!({
    "type": "phrase",
    "field": "content",
    "value": "exact phrase match"
}), 10, 0).await?;
```

### Range Query (Numeric)
```rust
// Products between $50 and $100
client.search(json!({
    "type": "range",
    "field": "price",
    "gte": 50.0,
    "lt": 100.0
}), 10, 0).await?;
```

### Boolean Query (Combined)
```rust
client.search(json!({
    "type": "boolean",
    "must": [
        {"type": "match", "field": "category", "value": "electronics"}
    ],
    "should": [
        {"type": "match", "field": "title", "value": "premium"}
    ],
    "must_not": [
        {"type": "term", "field": "status", "value": "discontinued"}
    ]
}), 10, 0).await?;
```

### Prefix Query
```rust
// Find all words starting with "auto"
client.search(json!({
    "type": "prefix",
    "field": "title",
    "value": "auto"
}), 10, 0).await?;
```

### Term Query (Exact Match)
```rust
// Exact match on keyword field
client.search(json!({
    "type": "term",
    "field": "id",
    "value": "prod-001"
}), 10, 0).await?;
```

## Error Handling

```rust
use heroindex_client::{Error, error_codes};

match client.db_select("nonexistent").await {
    Ok(_) => println!("Selected"),
    Err(Error::Rpc { code, message }) => {
        if code == error_codes::DATABASE_NOT_FOUND {
            println!("Database not found: {}", message);
        }
    }
    Err(e) => println!("Other error: {}", e),
}
```

## Response Types

All methods return strongly-typed responses:

- `PingResponse` - `status`, `version`
- `ServerStats` - `uptime_secs`, `databases`, `total_docs`
- `DatabaseList` - `databases: Vec<DatabaseInfo>`
- `DatabaseInfo` - `name`, `doc_count`, `size_bytes`, `segment_count`
- `SchemaInfo` - `fields: Vec<FieldInfo>`
- `SearchResult` - `total_hits`, `hits: Vec<SearchHit>`, `took_ms`
- `SearchHit` - `score`, `doc: serde_json::Value`
- `CountResult` - `count`
- `OpResult` - `success`, `opstamp`

## Related Crates

- [heroindex]https://crates.io/crates/heroindex - The HeroIndex server

## License

MIT License