Skip to main content

Installation

Add jasonisnthappy to your Cargo.toml:
[dependencies]
jasonisnthappy = "0.1.1"
For web UI support:
[dependencies]
jasonisnthappy = { version = "0.1.1", features = ["web-ui"] }

Quick start

1

Import the database

use jasonisnthappy::Database;
use serde_json::json;
use std::sync::Arc;
2

Open a database

let db = Arc::new(Database::open("my_database.db")?);
3

Get a collection and insert documents

let users = db.collection("users");

let alice = json!({
    "name": "Alice",
    "age": 30,
    "city": "New York"
});

let id = users.insert(alice)?;
println!("Inserted document with ID: {}", id);
4

Query documents

// Find by ID
let doc = users.find_by_id(&id)?;
println!("Found: {}", serde_json::to_string_pretty(&doc)?);

// Find all
let all_users = users.find_all()?;
for user in &all_users {
    println!("User: {}", user["name"]);
}

Complete example

Here’s a full working example demonstrating CRUD operations:
use jasonisnthappy::Database;
use serde_json::json;
use std::sync::Arc;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    println!("=== Collection CRUD Example ===\n");

    let db = Arc::new(Database::open("crud_example.db")?);
    let users = db.collection("users");

    println!("1. Inserting documents...");
    let alice = json!({"name": "Alice", "age": 30, "city": "New York"});
    let bob = json!({"name": "Bob", "age": 25, "city": "San Francisco"});
    let charlie = json!({"name": "Charlie", "age": 35, "city": "Seattle"});

    let id1 = users.insert(alice)?;
    let id2 = users.insert(bob)?;
    let id3 = users.insert(charlie)?;

    println!("   Inserted Alice: {}", id1);
    println!("   Inserted Bob: {}", id2);
    println!("   Inserted Charlie: {}", id3);

    println!("\n2. Counting documents...");
    let count = users.count()?;
    println!("   Total documents: {}", count);

    println!("\n3. Finding document by ID...");
    let found = users.find_by_id(&id1)?;
    println!("   Found: {}", serde_json::to_string_pretty(&found)?);

    println!("\n4. Finding all documents...");
    let all_users = users.find_all()?;
    for user in &all_users {
        println!("   - {}", user["name"]);
    }

    println!("\n5. Updating Alice's age...");
    users.update_by_id(&id1, json!({"age": 31}))?;
    let updated = users.find_by_id(&id1)?;
    println!("   Updated age: {}", updated["age"]);

    println!("\n6. Deleting Bob...");
    users.delete_by_id(&id2)?;
    let count_after_delete = users.count()?;
    println!("   Documents after delete: {}", count_after_delete);

    println!("\n7. Remaining documents:");
    let remaining = users.find_all()?;
    for user in &remaining {
        println!("   - {} (age {})", user["name"], user["age"]);
    }

    db.close()?;
    println!("\n=== Example Complete ===");

    Ok(())
}

API reference

Database

Opening databases

pub fn open(path: &str) -> Result<Database>
pub fn open_with_options(path: &str, options: DatabaseOptions) -> Result<Database>
The database uses write-ahead logging (WAL) for ACID transactions and MVCC support.

Collection access

pub fn collection<T: Document>(&self, name: &str) -> Collection<T>
pub fn get_collection<T: Document>(&self, name: &str) -> Collection<T>

Transaction operations

pub fn begin_transaction(&self) -> Result<Transaction>
let mut tx = db.begin_transaction()?;
tx.insert("users", &doc)?;
tx.commit()?;

Collection

Basic CRUD

// Insert a document
pub fn insert(&self, doc: impl Serialize) -> Result<String>

// Find by ID
pub fn find_by_id(&self, id: &str) -> Result<Value>

// Update by ID
pub fn update_by_id(&self, id: &str, updates: impl Serialize) -> Result<()>

// Delete by ID
pub fn delete_by_id(&self, id: &str) -> Result<()>

// Find all documents
pub fn find_all(&self) -> Result<Vec<Value>>

