• Stars
    star
    5,582
  • Rank 7,325 (Top 0.2 %)
  • Language
    TypeScript
  • License
    ISC License
  • Created over 9 years ago
  • Updated almost 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

IndexedDB, but with promises

IndexedDB with usability.

This is a tiny (~1.19kB brotli'd) library that mostly mirrors the IndexedDB API, but with small improvements that make a big difference to usability.

  1. Installation
  2. Changes
  3. Browser support
  4. API
    1. openDB
    2. deleteDB
    3. unwrap
    4. wrap
    5. General enhancements
    6. IDBDatabase enhancements
    7. IDBTransaction enhancements
    8. IDBCursor enhancements
    9. Async iterators
  5. Examples
  6. TypeScript

Installation

Using npm

npm install idb

Then, assuming you're using a module-compatible system (like webpack, Rollup etc):

import { openDB, deleteDB, wrap, unwrap } from 'idb';

async function doDatabaseStuff() {
  const db = await openDB(…);
}

Directly in a browser

Using the modules method directly via jsdelivr:

<script type="module">
  import { openDB, deleteDB, wrap, unwrap } from 'https://cdn.jsdelivr.net/npm/idb@8/+esm';

  async function doDatabaseStuff() {
    const db = await openDB(…);
  }
</script>

Using external script reference

<script src="https://cdn.jsdelivr.net/npm/idb@8/build/umd.js"></script>
<script>
  async function doDatabaseStuff() {
    const db = await idb.openDB(…);
  }
</script>

A global, idb, will be created, containing all exports of the module version.

Changes

See details of (potentially) breaking changes.

Browser support

This library targets modern browsers, as in Chrome, Firefox, Safari, and other browsers that use those engines, such as Edge. IE is not supported.

API

openDB

This method opens a database, and returns a promise for an enhanced IDBDatabase.

const db = await openDB(name, version, {
  upgrade(db, oldVersion, newVersion, transaction, event) {
    // …
  },
  blocked(currentVersion, blockedVersion, event) {
    // …
  },
  blocking(currentVersion, blockedVersion, event) {
    // …
  },
  terminated() {
    // …
  },
});
  • name: Name of the database.
  • version (optional): Schema version, or undefined to open the current version.
  • upgrade (optional): Called if this version of the database has never been opened before. Use it to specify the schema for the database. This is similar to the upgradeneeded event in plain IndexedDB.
    • db: An enhanced IDBDatabase.
    • oldVersion: Last version of the database opened by the user.
    • newVersion: Whatever new version you provided.
    • transaction: An enhanced transaction for this upgrade. This is useful if you need to get data from other stores as part of a migration.
    • event: The event object for the associated upgradeneeded event.
  • blocked (optional): Called if there are older versions of the database open on the origin, so this version cannot open. This is similar to the blocked event in plain IndexedDB.
    • currentVersion: Version of the database that's blocking this one.
    • blockedVersion: The version of the database being blocked (whatever version you provided to openDB).
    • event: The event object for the associated blocked event.
  • blocking (optional): Called if this connection is blocking a future version of the database from opening. This is similar to the versionchange event in plain IndexedDB.
    • currentVersion: Version of the open database (whatever version you provided to openDB).
    • blockedVersion: The version of the database that's being blocked.
    • event: The event object for the associated versionchange event.
  • terminated (optional): Called if the browser abnormally terminates the connection, but not on regular closures like calling db.close(). This is similar to the close event in plain IndexedDB.

deleteDB

Deletes a database.

await deleteDB(name, {
  blocked() {
    // …
  },
});
  • name: Name of the database.
  • blocked (optional): Called if the database already exists and there are open connections that don’t close in response to a versionchange event, the request will be blocked until they all close.
    • currentVersion: Version of the database that's blocking the delete operation.
    • event: The event object for the associated 'versionchange' event.

unwrap

Takes an enhanced IndexedDB object and returns the plain unmodified one.

const unwrapped = unwrap(wrapped);

This is useful if, for some reason, you want to drop back into plain IndexedDB. Promises will also be converted back into IDBRequest objects.

wrap

Takes an IDB object and returns a version enhanced by this library.

const wrapped = wrap(unwrapped);

This is useful if some third party code gives you an IDBDatabase object and you want it to have the features of this library.

General enhancements

Once you've opened the database the API is the same as IndexedDB, except for a few changes to make things easier.

Firstly, any method that usually returns an IDBRequest object will now return a promise for the result.

const store = db.transaction(storeName).objectStore(storeName);
const value = await store.get(key);

Promises & throwing

The library turns all IDBRequest objects into promises, but it doesn't know in advance which methods may return promises.

As a result, methods such as store.put may throw instead of returning a promise.

If you're using async functions, there's no observable difference.

Transaction lifetime

TL;DR: Do not await other things between the start and end of your transaction, otherwise the transaction will close before you're done.

An IDB transaction auto-closes if it doesn't have anything left do once microtasks have been processed. As a result, this works fine:

const tx = db.transaction('keyval', 'readwrite');
const store = tx.objectStore('keyval');
const val = (await store.get('counter')) || 0;
await store.put(val + 1, 'counter');
await tx.done;

But this doesn't:

const tx = db.transaction('keyval', 'readwrite');
const store = tx.objectStore('keyval');
const val = (await store.get('counter')) || 0;
// This is where things go wrong:
const newVal = await fetch('/increment?val=' + val);
// And this throws an error:
await store.put(newVal, 'counter');
await tx.done;

In this case, the transaction closes while the browser is fetching, so store.put fails.

IDBDatabase enhancements

Shortcuts to get/set from an object store

It's common to create a transaction for a single action, so helper methods are included for this:

// Get a value from a store:
const value = await db.get(storeName, key);
// Set a value in a store:
await db.put(storeName, value, key);

The shortcuts are: get, getKey, getAll, getAllKeys, count, put, add, delete, and clear. Each method takes a storeName argument, the name of the object store, and the rest of the arguments are the same as the equivalent IDBObjectStore method.

Shortcuts to get from an index

The shortcuts are: getFromIndex, getKeyFromIndex, getAllFromIndex, getAllKeysFromIndex, and countFromIndex.

// Get a value from an index:
const value = await db.getFromIndex(storeName, indexName, key);

Each method takes storeName and indexName arguments, followed by the rest of the arguments from the equivalent IDBIndex method.

IDBTransaction enhancements

tx.store

If a transaction involves a single store, the store property will reference that store.

const tx = db.transaction('whatever');
const store = tx.store;

If a transaction involves multiple stores, tx.store is undefined, you need to use tx.objectStore(storeName) to get the stores.

tx.done

Transactions have a .done promise which resolves when the transaction completes successfully, and otherwise rejects with the transaction error.

const tx = db.transaction(storeName, 'readwrite');
await Promise.all([
  tx.store.put('bar', 'foo'),
  tx.store.put('world', 'hello'),
  tx.done,
]);

If you're writing to the database, tx.done is the signal that everything was successfully committed to the database. However, it's still beneficial to await the individual operations, as you'll see the error that caused the transaction to fail.

IDBCursor enhancements

Cursor advance methods (advance, continue, continuePrimaryKey) return a promise for the cursor, or null if there are no further values to provide.

let cursor = await db.transaction(storeName).store.openCursor();

while (cursor) {
  console.log(cursor.key, cursor.value);
  cursor = await cursor.continue();
}

Async iterators

You can iterate over stores, indexes, and cursors:

const tx = db.transaction(storeName);

for await (const cursor of tx.store) {
  // …
}

Each yielded object is an IDBCursor. You can optionally use the advance methods to skip items (within an async iterator they return void):

const tx = db.transaction(storeName);

for await (const cursor of tx.store) {
  console.log(cursor.value);
  // Skip the next item
  cursor.advance(2);
}

If you don't manually advance the cursor, cursor.continue() is called for you.

Stores and indexes also have an iterate method which has the same signature as openCursor, but returns an async iterator:

const index = db.transaction('books').store.index('author');

for await (const cursor of index.iterate('Douglas Adams')) {
  console.log(cursor.value);
}

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.

import { openDB } from 'idb';

const dbPromise = openDB('keyval-store', 1, {
  upgrade(db) {
    db.createObjectStore('keyval');
  },
});

export async function get(key) {
  return (await dbPromise).get('keyval', key);
}
export async function set(key, val) {
  return (await dbPromise).put('keyval', val, key);
}
export async function del(key) {
  return (await dbPromise).delete('keyval', key);
}
export async function clear() {
  return (await dbPromise).clear('keyval');
}
export async function keys() {
  return (await dbPromise).getAllKeys('keyval');
}

Article store

import { openDB } from 'idb/with-async-ittr.js';

async function demo() {
  const db = await openDB('Articles', 1, {
    upgrade(db) {
      // Create a store of objects
      const store = db.createObjectStore('articles', {
        // The 'id' property of the object will be the key.
        keyPath: 'id',
        // If it isn't explicitly set, create a value by auto incrementing.
        autoIncrement: true,
      });
      // Create an index on the 'date' property of the objects.
      store.createIndex('date', 'date');
    },
  });

  // Add an article:
  await db.add('articles', {
    title: 'Article 1',
    date: new Date('2019-01-01'),
    body: '…',
  });

  // Add multiple articles in one transaction:
  {
    const tx = db.transaction('articles', 'readwrite');
    await Promise.all([
      tx.store.add({
        title: 'Article 2',
        date: new Date('2019-01-01'),
        body: '…',
      }),
      tx.store.add({
        title: 'Article 3',
        date: new Date('2019-01-02'),
        body: '…',
      }),
      tx.done,
    ]);
  }

  // Get all the articles in date order:
  console.log(await db.getAllFromIndex('articles', 'date'));

  // Add 'And, happy new year!' to all articles on 2019-01-01:
  {
    const tx = db.transaction('articles', 'readwrite');
    const index = tx.store.index('date');

    for await (const cursor of index.iterate(new Date('2019-01-01'))) {
      const article = { ...cursor.value };
      article.body += ' And, happy new year!';
      cursor.update(article);
    }

    await tx.done;
  }
}

TypeScript

This library is fully typed, and you can improve things by providing types for your database:

import { openDB, DBSchema } from 'idb';

interface MyDB extends DBSchema {
  'favourite-number': {
    key: string;
    value: number;
  };
  products: {
    value: {
      name: string;
      price: number;
      productCode: string;
    };
    key: string;
    indexes: { 'by-price': number };
  };
}

async function demo() {
  const db = await openDB<MyDB>('my-db', 1, {
    upgrade(db) {
      db.createObjectStore('favourite-number');

      const productStore = db.createObjectStore('products', {
        keyPath: 'productCode',
      });
      productStore.createIndex('by-price', 'price');
    },
  });

  // This works
  await db.put('favourite-number', 7, 'Jen');
  // This fails at compile time, as the 'favourite-number' store expects a number.
  await db.put('favourite-number', 'Twelve', 'Jake');
}

To define types for your database, extend DBSchema with an interface where the keys are the names of your object stores.

For each value, provide an object where value is the type of values within the store, and key is the type of keys within the store.

Optionally, indexes can contain a map of index names, to the type of key within that index.

Provide this interface when calling openDB, and from then on your database will be strongly typed. This also allows your IDE to autocomplete the names of stores and indexes.

Opting out of types

If you call openDB without providing types, your database will use basic types. However, sometimes you'll need to interact with stores that aren't in your schema, perhaps during upgrades. In that case you can cast.

Let's say we were renaming the 'favourite-number' store to 'fave-nums':

import { openDB, DBSchema, IDBPDatabase } from 'idb';

interface MyDBV1 extends DBSchema {
  'favourite-number': { key: string; value: number };
}

interface MyDBV2 extends DBSchema {
  'fave-num': { key: string; value: number };
}

const db = await openDB<MyDBV2>('my-db', 2, {
  async upgrade(db, oldVersion) {
    // Cast a reference of the database to the old schema.
    const v1Db = db as unknown as IDBPDatabase<MyDBV1>;

    if (oldVersion < 1) {
      v1Db.createObjectStore('favourite-number');
    }
    if (oldVersion < 2) {
      const store = v1Db.createObjectStore('favourite-number');
      store.name = 'fave-num';
    }
  },
});

You can also cast to a typeless database by omitting the type, eg db as IDBPDatabase.

Note: Types like IDBPDatabase are used by TypeScript only. The implementation uses proxies under the hood.

Developing

npm run dev

This will also perform type testing.

To test, navigate to build/test/ in a browser. You'll need to set up a basic web server for this.

More Repositories

1

svgomg

Web GUI for SVGO
JavaScript
5,792
star
2

idb-keyval

A super-simple-small promise-based keyval store implemented with IndexedDB
TypeScript
2,446
star
3

sprite-cow

Sprite Cow helps you get the background-position, width and height of sprites within a spritesheet as a nice bit of copyable css.
JavaScript
1,280
star
4

offline-wikipedia

Demo of how something like Wikipedia could be offline-first
HTML
812
star
5

isserviceworkerready

Tracking the status of ServiceWorker in browsers
HTML
563
star
6

simple-serviceworker-tutorial

A really simple ServiceWorker example, designed to be an interactive introduction to ServiceWorker
JavaScript
390
star
7

wittr

Silly demo app for an online course
JavaScript
387
star
8

navigation-transitions

335
star
9

trained-to-thrill

Trains! Yey!
JavaScript
325
star
10

appcache-demo

Python
242
star
11

jakearchibald.com

TypeScript
226
star
12

sass-ie

Writing mobile-first styles without leaving IE<9 behind
Shell
185
star
13

big-web-quiz

JavaScript
110
star
14

linear-easing-generator

TypeScript
102
star
15

wordle-analyzer

TypeScript
95
star
16

tweetdeck-prototype

(mobile|offline)-first Tweetdeck prototype
JavaScript
79
star
17

request-quest

JavaScript
66
star
18

git-convenience

Tools to make git on the terminal a little more pleasureable
Shell
61
star
19

I-rudely-reject-pull-requests-to-this-repo

I will rudely and childishly reject pull requests to the repo
57
star
20

typescript-worker-example

TypeScript
55
star
21

streaming-html

HTML
52
star
22

http2-push-test

JavaScript
51
star
23

safari-14-idb-fix

Working around a Safari IndexedDB bug
JavaScript
42
star
24

cors-playground

TypeScript
41
star
25

sw-routes

Just playing with some ideas
TypeScript
41
star
26

byte-storage

41
star
27

http203-playlist

JavaScript
33
star
28

preso

JavaScript
32
star
29

async-waituntil-polyfill

JavaScript
30
star
30

streaming-include

Throwing around design ideas for a streaming include api
JavaScript
29
star
31

Woosh

Speed testing framework
JavaScript
28
star
32

houdini-paint-flecks

JavaScript
22
star
33

responsive-gallery

An experiment in client-side responsive imagery
JavaScript
18
star
34

LinkTracker

A simple bit of JavaScript to catch clicks to a link that can be logged by any server-side tracking system
JavaScript
18
star
35

sse-fetcher

Server-sent events rewritten on top of fetch
TypeScript
18
star
36

wittr-modern

CSS
17
star
37

easing-worklet

15
star
38

canvas-snow

JavaScript
15
star
39

jank-invaders

JavaScript
15
star
40

mankini

JavaScript
15
star
41

google-album-downloader

Just playing around with some ideas
TypeScript
13
star
42

minesweeper

Just doodling
TypeScript
13
star
43

payments

JavaScript
12
star
44

me

All about me.
12
star
45

ebook-demo

JavaScript
10
star
46

remove-old-service-worker

JavaScript
10
star
47

sw-cache-update-example

JavaScript
10
star
48

scrolly-cliche

JavaScript
10
star
49

font-testing

Testing @font-face support in browsers
PHP
10
star
50

f1-site-optim

Just playing around
TypeScript
9
star
51

supercharged-blog

CSS
9
star
52

range-request-test

This tests how browsers cope with video & range requests.
JavaScript
9
star
53

sass-lessons

Small sass project for a workshop
Shell
9
star
54

frontend-lessons

Teaching someone bits of frontend
8
star
55

flickr-set-fetcher

One-way syncs a Flickr set to disk
JavaScript
7
star
56

streaming-html-spec

JavaScript
7
star
57

image-experiments

Just playing
TypeScript
7
star
58

send-more-bytes-than-length

JavaScript
6
star
59

wikipedia-and-dictionary-title-search

TypeScript
6
star
60

rollup-import-maps-demo

JavaScript
6
star
61

service-worker-benchmark

HTML
6
star
62

configs

Shell
6
star
63

google-photos-downloader-deno

TypeScript
6
star
64

simple-transition

A small library for controlling transitions with JavaScript
JavaScript
5
star
65

sketch-chain

TypeScript
5
star
66

appcache2serviceworker

Not ready yet
JavaScript
5
star
67

ZoomFix-jQuery-Plugin

Patching jQuery functions that return incorrect values when various browsers are zoomed
JavaScript
5
star
68

webwords

JavaScript
5
star
69

http-tinkering

JavaScript
5
star
70

portal-demos

HTML
5
star
71

img-source-sizes-test

TypeScript
4
star
72

jakearchibalddemos

HTML
4
star
73

project-start

Project bootstrapping
JavaScript
4
star
74

f1predictions

F1 predictions thingy. Just me playing with Django really
Python
4
star
75

module-script-demos

JavaScript
3
star
76

accept-encoding-range-test

JavaScript
3
star
77

showrss-downloader

JavaScript
3
star
78

mq-apply

JavaScript
3
star
79

http203-slides

JavaScript
3
star
80

h2-priority-test

JavaScript
3
star
81

streaming-handlebars

An experiment
JavaScript
3
star
82

appcache-credentials

Demo of appcache security issue
JavaScript
3
star
83

money-manager

JavaScript
3
star
84

cache-credentials

JavaScript
2
star
85

idb-keyval-build-example

JavaScript
2
star
86

rollup-hash-bug

JavaScript
2
star
87

thing.html

HTML
2
star
88

svgomg-slow

HTML
2
star
89

idb-minimal-webpack

A minimal webpack config showing the IDB library
JavaScript
2
star
90

rollup-hash-bug-2

JavaScript
2
star
91

pinch-zoomer

JavaScript
2
star
92

chunked-encoding-request-test

JavaScript
1
star
93

jakearchibald

1
star
94

thing.txt

HTML
1
star
95

xbmc-remote

JavaScript
1
star
96

multi-dev

Test project for a workshop
1
star
97

rollup-child-build

Just playin
JavaScript
1
star
98

multi-thread-svg-render

1
star
99

static-stuff

GLSL
1
star
100

history-timeline-generator

TypeScript
1
star