{"id":29089854,"url":"https://github.com/0xgeorgii/json-mutex-db","last_synced_at":"2026-05-07T17:37:49.517Z","repository":{"id":286095881,"uuid":"960309640","full_name":"0xGeorgii/json-mutex-db","owner":"0xGeorgii","description":"Ridiculously simple, fast and thread safe JSON file database","archived":false,"fork":false,"pushed_at":"2025-06-09T04:35:25.000Z","size":69,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-28T04:05:20.656Z","etag":null,"topics":["database","file-database","json"],"latest_commit_sha":null,"homepage":"https://crates.io/crates/json-mutex-db","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/0xGeorgii.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":["Inferara"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"lfx_crowdfunding":null,"polar":null,"buy_me_a_coffee":null,"thanks_dev":null,"custom":null}},"created_at":"2025-04-04T08:01:23.000Z","updated_at":"2025-06-09T04:35:29.000Z","dependencies_parsed_at":"2025-04-04T10:33:38.190Z","dependency_job_id":"98c6c51c-c4e3-4121-8c48-13d25e0d950f","html_url":"https://github.com/0xGeorgii/json-mutex-db","commit_stats":null,"previous_names":["0xgeorgii/json-mutex-db"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/0xGeorgii/json-mutex-db","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0xGeorgii%2Fjson-mutex-db","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0xGeorgii%2Fjson-mutex-db/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0xGeorgii%2Fjson-mutex-db/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0xGeorgii%2Fjson-mutex-db/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/0xGeorgii","download_url":"https://codeload.github.com/0xGeorgii/json-mutex-db/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0xGeorgii%2Fjson-mutex-db/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32749175,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-07T02:14:30.463Z","status":"ssl_error","status_checked_at":"2026-05-07T02:14:29.405Z","response_time":62,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["database","file-database","json"],"created_at":"2025-06-28T04:05:18.834Z","updated_at":"2026-05-07T17:37:49.502Z","avatar_url":"https://github.com/0xGeorgii.png","language":"Rust","funding_links":["https://github.com/sponsors/Inferara"],"categories":[],"sub_categories":[],"readme":"[![Build](https://github.com/Inferara/inf-wasm-tools/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/Inferara/inf-wasm-tools/actions/workflows/build.yml)\n![Crates.io Version](https://img.shields.io/crates/v/json-mutex-db?label=json-mutex-db)\n![Crates.io Total Downloads](https://img.shields.io/crates/d/json-mutex-db)\n\n# JsonMutexDB 💾\n\nRidiculously simple, fast and thread safe JSON file database\n\nEver found yourself needing a *really* simple way to persist some state in a Rust application? Maybe a configuration file, some user preferences, or the results of that *one* calculation you don't want to run again? And did you also need multiple threads to poke at that state without setting your data on fire? 🔥\n\n`JsonMutexDB` was born out of a desire for a straightforward, thread-safe mechanism to manage data stored in a single JSON file. It doesn't try to be a full-fledged database, but it's pretty handy for those \"I just need to save this `struct` somewhere\" moments.\n\n## What's Inside? ✨\n\n* **Thread-Safe Access:** Uses `std::sync::Mutex` (or potentially `parking_lot::Mutex` depending on historical versions) under the hood, allowing multiple threads to safely read (`get`) and write (`update`) data.\n* **JSON Persistence:** Reads from and saves data to a JSON file you specify. Handles empty or non-existent files gracefully on startup.\n* **Atomic Saves:** Writes are performed atomically by default (using `tempfile` and rename) to prevent data corruption if your application crashes mid-save. Safety first!\n* **Serialization Options:**\n    * Save JSON in a compact format (default) or human-readable \"pretty\" format.\n    * Optionally use `simd-json` for potentially faster serialization when saving in compact mode. Speed boost! 🚀\n* **Optional Asynchronous Updates:** For scenarios where you don't want your main threads blocked by updates, you can enable `async_updates`. Updates are sent to a dedicated background thread for processing.\n    * **State Synchronization:** When async mode is enabled, `get()` and `save_sync()` intelligently query the background thread to ensure they operate on the *absolute latest* state. (This involves some channel communication overhead).\n* **Asynchronous Saving:** Offload the potentially slow file I/O of saving to a background thread with `save_async()`.\n\n## Quick Start 🚀\n\n```bash\ncargo add json-mutex-db\n```\n\nor add the following line to your `Cargo.toml`:\n\n```toml\njson-mutex-db = \"0.0.2\"\n```\n\nAnd get going in your code:\n\n```rust\nuse json_mutex_db::{JsonMutexDB, DbError};\nuse serde::{Serialize, Deserialize};\nuse serde_json::json;\n\n#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]\nstruct Config {\n    api_key: Option\u003cString\u003e,\n    retries: u32,\n}\n\nfn main() -\u003e Result\u003c(), DbError\u003e {\n    let db_path = \"my_app_state.json\";\n    // Create DB (sync mode, compact, standard serialization)\n    let db = JsonMutexDB::new(db_path, false, false, false)?;\n\n    // Get initial data (starts empty if file doesn't exist)\n    let initial_val = db.get()?;\n    println!(\"Initial value: {}\", initial_val);\n\n    // Update the data - replace the whole value\n    let initial_config = Config { api_key: None, retries: 3 };\n    db.update(move |data| {\n        *data = serde_json::to_value(\u0026initial_config).unwrap();\n    })?;\n\n    // Update part of the data (if it's an object)\n    db.update(move |data| {\n        if let Some(obj) = data.as_object_mut() {\n            obj.insert(\"retries\".to_string(), json!(5));\n            obj.insert(\"new_feature_enabled\".to_string(), json!(true));\n        }\n    })?;\n\n    // Get the current state\n    let current_val = db.get()?;\n    println!(\"Current value: {}\", current_val);\n\n    // Try to deserialize it back\n    let current_config: Config = serde_json::from_value(current_val.clone())\n        .expect(\"Failed to deserialize\");\n    assert_eq!(current_config.retries, 5);\n\n    // Save it synchronously (atomic by default)\n    db.save_sync()?;\n\n    // Cleanup the file for the example\n    std::fs::remove_file(db_path).ok();\n\n    Ok(())\n}\n```\n\n## Configuration Options (new) ⚙️\n\nWhen creating a JsonMutexDB, you have a few choices:\n\n```rust,ignore\npub fn new(\n    path: \u0026str,            // Path to the JSON file\n    pretty: bool,          // `true` for pretty-printed JSON, `false` for compact\n    async_updates: bool,   // `true` to enable background thread for updates\n    fast_serialization: bool, // `true` to use simd-json for compact serialization (if `pretty` is false)\n) -\u003e Result\u003cSelf, DbError\u003e\n```\n* `path`: The path to the JSON file. If it doesn't exist, it will be created.\n* `pretty`: If true, the JSON will be saved in a human-readable format. If false, it will be compact. This affects both `save_sync()` and `save_async()`.\n* `async_updates`: If true, updates are sent to a background thread. This allows the main thread to continue without waiting for the update to complete. If false, updates are synchronous and block until completed.\n* `fast_serialization`: If true and pretty is false, uses the `simd-json` crate for faster serialization. This is only effective when saving in compact mode.\n\n## Examples 🧐\n\n### Async Updates\n\n```rust\nuse json_mutex_db::{JsonMutexDB, DbError};\nuse serde_json::json;\nuse std::sync::Arc;\nuse std::thread;\nuse std::time::Duration;\n\nfn main() -\u003e Result\u003c(), DbError\u003e {\n    let db_path = \"async_example.json\";\n    // Enable async updates, use fast compact saving\n    let db = Arc::new(JsonMutexDB::new(db_path, false, true, true)?);\n\n    let db_clone = Arc::clone(\u0026db);\n    thread::spawn(move || {\n        println!(\"Background thread updating...\");\n        db_clone.update(|data| {\n            let obj = data.as_object_mut().unwrap();\n            obj.insert(\"worker_id\".to_string(), json!(123));\n            obj.insert(\"status\".to_string(), json!(\"running\"));\n        }).expect(\"Failed to send update\");\n        println!(\"Background thread update sent.\");\n    });\n\n    // Give the background thread a moment to process\n    thread::sleep(Duration::from_millis(50));\n\n    // Get the latest state (will block briefly to query background thread)\n    let current_state = db.get()?;\n    println!(\"State after async update: {}\", current_state);\n    assert_eq!(current_state[\"status\"], \"running\");\n\n    db.save_sync()?; // Save the state fetched from background\n    println!(\"Async state saved.\");\n\n    // Required: Drop the Arc to signal background thread shutdown before cleanup\n    drop(db);\n    thread::sleep(Duration::from_millis(50)); // Allow time for shutdown/final save\n\n    std::fs::remove_file(db_path).ok();\n\n    Ok(())\n}\n```\n\n### Async Saving\n\n```rust\nuse json_mutex_db::{JsonMutexDB, DbError};\nuse serde_json::json;\nuse std::thread;\nuse std::time::Duration;\n\nfn main() -\u003e Result\u003c(), DbError\u003e {\n    let db_path = \"async_save_example.json\";\n    // Sync updates, pretty printing\n    let db = JsonMutexDB::new(db_path, true, false, false)?;\n\n    db.update(|d| *d = json!({\"message\": \"Hello from async save!\"}))?;\n\n    println!(\"Triggering async save...\");\n    db.save_async()?; // Returns immediately\n\n    println!(\"Main thread doing other work...\");\n    thread::sleep(Duration::from_millis(100));\n\n    println!(\"Checking file...\");\n    let content = std::fs::read_to_string(db_path)?;\n    println!(\"File content:\\n{}\", content);\n    assert!(content.contains(\"  \\\"message\\\":\")); // Check for pretty printing\n\n    std::fs::remove_file(db_path).ok();\n\n    Ok(())\n}\n```\n\n## Performance Notes ⚡️\n\n* Atomic Sync Saves (`save_sync`): No longer deep-clones the JSON data to avoid extra allocations and copying. Instead, holds a read lock during serialization into a thread-local buffer, reducing memory operations at the cost of blocking concurrent updates during the save.\n* Asynchronous Saves (`save_async`): No longer deep-clones the JSON data; holds a read lock during serialization into a thread-local buffer in the background thread, reducing memory operations at the cost of blocking concurrent updates until the save completes.\n* Serialization Buffer: The thread-local buffer is pre-allocated based on initial file size to minimize reallocations.\n* I/O: Saves serialize into a thread-local in-memory buffer and issue a single `write_all` + `flush`, drastically reducing the number of write syscalls. Atomic saves still involve writing to a temporary file and renaming.\n* Async Updates: Updates are non-blocking and queued to a background thread. Multiple rapid updates are coalesced into a single disk write, reducing redundant I/O.\n\n## Error Handling ⚠️\n\nMost operations return `Result\u003c_, DbError\u003e`. This enum covers:\n* `DbError::Io(std::io::Error)`: Filesystem errors, invalid JSON loading, serialization errors.\n* `DbError::Sync(String)`: Errors related to the async background thread communication (channel errors, poisoned mutexes in sync mode).\nMatch on the result or use ? to propagate errors.\n\n## Limitations \u0026 Considerations 🤔\n\nSingle File: This manages one JSON file. It's not designed for complex relational data or large datasets where a real database would be more appropriate.\nMemory Usage: The entire JSON structure is loaded into memory. Very large JSON files might consume significant RAM.\nAsync Mode Latency: While async_updates: true makes update() non-blocking, get() and save_sync() do block while communicating with the background thread to retrieve the latest state.\nunsafe: Uses unsafe internally for simd-json's from_str for performance. While believed to be safe in this context, be aware if auditing for unsafe.\n\n## Contributing 🤝\n\nWelcome!\n\n## Happy JSON juggling!\n\n🎉\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F0xgeorgii%2Fjson-mutex-db","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F0xgeorgii%2Fjson-mutex-db","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F0xgeorgii%2Fjson-mutex-db/lists"}