# somnia
[](https://crates.io/crates/somnia)
[](https://docs.rs/somnia)
[](#license)
**A type-safe [SurrealDB](https://surrealdb.com) 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.
```toml
[dependencies]
somnia = "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()` emit `DEFINE TABLE` / `DEFINE FIELD` /
`REMOVE TABLE` from the Rust type.
- **Diesel-style migrations** — a `Migrator` that applies `up.surql` /
reverts `down.surql` from 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
```rust
use somnia::{SurrealRecord, Thing};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, SurrealRecord)]
#[table("post")]
struct Post {
#[field(thing)]
id: Thing<Post>,
title: String,
body: String,
published_at: Option<String>,
}
```
### Build queries
```rust
use somnia::{col, field, ident, RecordLink, Returning};
// SELECT with typed columns + function-wrapped projections
let sql = Post::table()
.project(vec![
field("record::id(id)", "id"),
col("title"),
field("type::string(published_at)", "published_at"),
])
.filter(Post::published_at().ne(None))
.order_desc(ident("published_at"))
.limit(20)
.to_surrealql();
// CREATE … with record links
let create = Post::table()
.create()
.record("post-1".to_string())
.set_lit("title", "Hello, world".to_string())
.set_expr("author", RecordLink::new("author", "bob".to_string()))
.set_raw("published_at", "time::now()")
.returning(Returning::After)
.to_surrealql();
// UPDATE / DELETE with RETURN variants
let del = Post::table()
.delete()
.filter(ident("id").eq_expr(RecordLink::new("post", "post-1".to_string())))
.returning(Returning::Before)
.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`:
```rust
use somnia::SurrealSchema;
#[derive(Debug, Clone, Serialize, Deserialize, SurrealRecord)]
#[table("comment")]
struct Comment {
#[field(thing)] id: Thing<Comment>,
#[field(record = "post")] post: serde_json::Value,
body: String,
#[field(ty = "datetime", default = "time::now()")] created_at: String,
}
Comment::up(); // DEFINE TABLE … ; DEFINE FIELD … ;
Comment::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
```
```rust
use somnia::SomniaClient;
let client = SomniaClient::connect("ws://localhost:8000", "root", "root", "ns", "db").await?;
let migrator = client.migrator("migrations");
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? {
println!("{} {}", if m.applied { "✓" } else { " " }, m.id);
}
```
Applied migrations are tracked in a `_somnia_migrations` table, so re-running only
applies what's pending.
## Crates
| [`somnia`](crates/somnia) | Umbrella crate: client, migrator, re-exports. Start here. |
| [`somnia-core`](crates/somnia-core) | Query builder, expression tree, `SurrealRecord`/`SurrealSchema` traits. |
| [`somnia-derive`](crates/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.