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
//! Host example for wasm-sql guest-app
//!
//! ## Prerequisites
//!
//! 1. Build the guest component:
//!    ```bash
//!    cd examples/guest-app && cargo build --release --target wasm32-wasip1
//!    ```
//!
//! 2. Run the host:
//!    ```bash
//!    DATABASE_URL="postgres://user:pass@localhost/db" cargo run --example host
//!    ```

use std::sync::Arc;

use anyhow::{Context, Result};
use wasm_sql::SqlHostStateView;
use wasmtime::component::{Component, Linker, ResourceTable};
use wasmtime::{Config, Engine, Store};
use wasmtime_wasi::{WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView};

use wasm_sql::core::bindings::SqlHostState;
use wasm_sql::sqldb::SqlDB;
use wasm_sql::sqldb::sqlx::postgres::PgPoolOptions;

/// Combined state for wasmtime store
struct HostState {
    sql: SqlHostState,
    wasi: WasiCtx,
    table: ResourceTable,
}

mod bindings {
    wasmtime::component::bindgen!({
        world: "example:guest-app/app",
        path: ["./wit", "./wit/postgres", "./examples/guest-app/wit"],
    });
}

impl WasiView for HostState {
    fn ctx(&mut self) -> WasiCtxView<'_> {
        WasiCtxView {
            ctx: &mut self.wasi,
            table: &mut self.table,
        }
    }
}

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

#[tokio::main]
async fn main() -> Result<()> {
    // Connect to database
    let database_url =
        std::env::var("DATABASE_URL").expect("DATABASE_URL variable is not set");

    println!("Connecting to database: {}", database_url);
    let pool = PgPoolOptions::new()
        .max_connections(5)
        .connect(&database_url)
        .await
        .context("Failed to connect to database")?;

    let sql_db = Arc::new(SqlDB::new(pool));

    // Set up wasmtime
    let mut config = Config::new();
    config.async_support(true);
    config.wasm_component_model(true);
    config.wasm_component_model_async(true);

    let engine = Engine::new(&config)?;
    let mut linker: Linker<HostState> = Linker::new(&engine);

    wasmtime_wasi::p2::add_to_linker_async(&mut linker)?;
    wasm_sql::add_to_linker(&mut linker)?;

    // Load the guest component
    let wasm_path = "target/wasm32-wasip2/debug/wasm_sql_guest_app_example.wasm";
    println!("Loading component from: {}", wasm_path);
    let component = Component::from_file(&engine, wasm_path)
        .context("Failed to load WASM component. Did you build guest-app?")?;

    // Create store with state
    let state = HostState {
        sql: SqlHostState::new(sql_db),
        wasi: WasiCtxBuilder::new().inherit_stdio().build(),
        table: ResourceTable::new(),
    };
    let mut store = Store::new(&engine, state);

    let instance = bindings::App::instantiate_async(&mut store, &component, &mut linker).await.expect("failed to instantiate guest-app");

    let _ = store.run_concurrent(async move |accessor| {
        let _ = instance.example_guest_app_main().call_run(accessor).await;
    }).await;

    println!("Guest executed successfully!");
    Ok(())
}