// Count documents
pub fn count(&self) -> Result<u64>

Querying

// Find documents matching a filter
pub fn find(&self, filter: &str) -> Result<Vec<Value>>

// Find one document
pub fn find_one(&self, filter: &str) -> Result<Option<Value>>

// Update multiple documents
pub fn update(&self, filter: &str, updates: impl Serialize) -> Result<u64>

// Delete multiple documents
pub fn delete(&self, filter: &str) -> Result<u64>
let adults = users.find("age >= 18")?;

Bulk operations

// Insert multiple documents
pub fn insert_many(&self, docs: Vec<impl Serialize>) -> Result<Vec<String>>

// Upsert by ID
pub fn upsert_by_id(&self, id: &str, doc: impl Serialize) -> Result<UpsertResult>

// Bulk write operations
pub fn bulk_write(&self, operations: Vec<BulkOperation>, ordered: bool) -> Result<BulkWriteResult>

Transaction

For manual transaction control:
pub struct Transaction {
    // ...
}

impl Transaction {
    pub fn insert(&mut self, collection: &str, doc: impl Serialize) -> Result<String>
    pub fn find_by_id(&self, collection: &str, id: &str) -> Result<Option<Value>>
    pub fn update_by_id(&mut self, collection: &str, id: &str, updates: impl Serialize) -> Result<()>
    pub fn delete_by_id(&mut self, collection: &str, id: &str) -> Result<()>
    pub fn find_all(&self, collection: &str) -> Result<Vec<Value>>
    pub fn count(&self, collection: &str) -> Result<u64>
    
    pub fn commit(self) -> Result<()>
    pub fn rollback(self)
    pub fn is_active(&self) -> bool
}
Transactions must be explicitly committed or rolled back. Dropping a transaction without committing will automatically roll it back.

Advanced features

Indexing

// Create a single-field index
db.create_index("users", "idx_email", "email", true)?; // unique

// Create a compound index
db.create_compound_index("users", "idx_name_age", vec!["name", "age"], false)?;

// Create a text search index
db.create_text_index("posts", "idx_content", "content")?;

// List indexes
let indexes = db.list_indexes("users")?;

Schema validation

let schema = json!({
    "type": "object",
    "properties": {
        "name": {"type": "string"},
        "age": {"type": "number"},
        "email": {"type": "string"}
    },
    "required": ["name", "email"]
});

db.set_schema("users", schema)?;

Change streams

use jasonisnthappy::{ChangeOperation, WatchCallback};

let handle = users.watch(Some("age > 18"), |operation, doc_id, document| {
    match operation {
        ChangeOperation::Insert => println!("Inserted: {}", doc_id),
        ChangeOperation::Update => println!("Updated: {}", doc_id),
        ChangeOperation::Delete => println!("Deleted: {}", doc_id),
    }
});

// Later, stop watching
handle.stop();

Aggregation pipeline

let pipeline = vec![
    json!({"match": "age >= 18"}),
    json!({"group_by": "city"}),
    json!({"count": "population"}),
    json!({"sort": {"field": "population", "asc": false}}),
];

let results = users.aggregate(&pipeline)?;

Error handling

use jasonisnthappy::{Error, Result};

match users.find_by_id(&id) {
    Ok(doc) => println!("Found: {:?}", doc),
    Err(Error::NotFound) => println!("Document not found"),
    Err(Error::IoError(e)) => eprintln!("IO error: {}", e),
    Err(e) => eprintln!("Error: {}", e),
}

Platform support

  • Linux (x86_64, ARM64)
  • macOS (Intel, Apple Silicon)
  • Windows (x86_64)

Type safety

For type-safe document handling, define your own structs:
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
struct User {
    #[serde(skip_serializing_if = "Option::is_none")]
    _id: Option<String>,
    name: String,
    age: u32,
    email: String,
}

let user = User {
    _id: None,
    name: "Alice".to_string(),
    age: 30,
    email: "alice@example.com".to_string(),
};

let id = users.insert(&user)?;

Build docs developers (and LLMs) love