rainier-database
Rainier's data layer, built on Rainier ORM — the multi-dialect DBAL
where one #[derive(Entity)] serves SQLite, Cloudflare D1, MySQL and
Postgres, and where the stack of hand-written per-engine repositories
collapses to zero.
This crate adds the four things a framework needs on top of that:
[Connection] / [Database] |
a dyn-safe port over Rainier ORM's Executor, so a backend can live in the IoC container |
[Model] |
an entity the framework manages, with lifecycle hooks |
[Repository] / [EntityRepository] |
a contract to depend on, implemented once for every model |
[Criteria] / [Paginated] |
composable scopes and paging |
use rainier_orm::{repo, Entity, SeaOrmExecutor, PoolConfig};
use rainier_database::{bind_executor, Criteria, Database, EntityRepository, Model, Repository};
#[derive(Entity, Clone)]
#[orm(table = "posts")]
struct Post {
#[orm(pk, auto_increment)] id: u64,
title: String,
published: bool,
}
impl Model for Post {}
bind_executor!(SeaOrmExecutor);
# async fn run() -> rainier_support::Result<()> {
let executor = SeaOrmExecutor::connect("sqlite::memory:", &PoolConfig::default()).await?;
let db = Database::new(executor);
let posts = EntityRepository::<Post>::new(db.clone());
// Through the repository contract…
let page = posts
.paginate_matching(Criteria::new().where_eq("published", true), 1, 20)
.await?;
// …or straight through Rainier ORM, since `Database` is an `Executor`.
let newest: Option<Post> = repo::query::<Post>().order_by_desc("id").first(&db).await?;
# Ok(()) }
The Executor problem, and how it is solved
Rainier ORM's Executor uses async fn in trait and carries no Send + Sync
bound — deliberately, so the same code runs in a single-threaded Cloudflare
Worker over a !Send D1 binding. That makes it unusable as dyn Executor,
which is exactly what a container needs to store.
[Database] resolves this by holding an Arc<dyn Connection> (Rainier's
dyn-safe mirror) and re-implementing Executor on top of it. The
whole ORM surface therefore works against a Database unchanged — no
wrapper per operation, and no gap between what Rainier ORM can do and what
Rainier exposes. Register a concrete backend with
[bind_executor!].