How-To: Use the Database Layer
Vox utilizes a unified storage paradigm known as Codex, which compiles into type-safe SQLite database schemas and Rust structs. You never need to write raw migrations; they are deterministically derived from your file structures.
Defining a Table
Any type struct adorned with the @table decorator becomes a persistent database entity.
@table type Note {
title: str
content: str
}
Indexing for Performance
To speed up lookups on large datasets, use the @index syntax. Vox determines the optimal storage engine (B-Tree or Hash) and generates the SQL automatically.
// vox:skip
@table type User {
email: str
team_id: Id[Team]
}
// Unique index: prevents duplicate emails
@index User.unique_email on (email) unique
// Composite index: speeds up filtered team lookups
@index User.by_team on (team_id, email)
[!TIP] Always index foreign keys (like
Id[T]) if you plan to filter or join on them frequently.
Basic CRUD Accessors
The built-in db module uses code-generation to inject statically typed accessors for all your @table types.
- Create:
// vox:skip let new_id: Id[Task] = db.Task.insert({ title: "Clean desk", done: false, priority: 1, owner: "alice" }) - Read:
// vox:skip match db.Task.find(new_id) { Some(t) -> println(t.title) None -> println("Not found") } - Update:
// vox:skip db.Task.update(new_id, { done: true }) - Delete:
// vox:skip db.Task.delete(new_id)
Advanced Filtering
Instead of raw string interpolation, use Vox's exact literal querying to avoid injection attacks.
// Fetch simple exact match parameters
// vox:skip
let alice_tasks = db.Task.filter({ owner: "alice" })
// Advanced predicate-object queries
// vox:skip
let urgent_tasks = db.Task.where({ priority: { gt: 10 }, done: { eq: false } }).all()
Query Chaining
You can apply limits, multi-field ordering, and select specific field projections by chaining.
// vox:skip
let feed = db.Task
.where({ done: false })
.order_by("priority", "desc")
.limit(10)
.all()
Guarding Reads/Writes with @query and @mutation
For security, you should rarely expose db.* calls directly to UI islands or agents. Instead, wrap your database interactions in @query (read-only) and @mutation (write-enabled) functions.
The compiler verifies that a @query function does not contain .insert, .update, or .delete operations.
Transactional Integrity with @mutation
Every function marked with @mutation is automatically wrapped in a database transaction. If the function returns an Error or panics, the transaction is rolled back.
// vox:skip
@mutation
fn transfer_funds(from: Id[Account], to: Id[Account], amount: int) -> Result[Unit] {
let mut sender = db.Account.find(from)?
let mut receiver = db.Account.find(to)?
sender.balance -= amount
receiver.balance += amount
db.Account.update(from, sender)
db.Account.update(to, receiver)
return Ok(())
}
Under the hood, this uses Codex::transaction to ensure ACID compliance across the local SQLite or distributed Turso mesh.
The Escape Hatch: Raw SQL
Occasionally, complex analytic aggregations exceed the currently supported ORM builder patterns. You can drop down to raw SQL using db.query.
[!WARNING] Use this only as a last resort. Raw SQL queries bypass Vox's type checking checks on schema changes.
// vox:skip
let count = db.query("SELECT COUNT(*) FROM Task WHERE owner = ?", ["alice"])
A Note on Codex
When running vox-run, the backing data source is the Local Codex Store (an embedded SQLite engine on disk). For enterprise orchestration and Populi GPU meshes, the database seamlessly promotes to Turso cloud sync clusters dynamically, without requiring any changes to your .vox schema definitions!
Related Topics: