idb-model
Small and simple model-based IndexedDB wrapper.
What for?
If you have ever dealt with IndexedDB, you've probably noticed that its API is not really usable in the modern world of promises and async
functions.
This library is designed the way that you wouldn't need to touch the weird native API.
If you only need a promise wrapper for the native API or a simple key-val store, you can take a look at idb or idb-keyval.
If you need something more advanced idb-model
is what you need.
Usage
The library consists of two classes: Database
and Model
.
You migrate the database and perform transactions through an instance of Database
and you manage records in an object store through your custom subclasses of Model
(if you're familiar with sequelize this should be easy to understand as idb-model
is inspired by it).
Database
This class is responsible for connecting to the database and opening transactions to it.
new Database(name[, options])
- name (required) - database name.
- options (optional):
- options.transactionMode (optional) - is either
"readonly"
or"readwrite"
- default level for Database#transaction. Default is"readonly"
. - options.onBlocked (optional) - is just passed to this.
- options.onVersionChange (optional) - is just passed to this.
- options.transactionMode (optional) - is either
Instance methods
Database#close()
Closes the connection.
db;
Database#delete()
Deletes the database. Returns Promise
.
async { await db;};
Database#getConnection()
Returns Promise
resolved with the instance of IDBDatabase.
Used primarily internally, and you probably wouldn't need this in most cases, but if you need to access the native API use this method.
async { const connection = await db; console;};
Database#migrate(migrations)
- migrations (required) - An array of functions (may be async) that take two arguments:
- db - an IDBDatabase instance.
- transaction - a version change transaction. You can pass it to any
Model
method that accepts transaction.
Returns Promise
.
Use this function to migrate between different versions of your database.
Note: if a migration is an async
function you can't really fetch resources or perform any other non-idb-related async actions because IndexedDB transactions auto-close when there is nothing to do.
But you can await
some Model
methods, but only if you're passing the transaction
parameter to them.
Also if you're going to use some native transaction API in a migration, use only the transaction
from the parameter.
For example:
const db = 'db'; static modelName = 'users'; static primaryKey = 'id'; db; async { await db;};
Database#model(model)
- model (required) - a subclass of
Model
.
Attaches model
to the database instance.
static modelName = 'users'; static primaryKey = 'id'; db;
Database#transaction(storeNames[, mode], callback)
- storeNames (required) - a string or string array of store names that this transaction is using.
- mode (optional) -
"readonly"
or"readwrite"
. If not specified thendb.transactionMode
is used that was specified when creating the database. - callback (required) - function that takes transaction argument that can then be passed to any
Model
method.
Returns Promise
resolved with the return value of callback when the transaction is completed.
Performs a transaction to the database.
Note: Don't await
asynchronous actions inside the callback before doing something with transaction
as the transaction completes when there's nothing to do. Example:
const db = 'db' transactionMode: 'readwrite'; static modelName = 'users'; static primaryKey = 'id'; { const result = await db; console;}
Model
To manage records in an object store use this class, but not directly - only through your custom subclasses. Example:
static modelName = 'users'; static primaryKey = 'id'; const user = name: 'John' age: 30; async { await user;};
new Model(values)
- values (required) - object with values of the instance.
Model.defaultValues
is assigned to the instance before them.
Static fields
- modelName (required): used to select
objectStore
from the database. - primaryKey (required): for now
save
anddelete
operations are primary-key-based, so that all of your models have to have a primary key. - fields (optional): an array of instance fields to save. By default all instance enumerable fields are saved. You can use this field to filter out the fields that don't need to be stored. To customize stored fields more use Model#toJSON hook.
- defaultValues (optional): an object with default values.
Static methods
Model.build(values)
Model.bulkBuild(values[])
- values (required) - object with values of the instance.
Returns new instance (array of instances for bulkBuild
).
Alias of new Model(values)
.
const user = User;const users = User;
Model.clear([options])
- options (optional):
- options.transaction (optional) - if present, the operation is performed in this transaction.
Clears the object store. Returns Promise
.
async { await Userclear;};
Model.count([options])
- options (optional):
- options.transaction (optional) - if present, the operation is performed in this transaction.
Counts the records in the object store. Returns Promise
resolved with the number of records.
async { console;};
Model.create(values[, options])
Model.bulkCreate(values[][, options])
- values (required) - object with values of the instance.
- options (optional):
- options.transaction (optional) - if present, the operation is performed in this transaction.
- options.storeNames (optional) - an array of object stores, that may be needed for beforeSave hook.
Creates the instance(s) using values
and saves it (them) in the object store right away.
Assigns the new primary key value to the instance after save.
Returns Promise
resolved with the instance(s). (In bulkCreate
all records are created in one transaction).
async { const user = await User; // in case primary key is 'id' console; console; const users = await User; console;};
Model.delete([filter][, options])
- filter (optional) - callback that takes an instance. If returns truthy value then the record is deleted, otherwise not.
- options (optional):
- options.transaction (optional) - if present, the operation is performed in this transaction.
- options.storeNames (optional) - an array of object stores, that may be needed for beforeDelete hook.
Deletes records that match the filter. If no filter specified, all records are deleted. Returns Promise
resolved with the deleted instances.
async { // deletes all records await User; // deletes all records that have age < 20 await User;};
Model.bulkDelete(instances[, options])
- instances (required) - an array of instances of the model to delete.
- options (optional):
- options.transaction (optional) - if present, the operation is performed in this transaction.
- options.storeNames (optional) - an array of object stores, that may be needed for beforeDelete hook.
Returns Promise
resolved with the instances.
Deletes instances from the object store.
Under the hood just calls delete method for each instance.
The main difference is that all records are deleted in one transaction.
async { const users = await User; await User;};
Model.findAll([filter][, options])
- filter (optional) - callback that takes an instance. If returns truthy value then the record is included, otherwise not.
- options (optional):
- options.transaction (optional) - if present, the operation is performed in this transaction.
async { const users = await User; console;};
Returns Promise
resolved with an array of instances that match the filter. If no filter specified, all records are included.
Model.findOne([filter][, options])
- filter (optional) - callback that takes an instance. If returns truthy value then this record is returned.
- options (optional):
- options.transaction (optional) - if present, the operation is performed in this transaction.
async { const user = await User; console;};
Returns Promise
resolved with the first instance that matches the filter or null
if no records match the filter.
Model.findByPrimary(primary[, options])
- primary (required) - value of the primary key field of the record to find.
- options (optional):
- options.transaction (optional) - if present, the operation is performed in this transaction.
async { const user = await User; console;};
Returns Promise
resolved with the instance that matches primary
or null if no records match.
Model.bulkSave(instances[, options])
- instances (required) - an array of instances of the model to save.
- options (optional):
- options.transaction (optional) - if present, the operation is performed in this transaction.
async { const users = await User; users; await User;};
Saves multiple instances in one transaction. Returns Promise
resolved with the instances.
Model.update(values[filter][, options])
- values (required) - either an object with fields to update or a callback that is called with an instance to update. In case of callback don't return a new value, rather modify the instance.
- filter (optional) - callback that takes an instance. If returns truthy value then the record is updated, otherwise not.
- options (optional):
- options.transaction (optional) - if present, the operation is performed in this transaction.
- options.storeNames (optional) - an array of object stores, that may be needed for beforeSave hook.
Updates records that match the filter. If no filter specified, all records are updated. Returns Promise
resolved with the updated instances.
async { // increments age by 1 in all records await User; // increments age by 1 in all records that have age < 20 await User; // sets age to 1 in all records that have age < 20 await User;};
Instance methods
Model#delete([options])
- options (optional):
- options.transaction (optional) - if present, the operation is performed in this transaction.
- options.storeNames (optional) - an array of object stores, that may be needed for beforeDelete hook.
Deletes the record using the primary key.
If the instance doesn't have the primary key field, then the method does nothing.
Returns Promise
resolved with the instance.
async { const user = await User; await user;};
Model#save([options])
- options (optional):
- options.transaction (optional) - if present, the operation is performed in this transaction.
- options.storeNames (optional) - an array of object stores, that may be needed for beforeSave hook.
Saves the record using the primary key.
The record is updated if the instance has the primary key field, otherwise the record is added.
Returns Promise
resolved with the instance.
async { // update const user = await User; userage += 1; await user; // create const user = name: 'John' age: 30 ; await user;};
Hooks
Model#beforeDelete(transaction, options)
- transaction - the transaction which deletes the record.
- options - the options with which a delete method was called.
Set this method in your model to add some operations before the record is deleted using Model.delete, Model.bulkDelete or Model#delete.
The method may be asynchronous, though you should perform only asynchronous actions related to the transaction
.
options.storeNames
from delete methods is used to open a delete transaction, so that if you need to do something involving other stores in the hook, specify options.storeNames
in the corresponding delete method.
Model#beforeSave(transaction, options)
- transaction - the transaction which deletes the record.
- options - the options with which a save (or update) method was called.
Set this method in your model to add some operations before the record is saved using Model.create, Model.bulkCreate, Model.bulkSave, Model.update or Model#save.
The method may be asynchronous, though you should perform only asynchronous actions related to the transaction
.
options.storeNames
from save (or update) methods is used to open a save transaction, so that if you need to do something involving other stores in the hook, specify options.storeNames
in the corresponding save (or update) method.
Model#toJSON()
Set this method in your model to customize what is saved to the database returning the desired object.
If this method is specified Model.fields
is not used.
Typescript Usage
Here is an example of a user model:
// you need to create a separate interface to pass it to Model // you need User to extend UserAttributes, so that you can access your custom fields // first argument is values interface// second argument is optional fields that are not necessary to set when creating an instance
Conclusion
-
Also the library exports a small helper -
promisifyRequest
, that takes an IDBRequest instance and "promisifies" it:promisifyRequest(request[, defaultValue])
Returns a
Promise
resolved with the request result ordefaultValue
. Use this helper if you need some native API requests to be promisified. -
For now
idb-model
doesn't support IDBKeyRange API, IDBIndex API and IDBCursor API. Although the first one and the second one may be useful together, the third one does not seem very useful considering that it's already used internally in someModel
methods.