# light-magic
[](https://crates.io/crates/light-magic)
[](https://crates.io/crates/light-magic)
[](https://docs.rs/light-magic)
A lightweight, fast and easy-to-use implementation of a `persistent or optionally encrypted in-memory database`.
## Features
Please note that this database is highly optimized for read operations. Writing to the database is relatively slow when using `open` because each write operation involves writing data to the disk. These writes are done atomically, ensuring no data loss on a system-wide crash.
- **Persistent Data Storage**: Data can be saved automatically and persistently to a formatted `JSON` file via `open`, or it can be operated in-memory using `open_in_memory`.
- **Encrypted Persistent Data Storage**: Data can be also saved encrypted via the `encrypted` module using the same `open` method.
- **Easy Table Markup**: Utilizes Rusts beautiful type system, structs and traits.
- **Powerful Data Access Functions**: Utilize functions like `search` / `search_ordered` and the `join!` macro for efficient data searching and joining.
- **Efficient Storage**: The database employs a custom `Table` data type, which uses the `BTreeMap` type from `std::collections` under the hood, for efficient storage and easy access of its tables.
- **Parallel Access Support**: Access the database in parallel using `Arc<AtomicDatabase<_>>`.
## Installation
Add this to your `Cargo.toml`:
```toml
[dependencies]
light_magic = "0.8.2"
```
## Feature Flags
`light-magic` is feature-flag driven. By default, only the `atomic` module is enabled.
You can enable additional functionality in your `Cargo.toml`:
- `atomic`: _Enabled by default_. Provides the basic atomic database with persistent JSON storage, type-safe tables, and the `DataStore` trait.
- `encrypted`: Enables the `encrypted` module, adding Argon2id password-based key derivation, AES-256-GCM authenticated encryption (96-bit nonces), and compact bincode serialization on top of the atomic database.
## Examples
Using it in an `axum` Server? Look here: [maud-magic-rs](https://github.com/nwrenger/maud-magic-rs). Otherwise, look at this general example:
```rust
use light_magic::{
atomic::DataStore,
join,
serde::{Deserialize, Serialize},
table::{PrimaryKey, Table},
};
#[derive(Default, Debug, Serialize, Deserialize)]
struct Database {
users: Table<User>,
permissions: Table<Permission>,
criminals: Table<Criminal>,
settings: Settings,
}
impl light_magic::atomic::DataStore for Database {}
// or with features = ["encrypted"]
// impl light_magic::encrypted::EncryptedDataStore for Database {}
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
struct User {
id: usize,
name: String,
kind: String,
}
impl PrimaryKey for User {
type PrimaryKeyType = usize;
fn primary_key(&self) -> &Self::PrimaryKeyType {
&self.id
}
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
struct Permission {
user_name: String,
level: Level,
}
impl PrimaryKey for Permission {
type PrimaryKeyType = String;
fn primary_key(&self) -> &Self::PrimaryKeyType {
&self.user_name
}
}
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
enum Level {
#[default]
Admin,
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
struct Criminal {
user_name: String,
entry: String,
}
impl PrimaryKey for Criminal {
type PrimaryKeyType = String;
fn primary_key(&self) -> &Self::PrimaryKeyType {
&self.user_name
}
}
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
struct Settings {
time: usize,
password: String,
}
fn main() {
let db = Database::open("./tests/test.json");
// or with features = ["encrypted"]
// let db = Database::open("./tests/test.json", "somePassword");
db.write().users.add(User {
id: 0,
name: String::from("Nils"),
kind: String::from("Young"),
});
println!("{:?}", db.read().users.get(&0));
println!("{:?}", db.read().users.search(|user| { user.name.contains("Nils") }));
db.write().permissions.add(Permission {
user_name: String::from("Nils"),
level: Level::Admin,
});
println!("{:?}", db.read().permissions.get(&String::from("Nils")));
println!("{:?}", db.read().permissions.search(|permission| { permission.level == Level::Admin }));
db.write().criminals.add(Criminal {
user_name: String::from("Nils"),
entry: String::from("No records until this day! Keep ur eyes pealed!"),
});
println!("{:?}", db.read().criminals.get(&String::from("Nils")));
println!("{:?}", db.read().criminals.search(|criminal| { criminal.entry.contains("No records") }));
db.write().settings = Settings {
time: 1718744090,
password: String::from("password"),
};
println!("{:?}", db.read().settings);
let joined = join!(db.read(), "Nils", users => name, permissions => user_name, criminals => user_name);
println!("{:?}", joined);
}
```