Wasmtime Example
- Overview
- How It Works
- FileMemoryProvider
- Key-Value Guest Variant
- Building and Running
- Extending with Custom Tables
- Key Concepts
This guide explains how to use wasm-dbms with the WebAssembly Component Model (WIT) and Wasmtime. It walks through the example in crates/wasm-dbms/example/.
Overview
The WebAssembly Component Model defines a standard way for WASM modules to expose typed interfaces using WIT (WebAssembly Interface Types). This example shows how to:
- Define a WIT interface for the wasm-dbms CRUD and transaction API
- Build a guest WASM component that wraps wasm-dbms behind the WIT interface
- Run the guest inside a native Wasmtime host
This approach makes wasm-dbms usable from any Component Model host, not just Rust. The WIT contract at /wit/dbms.wit can be consumed by hosts written in Go, Python, JavaScript, or any language with Component Model tooling.
How It Works
WIT Interface
The WIT definition (/wit/dbms.wit) exposes a database interface with these operations:
- select — query rows from a table with optional filter, ordering, limit, and offset
- insert — insert a row into a table, optionally within a transaction
- update — update rows matching a filter, optionally within a transaction
- delete — delete rows matching a filter, optionally within a transaction
- begin-transaction — start a new ACID transaction
- commit / rollback — finalize or abort a transaction
Values are passed as a value variant type that covers booleans, integers, floats, strings, blobs, and null. Filters are JSON-serialized strings matching the wasm_dbms_api::Filter type.
Decimals, dates, date-times, JSON documents, and UUIDs travel as strings. The guest parses them strictly and returns an invalid-query error for a malformed value, such as 2025-02-30 or {, instead of storing a default or null value:
| Variant | Accepted form |
|---|---|
decimal-val | Exact decimal notation, for example -12.3450 |
date-val | YYYY-MM-DD, validated against the calendar |
datetime-val | YYYY-MM-DDTHH:MM:SS, optional .ffffff, then Z or ±HH:MM |
json-val | Any well-formed JSON document |
uuid-val | Hyphenated form, for example 550e8400-e29b-41d4-a716-446655440000 |
This raw/dynamic API is intentional: WIT cannot express Rust generics or user-defined table schemas, so type safety is enforced inside the guest by the wasm-dbms engine.
Guest Component
The guest (crates/wasm-dbms/example/guest/) compiles to wasm32-wasip2 and exports the WIT database interface. Internally it:
- Initializes a file-backed or key-value
DbmsContextlazily on first call; if storage cannot be opened or the tables cannot be registered, the call returns adbms-error(for examplememory-error) instead of trapping, and the next call retries - Registers example tables (
users,posts) using#[derive(Table)] - Converts between WIT variant values and wasm-dbms
Valuetypes - Dispatches operations through a
DatabaseSchemaimplementation
The bridge layer in lib.rs handles all the type conversions between the WIT boundary and the typed wasm-dbms internals.
Host Binary
The host (crates/wasm-dbms/example/host/) is a native Rust binary using Wasmtime. It:
- Creates a Wasmtime engine with Component Model enabled
- Creates a fresh, uniquely named directory under the system temporary directory and preopens it as the guest’s root, so the database file never touches existing data
- Loads the guest
.wasmcomponent and instantiates it - Calls the exported
databasefunctions to demonstrate all operations
FileMemoryProvider
The FileMemoryProvider implements the MemoryProvider trait using std::fs file I/O. It provides persistent, file-backed storage so that data survives across invocations.
#![allow(unused)]
fn main() {
use wasm_dbms_memory::prelude::MemoryProvider;
pub struct FileMemoryProvider {
file: File, // open file handle
size: u64, // current size in bytes
pages: u64, // allocated pages (size / PAGE_SIZE)
}
}
Operations:
- grow(n) — extends the file by
n × 65536bytes - read(offset, buf) — seeks to offset and reads into buffer
- write(offset, buf) — seeks to offset, writes buffer, and flushes
The provider is initialized with a file path relative to the WASI preopened directory (defaults to wasm-dbms.db).
Note:
FileMemoryProviderdoes not handle concurrent access. It assumes single-writer usage.
Key-Value Guest Variant
The guest also has a key-value feature. Build and test it with:
just test_wasm_dbms_key_value_example
wasm-tools component wit .artifact/wasm-dbms-example-guest-key-value.wasm
This variant opens bucket default and database namespace example. It
imports only wasi:keyvalue/store@0.2.0-draft2 and
wasi:keyvalue/batch@0.2.0-draft2 for persistence; it does not require a
filesystem preopen. Successful WIT operations checkpoint through ctx.flush().
The host must retain the bucket across component restarts, provide durable
backing, and coordinate a single writer. See the key-value provider guide
for the checkpoint and failure model.
Building and Running
Prerequisites
-
Rust 1.91.1+
-
wasm32-wasip2target:rustup target add wasm32-wasip2 -
just command runner
Build
# Build guest + host
just build_wasm_dbms_example
This compiles the guest to wasm32-wasip2 (producing a WASM component at .artifact/wasm-dbms-example-guest.wasm) and builds the native host binary.
Run
just test_wasm_dbms_example
Or run manually:
cargo run --release -p wasm-dbms-example-host -- .artifact/wasm-dbms-example-guest.wasm
The demo inserts users and posts, queries them with filters and ordering, demonstrates transaction commit (data persists) and rollback (data discarded), then removes the directory it created for this run. A wasm-dbms.db file in your current directory is never read, modified, or deleted.
Extending with Custom Tables
To add your own tables to the example:
-
Define the table in
guest/src/schema.rs:#![allow(unused)] fn main() { #[derive(Debug, Table, Clone, PartialEq, Eq)] #[table = "comments"] pub struct Comment { #[primary_key] pub id: Uint32, pub body: Text, #[foreign_key(entity = "Post", table = "posts", column = "id")] pub post_id: Uint32, } } -
Add dispatch arms for
"comments"in every method ofExampleDatabaseSchemainguest/src/schema.rs(select,insert,update,delete,validate_insert,validate_update,referenced_tables). -
Register the table in
register_tables():#![allow(unused)] fn main() { ctx.register_table::<Comment>()?; } -
Update the column lookup in
table_columns()(guest/src/lib.rs):#![allow(unused)] fn main() { "comments" => Ok(schema::Comment::columns()), } -
Register the table name in
registered_table_names()(guest/src/lib.rs). The guest resolves caller-provided table names against this finite list, so unknown names are rejected withtable-not-foundand are never retained:#![allow(unused)] fn main() { fn registered_table_names() -> [&'static str; 3] { [ schema::User::table_name(), schema::Post::table_name(), schema::Comment::table_name(), ] } } -
Rebuild with
just build_wasm_dbms_example.
Key Concepts
| Concept | Description |
|---|---|
| WIT | WebAssembly Interface Types — a language for defining typed component interfaces |
| Component Model | The standard for composing WASM modules with defined imports/exports |
wasm32-wasip2 | Rust compilation target that produces WASM components with WASI Preview 2 support |
wit-bindgen | Guest-side code generator that creates Rust types from WIT definitions |
wasmtime::component::bindgen! | Host-side macro that generates Rust types for calling WIT interfaces |
DatabaseSchema | wasm-dbms trait that dispatches generic operations to concrete table types |
FileMemoryProvider | File-backed MemoryProvider implementation for persistent storage |
Next Steps
- Getting Started — Set up wasm-dbms from scratch with the
Databasetrait - CRUD Operations — Detailed guide on all database operations
- Transactions — ACID transactions with commit/rollback
- Schema Definition — Complete schema reference