Package Exports
- idb
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (idb) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
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.
Installation
If you're using Rollup/Webpack or similar:
npm install idb
Then in your JS:
import idb from 'idb';
await idb.open(…);
Or include the script as it is, and idb
will exist on the global scope.
Examples
Keyval Store
This is very similar to localStorage
, but async. If this is all you need, you may be interested in idb-keyval, you can always upgrade to this library later.
const dbPromise = idb.open('keyval-store', 1, upgradeDB => {
upgradeDB.createObjectStore('keyval');
});
const idbKeyval = {
get(key) {
return dbPromise.then(db => {
return db.transaction('keyval')
.objectStore('keyval').get(key);
});
},
set(key, val) {
return dbPromise.then(db => {
const tx = db.transaction('keyval', 'readwrite');
tx.objectStore('keyval').put(val, key);
return tx.complete;
});
},
delete(key) {
return dbPromise.then(db => {
const tx = db.transaction('keyval', 'readwrite');
tx.objectStore('keyval').delete(key);
return tx.complete;
});
},
clear() {
return dbPromise.then(db => {
const tx = db.transaction('keyval', 'readwrite');
tx.objectStore('keyval').clear();
return tx.complete;
});
},
keys() {
return dbPromise.then(db => {
const tx = db.transaction('keyval');
const keys = [];
const store = tx.objectStore('keyval');
// This would be store.getAllKeys(), but it isn't supported by Edge or Safari.
// openKeyCursor isn't supported by Safari, so we fall back
(store.iterateKeyCursor || store.iterateCursor).call(store, cursor => {
if (!cursor) return;
keys.push(cursor.key);
cursor.continue();
});
return tx.complete.then(() => keys);
});
}
};
Usage
keyValStore.set('foo', {hello: 'world'});
// logs: {hello: 'world'}
keyValStore.get('foo').then(val => console.log(val));
Set of objects
Imagine we had a set of objects like…
{
"id": 123456,
"data": {"foo": "bar"}
}
Upgrading existing DB
const dbPromise = idb.open('keyval-store', 2, upgradeDB => {
// Note: we don't use 'break' in this switch statement,
// the fall-through behaviour is what we want.
switch (upgradeDB.oldVersion) {
case 0:
upgradeDB.createObjectStore('keyval');
case 1:
upgradeDB.createObjectStore('objs', {keyPath: 'id'});
}
});
Adding
dbPromise.then(db => {
const tx = db.transaction('objs', 'readwrite');
tx.objectStore('objs').put({
id: 123456,
data: {foo: "bar"}
});
return tx.complete;
});
Getting all
dbPromise.then(db => {
return db.transaction('objs')
.objectStore('objs').getAll();
}).then(allObjs => console.log(allObjs));
Getting by ID
dbPromise.then(db => {
return db.transaction('objs')
.objectStore('objs').get(123456);
}).then(obj => console.log(obj));
Limitations
Transaction lifetime
An IDB transaction will auto-close if it doesn't have anything to do once microtasks have been processed. As a result, this works fine:
dbPromise.then(async db => {
const tx = db.transaction('keyval', 'readwrite');
const store = tx.objectStore('keyval');
const val = await store.get('counter') || 0;
store.put(val + 1, 'counter');
return tx.complete;
});
But this doesn't:
dbPromise.then(async db => {
const tx = db.transaction('keyval', 'readwrite');
const store = tx.objectStore('keyval');
const val = await store.get('counter') || 0;
// The transaction will auto-close while the fetch is in-progress
const newVal = await fetch('/increment?val=' + val)
store.put(newVal, 'counter');
return tx.complete;
});
Promise issues in older browsers
Some older browsers don't handle promises properly, which causes issues if you do more than one thing in a transaction:
dbPromise.then(async db => {
const tx = db.transaction('keyval', 'readwrite');
const store = tx.objectStore('keyval');
const val = await store.get('counter') || 0;
// In some older browsers, the transaction closes here.
// Meaning this next line fails:
store.put(val + 1, 'counter');
return tx.complete;
});
All modern browsers have fixed this. Test your browser.
You can work around this in some versions of Firefox by using a promise polyfill that correctly uses microtasks, such as es6-promise.
API
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('keyval-store', 2, upgradeDB => {
// Note: we don't use 'break' in this switch statement,
// the fall-through behaviour is what we want.
switch (upgradeDB.oldVersion) {
case 0:
upgradeDB.createObjectStore('keyval');
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('keyval-store').then(() => console.log('done!'));
DB
Properties:
- Same as equivalent properties on an instance of
IDBDatabase
:name
version
objectStoreNames
Methods:
close
- asidbDatabase.close
transaction
- asidbDatabase.transaction
, but returns aTransaction
UpgradeDB
As DB
, except:
Properties:
transaction
- this is a property rather than a method. It's aTransaction
representing the upgrade transactionoldVersion
- the previous version of the DB seen by the browser, or 0 if it's new
Methods:
createObjectStore
- asidbDatabase.createObjectStore
, but returns anObjectStore
deleteObjectStore
- asidbDatabase.deleteObjectStore
Transaction
Properties:
complete
- a promise. Resolves when transaction completes, rejects if transaction aborts or errors- Same as equivalent properties on an instance of
IDBTransaction
:objectStoreNames
mode
Methods:
abort
- asidbTransaction.abort
objectStore
- asidbTransaction.objectStore
, but returns anObjectStore
idb.open('keyval-store', 1, upgradeDB => {
switch (upgradeDB.oldVersion) {
case 0:
upgradeDB.createObjectStore('keyval');
}
}).then(db => {
const tx = db.transaction('keyval', 'readwrite');
tx.objectStore('keyval').put('hello', 'world');
return tx.complete;
}).then(() => console.log("Done!"));
ObjectStore
Properties:
- Same as equivalent properties on an instance of
IDBObjectStore
:name
keyPath
indexNames
autoIncrement
Methods:
- Same as equivalent methods on an instance of
IDBObjectStore
, but returns a promise that resolves/rejects based on operation success/failure:put
add
delete
clear
get
getAll
getAllKeys
count
- Same as equivalent methods on an instance of
IDBObjectStore
, but returns a promise that resolves with aCursor
:openCursor
openKeyCursor
deleteIndex
- asidbObjectStore.deleteIndex
- Same as equivalent methods on an instance of
IDBObjectStore
, but returns anIndex
:createIndex
index
iterateCursor
- see belowiterateKeyCursor
- see below
iterateCursor
& iterateKeyCursor
Due to the microtask issues in some browsers, iterating over a cursor using promises doesn't always work:
const 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:
const 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:
- Same as equivalent properties on an instance of
IDBIndex
:name
keyPath
multiEntry
unique
Methods:
- Same as equivalent methods on an instance of
IDBIndex
, but returns a promise that resolves/rejects based on operation success/failure:get
getKey
getAll
getAllKeys
count
- Same as equivalent methods on an instance of
IDBIndex
, but returns a promise that resolves with aCursor
:openCursor
openKeyCursor
iterateCursor
- asobjectStore.iterateCursor
but over the indexiterateKeyCursor
- asobjectStore.iterateKeyCursor
but over the index
Cursor
Properties:
- Same as equivalent properties on an instance of
IDBCursor
:direction
key
primaryKey
value
Methods:
- Same as equivalent methods on an instance of
IDBCursor
, but returns a promise that resolves/rejects based on operation success/failure:update
delete
- Same as equivalent methods on an instance of
IDBCursor
, but returns a promise that resolves with aCursor
:advance
continue
continuePrimaryKey