wasm-sql 0.1.6

Wasmtime host implementation for a SQL component WIT interface. Enables Wasm components to interact with SQL databases via the WebAssembly Component Model.
Documentation
# wasm-sql

Wasmtime host capability for SQL database access from WebAssembly components. Uses [sqlx](https://github.com/launchbadge/sqlx) as the underlying database driver.

> **Note:** This library uses experimental async features from wasmtime component model.
> Required versions: **wasmtime 0.41+**, **wit-bindgen 0.51+**

## Features

- **Connection pooling** — efficient connection management via sqlx
- **Transactions** — full support for begin/commit/rollback with automatic rollback on drop
- **Async support** — fully async API using wasmtime component model async

### Database Support

**PostgreSQL** — Supported

- Codecs: int16/32/64, float32/64, string, bool, json, uuid, hstore, date, time, timestamp, timestamptz, interval, inet, cidr, macaddr, numeric

**SQLite** — Supported

- Codecs: int64, float64, string, blob, bool, json, uuid, date, time, datetime, datetime-utc

**MySQL** — Planned

- Driver ready, codecs pending

> **Missing a codec?** Please [create an issue]https://github.com/ashitikov/wasm-sql/issues with your use case!

## Usage

### Examples

- [examples/host.rs]examples/host.rs — Host implementation (wasmtime runtime)
- [examples/guest-app/]examples/guest-app/ — Guest WASM component example

### Host Side (Rust)

Add dependency:

```toml
[dependencies]
wasm-sql = { version = "0.1.5", features = ["postgres"] }  # or "sqlite"
```

Set up the host:

```rust
use std::sync::Arc;
use wasm_sql::{SqlHostState, SqlHostStateView};
use wasm_sql::sqldb::SqlDB;
use wasm_sql::sqldb::sqlx::postgres::PgPoolOptions;
use wasmtime::component::Linker;

// Your state struct
struct HostState {
    sql: SqlHostState,
    // ... other fields (e.g., WasiCtx if needed)
}

impl SqlHostStateView for HostState {
    fn sql_host_state(&mut self) -> &mut SqlHostState {
        &mut self.sql
    }
}

// Create database pool
let pool = PgPoolOptions::new()
    .max_connections(5)
    .connect("postgres://user:pass@localhost/db")
    .await?;
let sql_db = Arc::new(SqlDB::new(pool));

// Add wasm-sql imports to linker
let mut linker: Linker<HostState> = Linker::new(&engine);
wasm_sql::add_to_linker(&mut linker)?;

// Create state
let state = HostState {
    sql: SqlHostState::new(sql_db),
};
```

See full working example in [examples/host.rs](examples/host.rs).

### Guest Side (WASM Component)

See full example in [examples/guest-app/](examples/guest-app/).

Add dependency:

```toml
[dependencies]
wit-bindgen = "0.51"
```

Generate bindings and use:

```rust
use bindings::wasm_sql::{
    core::query::{self, QueryExecutor},
    core::query_types::{SqlArguments, SqlQuery},
    core::util_types::Error as SqlError,
    postgres::codecs,
};
use bindings::wasm_sql::core::{pool, transaction::Transaction};

mod bindings {
    wit_bindgen::generate!({
        world: "your-app-world",
        path: ["path/to/wasm-sql/wit", "path/to/wasm-sql/wit/postgres", "./wit"],
    });
}

// Execute a parameterized query
async fn insert_user(name: &str, age: i32) -> Result<(), SqlError> {
    let args = SqlArguments::new();
    codecs::push_string(Some(name), &args)?;
    codecs::push_int32(Some(age), &args)?;

    let query = SqlQuery {
        sql: "INSERT INTO users (name, age) VALUES ($1, $2)".to_string(),
        args: Some(args),
        persistent: Some(true),
    };

    query::execute(query, QueryExecutor::Pool).await?;
    Ok(())
}

// Fetch data and decode results using codecs
async fn get_user(id: i64) -> Result<(String, i32, String), SqlError> {
    use bindings::wasm_sql::core::codecs::ColumnIndex;

    let args = SqlArguments::new();
    codecs::push_int64(Some(id), &args)?;

    let query = SqlQuery {
        sql: "SELECT name, age, email FROM users WHERE id = $1".to_string(),
        args: Some(args),
        persistent: Some(true),
    };

    let result = query::fetch_all(query, QueryExecutor::Pool).await?;

    // Decode values from row 0 using get_* functions
    let name = codecs::get_string(&result, &(0, ColumnIndex::ColumnName("name")))?
        .ok_or(SqlError::Decode("name is null".to_string()))?;
    let age = codecs::get_int32(&result, &(0, ColumnIndex::ColumnName("age")))?
        .ok_or(SqlError::Decode("age is null".to_string()))?;
    let email = codecs::get_string(&result, &(0, ColumnIndex::ColumnName("email")))?
        .ok_or(SqlError::Decode("email is null".to_string()))?;

    Ok((name, age, email))
}
```

---

## Architecture

```
┌─────────────────────┐
│   WASM Component    │
└─────────┬───────────┘
          │ WIT imports
┌─────────────────────┐
│   wasm-sql (host)   │
└─────────┬───────────┘
          │ sqlx
┌─────────────────────┐
│      Database       │
│ (PostgreSQL/MySQL/  │
│      SQLite)        │
└─────────────────────┘
```

## WIT Worlds

- `wasm-sql:core/host` — Core SQL operations (pool, connections, transactions, queries)
- `wasm-sql:postgres/host` — PostgreSQL-specific type codecs
- `wasm-sql:sqlite/host` — SQLite-specific type codecs

## License

MIT