The indexed-db.es6 is ES2015-style wrapper of the native IndexedDB HTML5 document-oriented database.
The indexed-db.es6 provides the following improvements and modifications over the native IndexedDB:
- declarative schema with data-migration support
- Promise-oriented API
- renaming of some declaratively-named methods (for example
transaction
) to imperative names (startTransaction
). - API does not expose the native schema-manipulation methods
- read-only transactions expose only read-only API
- support for advanced record filtering
- advanced query API
- record lists allowing lazy-fetching of "pages" of records
- very-well documented code :)
- API split into ES2015 modules
- practically all native IndexedDB features are available through the API
Here you will find the basic information on how to use the indexed-db.es6 library. Please check the Wiki for a more detailed description and examples.
You can install the indexed-db.es6 library into your project using npm:
npm install --save indexed-db.es6
...or using bower:
bower install --save indexed-db.es6
Next you can choose to use either the ES2015 modules (located in es2015/
), or
you may use any transpiler you like (for example Babel or Traceur) to transpile
the ES2015 modules to a module system of your choice.
To use indexed-db.es6 to create and connect to a database, use the DBFactory
class:
import DBFactory from "indexed-db.es6/es2015/DBFactory.js"
DBFactory.open("my database", {
version: 1,
objectStores: [{
name: "fooBar",
keyPath: null,
autoIncrement: true,
indexes: [{
name: "some index",
keyPath: "id",
unique: false,
multiEntry: true
}]
}]
}).then((database) => {
// do some stuff
database.close()
})
When using plain objects to describe the schema, many fields may be left out if the default value is meant to be used (see the schema classes).
Alternatively, if you prefer, you may use the following syntax to specify your database schema:
import DBFactory from "indexed-db.es6/es2015/DBFactory.js"
import DatabaseSchema from "indexed-db.es6/es2015/schema/DatabaseSchema.js"
import ObjectStoreSchema from "indexed-db.es6/es2015/schema/ObjectStoreSchema.js"
import IndexSchema from "indexed-db.es6/es2015/schema/IndexSchema.js"
DBFactory.open("my database",
new DatabaseSchema(1,
new ObjectStoreSchema("fooBar", null, true,
new IndexSchema("some index", "id", false, true)
)
)
}).then((database) => {
// do some stuff
database.close()
})
All operations performed on an IndexedDB database must always be performed in a transaction.
To start a transaction, you need to specify whether the transaction should be a read-only or read-write transaction, and the names of the object stores you want to access in the transaction:
database.runReadOnlyTransaction(["foo", "bar"], (foo, bar, abort) => {
// do some stuff, or abort the transaction by calling abort()
}).then((result) => {
// Do something with the result of the promise returned from the transaction
// callback. This callback will be called after the transaction is completed.
}).catch((error) => {
// Something went wrong. Check the error for details.
})
// read-write transaction:
database.runTransaction(["foo", "bar"], (foo, bar, abort) => {
// do some stuff, or abort the transaction by calling abort()
}).then((result) => {
// Do something with the result of the promise returned from the transaction
// callback. This callback will be called after the transaction is completed.
}).catch((error) => {
// Something went wrong. Check the error for details.
})
Note that while a read-only transaction has shared locks on the object stores, a read-write transaction has an exclusive lock on all object stores available to it, meaning that any other transaction on any of the object stores in the read-write transaction will be delayed until the read-write transaction is either completed or aborted. This will not prevent yout to start another transaction or perform operations, but executing those operations on the database will be delayed.
It you need a more low-level access to the transaction, use the
startTransaction
and startReadOnlyTransaction
methods instead. There is
also the getObjectStore
method for creating a new read-only transaction with
access to only a single object store.
To fetch records from object stores (or through indexes), you can either fetch single records:
myObjectStore.get(primaryKey).then((record) => {
// do something
})
...or execute a query (the API will attempt to use the defined indexes to optimize the performance):
myObjectStore.query(filter, sortBy, offset, limit).then((records) => {
// do something
})
If that is too fancy for you, you can go more low-level to iterate through the records:
import CursorDirection
from "indexed-db.es6/es2015/object-store/CursorDirection.js"
// or you can import the NEXT and PREVIOUS constants like this:
// import {NEXT, PREVIOUS}
// from "indexed-db.es6/es2015/object-store/CursorDirection"
// connect to the database, start a transaction
myObjectStore.forEach(someFilter, CursorDirection.NEXT, (record) => {
// do something with the record
}).then((recordCount) => {
// all records matching the filter have been traversed
})
// you may also use the "NEXT" and "PREVIOUS" strings (letter case does not
// matter):
myObjectStore.forEach(someFilter, "previous", (record) => {
// do something with the record
}).then((recordCount) => {
// all records matching the filter has been traversed
})
...or fetch all records to an array:
myObjectStore.getAll(optionalFilter, optionalDirection).then((allRecords) => {
// do something
})
...or just count records matching a filter:
myObjectStore.count(optionalFilter).then((recordCount) => {
// do something with the record count
})
...or you can use the record list (extension of the native JavaScript Array
)
which allows processing the records in "pages", allowing you to fetch the next
page of records lazily even if the original transaction has already been
terminated:
import CursorDirection
from "indexed-db.es6/es2015/object-store/CursorDirection.js"
// connect to the database, start a transaction
myObjectStore.list(myFilter, CursorDirection.PREVIOUS, myPageSize).
then((list) => {
// do something with the first page
// after some time:
if (list.hasNextPage) {
list.fetchNextPage().then((nextPage) => {
// do something with the next page of records
})
}
})
Alternatively, you may open a cursor (or a key cursor) and iterate the object store or index, but this if fairly low-level API, close to the native Indexed DB API.
All operations listed here are available only within a read-write transaction.
New records can be created in an object store using the add
method:
myObjectStore.add("this is a record").then((primaryKey) => {
// record will be added when the transaction completes
})
myObjectStore.add({
created: new Date()
note: "this is also a record"
}).then((primaryKey) => {
// record will be added when the transaction completes
})
Existing records may be updated using the put
method:
myObjectStore.put({
id: 123,
updated: new Date(),
note: "this record will be updated"
}).then((primaryKey) => {
// record will be updated when the transaction completes
})
// for object stores using out-of-line keys (stored outside of records),
// specify the key as the second argument:
myObjectStore.put({
updated: new Date(),
note: "this record will be updated"
}, 123).then((primaryKey) => {
// record will be updated when the transaction completes
})
To update multiple records, use the updateQuery
method:
myObjectStore.updateQuery(filter, sortBy, offset, limit)((record, id) => {
// modify the record or create a new one with the same ID
return modifiedRecord
}).then(() => {
// query finished
})
Records may be deleted using the delete
method:
myObjectStore.delete(primaryKeyOrFilter).then(() => {
// the record(s) will be deleted when the transaction completes
})
To delete multiple records, it is preferable to use the deleteQuery
method:
myObjectStore.deleteQuery(filter, sortBy, offset, limit).then(() => {
// query finished
})
Finally, you may delete all records in an object store using the clear
method:
myObjectStore.clear().then(() => {
// the object store will be empty when the transaction completes
})
Alternatively, you may modify and / or delete records through a cursor opened within a read-write transaction, but this is fairly low-level API, close to the native Indexed DB API.
The source code is well documented using JSDoc docblock comments. Go ahead and take a look!
The following browsers are supported (all tests are passing):
- Google Chrome
- Chromium
- Firefox
- Seznam.cz Browser
- Opera
- Chrome for Android
- Firefox for Android
- Opera for Android
The following browsers are theoretically compatible (each seems to use engine of one of the supported browsers), but not tested:
- Android Browser (4.4+)
The following browsers are not fully supported at the moment:
- Internet Explorer: Does not support compound keys and key paths and no new versions are expected to be released
- Safari:
- Does not allow records in separate object stores to have the same primary key, the next version should fix this
- Requires Apple hardware to run legally
- iOS Safari: same issues as Safari
- Chrome for iOS: uses Safari-like UIWebView (due to Apple's App Store terms), same issues as Safari
- Firefox for iOS: uses Safari-like UIWebView (due to Apple's App Store terms), same issues as Safari
- Opera for iOS: uses Safari-like UIWebView (due to Apple's App Store terms), same issues as Safari
You can still use these browsers with indexed-db.es6
, or any browser without
any native IndexedDB support that supports
WebSQL, using the
IndexedDBShim polyfill.
There are no current plans for additional features (unless a good case for adding them is made), but the project accepts bug fixes if new bugs are discovered.
Time is precious, so, if you would like to contribute (bug fixing, test writing or feature addition), follow these steps:
- fork
- implement (follow the code style)
- pull request
- wait for approval/rejection of the pull request
Filing a bug issue might get me to fix the bug, but filing a feature request will not as I don't have the time to work on this project full-time. Thank you for understanding.
The indexed-db.es6 is a relatively complex library that allows you to use the full power of IndexedDB through both the low- and high-level APIs. That being said, you may not need such a tool and may be looking for a simpler solution:
- PouchDB if you require only a single object store per database.
- DB.js if you do not need transaction support, or don't need certain low-level or high-level features of indexed-db.es6, or you are not concerned about the database being opened from several browser tabs simultaneously.
- IDB Wrapper if you preffer callbacks to Promises.
- jQuery IndexedDB if you think you need to use jQuery for everything (which is a bad idea, in my opinion, but that's up to you to decide).
- YDN DB if you don't need any support for versioning your database.
- IDBWrapper if you don't need transacions on multiple object stores and prefer callbacks to promises, and don't need any high-level API.