datastack 0.4.0

A document-based acid local database.
Documentation
#[cfg(test)]
mod tests {
    use datastack::{DataStack, increment, array_union};
    use serde_json::json;
    use std::fs;

    async fn setup_db(path: &str) -> DataStack {
        // Clean up previous test runs
        let _ = fs::remove_dir_all(path);
        DataStack::new(path).await.expect("Failed to create DataStack")
    }

    #[tokio::test]
    async fn test_basic_crud() {
        let db = setup_db("./test_db_basic").await;

        // 1. Add
        let user = json!({"name": "Alice", "age": 25});
        db.add("users", "u1", &user).await.unwrap();

        // 2. Get
        let fetched = db.get("users", "u1").await.unwrap().unwrap();
        assert_eq!(fetched["name"], "Alice");

        // 3. Delete
        db.delete("users", "u1").await.unwrap();
        let after_delete = db.get("users", "u1").await.unwrap();
        assert!(after_delete.is_none());
    }

    #[tokio::test]
    async fn test_complex_updates() {
        let db = setup_db("./test_db_updates").await;

        // Initial state
        db.add("users", "u1", &json!({
            "balance": 100,
            "profile": { "points": 10 },
            "tags": ["rust"]
        })).await.unwrap();

        // Perform multiple operations in one update call
        db.update("users", "u1", &json!({
            "balance": increment(50),           // 100 + 50 = 150
            "profile.points": increment(-5),    // 10 - 5 = 5
            "tags": array_union(json!(["db"])), // ["rust", "db"]
            "active": true                      // New field
        })).await.unwrap();

        let updated = db.get("users", "u1").await.unwrap().unwrap();
        
       assert_eq!(updated["balance"].as_f64().unwrap(), 150.0);
assert_eq!(updated["profile"]["points"].as_f64().unwrap(), 5.0);
        assert!(updated["tags"].as_array().unwrap().contains(&json!("db")));
        assert_eq!(updated["active"], true);
    }

    #[tokio::test]
    async fn test_subcollections_and_scanning() {
        let db = setup_db("./test_db_scan").await;

        // Add to subcollection
        db.add("users:u1:inbox", "m1", &json!({"msg": "first"})).await.unwrap();
        db.add("users:u1:inbox", "m2", &json!({"msg": "second"})).await.unwrap();
        db.add("users:u1:inbox", "m3", &json!({"msg": "third"})).await.unwrap();

        // Scan Ascending
        let scan_res = db.scan("users:u1:inbox", 2, "", "a").await.unwrap();
        let obj = scan_res.as_object().unwrap();
        assert_eq!(obj.len(), 2);
        assert!(obj.contains_key("m1"));
        assert!(obj.contains_key("m2"));

        // Scan Descending from cursor
        let scan_desc = db.scan("users:u1:inbox", 10, "m3", "d").await.unwrap();
        let obj_desc = scan_desc.as_object().unwrap();
        // m3 is the cursor, so it starts there and goes back
        assert!(obj_desc.contains_key("m3"));
        assert!(obj_desc.contains_key("m2"));
    }

    #[tokio::test]
    async fn test_batch_operations() {
        let db = setup_db("./test_db_add_get_delete.redb").await;

        let batch_data = json!({
            "tx1": { "amount": 100 },
            "tx2": { "amount": 200 },
            "tx3": { "amount": 300 }
        });

        // -------- Batch Add --------
        db.batch_add("ledger", &batch_data).await.unwrap();

        // -------- Single Get --------
        let tx2 = db.get("ledger", "tx2").await.unwrap().unwrap();
        assert_eq!(tx2["amount"], 200);

        // -------- Batch Get --------
        let ids = json!(["tx1", "tx3"]);
        let docs = db.batch_get("ledger", &ids).await.unwrap();

        assert_eq!(docs["tx1"]["amount"], 100);
        assert_eq!(docs["tx3"]["amount"], 300);
        assert!(docs.get("tx2").is_none());

        // -------- Batch Delete --------
        db.batch_delete("ledger", &ids).await.unwrap();

        // -------- Verify Deleted --------
        assert!(db.get("ledger", "tx1").await.unwrap().is_none());
        assert!(db.get("ledger", "tx3").await.unwrap().is_none());

        // -------- Verify Remaining --------
        let tx2_after = db.get("ledger", "tx2").await.unwrap().unwrap();
        assert_eq!(tx2_after["amount"], 200);
    }
}