# deli Deli is a convenience wrapper on `idb` create for easily creating and managing object stores in an indexed db on browsers using derive macros. ## Usage To use `deli`, you need to add the following in your `Cargo.toml`: ```toml [dependencies] deli = "0.1" ``` `deli` is intended to be used on browsers using webassembly. So, make sure to compile your project with `--target wasm32-unknown-unknown`. Alternatively, you can add following build configuration in your `.cargo/config.toml`: ```toml [build] target = "wasm32-unknown-unknown" ``` ### Example #### Defining a `Model` The first step is to define your data model using `Model` derive macro. You also need to implement `serde::Serialize` and `serde::Deserialize` trait for your model so that the data can be converted to `json` before saving it into the store. ```rust use deli::Model; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Model)] pub struct Employee { #[deli(auto_increment)] pub id: u32, pub name: String, #[deli(unique)] pub email: String, #[deli(index)] pub age: u8, } ``` `Model` derive macro automatically implements `Model` trait for your struct and creates a `Store` for accessing and writing data to the store. ##### Container attributes - `#[deli(name)]`: In indexed DB, by default, it creates a new object store with name of the struct (in the above example, it'll create an object store `Employee` in indexed db) when creating a database. To change the default object store name, use `#[deli(name = "your_object_store_name")]`. - `#[deli(store_name)]`: By default, the derive macro will create a `Store` struct (in the above example, it'll create a `EmployeeStore` struct). To change the default name, use `#[deli(store_name = "YourStoreName")]`. ##### Field attributes - `#[deli(key)]`: Defines the primary key path for object store. - `#[deli(auto_increment)]`: Defines the primary key path for object store with `auto_increment` values (implies `#[deli(key)]`). - `#[deli(index)]`: Creates an index for the field. - `#[deli(unique)]`: Creates an unique index for the field (implies `#[deli(index)]`). - `#[deli(multi_entry)]`: Creates a multi entry index for the field (implies `#[deli(index)]`). - `#[deli(rename)]`: Rename a field in object store. Note that this should be consistent with `serde` serialization. For example, if you use `#[serde(rename_all = "camelCase")]` you need to appropriately rename the fields for `deli` to be in sync with serde serialization. #### Creating a `Database` Next step is to create a new `Database` and register your models with it. ```rust use deli::{Database, Error}; async fn create_database() -> Result { let database = Database.builder("test_db", 1).register_model::().await?; } ``` #### Starting a `Transaction` Once you've created a `Database` instance, you can start reading and writing data to database using transactions. ```rust use deli::{Database, Error, Transaction}; fn create_read_transaction(database: &Database) -> Result { database.transaction().with_model::().build() } fn create_write_transaction(database: &Database) -> Result { database.transaction().writable().with_model::().build() } ``` You can add multiple `.with_model::()` calls to add more than one model to the transaction. #### Reading and writing data to a `Model` store Once you have a transaction for a model, you can read or write data to that model. ```rust use deli::{Error, Model, Transaction}; async fn add_employee(transaction: &Transaction) -> Result { Employee::with_transaction(transaction)?.add("Alice", "alice@example.com", &25).await } async fn get_employee(transaction: &Transaction, id: u32) -> Result, Error> { Employee::with_transaction(transaction)?.get(&id).await } async fn get_all_employees(transaction: &Transaction) -> Result, Error> { // NOTE: Here `..` (i.e., `RangeFull`) means fetch all values from store Employee::with_transaction(transaction)?.get_all(.., None).await } async fn get_employees_with_bounds( transaction: &Transaction, from_id: u32, to_id: u32, ) -> Result, Error> { Employee::with_transaction(transaction)?.get_all(&from_id..=&to_id, None).await } ``` #### Commiting a `Transaction` After all your writes are done, you can commit the transaction: ```rust async fn commit_transaction(transaction: Transaction) -> Result<(), Error> { transaction.commit().await } ``` Note that `commit()` doesn’t normally have to be called — a transaction will automatically commit when all outstanding requests have been satisfied and no new requests have been made. Also, be careful when using long-lived indexed db transactions as the behavior may change depending on the browser. For example, the transaction may get auto-committed when doing IO (network request) in the event loop. ## License Licensed under either of - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE)) - MIT license ([LICENSE-MIT](LICENSE-MIT)) at your option. ## Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.