deli
deli is a Rust crate that simplifies working with IndexedDB in the browser using WebAssembly. It provides an
ergonomic way to define data models that are seamlessly converted into IndexedDB object stores, utilizing derive
macros to eliminate boilerplate code.
With deli, you can define your data structures using familiar Rust syntax while annotating them with attributes to
specify keys, unique constraints, and indexes. The crate integrates with serde for serialization and
deserialization, ensuring a smooth workflow.
Features
- Automatically map Rust structs to IndexedDB object stores.
- Define primary keys, unique constraints, and indexed fields with simple annotations.
- Leverages serde for seamless serialization and deserialization.
- Write concise, readable, and maintainable data models.
Usage
To use deli, run the following command in your project directory:
In addition to the deli crate, you'll need to add serde with derive feature enabled:
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:
[]
= "wasm32-unknown-unknown"
Model derive macro
To map a Rust struct to an IndexedDB object store, you need to derive the Model trait on the struct. The Model
derive macro generates the necessary code to create an object store with the specified keys, unique constraints, and
indexes.
use Model;
use ;
// <- This derives the Model trait on the struct and creates an object store.
This example defines an Employee struct that will be mapped to an IndexedDB object store. The id field is
annotated with #[deli(auto_increment)] to specify that it should be an auto-incrementing primary key. The email
field is annotated with #[deli(unique)] to create a unique constraint, and the age field is annotated with
#[deli(index)] to create an index for faster lookups.
You can modify the name of the object store in Indexed DB and the name of generated object store struct as follows:
use Model;
use ;
// <- This changes the object store name and object store struct name
By default, the object store name is the lowercase version of the struct name (employee). The object store struct
name is the struct name followed by Store (EmployeeStore).
To use the generated object store, you need to create a database as follows:
use ;
async
Next, you'll need to begin a transaction to interact with the object store:
use ;
To add a record in the object store:
use ;
async
The AddEmployee struct is generated by the Model derive macro and is used to add a new employee to the object
store. By default, the name of the struct is Add followed by the name of the model struct. To customize the name
of the struct, you can use the add_struct_name attribute in the Model derive macro.
For example:
use Model;
use ;
The AddEmployee struct contains the same fields as the Employee struct, except the id field because it is an
auto-incrementing primary key.
In general, all the fields except for auto-incrementing primary keys should be present in the Add struct. If your
model does not have any auto-incrementing primary keys, you can use the original struct to add new records.
To query records from the object store:
use ;
async
async
async
After all the operations are done, you can commit the transaction:
use ;
async
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.
Primary keys
In IndexedDB, each object store must have a primary key. deli supports three types of primary keys:
- Auto-incrementing primary keys
- Non auto-incrementing primary keys
- Composite primary keys
Indexed DB also supports not specifying a primary key, in which case it implicitly creates an auto-incrementing
primary key. However, deli requires you to explicitly define a primary key.
Defining auto-incrementing primary keys
To define an auto-incrementing primary key, you can use the #[deli(auto_increment)] attribute on the field.
use Model;
use ;
Defining non auto-incrementing primary keys
To define a non auto-incrementing primary key, you can use the #[deli(key)] attribute on the field.
use Model;
use ;
Defining composite primary keys
To define composite primary keys, you can use the #[deli(key)] attribute on the struct with all the field names
that are part of the composite key.
use Model;
use ;
// <- This defines a composite primary key
Indexes
In IndexedDB, you can create indexes on fields to speed up queries and add constraints. deli supports six types of
indexes:
- Single field indexes
- Single field unique indexes
- Single field multi-entry indexes
- Composite indexes
- Composite unique indexes
- Composite multi-entry indexes
Defining single field indexes
To define a single field index, you can use the #[deli(index)] attribute on the field.
use Model;
use ;
The Model derive macro generates a struct for each index. By default, the name of the struct is the name of the
model followed by the name of the field followed by Index in pascal case (EmployeeNameIndex). You can customize
the name of the generated struct by using the struct_name attribute in the deli(index) attribute.
use Model;
use ;
The Model derive macro also generates a name for the index to be used in the object store. By default, the name of
the index is the name of the model followed by the name of the field followed by index in snake case
(employee_name_index). You can customize the name of the index by using the name attribute in the deli(index)
attribute.
use Model;
use ;
Note that the default naming convention for the generated struct and index name is different for different index types.
Defining single field unique indexes
To define a single field unique index, you can use the #[deli(unique)] attribute on the field.
use Model;
use ;
Defining single field multi-entry indexes
To define a single field multi-entry index, you can use the #[deli(multi_entry)] attribute on the field.
use Model;
use ;
Defining composite indexes
To define composite indexes, you can use the #[deli(index)] attribute on the struct with all the field names that
are part of the composite index.
use Model;
use ;
// <- This defines a composite index on the `id` and `name` fields
Defining composite unique indexes
To define composite unique indexes, you can use the #[deli(unique)] attribute on the struct with all the field
names that are part of the composite unique index.
use Model;
use ;
// <- This defines a composite unique index on the `id` and `name` fields
Defining composite multi-entry indexes
To define composite multi-entry indexes, you can use the #[deli(multi_entry)] attribute on the struct with all the
fields that are part of the composite multi-entry index.
use Model;
use ;
// <- This defines a composite multi-entry index on the `id` and `permissions` fields
Using indexes
Model derive macro generates a function to get Index for each index. You can use this function to interact
with the index.
use Model;
use ;
async
Here are the naming conventions for the generated functions:
- Single field indexes:
by_{field_name} - Single field unique indexes:
by_{field_name}_unique - Single field multi-entry indexes:
by_{field_name}_multi_entry - Composite indexes:
by_{field_name1}_{field_name2}_composite - Composite unique indexes:
by_{field_name1}_{field_name2}_composite_unique - Composite multi-entry indexes:
by_{field_name1}_{field_name2}_composite_multi_entry
Field renaming
If you use #[serde(rename = "new_name")] attribute on a field, you also need to use #[deli(rename = "new_name")]
attribute to specify the new name of the field in the object store.
use Model;
use ;
If you use #[serde(rename_all = "camelCase")] attribute on the struct, you have to use
#[deli(rename = "new_name")] for each field individually. Unfortunately, deli does not support renaming all
fields at once.
License
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (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.