deli 0.2.0

Provides an ergonomic way to define data models that are seamlessly converted into IndexedDB object stores, utilizing derive macros
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
#![deny(missing_docs)]
#![forbid(unsafe_code, unstable_features)]
//! `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:
//!
//! ```sh
//! cargo add deli
//! ```
//!
//! In addition to the `deli` crate, you'll need to add `serde` with `derive` feature enabled:
//!
//! ```sh
//! cargo add serde --features derive
//! ```
//!
//! `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"
//! ```
//!
//! ## `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.
//!
//! ```rust
//! use deli::Model;
//! use serde::{Deserialize, Serialize};
//!
//! #[derive(Serialize, Deserialize, Model)] // <- This derives the Model trait on the struct and creates an object store.
//! pub struct Employee {
//!     #[deli(auto_increment)]
//!     id: u32,
//!     name: String,
//!     #[deli(unique)]
//!     email: String,
//!     #[deli(index)]
//!     age: u32,
//! }
//! ```
//!
//! 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:
//!
//! ```rust
//! use deli::Model;
//! use serde::{Deserialize, Serialize};
//!
//! #[derive(Serialize, Deserialize, Model)]
//! #[deli(object_store_name = "employees", object_store_struct = "EmployeeStore")] // <- This changes the object store name and object store struct name
//! pub struct Employee {
//!     #[deli(auto_increment)]
//!     id: u32,
//!     name: String,
//!     #[deli(unique)]
//!     email: String,
//!     #[deli(index)]
//!     age: u32,
//! }
//! ```
//!
//! 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:
//!
//! ```rust
//! use deli::{Database, Error};
//!
//! async fn create_database() -> Result<Database, Error> {
//!     Database::builder("test_db")
//!         .version(1)
//!         .add_model::<Employee>()
//!         .build()
//!         .await
//! }
//! ```
//!
//! Next, you'll need to begin a transaction to interact with the object store:
//!
//! ```rust
//! use deli::{Database, Error, Transaction};
//!
//! fn create_read_transaction(database: &Database) -> Result<Transaction, Error> {
//!     database
//!         .transaction()
//!         .with_model::<Employee>()
//!         .build()
//! }
//!
//! fn create_write_transaction(database: &Database) -> Result<Transaction, Error> {
//!     database
//!         .transaction()
//!         .writable()
//!         .with_model::<Employee>()
//!         .build()
//! }
//! ```
//!
//! To add a record in the object store:
//!
//! ```rust
//! use deli::{Error, Transaction};
//!
//! async fn add_employee(transaction: &Transaction, employee: &AddEmployee) -> Result<u32, Error> {
//!     Employee::with_transaction(transaction)?.add(employee).await
//! }
//! ```
//!
//! 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:
//!
//! ```rust
//! use deli::Model;
//! use serde::{Deserialize, Serialize};
//!
//! #[derive(Serialize, Deserialize, Model)]
//! #[deli(add_struct_name = "EmployeeAdd")]
//! pub struct Employee {
//!     #[deli(auto_increment)]
//!     id: u32,
//!     name: String,
//!     #[deli(unique)]
//!     email: String,
//!     #[deli(index)]
//!     age: u32,
//! }
//! ```
//!
//! 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:
//!
//! ```rust
//! use deli::{Error, Transaction};
//!
//! async fn get_employee(transaction: &Transaction, id: u32) -> Result<Option<Employee>, Error> {
//!     Employee::with_transaction(transaction)?.get(&id).await
//! }
//!
//! async fn get_all_employees(transaction: &Transaction) -> Result<Vec<Employee>, 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<Vec<Employee>, Error> {
//!     Employee::with_transaction(transaction)?.get_all(&from_id..=&to_id, None).await
//! }
//! ```
//!
//! After all the operations are done, you can commit the transaction:
//!
//! ```rust
//! use deli::{Error, Transaction};
//!
//! 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.
//!
//! ## 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.
//!
//! ```rust
//! use deli::Model;
//! use serde::{Deserialize, Serialize};
//!
//! #[derive(Serialize, Deserialize, Model)]
//! pub struct Employee {
//!     #[deli(auto_increment)] // <- This defines an auto-incrementing primary key
//!     pub id: u32,
//! }
//! ```
//!
//! ### Defining non auto-incrementing primary keys
//!
//! To define a non auto-incrementing primary key, you can use the `#[deli(key)]` attribute on the field.
//!
//! ```rust
//! use deli::Model;
//! use serde::{Deserialize, Serialize};
//!
//! #[derive(Serialize, Deserialize, Model)]
//! pub struct Employee {
//!     #[deli(key)] // <- This defines a non auto-incrementing primary key
//!     pub id: u32,
//! }
//! ```
//!
//! ### 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.
//!
//! ```rust
//! use deli::Model;
//! use serde::{Deserialize, Serialize};
//!
//! #[derive(Serialize, Deserialize, Model)]
//! #[deli(key(id, name))] // <- This defines a composite primary key
//! pub struct Employee {
//!     id: u32,
//!     name: String,
//! }
//! ```
//!
//! ## 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.
//!
//! ```rust
//! use deli::Model;
//! use serde::{Deserialize, Serialize};
//!
//! #[derive(Serialize, Deserialize, Model)]
//! pub struct Employee {
//!     #[deli(auto_increment)]
//!     pub id: u32,
//!     #[deli(index)] // <- This defines a single field index on the `name` field for faster lookups
//!     pub name: String,
//! }
//! ```
//!
//! 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.
//!
//! ```rust
//! use deli::Model;
//! use serde::{Deserialize, Serialize};
//!
//! #[derive(Serialize, Deserialize, Model)]
//! pub struct Employee {
//!     #[deli(auto_increment)]
//!     pub id: u32,
//!     #[deli(index(struct_name = "MyEmployeeNameIndex"))] // <- This changes the name of the generated struct to `NameIndex`
//!     pub name: String,
//! }
//! ```
//!
//! 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.
//!
//! ```rust
//! use deli::Model;
//! use serde::{Deserialize, Serialize};
//!
//! #[derive(Serialize, Deserialize, Model)]
//! pub struct Employee {
//!     #[deli(auto_increment)]
//!     pub id: u32,
//!     #[deli(index(name = "my_employee_name_index"))] // <- This changes the name of the index to `my_employee_name_index`
//!     pub name: String,
//! }
//! ```
//!
//! > 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.
//!
//! ```rust
//! use deli::Model;
//! use serde::{Deserialize, Serialize};
//!
//! #[derive(Serialize, Deserialize, Model)]
//! pub struct Employee {
//!     #[deli(auto_increment)]
//!     pub id: u32,
//!     #[deli(unique)] // <- This defines a single field unique index on the `email` field
//!     pub email: String,
//! }
//! ```
//!
//! ### 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.
//!
//! ```rust
//! use deli::Model;
//! use serde::{Deserialize, Serialize};
//!
//! #[derive(Serialize, Deserialize, Model)]
//! pub struct Employee {
//!     #[deli(auto_increment)]
//!     pub id: u32,
//!     #[deli(multi_entry)] // <- This defines a single field multi-entry index on the `permissions` field
//!     pub permissions: Vec<String>,
//! }
//! ```
//!
//! ### 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.
//!
//! ```rust
//! use deli::Model;
//! use serde::{Deserialize, Serialize};
//!
//! #[derive(Serialize, Deserialize, Model)]
//! #[deli(index(fields(id, name)))] // <- This defines a composite index on the `id` and `name` fields
//! pub struct Employee {
//!     #[deli(auto_increment)]
//!     id: u32,
//!     name: String,
//! }
//! ```
//!
//! ### 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.
//!
//! ```rust
//! use deli::Model;
//! use serde::{Deserialize, Serialize};
//!
//! #[derive(Serialize, Deserialize, Model)]
//! #[deli(unique(fields(id, name)))] // <- This defines a composite unique index on the `id` and `name` fields
//! pub struct Employee {
//!     #[deli(auto_increment)]
//!     id: u32,
//!     name: String,
//! }
//! ```
//!
//! ### 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.
//!
//! ```rust
//! use deli::Model;
//! use serde::{Deserialize, Serialize};
//!
//! #[derive(Serialize, Deserialize, Model)]
//! #[deli(unique(fields(id, permissions)))] // <- This defines a composite multi-entry index on the `id` and `permissions` fields
//! pub struct Employee {
//!     #[deli(auto_increment)]
//!     id: u32,
//!     permissions: Vec<String>,
//! }
//! ```
//!
//! ## Using indexes
//!
//! `Model` derive macro generates a function to get [`Index`] for each index. You can use this function to interact
//! with the index.
//!
//! ```rust
//! use deli::Model;
//! use serde::{Deserialize, Serialize};
//!
//! #[derive(Serialize, Deserialize, Model)]
//! pub struct Employee {
//!     #[deli(auto_increment)]
//!     pub id: u32,
//!     #[deli(index)]
//!     pub name: String,
//! }
//!
//! async fn get_employee_by_name(transaction: &Transaction, name: &str) -> Result<Option<Employee>, Error> {
//!     Employee::with_transaction(transaction)?.by_name().get(name).await
//! }
//! ```
//!
//! 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.
//!
//! ```rust
//! use deli::Model;
//! use serde::{Deserialize, Serialize};
//!
//! #[derive(Serialize, Deserialize, Model)]
//! pub struct Employee {
//!     #[deli(auto_increment)]
//!     pub id: u32,
//!     #[serde(rename = "emailAddress")]
//!     #[deli(rename = "emailAddress")] // <- This renames the field to `emailAddress` in the object store
//!     pub email: String,
//! }
//! ```
//!
//! 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.
mod cursor;
mod database;
mod database_builder;
mod error;
mod index;
mod key_cursor;
mod key_range;
mod model;
mod model_index;
mod object_store;
mod transaction;
mod transaction_builder;

#[doc(inline)]
pub use idb::{CursorDirection, TransactionMode, TransactionResult};

pub use self::{
    cursor::Cursor,
    database::Database,
    database_builder::DatabaseBuilder,
    error::Error,
    index::Index,
    key_cursor::KeyCursor,
    key_range::{BoundedRange, KeyRange, RangeType, UnboundedRange},
    model::Model,
    model_index::ModelIndex,
    object_store::ObjectStore,
    transaction::Transaction,
    transaction_builder::TransactionBuilder,
};

const JSON_SERIALIZER: serde_wasm_bindgen::Serializer =
    serde_wasm_bindgen::Serializer::json_compatible();

/// Re-exports of the `deli` crate.
#[doc(hidden)]
pub mod reexports {
    pub use idb;
    pub use serde;
}

#[cfg(feature = "derive")]
pub use deli_derive::Model;