d1-orm
A lightweight ORM and SQL builder for Rust, designed to run anywhere, perfectly suited for Cloudflare D1 and SQLite.
Features
- Backend Agnostic: Bring your own database driver or use the built-in integrations for Cloudflare D1 (via
worker crate) and SQLite (via rusqlite).
- Type-Safe SQL Builder:
define_sql! macro for type-safe parameter binding and zero-overhead declarative SQL queries.
- Model Definition:
define_model! macro for defining structs, mapping database models, and generating update structs automatically.
- WASM Compatible: Works flawlessly within WASM targets like Cloudflare Workers (non-Send environments).
- Migration System: Built-in migration utilities for easy schema management.
- Async Trait: Implements an ecosystem agnostic
DatabaseExecutor trait for async database operations.
Usage
Add this to your Cargo.toml:
[dependencies]
d1-orm = { version = "0.1", features = ["d1"] }
d1-orm = { version = "0.1", features = ["sqlite"] }
Quick Start
See examples/basic.rs for the complete runnable example.
1. Define Model
use d1_orm::define_model;
define_model!(
User,
UserField,
UserUpdate {
id: i32 [pk],
username: String,
email: String,
}
);
2. Define Queries
use d1_orm::{define_sql, build_update_sql};
define_sql!(
MySql
GetUser { id: i32 } => "SELECT * FROM users WHERE id = ?",
CreateUser { username: &'a str, email: &'a str } =>
"INSERT INTO users (username, email) VALUES (?, ?)",
UpdateUser { updates: Vec<UserUpdate> [skip_primary_key], id: i32 } =>
build_update_sql("users", "id", &updates),
);
3. Execute
use d1_orm::{DatabaseExecutor, sqlite::SqliteExecutor};
use d1_orm::{define_sql, build_update_sql, define_model};
# define_model!(User, UserField, UserUpdate { id: i32 [pk], username: String, email: String, });
# define_sql!(MySql GetUser { id: i32 } => "SELECT 1", CreateUser { username: &'a str, email: &'a str } => "INSERT", UpdateUser { updates: Vec<UserUpdate> [skip_primary_key], id: i32 } => build_update_sql("users", "id", &updates),);
#[tokio::main]
async fn main() -> Result<(), d1_orm::Error> {
let conn = rusqlite::Connection::open_in_memory().unwrap();
conn.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT NOT NULL, email TEXT NOT NULL)", []).unwrap();
let executor = SqliteExecutor::new(conn);
executor.execute(MySql::CreateUser {
username: "alice",
email: "alice@example.com",
}).await?;
let alice: Option<User> = executor.query_first(MySql::GetUser { id: 1 }).await?;
println!("Alice: {:?}", alice);
executor.execute(MySql::UpdateUser {
updates: vec![UserUpdate::email("alice.new@example.com".to_string())],
id: 1,
}).await?;
Ok(())
}
4. Database Migrations
Easily manage incremental database changes with a generic migrate helper.
use d1_orm::{define_sql, migrate, Migration, sqlite::SqliteExecutor};
define_sql!(
MyMigrations
@table("users")
CreateUsersTable => "CREATE TABLE users (id INTEGER PRIMARY KEY)",
);
#[tokio::main]
async fn main() -> Result<(), d1_orm::Error> {
let conn = rusqlite::Connection::open_in_memory().unwrap();
let executor = SqliteExecutor::new(conn);
let migrations = vec![
Migration::new(
1,
"Initial setup",
vec![MyMigrations::CreateUsersTable],
),
];
migrate(
&executor,
migrations,
None, Some(|msg: &str| println!("{}", msg)),
).await?;
Ok(())
}
Credits
Part of the Housou project.