somnia today (0.8.x) is a typed query builder + derive + schema + migrations +
thin client over SurrealDB — covering CRUD, graph traversal, transactions,
schema DDL, control flow, auth, live queries, and full-text/vector search — with
Raw(...) / field("…", "alias") as a first-class escape hatch for everything
it doesn't model yet. This document tracks the path from the pragmatic 0.2.x
beginnings to the current 0.8.x and on toward a 1.0 that freezes the public
surface — without losing the escape-hatch philosophy.
Priorities are ordered by value × how often the gap forces a drop to raw SurrealQL. Checkboxes track status; nothing here is a commitment to a date.
Covered: SELECT (projections, WHERE, ORDER BY, LIMIT, START,
FETCH, GROUP BY/GROUP ALL, count()), graph traversal in SELECT
(->edge->table, recursive @.{..} paths), CREATE, INSERT, UPDATE
(SET/MERGE/CONTENT), UPSERT, DELETE, RELATE (+ edge content), Batch,
transactions (BEGIN/COMMIT/CANCEL);
then_select() mutate-and-reselect on CREATE/UPDATE/DELETE;
$param binding (to_surrealql_with_params(), Param<V>), LET $var;
comparison/logical operators, type::record(...) links, generic function calls;
DEFINE TABLE/DEFINE FIELD/DEFINE INDEX/REMOVE TABLE via derive; diesel-style migrations;
literals for string/int/float/bool/datetime(d'…')/uuid(u'…')/duration/array/object/record/Option;
typed auth (root/namespace/database/record SIGNIN/SIGNUP/authenticate/invalidate, Credentials);
live queries (LIVE SELECT → LiveQueryStream<Notification<T>>);
full-text + vector search helpers (Table::search/nearest); closures and record REFERENCEs.
Status: all P0–P3 tiers below have shipped (through 0.8.0); the remaining work is the 1.0 stabilization pass.
- Record-id key escaping.
Thingliterals andRELATEnow backtick-quote UUID and non-identifier string keys so they parse as a record id instead of an arithmetic expression. (0.3.0) -
INSERT$databinding.INSERT INTO t …now serializes the queued record(s) inline as object literals instead of an unbound$dataplaceholder. (0.3.0) - Geometry serialization.
Point/LineString/Polygonnow serialize as GeoJSON ({ type, coordinates }) so SurrealDB stores them asgeometry, plus query-literal support. (0.3.0, breaking)
- Graph traversal in
SELECT. The typedPathnode renders->edge->table,<-edge<-table,<->edge<->table, multi-hop chains,.field/.*accessors, per-hopWHEREfilters, record anchoring, and recursive paths (@.{..}/{..N}/{M..N}/{N}) — usable as aSELECTprojection (project_path/with_path) or in aWHERE. (0.5.2) -
DEFINE INDEX. Plain/composite/UNIQUEindexes plus full-text (SEARCH) and vector (HNSW/MTREE) variants, via the runtimeDefineIndexbuilder and the derive's repeatable#[index(...)](folded intoSurrealSchema::define_indexes()andup()). (0.5.2) -
UPSERT. First-class statement viaTable::upsert(), sharing theUPDATEbuilder surface. (0.4.1) - Richer type mapping. The derive now maps Rust types by real recursive
syn::Typeanalysis (no more substring matching): typed arrays (Vec<T>/sets/slices →array<…>), arrays of records, nestedOption,duration, anddecimal, withSurrealQLliteral impls forVec<T>andDuration. (0.5.2) Remaining nuance:bytesand array/object record-id key types still map structurally but lack dedicated literal support.
P2 closes the gap between "everything you need for typical CRUD + graph reads"
and "everything SurrealDB's query surface can express." Each item below turns a
frequent drop to Raw into a typed node.
-
Transactions. A
Transactionbuilder wraps pushed statements inBEGIN TRANSACTION; … ; COMMIT TRANSACTION;(orCANCEL TRANSACTIONvia.cancel()). Unlike the;-concatenatedBatch, the block is atomic — SurrealDB rolls every statement back if any errors. Verified against the live engine. (0.6.0) Still open as a path to typed control flow: a closure/block body andRETURN-inside-transaction surfaces. -
Parameters /
LET. Today somnia inlines all values as escaped literals — the output ofto_surrealql()is a self-contained string ready to send. That's transparent but wastes wire bytes for repeated statements and forces re-escaping of binary blobs. Add an optional$param-binding mode: a typedToParamstrait that emits{ key: value }pairs alongsideto_surrealql_with_params() -> (String, BTreeMap<String, Value>). The existing inlining path stays the default; opt-in binding is additive. SurrealDB'sLET $var = …also needs a builder for session-scoped variables. (0.6.0) -
SELECTextras. Subqueries (Select<T>is aDynExpr, rendered parenthesized — usable inWHERE x IN (<subquery>), as a scalar operand, and asFROM (<subquery>)viafrom_subquery),IN/NOT INoperators,VALUE-mode projections,SPLIT,OMIT,WITH INDEX/WITH NOINDEXhints,TIMEOUT/EXPLAIN[FULL] modifiers, andRETURN <projection>onINSERT/RELATE. (0.6.0)PARALLELis intentionally omitted — SurrealDB 3.1 rejects it as aSELECTclause (parse error). -
More schema DDL. Standalone builders
DefineEvent,DefineFunction,DefineAnalyzer,DefineParam(each with a matching::remove), and new field attributes#[field(assert = "…")],#[field(readonly)],#[field(permissions = "…")]that render into the derivedDEFINE FIELD. Validated against the live engine (ASSERT rejects invalid writes, the function is callable, the param resolves). (0.6.0) -
Control flow.
IfExpris aDynExprrenderingIF … THEN … ELSE IF … ELSE … END(usable inSET/projection/RETURN/WHERE), andForbuildsFOR $item IN <array> { <body> }. Both validated against the live engine. (0.6.0) -
Edge derive.
#[derive(SurrealEdge)]generates theimpl SurrealEdge(edge name from#[table(...)]), so it no longer needs to be hand-written — derive it alongsideSurrealRecord. The#[field(flexible)]drive-by is covered: the derive rendersDEFINE FIELD … FLEXIBLE TYPE …(regression test added). (0.6.0) -
1.0 cleanup. Removed the unused
_fieldparameter fromTable::count()(nowcount()), and widened the builder string args toimpl Into<String>so the olderSelect/Create/Updatemethods match the newer builders (record targets already accept anySurrealQLkey incl.Thing<T>). (0.7.0, breaking) Deeper public-trait-surface stabilization continues toward 1.0.
These are high-value but each touches a subsystem (client auth, streaming, SurrealDB 3.x type system) that benefited from doing P2 first.
-
Live queries.
SomniaClient::live_select::<T>()returns aLiveQueryStream<T>— afutures::StreamofNotification<T>carrying theaction(Create/Update/Delete),query_id, and the deserialized record (decoded throughserde_json::ValuesoThing<T>resolves). It wraps surrealdb'sselect().live()subscription; dropping the stream handle issuesKILL. Verified live onmem://(create/update/delete notifications). (0.8.0) Still open: filtered/record-scoped live selects and a standaloneLIVE SELECTSurrealQL node (the client path builds it internally). -
Auth. A
Credentialsenum (Root/Namespace/Database/Token) drivesSomniaClient::connect_with(endpoint, ns, db, creds)alongside the original root-onlyconnect;connect_anonymousconnects without signin (for embedded engines / deferred auth). On a live connection:signin(&creds), record/scopesignin_record/signup_record(params are anySerializevalue),authenticate(token)to attach a pre-issued JWT, andinvalidate(). Signin/signup return the issued access token; failures surface asSomniaError::Auth. Verified against the live engine (record signup → signin → invalidate, wrong-password rejection). (0.8.0) Still open: namespace/ database userSIGNUP(SurrealDB only allowsSIGNUPon record access), and refresh-token rotation. -
Vector / full-text search helpers.
Table::search(field, query) → Search<T>(full-text@@, withsearch::scoreprojection + relevance order) andTable::nearest(field, vec) → VectorSearch<T>(KNN<|k,ef|>HNSW or<|k,METRIC|>brute force, withvector::distance::knn()projection + nearest-first order), backed by composableMatchesExpr/KnnExprnodes andColumn::matches. The index side was also corrected:DefineIndex::search()now emitsFULLTEXT ANALYZER …(3.x renamedSEARCH). Verified live (ranked full-text, HNSW ordering). (0.8.0) -
SurrealDB 3.x type-system features. Closures (the typed
Closurenode:|$x: int| -> int $x * 2),references(the derive's#[field(reference[ = "cascade"])]emittingREFERENCE [ON DELETE …]), and union / literal field types (via#[field(ty = "int | string")]/#[field(ty = "'a' | 'b'")]) are covered and verified live.future<T>is intentionally omitted — the keyword was removed in SurrealDB 3.x (use a computed#[field(value = "…")]). (0.8.0) Anonymous-function / union / literal types in the type mapper remain expressible through thetyoverride rather than dedicated syntax, by design.
-
Escape hatch first. Every feature starts with
Rawas a baseline. The typed node is added when the pattern repeats and the drop-to-raw cost (typos, missing escaping, schema-drift) justifies the abstraction. If there isn't a clear typed API that's better thanRaw, we don't add one. -
Inline by default, bind when asked.
to_surrealql()always returns a ready-to-run string with literals inlined. Binding ($param) is opt-in and additive — it never replaces the inlined path. This keeps the builder transparent, easy to log, and safe against injection (all values are properly escaped). -
Statements own their structure. The builder renders complete SurrealQL statements; it does not produce fragments for manual splicing. Table names and record links are always rendered through typed paths (never raw strings from user input).
-
No ORM lifecycle. somnia is a query builder + thin client. It does not track dirty state, manage sessions, or provide
save()/load()methods. That layer can be built on top; it stays out of scope for the core crates. -
SurrealDB 3.x as the target. SurrealDB 2.x is end-of-life (no security patches). All new surface targets 3.x syntax and semantics. The in-memory engine used in tests pins
surrealdb = "3".
Versions 0.3–0.8 have shipped; 1.0 is the remaining target.
| Version | Focus | Key deliverables |
|---|---|---|
| 0.3–0.5 | P0/P1 — correctness + high-value | record-id escaping, geometry, graph traversal in SELECT, DEFINE INDEX, UPSERT, richer type mapping |
| 0.6 | P2 — transactions, params, SELECT extras, schema DDL, control flow | BEGIN/COMMIT/CANCEL, $param + LET, subqueries/VALUE/SPLIT/OMIT/WITH/TIMEOUT/EXPLAIN, DEFINE EVENT/FUNCTION/ANALYZER/PARAM, field ASSERT/READONLY/PERMISSIONS, IF/FOR, #[derive(SurrealEdge)] |
| 0.7 | Pre-1.0 cleanup (breaking) | count() arg dropped, builder string args unified, release-pipeline hardening |
| 0.8 | P3 — auth, live queries, search, 3.x type-system | Credentials + connect_with, record SIGNIN/SIGNUP/authenticate/invalidate, LIVE SELECT stream, Table::search/nearest, Closure, #[field(reference)] |
| 1.0 | Stabilization | Audit public surface, remove vestigial APIs, freeze trait bounds, ratify MSRV bump policy |
The shipped rows record what each release included; features ship when they're ready, so a feature's release isn't necessarily the one that first introduced it.
- Replacing raw SurrealQL.
Raw(...)/field(...)stay first-class; somnia owns statement structure, table names, and record links, and you drop to raw for anything unmodeled. - An ODM/active-record runtime. somnia stays a builder + thin client; persistence ergonomics can be layered on top, they don't replace explicit queries.
- SurrealDB 2.x compatibility. 2.x is end-of-life; all new features target 3.x.