somnia
A type-safe SurrealDB ORM for Rust — a typed query
builder, a #[derive(SurrealRecord)] macro, schema generation, and Diesel-style
migrations.
somnia — Latin for "dreams". SurrealDB is surreal (dreamlike); somnia is where your Rust types dream in SurrealQL.
[]
= "0.1"
Why
Writing SurrealQL as hand-spliced strings is error-prone: typo'd table names,
unescaped values, record-link mistakes, and projection drift. somnia lets your
Rust types describe the schema once and gives you:
- Typed query building —
Post::table().select(...).filter(Post::title().eq("hello")) #[derive(SurrealRecord)]— typed column accessors, table metadata, and schema DDL generated from the struct.- Schema as code —
up()/down()emitDEFINE TABLE/DEFINE FIELD/REMOVE TABLEfrom the Rust type. - Diesel-style migrations — a
Migratorthat appliesup.surql/ revertsdown.surqlfrom timestamped folders, with applied-state tracking.
somnia inlines literals (with proper escaping) rather than relying on bind
parameters — to_surrealql() returns a ready-to-run statement string, which keeps
generated queries transparent and easy to log.
Quick start
Define a record
use ;
use ;
Build queries
use ;
// SELECT with typed columns + function-wrapped projections
let sql = table
.project
.filter
.order_desc
.limit
.to_surrealql;
// CREATE … with record links
let create = table
.create
.record
.set_lit
.set_expr
.set_raw
.returning
.to_surrealql;
// UPDATE / DELETE with RETURN variants
let del = table
.delete
.filter
.returning
.to_surrealql;
For SurrealQL that isn't modeled as typed nodes (lambdas, IF/THEN/ELSE,
string::* chains), use the Raw(...) / field("…raw…", "alias") escape hatch —
the builder still owns the statement structure, table names, and record links.
Schema as code
#[derive(SurrealRecord)] also implements SurrealSchema:
use SurrealSchema;
up; // DEFINE TABLE … ; DEFINE FIELD … ;
down; // REMOVE TABLE IF EXISTS comment;
Field attributes: #[field(thing)] (record id), record = "table"
(record<table>), default = "…", value = "…", ty = "…" (full type
override), flexible, name = "…", skip. Table attributes:
#[table("name")], #[table("name", schemaless, permissions = "NONE")].
Migrations
Lay out migrations Diesel-style — one timestamped folder per migration with
up.surql and down.surql:
migrations/
2025-01-01-000000_create_posts/
up.surql
down.surql
2025-01-01-000100_seed_defaults/
up.surql
down.surql
use SomniaClient;
let client = connect.await?;
let migrator = client.migrator;
migrator.run.await?; // apply all pending up.surql in order
migrator.revert_last.await?; // run the latest down.surql
for m in migrator.status.await?
Applied migrations are tracked in a _somnia_migrations table, so re-running only
applies what's pending.
Crates
| Crate | Description |
|---|---|
somnia |
Umbrella crate: client, migrator, re-exports. Start here. |
somnia-core |
Query builder, expression tree, SurrealRecord/SurrealSchema traits. |
somnia-derive |
#[derive(SurrealRecord)] proc-macro. |
Status
0.1.x — early but tested against SurrealDB 3.x (query builder, derive, schema
generation, and migrator all covered by integration tests that run on an
in-memory engine). The API may evolve before 1.0.
License
Licensed under the Apache License, Version 2.0.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be licensed as above, without any additional terms or conditions.