Gathering detailed insights and metrics for idb
Gathering detailed insights and metrics for idb
Gathering detailed insights and metrics for idb
Gathering detailed insights and metrics for idb
npm install idb
99.6
Supply Chain
99.5
Quality
77
Maintenance
100
Vulnerability
100
License
Module System
Min. Node Version
Typescript Support
Node Version
NPM Version
6,445 Stars
184 Commits
364 Forks
64 Watching
5 Branches
27 Contributors
Updated on 28 Nov 2024
TypeScript (94.71%)
JavaScript (5.18%)
HTML (0.11%)
Cumulative downloads
Total Downloads
Last day
-4.8%
1,220,724
Compared to previous day
Last week
1.8%
6,800,822
Compared to previous week
Last month
4.9%
28,889,324
Compared to previous month
Last year
17.9%
325,904,774
Compared to previous year
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.
1npm install idb
Then, assuming you're using a module-compatible system (like webpack, Rollup etc):
1import { openDB, deleteDB, wrap, unwrap } from 'idb'; 2 3async function doDatabaseStuff() { 4 const db = await openDB(…); 5}
1<script type="module"> 2 import { openDB, deleteDB, wrap, unwrap } from 'https://cdn.jsdelivr.net/npm/idb@8/+esm'; 3 4 async function doDatabaseStuff() { 5 const db = await openDB(…); 6 } 7</script>
1<script src="https://cdn.jsdelivr.net/npm/idb@8/build/umd.js"></script> 2<script> 3 async function doDatabaseStuff() { 4 const db = await idb.openDB(…); 5 } 6</script>
A global, idb
, will be created, containing all exports of the module version.
See details of (potentially) breaking changes.
This library targets modern browsers, as in Chrome, Firefox, Safari, and other browsers that use those engines, such as Edge. IE is not supported.
openDB
This method opens a database, and returns a promise for an enhanced IDBDatabase
.
1const db = await openDB(name, version, { 2 upgrade(db, oldVersion, newVersion, transaction, event) { 3 // … 4 }, 5 blocked(currentVersion, blockedVersion, event) { 6 // … 7 }, 8 blocking(currentVersion, blockedVersion, event) { 9 // … 10 }, 11 terminated() { 12 // … 13 }, 14});
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.
1await deleteDB(name, { 2 blocked() { 3 // … 4 }, 5});
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.
1const 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.
1const 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.
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.
1const store = db.transaction(storeName).objectStore(storeName); 2const value = await store.get(key);
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.
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:
1const tx = db.transaction('keyval', 'readwrite'); 2const store = tx.objectStore('keyval'); 3const val = (await store.get('counter')) || 0; 4await store.put(val + 1, 'counter'); 5await tx.done;
But this doesn't:
1const tx = db.transaction('keyval', 'readwrite'); 2const store = tx.objectStore('keyval'); 3const val = (await store.get('counter')) || 0; 4// This is where things go wrong: 5const newVal = await fetch('/increment?val=' + val); 6// And this throws an error: 7await store.put(newVal, 'counter'); 8await tx.done;
In this case, the transaction closes while the browser is fetching, so store.put
fails.
IDBDatabase
enhancementsIt's common to create a transaction for a single action, so helper methods are included for this:
1// Get a value from a store: 2const value = await db.get(storeName, key); 3// Set a value in a store: 4await 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.
The shortcuts are: getFromIndex
, getKeyFromIndex
, getAllFromIndex
, getAllKeysFromIndex
, and countFromIndex
.
1// Get a value from an index:
2const 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
enhancementstx.store
If a transaction involves a single store, the store
property will reference that store.
1const tx = db.transaction('whatever'); 2const 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.
1const tx = db.transaction(storeName, 'readwrite'); 2await Promise.all([ 3 tx.store.put('bar', 'foo'), 4 tx.store.put('world', 'hello'), 5 tx.done, 6]);
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
enhancementsCursor advance methods (advance
, continue
, continuePrimaryKey
) return a promise for the cursor, or null if there are no further values to provide.
1let cursor = await db.transaction(storeName).store.openCursor(); 2 3while (cursor) { 4 console.log(cursor.key, cursor.value); 5 cursor = await cursor.continue(); 6}
You can iterate over stores, indexes, and cursors:
1const tx = db.transaction(storeName); 2 3for await (const cursor of tx.store) { 4 // … 5}
Each yielded object is an IDBCursor
. You can optionally use the advance methods to skip items (within an async iterator they return void):
1const tx = db.transaction(storeName); 2 3for await (const cursor of tx.store) { 4 console.log(cursor.value); 5 // Skip the next item 6 cursor.advance(2); 7}
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:
1const index = db.transaction('books').store.index('author'); 2 3for await (const cursor of index.iterate('Douglas Adams')) { 4 console.log(cursor.value); 5}
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.
1import { openDB } from 'idb'; 2 3const dbPromise = openDB('keyval-store', 1, { 4 upgrade(db) { 5 db.createObjectStore('keyval'); 6 }, 7}); 8 9export async function get(key) { 10 return (await dbPromise).get('keyval', key); 11} 12export async function set(key, val) { 13 return (await dbPromise).put('keyval', val, key); 14} 15export async function del(key) { 16 return (await dbPromise).delete('keyval', key); 17} 18export async function clear() { 19 return (await dbPromise).clear('keyval'); 20} 21export async function keys() { 22 return (await dbPromise).getAllKeys('keyval'); 23}
1import { openDB } from 'idb/with-async-ittr.js'; 2 3async function demo() { 4 const db = await openDB('Articles', 1, { 5 upgrade(db) { 6 // Create a store of objects 7 const store = db.createObjectStore('articles', { 8 // The 'id' property of the object will be the key. 9 keyPath: 'id', 10 // If it isn't explicitly set, create a value by auto incrementing. 11 autoIncrement: true, 12 }); 13 // Create an index on the 'date' property of the objects. 14 store.createIndex('date', 'date'); 15 }, 16 }); 17 18 // Add an article: 19 await db.add('articles', { 20 title: 'Article 1', 21 date: new Date('2019-01-01'), 22 body: '…', 23 }); 24 25 // Add multiple articles in one transaction: 26 { 27 const tx = db.transaction('articles', 'readwrite'); 28 await Promise.all([ 29 tx.store.add({ 30 title: 'Article 2', 31 date: new Date('2019-01-01'), 32 body: '…', 33 }), 34 tx.store.add({ 35 title: 'Article 3', 36 date: new Date('2019-01-02'), 37 body: '…', 38 }), 39 tx.done, 40 ]); 41 } 42 43 // Get all the articles in date order: 44 console.log(await db.getAllFromIndex('articles', 'date')); 45 46 // Add 'And, happy new year!' to all articles on 2019-01-01: 47 { 48 const tx = db.transaction('articles', 'readwrite'); 49 const index = tx.store.index('date'); 50 51 for await (const cursor of index.iterate(new Date('2019-01-01'))) { 52 const article = { ...cursor.value }; 53 article.body += ' And, happy new year!'; 54 cursor.update(article); 55 } 56 57 await tx.done; 58 } 59}
This library is fully typed, and you can improve things by providing types for your database:
1import { openDB, DBSchema } from 'idb'; 2 3interface MyDB extends DBSchema { 4 'favourite-number': { 5 key: string; 6 value: number; 7 }; 8 products: { 9 value: { 10 name: string; 11 price: number; 12 productCode: string; 13 }; 14 key: string; 15 indexes: { 'by-price': number }; 16 }; 17} 18 19async function demo() { 20 const db = await openDB<MyDB>('my-db', 1, { 21 upgrade(db) { 22 db.createObjectStore('favourite-number'); 23 24 const productStore = db.createObjectStore('products', { 25 keyPath: 'productCode', 26 }); 27 productStore.createIndex('by-price', 'price'); 28 }, 29 }); 30 31 // This works 32 await db.put('favourite-number', 7, 'Jen'); 33 // This fails at compile time, as the 'favourite-number' store expects a number. 34 await db.put('favourite-number', 'Twelve', 'Jake'); 35}
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.
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':
1import { openDB, DBSchema, IDBPDatabase } from 'idb'; 2 3interface MyDBV1 extends DBSchema { 4 'favourite-number': { key: string; value: number }; 5} 6 7interface MyDBV2 extends DBSchema { 8 'fave-num': { key: string; value: number }; 9} 10 11const db = await openDB<MyDBV2>('my-db', 2, { 12 async upgrade(db, oldVersion) { 13 // Cast a reference of the database to the old schema. 14 const v1Db = db as unknown as IDBPDatabase<MyDBV1>; 15 16 if (oldVersion < 1) { 17 v1Db.createObjectStore('favourite-number'); 18 } 19 if (oldVersion < 2) { 20 const store = v1Db.createObjectStore('favourite-number'); 21 store.name = 'fave-num'; 22 } 23 }, 24});
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.
1npm 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.
No vulnerabilities found.
Reason
no binaries found in the repo
Reason
license file detected
Details
Reason
5 existing vulnerabilities detected
Details
Reason
Found 6/30 approved changesets -- score normalized to 2
Reason
0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
security policy file not detected
Details
Reason
project is not fuzzed
Details
Reason
branch protection not enabled on development/release branches
Details
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
Score
Last Scanned on 2024-11-25
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More