What is idb?
The idb npm package is a small library that mirrors IndexedDB, but features a more user-friendly API. It allows developers to interact with IndexedDB using promises instead of events, which simplifies the code and improves readability and maintainability. It's particularly useful for applications that need to store large amounts of data on the client side.
What are idb's main functionalities?
Database Creation and Versioning
This code sample demonstrates how to create a new IndexedDB database and an object store within it using idb. The `openDB` function is used to open a database, and it takes the database name, version number, and an upgrade callback as arguments. The upgrade callback is executed when the database is created or upgraded.
const dbPromise = idb.openDB('my-database', 1, {
upgrade(db) {
db.createObjectStore('keyval');
}
});
Adding and Retrieving Data
This code sample shows how to add data to and retrieve data from an object store in IndexedDB using idb. The `addData` function creates a transaction, accesses the object store, and uses the `put` method to add data. The `getData` function retrieves data using the `get` method.
const dbPromise = idb.openDB('my-database', 1);
async function addData(key, val) {
const db = await dbPromise;
const tx = db.transaction('keyval', 'readwrite');
const store = tx.objectStore('keyval');
store.put(val, key);
await tx.done;
}
async function getData(key) {
const db = await dbPromise;
return db.transaction('keyval').objectStore('keyval').get(key);
}
Cursor Iteration
This code sample illustrates how to iterate over entries in an object store using a cursor. The `iterateCursor` function opens a read-only transaction, accesses the object store, and uses `openCursor` to start the iteration. The cursor is advanced using the `continue` method.
const dbPromise = idb.openDB('my-database', 1);
async function iterateCursor() {
const db = await dbPromise;
const tx = db.transaction('keyval', 'readonly');
const store = tx.objectStore('keyval');
let cursor = await store.openCursor();
while (cursor) {
console.log(cursor.key, cursor.value);
cursor = await cursor.continue();
}
}
Other packages similar to idb
dexie
Dexie.js is a wrapper library for IndexedDB that allows for simpler syntax and complex queries. It provides a more powerful query language and schema handling compared to idb, which makes it suitable for more complex applications that require advanced database features.
pouchdb
PouchDB is an open-source JavaScript database inspired by Apache CouchDB that is designed to run well within the browser. It enables applications to store data locally while offline, then synchronize it with CouchDB and compatible servers when the application is back online, unlike idb which is purely a client-side solution.
IndexedDB Promised
This is a tiny library that mirrors IndexedDB, but replaces the weird IDBRequest
objects with promises, plus a couple of other small changes.
Limitations
Transaction lifetime
At time of writing, all browsers aside from Chrome don't treat promise callbacks as microtasks, or call microtasks incorrectly. This means transactions end by the time promise callbacks are called. In practice, this means you cannot perform transactions that involve waiting for a value, then using it within the same transaction.
let tx = db.transaction('store', 'readwrite');
let store = db.objectStore('store');
store.get('hello').then(val => store.put(val, 'foo'));
The above will fail in browsers other than Chrome, because the transaction has closed by the time we get to the .put
.
You can work around this in Firefox by using a promise polyfill that correctly uses microtasks, such as es6-promise.
Safari
This is a simple wrapper library, so you're exposed to bugs in the underlying implementation. Unfortunately Safari has a lot of these.
idb
This is your entry point to the API. It's exposed to the global scope unless you're using a module system such as browserify, in which case it's the exported object.
idb.open(name, version, upgradeCallback)
This method returns a promise that resolves to a DB
.
name
and version
behave as they do in indexedDB.open
.
upgradeCallback
is called if version
is greater than the version last opened. It's similar to IDB's onupgradeneeded
. The callback receives an instance of UpgradeDB
.
idb.open('my-database', 2, upgradeDB => {
switch (upgradeDB.oldVersion) {
case 0:
upgradeDB.createObjectStore('key-val');
case 1:
upgradeDB.createObjectStore('stuff', {keyPath: ''});
}
}).then(db => console.log("DB opened!", db));
idb.delete(name)
Behaves like indexedDB.deleteDatabase
, but returns a promise.
idb.delete('my-database').then(_ => console.log('done!'));
DB
Properties:
name
- as idbDatabase.name
version
- as idbDatabase.version
objectStoreNames
- as idbDatabase.objectStoreNames
Methods:
close
- as idbDatabase.close
transaction
- as idbDatabase.transaction
, but returns a Transaction
UpgradeDB
As DB
, except:
Properties:
transaction
- this is a property rather than a method. It's a Transaction
representing the upgrade transactionoldVersion
- the previous version of the DB seen by the browser, or 0 if it's new
Methods:
createObjectStore
- as idbDatabase.createObjectStore
, but returns an ObjectStore
deleteObjectStore
- as idbDatabase.deleteObjectStore
Transaction
Properties:
complete
- a promise. Resolves when transaction completes, rejects if transaction aborts or errorsobjectStoreNames
- as idbTransaction.objectStoreNames
mode
- as idbTransaction.mode
Methods:
abort
- as idbTransaction.abort
objectStore
- as idbTransaction.objectStore
, but returns an ObjectStore
idb.open('my-database', 1, upgradeDB => {
switch (upgradeDB.oldVersion) {
case 0:
upgradeDB.createObjectStore('key-val');
}
}).then(db => {
let tx = db.transaction('key-val');
tx.objectStore('key-val').put('hello', 'world');
return tx.complete;
}).then(_ => console.log("Done!"));
ObjectStore
Properties:
name
- as idbObjectStore.name
keyPath
- as idbObjectStore.keyPath
indexNames
- as idbObjectStore.indexNames
autoIncrement
- as idbObjectStore.autoIncrement
Methods:
put
- as idbObjectStore.put
but returns a promise that resolves/rejects based on operation success/failureadd
- as idbObjectStore.add
but returns a promise that resolves/rejects based on operation success/failuredelete
- as idbObjectStore.delete
but returns a promise that resolves/rejects based on operation success/failureclear
- as idbObjectStore.clear
but returns a promise that resolves/rejects based on operation success/failureget
- as idbObjectStore.get
but returns a promise that resolves/rejects based on operation success/failuregetAll
- as idbObjectStore.getAll
but returns a promise that resolves/rejects based on operation success/failuregetAllKeys
- as idbObjectStore.getAllKeys
but returns a promise that resolves/rejects based on operation success/failurecount
- as idbObjectStore.count
but returns a promise that resolves/rejects based on operation success/failureopenCursor
- as idbObjectStore.openCursor
but returns a promise that resolves with a Cursor
openKeyCursor
- as idbObjectStore.openKeyCursor
but returns a promise that resolves with a Cursor
deleteIndex
- as idbObjectStore.deleteIndex
createIndex
- as idbObjectStore.createIndex
but returns an Index
index
- as idbObjectStore.index
but returns an Index
iterateCursor
- see belowiterateKeyCursor
- see below
iterateCursor
& iterateKeyCursor
Due to the microtask issues in most browsers, iterating over a cursor using promises doesn't really work:
let tx = db.transaction('stuff');
tx.objectStore('stuff').openCursor().then(function cursorIterate(cursor) {
if (!cursor) return;
console.log(cursor.value);
return cursor.continue().then(cursorIterate);
});
tx.complete.then(_ => console.log('done'));
So in the mean time, iterateCursor
and iterateKeyCursor
map to openCursor
& openKeyCursor
, take identical arguments, plus an additional callback that receives an IDBCursor
, so the above example becomes:
let tx = db.transaction('stuff');
tx.objectStore('stuff').iterateCursor(cursor => {
if (!cursor) return;
console.log(cursor.value);
cursor.continue();
});
tx.complete.then(_ => console.log('done'));
The intent is to remove iterateCursor
and iterateKeyCursor
from the library once browsers support promises and microtasks correctly.
Index
Properties:
name
- as idbIndex.name
keyPath
- as idbIndex.keyPath
multiEntry
- as idbIndex.multiEntry
unique
- as idbIndex.unique
Methods:
get
- as idbIndex.get
, but returns a promise that resolves/rejects based on operation success/failuregetKey
- as idbIndex.getKey
, but returns a promise that resolves/rejects based on operation success/failuregetAll
- as idbIndex.getAll
, but returns a promise that resolves/rejects based on operation success/failuregetAllKeys
- as idbIndex.getAllKeys
, but returns a promise that resolves/rejects based on operation success/failurecount
- as idbIndex.count
, but returns a promise that resolves/rejects based on operation success/failureopenCursor
- as idbIndex.openCursor
but returns a promise that resolves with a Cursor
openKeyCursor
- as idbIndex.openKeyCursor
but returns a promise that resolves with a Cursor
iterateCursor
- as objectStore.iterateCursor
but over the indexiterateKeyCursor
- as objectStore.iterateKeyCursor
but over the index
Cursor
Properties:
direction
- as idbCursor.direction
key
- as idbCursor.key
primaryKey
- as idbCursor.primaryKey
value
- as idbCursor.value
Methods:
update
- as idbCursor.update
but returns a promise that resolves/rejects based on operation success/failuredelete
- as idbCursor.delete
but returns a promise that resolves/rejects based on operation success/failureadvance
- as idbCursor.advance
but returns a promise that resolves to a Cursor
continue
- as idbCursor.continue
but returns a promise that resolves to a Cursor
continuePrimaryKey
- as idbCursor.continuePrimaryKey
but returns a promise that resolves to a Cursor