389 lines
9.9 KiB
Markdown
389 lines
9.9 KiB
Markdown
|
# 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:
|
||
|
|
||
|
```sh
|
||
|
npm install idb
|
||
|
```
|
||
|
|
||
|
Then in your JS:
|
||
|
|
||
|
```js
|
||
|
import { openDb, deleteDb } from 'idb';
|
||
|
|
||
|
await openDb(…);
|
||
|
```
|
||
|
|
||
|
Or include [the script](https://github.com/jakearchibald/idb/blob/master/build/idb.js) as it is, and `idb` will exist on the global scope.
|
||
|
|
||
|
# Changes from 2.x
|
||
|
|
||
|
The library is now a module. To take advantage of this, importing has changed slightly:
|
||
|
|
||
|
```js
|
||
|
// Old 2.x way:
|
||
|
import idb from 'idb';
|
||
|
idb.open(…);
|
||
|
idb.delete(…);
|
||
|
|
||
|
// New way:
|
||
|
import { openDb, deleteDb } from 'idb';
|
||
|
openDb(…);
|
||
|
deleteDb(…);
|
||
|
```
|
||
|
|
||
|
# Examples
|
||
|
|
||
|
## Keyval Store
|
||
|
|
||
|
This is very similar to `localStorage`, but async. If this is *all* you need, you may be interested in [idb-keyval](https://www.npmjs.com/package/idb-keyval), you can always upgrade to this library later.
|
||
|
|
||
|
```js
|
||
|
const dbPromise = openDb('keyval-store', 1, upgradeDB => {
|
||
|
upgradeDB.createObjectStore('keyval');
|
||
|
});
|
||
|
|
||
|
const idbKeyval = {
|
||
|
async get(key) {
|
||
|
const db = await dbPromise;
|
||
|
return db.transaction('keyval').objectStore('keyval').get(key);
|
||
|
},
|
||
|
async set(key, val) {
|
||
|
const db = await dbPromise;
|
||
|
const tx = db.transaction('keyval', 'readwrite');
|
||
|
tx.objectStore('keyval').put(val, key);
|
||
|
return tx.complete;
|
||
|
},
|
||
|
async delete(key) {
|
||
|
const db = await dbPromise;
|
||
|
const tx = db.transaction('keyval', 'readwrite');
|
||
|
tx.objectStore('keyval').delete(key);
|
||
|
return tx.complete;
|
||
|
},
|
||
|
async clear() {
|
||
|
const db = await dbPromise;
|
||
|
const tx = db.transaction('keyval', 'readwrite');
|
||
|
tx.objectStore('keyval').clear();
|
||
|
return tx.complete;
|
||
|
},
|
||
|
async keys() {
|
||
|
const db = await dbPromise;
|
||
|
return db.transaction('keyval').objectStore('keyval').getAllKeys(key);
|
||
|
},
|
||
|
};
|
||
|
```
|
||
|
|
||
|
### Usage
|
||
|
|
||
|
```js
|
||
|
idbKeyval.set('foo', {hello: 'world'});
|
||
|
|
||
|
// logs: {hello: 'world'}
|
||
|
idbKeyval.get('foo').then(val => console.log(val));
|
||
|
```
|
||
|
|
||
|
## Set of objects
|
||
|
|
||
|
Imagine we had a set of objects like…
|
||
|
|
||
|
```json
|
||
|
{
|
||
|
"id": 123456,
|
||
|
"data": {"foo": "bar"}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### Upgrading existing DB
|
||
|
|
||
|
```js
|
||
|
const dbPromise = openDb('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
|
||
|
|
||
|
```js
|
||
|
dbPromise.then(db => {
|
||
|
const tx = db.transaction('objs', 'readwrite');
|
||
|
tx.objectStore('objs').put({
|
||
|
id: 123456,
|
||
|
data: {foo: "bar"}
|
||
|
});
|
||
|
return tx.complete;
|
||
|
});
|
||
|
```
|
||
|
|
||
|
### Getting all
|
||
|
|
||
|
```js
|
||
|
dbPromise.then(db => {
|
||
|
return db.transaction('objs')
|
||
|
.objectStore('objs').getAll();
|
||
|
}).then(allObjs => console.log(allObjs));
|
||
|
```
|
||
|
|
||
|
### Getting by ID
|
||
|
|
||
|
```js
|
||
|
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:
|
||
|
|
||
|
```js
|
||
|
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:
|
||
|
|
||
|
```js
|
||
|
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:
|
||
|
|
||
|
```js
|
||
|
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](https://simple-idb-demo.glitch.me/microtask-issue.html).
|
||
|
|
||
|
You can work around this in some versions of Firefox by using a promise polyfill that correctly uses microtasks, such as [es6-promise](https://github.com/jakearchibald/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. If you are using native ES modules, the functions are provided as individual exports, so you can `import * as idb from 'idb'` or `import { openDb, deleteDb } from 'idb'`.
|
||
|
|
||
|
### `openDb(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`.
|
||
|
|
||
|
```js
|
||
|
openDb('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));
|
||
|
```
|
||
|
|
||
|
### `deleteDb(name)`
|
||
|
|
||
|
Behaves like `indexedDB.deleteDatabase`, but returns a promise.
|
||
|
|
||
|
```js
|
||
|
deleteDb('keyval-store').then(() => console.log('done!'));
|
||
|
```
|
||
|
|
||
|
## `DB`
|
||
|
|
||
|
Properties:
|
||
|
|
||
|
* Same as equivalent properties on an instance of `IDBDatabase`:
|
||
|
* `name`
|
||
|
* `version`
|
||
|
* `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 transaction
|
||
|
* `oldVersion` - 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 errors
|
||
|
* Same as equivalent properties on an instance of `IDBTransaction`:
|
||
|
* `objectStoreNames`
|
||
|
* `mode`
|
||
|
|
||
|
Methods:
|
||
|
|
||
|
* `abort` - as `idbTransaction.abort`
|
||
|
* `objectStore` - as `idbTransaction.objectStore`, but returns an `ObjectStore`
|
||
|
|
||
|
```js
|
||
|
openDb('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 a `Cursor`:
|
||
|
* `openCursor`
|
||
|
* `openKeyCursor`
|
||
|
* `deleteIndex` - as `idbObjectStore.deleteIndex`
|
||
|
* Same as equivalent methods on an instance of `IDBObjectStore`, but returns an `Index`:
|
||
|
* `createIndex`
|
||
|
* `index`
|
||
|
* `iterateCursor` - see below
|
||
|
* `iterateKeyCursor` - see below
|
||
|
|
||
|
|
||
|
### `iterateCursor` & `iterateKeyCursor`
|
||
|
|
||
|
Due to the microtask issues in some browsers, iterating over a cursor using promises doesn't always work:
|
||
|
|
||
|
```js
|
||
|
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:
|
||
|
|
||
|
```js
|
||
|
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 a `Cursor`:
|
||
|
* `openCursor`
|
||
|
* `openKeyCursor`
|
||
|
* `iterateCursor` - as `objectStore.iterateCursor` but over the index
|
||
|
* `iterateKeyCursor` - as `objectStore.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 a `Cursor`:
|
||
|
* `advance`
|
||
|
* `continue`
|
||
|
* `continuePrimaryKey`
|