d1-orm 0.1.1

A lightweight ORM/SQL builder for Cloudflare D1 and SQLite
Documentation

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]
# For Cloudflare Workers (D1)
d1-orm = { version = "0.1", features = ["d1"] } 

# For native environments (SQLite)
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 model representing the `users` table
    User,
    UserField,
    UserUpdate {
        id: i32 [pk],
        username: String,
        email: String,
    }
);

2. Define Queries

use d1_orm::{define_sql, build_update_sql};

define_sql!(
    MySql
    
    // Select queries map nicely
    GetUser { id: i32 } => "SELECT * FROM users WHERE id = ?",
    
    // Insert parameters
    CreateUser { username: &'a str, email: &'a str } => 
        "INSERT INTO users (username, email) VALUES (?, ?)",
        
    // Dynamic updates generated automagically
    // Note: 'updates' must come before 'id' because the generated SQL
    // is structured as 'UPDATE users SET ... WHERE id = ?'
    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> {
    // 1. Initialize SQLite Backend
    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);

    // 2. Strongly Typed Interactions 
    executor.execute(MySql::CreateUser {
        username: "alice",
        email: "alice@example.com",
    }).await?;

    // query_first and query_all methods provided by DatabaseExecutor
    let alice: Option<User> = executor.query_first(MySql::GetUser { id: 1 }).await?;
    println!("Alice: {:?}", alice);
    
    // 3. Perform programmatic updates effortlessly!
    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, // Uses default table "_d1_migrations"
        Some(|msg: &str| println!("{}", msg)),
    ).await?;
    
    Ok(())
}

Credits

Part of the Housou project.