Skip to content

@aibulat/indexeddb

One package, two APIs. Install it once and pick the entry point that suits the code you are writing: a thin promise wrapper over raw IndexedDB, or a full Dexie-compatible database layer with a schema DSL, a query builder and reactive queries.

It is a fork of idb by Jake Archibald, forked at v8.0.3 and maintained as an API-compatible superset, with the high-level API added alongside it. ESM only, zero runtime dependencies.

Two APIs

Low-levelNexie — high-level
Import@aibulat/indexeddb@aibulat/indexeddb/nexie
ShapeopenDB(), object stores, transactions, cursorsnew Nexie(), schema DSL, query builder
Mental modelraw IndexedDB, with promises and typesa small ORM over IndexedDB
Size~1.9 kB brotli'd~124 kB unminified
Transactionsnative — they auto-commit if you await anything elsejoin automatically across await
Reactive queriesliveQuery
Coming fromidb — it is a superset, nothing to changedexie — migration is a rename

Which one

Take the low-level API if you want IndexedDB itself with the sharp edges filed off. You keep full control of stores, transactions and cursors, you keep the ~1.9 kB, and if you already use idb you are already using it — this is an API-compatible superset.

Take Nexie if you would rather describe a schema and query it. You get db.friends.where("age").above(25).toArray() instead of a cursor walk, transactions that survive await without auto-committing, CRUD hooks, middleware, and liveQuery for views that re-render themselves when their own results change. If you are coming from Dexie 4, migration is DexieNexie and nothing else.

Install

bash
npm install @aibulat/indexeddb

That is the whole install for both. Nexie is not a separate package and adds no dependency — it is a second entry point in this one.

The two module graphs are disjoint: nothing in the Nexie graph imports the low-level entry, so importing one never pulls in the other. Choosing Nexie costs the low-level bundle nothing, and the ~1.9 kB figure stays true for anyone who never touches ./nexie.

The same task, both ways

Create a store, write a record, read it back.

ts
import { openDB } from "@aibulat/indexeddb";

const db = await openDB("friends-db", 1, {
    upgrade(db) {
        const store = db.createObjectStore("friends", {
            keyPath: "id",
            autoIncrement: true,
        });
        store.createIndex("age", "age");
    },
});

await db.add("friends", { name: "Alice", age: 30 });

const grownups = await db.getAllFromIndex(
    "friends",
    "age",
    IDBKeyRange.lowerBound(26),
);
ts
import Nexie from "@aibulat/indexeddb/nexie";

const db = new Nexie("friends-db");
db.version(1).stores({ friends: "++id, name, age" });

await db.friends.add({ name: "Alice", age: 30 });

const grownups = await db.friends.where("age").above(25).toArray();

Same database, same storage engine underneath — only the API you write against differs.

Released under the MIT License.