Expand description
§rainier_orm
A dynamic, multi-dialect DBAL/ORM. You define a plain struct, derive
Entity on it, and run generic CRUD across SQLite / Cloudflare D1 /
MySQL / Postgres — no per-entity repositories written by hand.
Three pieces compose it:
#[derive(Entity)]turns a struct into table + column metadata, a generated row decoder (Entity::from_row), and value projections. Column SQL types are deferred to each field type’sSqlTypeimpl and decoding to itsFromColumnimpl, so adding a supported type is one trait impl — not macro surgery.- SQL generation via
sea-query(re-exported), which already renders MySQL / Postgres / SQLite. Dialect translation is therefore free: the same statement is built for whatever theExecutorreports. - A pluggable
Executortrait — one impl (sea-orm) covers MySQL, Postgres, and SQLite; the Cloudflare path (D1 over HTTP, D1 native binding in a Worker) implements the same trait. The core is wasm-safe so the Worker can use it; native executors are feature-gated.
Constraints (unique columns, indexes, foreign keys) are derived too; see
schema. Relationships are not generated as navigation — a foreign key
is a flat id field you traverse with repo::find_by, because the related
rows may live in a different backend (a cross-backend JOIN is impossible).
§The executors live elsewhere
This crate defines the Executor port and implements none of it. Every
driver — sea-orm for native MySQL / Postgres / SQLite, Cloudflare D1 over
HTTP, libSQL / Turso — lives in
rainier-drivers, alongside Redis,
Memcached and the AWS clients.
That is not filing for its own sake. It is what keeps this crate
wasm-safe with no feature dance: there are no optional dependencies here
to accidentally enable, so the core compiles for wasm32 unconditionally and
a Worker takes the same rainier-orm a server does.
Prefer a native driver wherever the runtime allows it: sea-orm is faster,
supports real transactions, and pools locally. The HTTP executors exist for
one reason — a wasm32 runtime has no sockets, so a fetch-based transport
is the only way to reach a database there.
use rainier_drivers::sql::SeaOrmExecutor; // native: preferred
use rainier_drivers::sql::D1Executor; // wasm: HTTP transportuse rainier_orm::{Entity, repo};
#[derive(Entity)]
#[orm(table = "users")]
struct User {
#[orm(pk, auto_increment)]
id: u64,
#[orm(unique)]
email: String,
timezone: Option<String>,
}
let id = repo::insert(db, &User { id: 0, email: "a@b.c".into(), timezone: None }).await?;
let u: Option<User> = repo::find_by_pk(db, id).await?;
let by_email: Option<User> = repo::find_one_by(db, "email", "a@b.c").await?;Beyond CRUD: a fluent query builder (repo::query — predicates, joins,
first_or_create, partial update / atomic
increment), repo::upsert, a keyset
Cursor, derived constraints + DDL (schema), and column
types past the primitives: Json<T> (serde-as-text),
Base64Bytes (uniform binary), and string-backed enums via
impl_string_column!.
Re-exports§
pub use active::Tracked;pub use blueprint::Action;pub use blueprint::Blueprint;pub use blueprint::Column;pub use blueprint::ColumnKind;pub use blueprint::IndexDef;pub use blueprint::TableChanges;pub use shard::directory_db_name;pub use shard::shard_db_name;pub use shard::HashLocator;pub use shard::MapCatalog;pub use shard::ShardCatalog;pub use shard::ShardId;pub use shard::ShardLocator;pub use shard::ShardedExecutor;pub use shard::SlotLocator;pub use pool::PoolConfig;pub use query::Query;pub use sharding::IdAllocator;pub use sharding::ShardingSettings;pub use string_column::StringColumn;pub use sea_query;
Modules§
- active
- ActiveRecord-style change tracking: load a row, mutate it in place, then
savethe diff. - blueprint
- The schema builder — a table described once, rendered per dialect.
- ddl
- A declarative, dialect-portable schema-change language — the single way to express an incremental migration step.
- migrate
- Schema migrations — an ordered list of steps applied idempotently,
tracked in a
_orm_migrationstable, against one executor or many. - pool
- Connection-pool configuration — and an honest account of where pooling does and doesn’t apply.
- query
- A fluent query builder over an
Entity—where/where_not/like/in/ null checks, ordering, paging, single-backend joins, and the terminalsall/first/count/delete/first_or_create. - repo
- Generic CRUD over any
Entity, plus a keysetCursorfor walking large tables without holding them in memory. - schema
- Generic DDL: turn an
Entity’s metadata intoCREATE TABLE+CREATE INDEXstatements, rendered for anyDialect. - shard
- The sharding executor — turns N physical databases into one logical
Executorthat routes each operation to the right shard. - sharding
- Sharding policy on top of the routing primitives in
crate::shard. - string_
column - Support for custom column types stored as text — the common case being a
Rust enum persisted as a string (
"pending","verified", …).
Macros§
- impl_
string_ column - Wire a
StringColumntype into the ORM’s column traits. See the module docs.
Structs§
- Base64
Bytes - Bytes persisted as base64 in a
TEXTcolumn, uniformly across dialects. Wire it into the ORM with theimpl_string_column!machinery via itsStringColumnimpl below. - Column
Spec - One column’s metadata, produced by
#[derive(Entity)]as aconst. - Exec
Outcome - The result of a write statement.
- Foreign
KeySpec - A foreign-key constraint. The local columns reference
foreign_table(foreign_columns). Generated from#[orm(references = "table(col)")]on a field. - Index
Spec - A secondary index over one or more columns, produced as a separate
CREATE [UNIQUE] INDEX. Single-column indexes come from#[orm(index)]on a field; composite ones from#[orm(index = "a, b")]/#[orm(unique = "a, b")]on the struct. - Json
- A
serdevalue persisted as JSON text. Deref/Frommake it ergonomic to treat as the innerT. - Shard
Codec - Packs a physical shard number into the high bits of a
u64id, so any id (primary key or a shard-encoded foreign key) self-describes its shard.
Enums§
- Column
Type - The SQL column type, dialect-independent. Rendered per-dialect in
crate::schema. - Dialect
- The SQL dialect a backend speaks. Cloudflare D1 maps to
Sqlite(it is SQLite over HTTP); the executor handles the transport difference. - RefAction
- The referential action for a foreign key’s
ON DELETE/ON UPDATE. - Shard
Route - Where a single operation should run.
Traits§
- Entity
- A struct that maps to one table. Implemented by
#[derive(Entity)]; hand impls are possible but the derive is the intended path. - Entity
Trait - A struct that maps to one table. Implemented by
#[derive(Entity)]; hand impls are possible but the derive is the intended path. - Executor
- A database backend. The single integration point: implement this and the generic CRUD layer works against your store unchanged.
- From
Column - Decodes one column of a
Rowinto a Rust value. The generated decoder callsT::from_column(row, "col")?per field. The bareTimpls treat a NULL as a decode error; the blanketOption<T>impl below turns it intoNone. - Row
- A single result row, addressed by column name. Every getter returns
Optionso a NULL is distinguishable from a missing/zero value; the non-OptionFromColumnimpls turn aNoneinto a decode error. - SqlType
- Maps a Rust field type to its column type. The derive reads
<FieldType as SqlType>::{COLUMN_TYPE, NULLABLE}, soOption<T>becomes a nullableTautomatically and new types need only this impl. - ToColumn
- Lowers a field value to a
sea_query::Valuefor binding.
Functions§
- stable_
hash - Stable 64-bit hash (FNV-1a) — deterministic across processes/builds, unlike
std’s randomized hasher, so a string shard key always routes the same way.
Type Aliases§
- Error
- The crate error type.
anyhowkeeps the surface small and wasm-safe; a typed error is a later tightening. - Result
Derive Macros§
- Entity
- Factory
- Derive a test factory for an entity.