Crate libsql_orm

Source
Expand description

Β§libsql-orm

A powerful, async-first ORM for libsql with first-class support for Cloudflare Workers and WebAssembly environments.

§✨ Features

  • πŸš€ Cloudflare Workers Ready - Built specifically for edge computing environments
  • πŸ”„ Async/Await Support - Fully async API with excellent performance
  • 🎯 Type-Safe - Leverages Rust’s type system for compile-time safety
  • πŸ“Š Rich Query Builder - Fluent API for complex queries
  • πŸ” Advanced Filtering - Search, pagination, sorting, and aggregations
  • πŸ› οΈ Migration System - Database schema management and versioning
  • 🎨 Derive Macros - Automatic model generation with #[derive(Model)]
  • πŸ“¦ Bulk Operations - Efficient batch inserts, updates, and deletes
  • 🌐 WASM Compatible - Optimized for WebAssembly targets
  • πŸ”„ Type Conversion - Automatic conversion between SQLite and Rust types
  • πŸ”„ Upsert Operations - Smart create_or_update and upsert methods
  • πŸ“ Built-in Logging - Comprehensive logging for debugging and monitoring

Β§πŸš€ Quick Start

use libsql_orm::{Model, Database};
use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};

#[derive(Model, Debug, Clone, Serialize, Deserialize)]
struct User {
    pub id: Option<i64>,
    pub name: String,
    pub email: String,
    pub age: Option<i32>,
    pub is_active: bool,
    pub created_at: DateTime<Utc>,
}

async fn example() -> Result<(), Box<dyn std::error::Error>> {
    // Connect to database
    let db = Database::new_connect("libsql://your-db.turso.io", "your-auth-token").await?;
     
    // Create a user
    let user = User {
        id: None,
        name: "Alice".to_string(),
        email: "alice@example.com".to_string(),
        age: Some(30),
        is_active: true,
        created_at: Utc::now(),
    };
     
    // Save to database
    let saved_user = user.create(&db).await?;
    println!("Created user with ID: [MASKED]");
     
    // Find users
    let users = User::find_all(&db).await?;
    println!("Found {} users", users.len());
     
    // Use smart upsert operations
    let user_with_id = User {
        id: Some(123),  // Will update if exists, create if not
        name: "Updated Name".to_string(),
        email: "updated@example.com".to_string(),
        age: Some(31),
        is_active: true,
        created_at: Utc::now(),
    };
    let smart_saved = user_with_id.create_or_update(&db).await?;
     
    // Upsert by unique constraint
    let unique_user = User {
        id: None,
        name: "Unique User".to_string(),
        email: "unique@example.com".to_string(),  // Check by email
        age: Some(25),
        is_active: true,
        created_at: Utc::now(),
    };
    let upserted = unique_user.upsert(&["email"], &db).await?;
     
    Ok(())
}

Β§πŸ”„ Upsert Operations

Smart create-or-update operations for efficient data management:

use libsql_orm::{Model, Database};

// Method 1: create_or_update (based on primary key)
let user = User { id: Some(123), name: "John".to_string(), ... };
let saved = user.create_or_update(&db).await?;  // Updates if ID exists, creates if not

// Method 2: upsert (based on unique constraints)
let user = User { id: None, email: "john@example.com".to_string(), ... };
let saved = user.upsert(&["email"], &db).await?;  // Updates if email exists, creates if not

// Multiple unique constraints
let saved = user.upsert(&["email", "username"], &db).await?;

Β§πŸ“ Built-in Logging

Comprehensive logging for debugging and monitoring:

// All database operations are automatically logged
// Logs appear in browser console (WASM) or standard logging (native)

let user = User::new("John", "john@example.com");

// Logs: [INFO] users: Creating record in table: users
// Logs: [DEBUG] users: SQL: INSERT INTO users (...) VALUES (...)
let saved = user.create(&db).await?;

// Logs: [DEBUG] users: Finding record by ID: 123
let found = User::find_by_id(123, &db).await?;

// Logs: [INFO] users: Updating record with ID: 123
let updated = found.unwrap().update(&db).await?;

Β§πŸ“š Advanced Usage

