indexeddb-promisify
v1.0.0
Published
Modern, strongly typed, and safe promise-based wrapper for IndexedDB. Includes type-safe store definitions, migrations, transactions, and CRUD operations.
Maintainers
Readme
indexeddb-promisify
indexeddb-promisify is an async wrapper for IndexedB that provides an ergonomic interface for working with browser databases. Every operation runs inside a transaction to ensure consistency. For multi-store operations, the transaction helper allows safe handling of multiple object stores and provides a done promise to wait for the commit.
Installation
Install easily using your favorite package manager:
npm install indexeddb-promisify
# or
yarn add indexeddb-promisify
# or
pnpm add indexeddb-promisifyConfiguration
The configuration follows the IndexedDBConfig interface:
import openIndexedDB, { IndexedDBConfig } from 'indexeddb-promisify';
const config: IndexedDBConfig = {
name: 'my-database',
version: 1,
stores: [
{
name: 'users',
options: { keyPath: 'id', autoIncrement: true },
index: [
{ name: 'name_idx', keyPath: 'name', options: { unique: false } }
]
}
],
transactionTimeout: 5000,
events: {
onError: (error) => console.error(error),
onBlocked: ({ oldVersion, newVersion }) => {},
onUpgradeStart: ({ db, oldVersion, newVersion }) => {},
onUpgradeEnd: ({ db, oldVersion, newVersion }) => {},
onVersionChange: ({ db, oldVersion, newVersion }) => {},
onTransactionTimeout: ({ db, storeNames, timeout }) => {},
}
};
const db = await openIndexedDB(config);Explanation
name– database nameversion– database versionstores– object store definitions (key path, auto increment, indexes)transactionTimeout– maximum time for transactions before aborting (default: 5000ms)events– lifecycle and error callbacks:onError– triggered on any database erroronBlocked– triggered when an upgrade is blocked by other open connectionsonUpgradeStart/onUpgradeEnd– triggered before and after upgradesonVersionChange– triggered when the version changes while a connection is openonTransactionTimeout– triggered when a transaction exceeds the configured timeout
IndexedDBInstance API
openIndexedDB returns an IndexedDBInstance with the following properties and methods:
const { db, useStore, transaction, clearDatabase, deleteDatabase } = await openIndexedDB(config);
// Get operations for a specific store
const users = useStore<{ id?: number; name: string }>('users');
await users.add({ name: 'Alice' });
// Create a transaction across multiple stores
const { stores, done } = transaction(['users', 'orders']);
stores.users.add({ name: 'Bob' });
stores.orders.add({ orderId: 123, userId: 1 });
await done; // commit happens here
// Utility methods
await clearDatabase(); // clear all stores
await deleteDatabase(); // delete the database entirelyExplanation:
db– the underlyingIDBDatabaseuseStore– get operations for a single storetransaction– multi-store transaction with adonepromiseclearDatabase– clears all records in all storesdeleteDatabase– closes and deletes the database
Store Schema & Migrations
indexeddb-promisify allows you to define your database schema through the stores configuration and optionally handle upgrades via migrations.
Store Schema
export interface StoreSchema {
name: string;
options?: IDBObjectStoreParameters;
index?: { name: string; keyPath: string; options?: IDBIndexParameters }[];
}name– name of the object storeoptions– store options (e.g.,keyPath,autoIncrement)index– optional indexes with name, keyPath, and options
Schema Synchronization
By default, indexeddb-promisify synchronizes the schema automatically:
- Deletes object stores not present in the configuration.
- Creates missing stores.
- Deletes obsolete indexes.
- Creates missing indexes.
Note: If migrations are provided, automatic schema synchronization is disabled. Migrations are responsible for all store/index creation, deletion, or modification.
Migrations
Define migrations to safely upgrade your database:
export interface Migration {
version: number;
migration: ({
db,
transaction,
oldVersion,
newVersion,
migrationVersion
}: {
db: IDBDatabase;
transaction: IDBTransaction;
oldVersion: number;
newVersion: number;
migrationVersion: number;
}) => Promise<void>;
}Example migrations:
const migrations: Migration[] = [
{
version: 2,
migration: async ({ db, transaction, oldVersion, newVersion, migrationVersion }) => {
// Create a new store
if (!db.objectStoreNames.contains('orders')) {
db.createObjectStore('orders', { keyPath: 'orderId', autoIncrement: true });
}
// Create an index on an existing store
const usersStore = transaction.objectStore('users');
if (!usersStore.indexNames.contains('email_idx')) {
usersStore.createIndex('email_idx', 'email', { unique: true });
}
},
},
{
version: 3,
migration: async ({ db, transaction, oldVersion, newVersion, migrationVersion }) => {
// Remove an obsolete store
if (db.objectStoreNames.contains('temp')) {
db.deleteObjectStore('temp');
}
// Add a new index
const ordersStore = transaction.objectStore('orders');
if (!ordersStore.indexNames.contains('user_idx')) {
ordersStore.createIndex('user_idx', 'userId', { unique: false });
}
},
},
];Migrations run in ascending order by version.
Each migration receives:
db– the database instancetransaction– the upgrade transactionoldVersion– database version before the migrationnewVersion– target database versionmigrationVersion– the version of the migration itself
You are responsible for creating, modifying, or deleting stores and indexes within migrations.
Store Operations
Use useStore<T> to access CRUD and cursor operations for a given object store:
const users = db.useStore<{ id?: number; name: string }>('users');
await users.add({ name: 'Alice' });
await users.put({ id: 1, name: 'Alice Smith' });
const user = await users.get(1);
const allUsers = await users.getAll();
await users.delete(1);Available Methods
| Method | Description |
| --------------- | --------------------------------------------------------------------------- |
| add | Inserts a value into the store, returns generated or provided key |
| put | Updates or inserts a value, returns generated or provided key |
| get | Retrieves a value by key or key range |
| getAll | Retrieves all values, optionally filtered by key/range and limited by count |
| getKey | Retrieves the key of a value |
| getAllKeys | Retrieves all keys, optionally filtered and limited |
| count | Returns the number of records in the store |
| delete | Removes a record by key or key range |
| clear | Clears all records in the store |
| openCursor | Iterates over records using a cursor; returns results of the callback |
| openKeyCursor | Iterates over keys using a key cursor; returns keys or ranges |
Cursor Options
interface CursorOptions {
query?: IDBValidKey | IDBKeyRange;
direction?: IDBCursorDirection;
index?: string;
}query– key or key range filterdirection– cursor direction (next,prev, etc.)index– specify which index to use
Transactions
indexeddb-promisify provides a flexible transaction helper that wraps one or more object stores in a single IDBTransaction and exposes a done promise for commit handling.
Basic Usage
const { transaction, stores, done } = db.transaction(['users'], 'readwrite');
stores.users.add({ name: 'Bob' });
stores.users.add({ name: 'Carol' });
await done; // waits for the transaction to committransaction– theIDBTransactionobjectstores– mapped object stores for direct accessdone– promise resolved when the transaction completes successfully
Multi-Store Example
try {
const { stores, done } = db.transaction(['users']);
stores.users.add({ name: 'Eve' });
await done;
} catch (err) {
console.error('Transaction failed', err);
}Notes
- Transactions automatically respect the configured
transactionTimeout. - Errors inside a transaction reject the
donepromise and abort the transaction. - Always wrap transaction usage in a try-catch if you need to handle runtime errors safely
Events
All event callbacks are now promise-based and receive the db instance when applicable.
onBlocked – Triggered when a database upgrade is blocked by another open connection. Returns a promise that can resolve to an
Error. If no error is provided, a default upgrade-blocked error is used. The upgrade is rejected either way, and the database is closed automatically if the upgrade cannot proceed.onError – Triggered whenever an error occurs in the database. Receives the error object.
onTransactionTimeout – Triggered when a transaction exceeds the configured timeout. Receives the database instance, the store names involved, and the timeout duration.
onUpgradeStart – Called at the beginning of a database upgrade. Receives the database, old version, and new version.
onUpgradeEnd – Called at the end of a database upgrade. Receives the database, old version, and new version.
onVersionChange – Called when the database version changes while a connection is open. Receives the database, old version, and new version. After this event, the database is automatically closed to prevent conflicts.
Notes
Transactions timeout – All transactions are wrapped with a timeout (default 5000ms). If a transaction exceeds this limit, it is aborted and
onTransactionTimeoutis triggered.Schema sync vs. migrations – Automatic schema synchronization (adding/removing stores and indexes) is disabled when
migrationsare provided. In that case, database changes must be handled via migrations.Automatic database closure – The database is automatically closed when a version change is detected or when an upgrade is blocked by another open connection. This ensures that pending transactions are not interrupted unexpectedly.
Event promises – All event handlers now return promises, allowing asynchronous handling before the system continues.
Default errors – If
onBlockedor other events do not provide a customError, a default error is thrown and the operation is rejected.Multi-store transactions – Use the
transactionhelper for operations spanning multiple object stores. The returneddonepromise ensures that all operations are committed before continuing.Use with caution – While
useStoreprovides convenient single-store operations, prefer transactions for multi-step workflows to maintain atomicity and consistency.
