idb

repository·main·Indexed 27 days ago

https://github.com/jakearchibald/idb

A tiny Promise-based wrapper around the IndexedDB API (version 8.0.3) that improves usability and developer experience. It provides enhanced IDBDatabase and IDBTransaction objects, shortcut methods for object stores and indexes, and support for async iterators. The library includes strong typing via the DBSchema interface for TypeScript users and utilities like openDB, deleteDB, wrap, and unwrap to manage database lifecycles and convert between enhanced and plain IndexedDB objects.

Tokens
3.3K
Snippets
10
Records
15
Agent score
43%

What's inside idb

  1. Install the idb library

    main

    You can install idb via npm for module-compatible systems (like webpack or Rollup), or use it directly in a browser via jsdelivr.

    Using npm

    npm install idb

    Using jsdelivr (ES Modules)

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

    Using jsdelivr (UMD/External Script)

    <script src="https://cdn.jsdelivr.net/npm/idb@8/build/umd.js"></script>
    <script>
      // Access via the global 'idb' object
      const db = await idb.openDB(...);
    </script>
    npm install idb
  2. Cast database types for schema migrations

    main

    When performing database upgrades where the schema is changing (e.g., renaming a store), you can cast the database instance to a previous version of your schema using IDBPDatabase<OldSchema>. This allows you to interact with old stores that are no longer present in your current DBSchema definition. You can also cast to a typeless database using db as IDBPDatabase.

    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 to access old stores
        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';
        }
      },
    });
  3. Manage transaction lifetime and avoid premature closing

    main

    IndexedDB transactions auto-close if they have no pending work after microtasks are processed.

    Critical Rule: Do not await non-database tasks (like fetch) between the start and end of your transaction. If you do, the transaction will likely close before you can use it again, causing subsequent operations to fail.

    Correct Pattern:

    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;
  4. Implement strong typing for IndexedDB with DBSchema

    main

    To enable full TypeScript support and IDE autocompletion, extend the DBSchema interface. Define your object stores as keys in the interface. For each store, specify the value type, the key type, and an optional indexes map containing index names and their key types. Pass this interface as a generic to openDB<MyDB>(...).

    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 is type-safe
      await db.put('favourite-number', 7, 'Jen');
    }
  5. Manage IndexedDB stores with openDB

    main

    Use openDB to initialize or upgrade an IndexedDB database. You can define object stores, set keyPath for primary keys, enable autoIncrement, and create indexes within the upgrade callback. Once opened, you can perform operations like add, put, get, and getAllFromIndex directly on the database instance or via transactions.

    import { openDB } from 'idb/with-async-ittr.js';
    
    async function demo() {
      const db = await openDB('Articles', 1, {
        upgrade(db) {
          const store = db.createObjectStore('articles', {
            keyPath: 'id',
            autoIncrement: true,
          });
          store.createIndex('date', 'date');
        },
      });
    
      // Add an article
      await db.add('articles', {
        title: 'Article 1',
        date: new Date('2019-01-01'),
        body: '…',
      });
    
      // Get all articles in date order using an index
      console.log(await db.getAllFromIndex('articles', 'date'));
    }
  6. Delete a database with deleteDB()

    main

    The deleteDB method deletes a database. If the database has open connections that do not close in response to a versionchange event, the operation will be blocked.

    Options:

    • blocked(currentVersion, event): Called if the delete operation is blocked by existing connections.
    await deleteDB(name, {
      blocked() {
        // …
      },
    });
  7. Convert between enhanced and plain IndexedDB objects

    main

    Use wrap and unwrap to switch between the enhanced idb API and the standard browser IndexedDB API.

    • wrap(unwrapped): Takes a plain IDB object and returns a version enhanced by this library.
    • unwrap(wrapped): Takes an enhanced IndexedDB object and returns the plain unmodified one. Promises are converted back into IDBRequest objects.
    const wrapped = wrap(unwrapped);
    const unwrapped = unwrap(wrapped);
  8. Use IDBTransaction enhancements: .store and .done

    main

    Enhanced transactions provide two key properties:

    • tx.store: If the transaction involves only a single store, this property references that IDBObjectStore. If multiple stores are involved, it is undefined.
    • tx.done: A promise that resolves when the transaction completes successfully, or rejects with the transaction error if it fails.

    Example usage:

    const tx = db.transaction(storeName, 'readwrite');
    await Promise.all([
      tx.store.put('bar', 'foo'),
      tx.store.put('world', 'hello'),
      tx.done,
    ]);
    const tx = db.transaction('whatever');
    const store = tx.store;
  9. Iterate over stores and indexes using Async Iterators

    main

    You can use for await...of loops to iterate over stores, indexes, and cursors.

    Iterating a Store:

    const tx = db.transaction(storeName);
    for await (const cursor of tx.store) {
      console.log(cursor.value);
      // Optional: skip items using cursor.advance(n)
      cursor.advance(2);
    }

    Iterating an Index via .iterate(): Stores and indexes have an iterate method that 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);
    }
    let cursor = await db.transaction(storeName).store.openCursor();
    while (cursor) {
      console.log(cursor.key, cursor.value);
      cursor = await cursor.continue();
    }
  10. Use IDBDatabase shortcuts for object stores and indexes

    main

    The enhanced IDBDatabase provides shortcuts to perform operations directly on the database instance without manually creating transactions for every single call.

    Object Store Shortcuts: These methods take storeName as the first argument, followed by the standard IDBObjectStore arguments:

    • get(storeName, key)
    • getKey(storeName, key)
    • getAll(storeName)
    • getAllKeys(storeName)
    • count(storeName, ...)
    • put(storeName, value, key)
    • add(storeName, value, key)
    • delete(storeName, key)
    • clear(storeName)

    Index Shortcuts: These methods take storeName and indexName as the first two arguments, followed by standard IDBIndex arguments:

    • getFromIndex(storeName, indexName, key)
    • getKeyFromIndex(storeName, indexName, key)
    • getAllFromIndex(storeName, indexName)
    • getAllKeysFromIndex(storeName, indexName)
    • countFromIndex(storeName, indexName, ...)
    // 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);
    
    // Get a value from an index:
    const value = await db.getFromIndex(storeName, indexName, key);
  11. Open a database with openDB()

    main

    The openDB method opens a database and returns a promise for an enhanced IDBDatabase. It accepts a name, an optional version, and an options object to handle lifecycle events.

    Options:

    • upgrade(db, oldVersion, newVersion, transaction, event): Called if the version is new. Use this to define the schema.
    • blocked(currentVersion, blockedVersion, event): Called if older versions of the database are open on the origin, preventing this version from opening.
    • blocking(currentVersion, blockedVersion, event): Called if this connection is blocking a future version from opening.
    • terminated(): Called if the browser abnormally terminates the connection.
    const db = await openDB(name, version, {
      upgrade(db, oldVersion, newVersion, transaction, event) {
        // …
      },
      blocked(currentVersion, blockedVersion, event) {
        // …
      },
      blocking(currentVersion, blockedVersion, event) {
        // …
      },
      terminated() {
        // …
      },
    });
  12. Use IDBPDatabase shortcut methods

    main

    The IDBPDatabase interface provides several shortcut methods that create a single-action transaction automatically. These are useful for simple, one-off operations.

    Available shortcuts:

    • add(storeName, value, key?): Adds a value. Rejects if the key exists.
    • clear(storeName): Deletes all records in a store.
    • count(storeName, key?): Returns the number of records matching a query.
    • countFromIndex(storeName, indexName, key?): Counts records in an index.
    • delete(storeName, key): Deletes a specific record.
    • get(storeName, query): Retrieves the first matching value (returns undefined if not found).
    • getFromIndex(storeName, indexName, query): Retrieves the first matching value from an index.
    • getAll(storeName, query?, count?): Retrieves all matching values.
    • getAllFromIndex(storeName, indexName, query?, count?): Retrieves all matching values from an index.
    • getAllKeys(storeName, query?, count?): Retrieves all matching keys.
    • getAllKeysFromIndex(storeName, indexName, query?, count?): Retrieves all matching keys from an index.
    • getKey(storeName, query): Retrieves the key of the first matching record.
    • getKeyFromIndex(storeName, indexName, query): Retrieves the key of the first matching record from an index.
    • put(storeName, value, key?): Adds or replaces a value.