Β§Custom Table Names and Boolean Type Safety

use libsql_orm::{Model, orm_column, deserialize_bool};
use serde::{Serialize, Deserialize};

#[derive(Model, Debug, Clone, Serialize, Deserialize)]
#[table_name("user_accounts")]  // Custom table name
struct User {
    #[orm_column(type = "INTEGER PRIMARY KEY AUTOINCREMENT")]
    pub id: Option<i64>,
     
    #[orm_column(not_null, unique)]
    pub email: String,
     
    pub is_active: bool,        // βœ… Automatic SQLite integer ↔ Rust bool conversion
    pub is_verified: bool,      // βœ… Type-safe boolean operations
     
    // For edge cases, use custom deserializer
    #[serde(deserialize_with = "deserialize_bool")]
    pub has_premium: bool,      // βœ… Manual boolean conversion
     
    #[orm_column(type = "TEXT DEFAULT 'active'")]
    pub status: String,
}

// Boolean filtering works seamlessly
let active_users = User::find_where(
    FilterOperator::Eq("is_active".to_string(), Value::Boolean(true)),
    &db
).await?;

Β§Query Builder

use libsql_orm::{QueryBuilder, FilterOperator, Sort, SortOrder, Pagination};

// Complex query with filtering and pagination
let query = QueryBuilder::new("users")
    .select(&["id", "name", "email"])
    .r#where(FilterOperator::Gte("age".to_string(), Value::Integer(18)))
    .order_by(Sort::new("created_at", SortOrder::Desc))
    .limit(10)
    .offset(20);

let (sql, params) = query.build()?;

Β§Cloudflare Workers Integration

use worker::*;
use libsql_orm::{Model, Database, MigrationManager, generate_migration};

#[event(fetch)]
async fn fetch(req: Request, env: Env, _ctx: Context) -> Result<Response> {
    let database_url = env.var("LIBSQL_DATABASE_URL")?.to_string();
    let auth_token = env.var("LIBSQL_AUTH_TOKEN")?.to_string();
     
    let db = Database::new_connect(&database_url, &auth_token).await
        .map_err(|e| format!("Database connection failed: {}", e))?;
     
    // Your application logic here
    let users = User::find_all(&db).await
        .map_err(|e| format!("Query failed: {}", e))?;
         
    Response::from_json(&users)
}

Re-exportsΒ§

pub use database::Database;
pub use error::Error;
pub use error::Result;
pub use filters::Filter;
pub use filters::FilterOperator;
pub use filters::SearchFilter;
pub use filters::Sort;
pub use migrations::templates;
pub use migrations::Migration;
pub use migrations::MigrationBuilder;
pub use migrations::MigrationManager;
pub use model::Model;
pub use pagination::CursorPaginatedResult;
pub use pagination::CursorPagination;
pub use pagination::PaginatedResult;
pub use pagination::Pagination;
pub use query::QueryBuilder;
pub use query::QueryResult;
pub use types::deserialize_bool;
pub use chrono;
pub use types::*;

ModulesΒ§

database
Database connection and query execution
error
Error handling for libsql-orm
filters
Filtering and search functionality for libsql-orm
macros
migrations
Database migration system for libsql-orm
model
Model trait and core database operations
pagination
Pagination support for libsql-orm
query
Query building and execution for libsql-orm
types
Type definitions for libsql-orm

MacrosΒ§

filter
Helper macro for creating filter conditions
filter_op
Helper macro for creating filter operators
generate_migration
Re-export the Model macro for convenience Macro to generate migration from a model
pagination
Helper macro for creating pagination
query
Helper macro for creating query builders
search
Helper macro for creating search filters
sort
Helper macro for creating sort specifications

StructsΒ§

Uuid
A Universally Unique Identifier (UUID).

TraitsΒ§

Deserialize
A data structure that can be deserialized from any data format supported by Serde.
Serialize
A data structure that can be serialized into any data format supported by Serde.

Attribute MacrosΒ§

orm_column
Re-export the Model macro for convenience Column attribute macro for defining SQL column properties

Derive MacrosΒ§

Deserialize
Model
Re-export the Model macro for convenience Derive macro for the Model trait
Serialize