Core database functionality including connection management, schema definition, versioning, and transaction handling.
Creates a new database instance.
/**
* Creates a new Dexie database instance
* @param name - Database name (must be unique per origin)
* @param options - Optional configuration options
*/
constructor(name: string, options?: DexieOptions);
interface DexieOptions {
/** Add-on functions to apply to the database */
addons?: Array<(db: Dexie) => void>;
/** Whether to automatically open the database (default: true) */
autoOpen?: boolean;
/** Custom IndexedDB implementation (for testing/Node.js) */
indexedDB?: IDBFactory;
/** Custom IDBKeyRange implementation */
IDBKeyRange?: typeof IDBKeyRange;
/** Allow opening empty database without schema (default: false) */
allowEmptyDB?: boolean;
/** Chunk size for modify operations */
modifyChunkSize?: number;
/** Chrome transaction durability setting */
chromeTransactionDurability?: "default" | "strict" | "relaxed";
/** Cache strategy for queries and results */
cache?: "immutable" | "cloned" | "disabled";
}Usage Examples:
import Dexie from "dexie";
// Basic database creation
const db = new Dexie("MyAppDatabase");
// Database with options
const db = new Dexie("MyAppDatabase", {
autoOpen: false,
cache: "immutable",
modifyChunkSize: 100
});Opens the database connection.
/**
* Opens the database, creating it if it doesn't exist
* @returns Promise that resolves to the database instance
*/
open(): PromiseExtended<Dexie>;Usage Examples:
// Auto-open (default behavior)
const db = new Dexie("MyDatabase");
db.version(1).stores({ friends: "++id, name, age" });
// Database opens automatically when first used
// Manual open
const db = new Dexie("MyDatabase", { autoOpen: false });
db.version(1).stores({ friends: "++id, name, age" });
await db.open();
// Handle open errors
try {
await db.open();
console.log("Database opened successfully");
} catch (error) {
console.error("Failed to open database:", error);
}Closes the database connection.
/**
* Closes the database connection
* @param options - Closure options
*/
close(options?: { disableAutoOpen?: boolean }): void;Usage Examples:
// Close database
db.close();
// Close and prevent auto-reopening
db.close({ disableAutoOpen: true });
// Temporarily close database
db.close();
// Database will reopen automatically on next operation unless disableAutoOpen was setDeletes the entire database.
/**
* Deletes the entire database and all its data
* @param options - Deletion options
* @returns Promise that resolves when deletion completes
*/
delete(options?: { disableAutoOpen?: boolean }): Promise<void>;Usage Examples:
// Delete database
await db.delete();
// Delete and prevent auto-recreation
await db.delete({ disableAutoOpen: true });
// Safe deletion with error handling
try {
await db.delete();
console.log("Database deleted successfully");
} catch (error) {
console.error("Failed to delete database:", error);
}Defines database schema versions for evolution and migration.
/**
* Defines a database schema version
* @param versionNumber - Version number (must be sequential, starting from 1)
* @returns Version instance for chaining
*/
version(versionNumber: number): Version;
interface Version {
/** Define table schemas for this version */
stores(schema: { [tableName: string]: string | null }): Version;
/** Define upgrade function for migrating from previous version */
upgrade(upgradeFunction: (trans: Transaction) => PromiseLike<any> | void): Version;
}Schema Syntax:
"++id" - Auto-incrementing primary key"id" - Non-auto-incrementing primary key"id, name, age" - Primary key with indexes on name and age"id, *tags" - Multi-entry index (for array values)"id, [firstName+lastName]" - Compound indexnull - Delete table in this versionUsage Examples:
// Basic schema definition
db.version(1).stores({
friends: "++id, name, age",
messages: "++id, friendId, message, timestamp"
});
// Schema evolution
db.version(1).stores({
friends: "++id, name, age"
});
db.version(2)
.stores({
friends: "++id, name, age, email", // Add email index
messages: "++id, friendId, message, timestamp"
})
.upgrade(trans => {
// Migrate existing data
return trans.friends.toCollection().modify(friend => {
friend.email = friend.email || null;
});
});
db.version(3).stores({
friends: "++id, name, age, email",
messages: "++id, friendId, message, timestamp",
groups: "++id, name, [ownerId+name]" // Add new table with compound index
});
// Delete table in version 4
db.version(4).stores({
friends: "++id, name, age, email",
messages: null, // Delete messages table
groups: "++id, name, [ownerId+name]"
});Creates explicit transactions for atomic operations across multiple tables.
/**
* Creates an explicit transaction
* @param mode - Transaction mode ('r'/'readonly' or 'rw'/'readwrite')
* @param tables - Array of table names to include in transaction
* @param scope - Function to execute within the transaction
* @returns Promise that resolves to the scope function's result
*/
transaction<T>(
mode: TransactionMode,
tables: string[],
scope: () => T | PromiseLike<T>
): Promise<T>;
type TransactionMode = "r" | "rw" | "readonly" | "readwrite";
interface Transaction {
/** Database reference */
db: Dexie;
/** Whether transaction is still active */
active: boolean;
/** Transaction mode */
mode: IDBTransactionMode;
/** Table names included in transaction */
storeNames: string[];
/** Parent transaction (for sub-transactions) */
parent?: Transaction;
/** Get table bound to this transaction */
table(tableName: string): Table<any, any>;
/** Abort the transaction */
abort(): void;
}Usage Examples:
// Read-only transaction
const result = await db.transaction("readonly", ["friends", "messages"], async () => {
const friends = await db.friends.toArray();
const messages = await db.messages.toArray();
return { friends, messages };
});
// Read-write transaction
await db.transaction("readwrite", ["friends", "messages"], async () => {
const newFriend = await db.friends.add({ name: "Alice", age: 25 });
await db.messages.add({
friendId: newFriend,
message: "Welcome!",
timestamp: Date.now()
});
});
// Transaction with error handling
try {
await db.transaction("rw", ["friends"], async () => {
await db.friends.add({ name: "Bob", age: 30 });
await db.friends.add({ name: "Charlie", age: 35 });
// If any operation fails, entire transaction is rolled back
});
} catch (error) {
console.error("Transaction failed:", error);
}
// Access transaction object
await db.transaction("rw", ["friends"], async (trans) => {
const friends = trans.table("friends");
await friends.add({ name: "David", age: 40 });
});Accesses database tables for operations.
/**
* Gets a table instance for performing operations
* @param tableName - Name of the table
* @returns Table instance
*/
table(tableName: string): Table<any, any>;
table<T>(tableName: string): Table<T, any>;
table<T, Key>(tableName: string): Table<T, Key>;
table<T, Key, TInsertType>(tableName: string): Table<T, Key, TInsertType>;Usage Examples:
// Access table with inferred types
const friends = db.table("friends");
// Access table with explicit typing
interface Friend {
id: number;
name: string;
age: number;
}
const typedFriends = db.table<Friend, number>("friends");
await typedFriends.add({ name: "Alice", age: 25 });Key database properties and metadata.
interface Dexie {
/** Database name */
readonly name: string;
/** Current version number */
readonly verno: number;
/** All tables in the database */
readonly tables: Table[];
/** VIP instance for priority operations */
readonly vip: Dexie;
/** Whether database is currently open */
readonly isOpen: boolean;
/** Whether database has failed to open or had an error */
readonly hasFailed: boolean;
/** Whether database has been explicitly closed */
readonly hasBeenClosed: boolean;
/** Whether database was opened with auto-schema (no version specified) */
readonly dynamicallyOpened: boolean;
/** Native IndexedDB database instance (may be null if closed) */
readonly idbdb: IDBDatabase | null;
}Usage Examples:
console.log("Database name:", db.name);
console.log("Current version:", db.verno);
console.log("Available tables:", db.tables.map(t => t.name));
console.log("Database open:", db.isOpen);
console.log("Database failed:", db.hasFailed);
console.log("Database closed:", db.hasBeenClosed);
console.log("Auto-schema mode:", db.dynamicallyOpened);
// Check native IndexedDB instance
if (db.idbdb) {
console.log("Native IDB version:", db.idbdb.version);
console.log("Native IDB store names:", Array.from(db.idbdb.objectStoreNames));
}
// Use VIP instance for priority operations
await db.vip.friends.add({ name: "VIP User", age: 30 });
// Check database status before operations
if (db.hasBeenClosed) {
console.log("Database was explicitly closed");
}
if (db.hasFailed) {
console.log("Database has encountered errors");
}Static utilities available on the Dexie constructor.
interface DexieConstructor {
/** Library version */
static readonly version: string;
/** Semantic version */
static readonly semVer: string;
/** Maximum IndexedDB key value */
static readonly maxKey: any;
/** Minimum IndexedDB key value */
static readonly minKey: any;
/** Global addon registry */
static addons: Array<(db: Dexie) => void>;
/** Current active transaction */
static currentTransaction: Transaction | null;
/** Wait for external promises in transactions */
static waitFor<T>(promise: PromiseLike<T>, timeoutMs?: number): Promise<T>;
/** Run code outside transaction context */
static ignoreTransaction<T>(fn: () => T): T;
/** Run with VIP priority */
static vip<T>(fn: () => T): T;
/** Delete a database by name */
static delete(databaseName: string): Promise<void>;
/** Check if a database exists */
static exists(databaseName: string): Promise<boolean>;
/** Get list of all database names */
static getDatabaseNames(): Promise<string[]>;
static getDatabaseNames<R>(thenShortcut: ThenShortcut<string[], R>): Promise<R>;
/** Convert generator function to async function */
static async<T>(generatorFn: Function): (...args: any[]) => Promise<T>;
/** Spawn a generator function with arguments */
static spawn<T>(generatorFn: Function, args?: any[], thiz?: any): Promise<T>;
}Usage Examples:
console.log("Dexie version:", Dexie.version);
console.log("Semantic version:", Dexie.semVer);
console.log("Max key:", Dexie.maxKey);
console.log("Min key:", Dexie.minKey);
// Database management operations
const dbExists = await Dexie.exists("MyDatabase");
if (dbExists) {
console.log("Database exists");
}
// Get all database names
const dbNames = await Dexie.getDatabaseNames();
console.log("Available databases:", dbNames);
// Delete a database
await Dexie.delete("OldDatabase");
// Wait for external promise in transaction
await db.transaction("rw", ["friends"], async () => {
const externalData = await Dexie.waitFor(fetch("/api/friends"));
const friends = await externalData.json();
await db.friends.bulkAdd(friends);
});
// Check current transaction
if (Dexie.currentTransaction) {
console.log("Currently in transaction");
}
// Run with VIP priority
Dexie.vip(() => {
return db.friends.add({ name: "Priority User", age: 25 });
});
// Run code outside transaction context
const result = Dexie.ignoreTransaction(() => {
// This code runs outside any current transaction
return someNonTransactionalOperation();
});
// Using async/spawn with generators (legacy support)
const asyncFn = Dexie.async(function* () {
const result1 = yield somePromise();
const result2 = yield anotherPromise();
return result1 + result2;
});
const spawnResult = await Dexie.spawn(function* () {
return yield someGenerator();
});Utility functions available on the Dexie constructor for common operations.
interface DexieUtilities {
/** Get property value by key path */
static getByKeyPath(obj: Object, keyPath: string | string[]): any;
/** Set property value by key path */
static setByKeyPath(obj: Object, keyPath: string | string[], value: any): void;
/** Delete property by key path */
static delByKeyPath(obj: Object, keyPath: string | string[]): void;
/** Create shallow clone of object */
static shallowClone<T>(obj: T): T;
/** Create deep clone of object */
static deepClone<T>(obj: T): T;
/** Compare two IndexedDB keys */
static cmp(a: any, b: any): number;
/** Schedule function to run as soon as possible */
static asap(fn: Function): void;
/** Debug mode control */
static debug: false | true | "dexie";
}Usage Examples:
// Working with key paths
const obj = { user: { profile: { name: "John" } } };
// Get nested value
const name = Dexie.getByKeyPath(obj, "user.profile.name"); // "John"
const nameArray = Dexie.getByKeyPath(obj, ["user", "profile", "name"]); // "John"
// Set nested value
Dexie.setByKeyPath(obj, "user.profile.age", 25);
console.log(obj.user.profile.age); // 25
// Delete nested property
Dexie.delByKeyPath(obj, "user.profile.name");
console.log(obj.user.profile.name); // undefined
// Object cloning
const original = { a: 1, b: { c: 2 } };
const shallow = Dexie.shallowClone(original); // { a: 1, b: { c: 2 } }
const deep = Dexie.deepClone(original); // { a: 1, b: { c: 2 } }
// Note: shallow.b === original.b (same reference)
// Note: deep.b !== original.b (different reference)
// Key comparison (follows IndexedDB key ordering)
console.log(Dexie.cmp("a", "b")); // -1 (a < b)
console.log(Dexie.cmp(1, 2)); // -1 (1 < 2)
console.log(Dexie.cmp([1, 2], [1, 3])); // -1 ([1,2] < [1,3])
// Schedule immediate execution
Dexie.asap(() => {
console.log("This runs as soon as possible");
});
// Debug mode
Dexie.debug = true; // Enable debug logging
Dexie.debug = "dexie"; // Enable debug with Dexie stack frames
Dexie.debug = false; // Disable debugError classes and development utilities available on the Dexie constructor.
interface DexieErrorClasses {
/** Base class for all Dexie errors */
static DexieError: typeof DexieError;
/** Database failed to open */
static OpenFailedError: typeof OpenFailedError;
/** Version change error during upgrade */
static VersionChangeError: typeof VersionChangeError;
/** Schema definition error */
static SchemaError: typeof SchemaError;
/** Database upgrade error */
static UpgradeError: typeof UpgradeError;
/** Invalid table name or reference */
static InvalidTableError: typeof InvalidTableError;
/** Missing IndexedDB API */
static MissingAPIError: typeof MissingAPIError;
/** Database doesn't exist */
static NoSuchDatabaseError: typeof NoSuchDatabaseError;
/** Invalid argument passed to method */
static InvalidArgumentError: typeof InvalidArgumentError;
/** Sub-transaction constraint violation */
static SubTransactionError: typeof SubTransactionError;
/** Unsupported operation */
static UnsupportedError: typeof UnsupportedError;
/** Internal Dexie error */
static InternalError: typeof InternalError;
/** Database was closed */
static DatabaseClosedError: typeof DatabaseClosedError;
/** Transaction committed prematurely */
static PrematureCommitError: typeof PrematureCommitError;
}
interface DexieDependencies {
/** DOM dependencies for browser/Node.js compatibility */
static dependencies: {
indexedDB: IDBFactory;
IDBKeyRange: typeof IDBKeyRange;
};
/** Error name constants */
static errnames: DexieErrors;
}Usage Examples:
// Using error classes
try {
await db.open();
} catch (error) {
if (error instanceof Dexie.OpenFailedError) {
console.log("Failed to open database:", error.message);
} else if (error instanceof Dexie.VersionChangeError) {
console.log("Version change error:", error.message);
} else if (error instanceof Dexie.DexieError) {
console.log("General Dexie error:", error.message);
}
}
// Checking error types by name
try {
await db.table("nonexistent").get(1);
} catch (error) {
if (error.name === Dexie.errnames.InvalidTable) {
console.log("Invalid table error");
}
}
// Custom error handling
function handleDexieError(error: any) {
switch (error.constructor) {
case Dexie.DatabaseClosedError:
console.log("Database is closed, attempting to reopen...");
return db.open();
case Dexie.NoSuchDatabaseError:
console.log("Database doesn't exist, creating new one...");
return createNewDatabase();
default:
console.error("Unhandled Dexie error:", error);
throw error;
}
}
// Accessing DOM dependencies (useful for Node.js setup)
console.log("IndexedDB implementation:", Dexie.dependencies.indexedDB);
console.log("IDBKeyRange implementation:", Dexie.dependencies.IDBKeyRange);
// Setting custom dependencies (Node.js example)
// const FDBFactory = require('fake-indexeddb/lib/FDBFactory');
// const FDBKeyRange = require('fake-indexeddb/lib/FDBKeyRange');
//
// Object.assign(Dexie.dependencies, {
// indexedDB: new FDBFactory(),
// IDBKeyRange: FDBKeyRange
// });Register and manage middleware for extending database functionality.
/**
* Register middleware for extending database operations
* @param middleware - Middleware specification
* @returns Database instance for chaining
*/
use(middleware: Middleware<DBCore>): this;
/**
* Unregister middleware by function reference or name
* @param spec - Middleware specification to remove
* @returns Database instance for chaining
*/
unuse(spec: { stack: keyof DexieStacks; create: Function }): this;
unuse(spec: { stack: keyof DexieStacks; name: string }): this;
interface Middleware<TStack> {
/** Target middleware stack */
stack: keyof DexieStacks;
/** Factory function that creates the middleware */
create: (downlevelDatabase: TStack) => TStack;
/** Priority level (lower numbers run first, default: 10) */
level?: number;
/** Optional name for the middleware */
name?: string;
}Usage Examples:
// Custom logging middleware
const loggingMiddleware = {
stack: "dbcore" as const,
name: "logging",
create: (downlevelDatabase) => ({
...downlevelDatabase,
table(name) {
const table = downlevelDatabase.table(name);
return {
...table,
mutate(req) {
console.log(`${req.type} operation on table ${name}:`, req);
return table.mutate(req).then(result => {
console.log(`${req.type} completed:`, result);
return result;
});
}
};
}
})
};
// Register middleware
db.use(loggingMiddleware);
// Performance monitoring middleware
const performanceMiddleware = {
stack: "dbcore" as const,
level: 1, // Run early
create: (downlevelDatabase) => ({
...downlevelDatabase,
table(name) {
const table = downlevelDatabase.table(name);
return {
...table,
query(req) {
const start = performance.now();
return table.query(req).then(result => {
const duration = performance.now() - start;
console.log(`Query on ${name} took ${duration}ms`);
return result;
});
}
};
}
})
};
db.use(performanceMiddleware);
// Remove middleware by name
db.unuse({ stack: "dbcore", name: "logging" });
// Remove middleware by function reference
db.unuse({ stack: "dbcore", create: performanceMiddleware.create });Handle database lifecycle and operational events.
interface DbEvents {
/** Database ready event - fires when database is successfully opened */
ready: DexieOnReadyEvent;
/** Database blocked event - fires when database operations are blocked */
blocked: DexieEvent;
/** Version change event - fires when another connection wants to upgrade/delete database */
versionchange: DexieVersionChangeEvent;
/** Database close event - fires when database connection is closed */
close: DexieCloseEvent;
/** Populate event - fires during database creation for initial data setup */
populate: DexiePopulateEvent;
}
interface DexieOnReadyEvent {
/** Subscribe to ready event with optional sticky behavior */
subscribe(fn: (vipDb: Dexie) => any, bSticky?: boolean): void;
/** Unsubscribe from ready event */
unsubscribe(fn: (vipDb: Dexie) => any): void;
/** Fire ready event (internal use) */
fire(vipDb: Dexie): any;
}
interface DbEventFns {
/** Subscribe to events with a single function call */
(eventName: "ready", subscriber: (vipDb: Dexie) => any, bSticky?: boolean): void;
(eventName: "blocked", subscriber: (event: IDBVersionChangeEvent) => any): void;
(eventName: "versionchange", subscriber: (event: IDBVersionChangeEvent) => any): void;
(eventName: "close", subscriber: (event: Event) => any): void;
(eventName: "populate", subscriber: (trans: Transaction) => any): void;
}
/**
* Subscribe to database events using 'once' - automatically unsubscribes after first trigger
* @param eventName - Name of the event to subscribe to
* @param callback - Function to call when event fires
*/
once: DbEventFns;Usage Examples:
// Ready event - database successfully opened
db.on("ready", (vipDb) => {
console.log("Database is ready!");
// You can perform VIP operations here
return vipDb.friends.count();
});
// Sticky ready subscription - will fire immediately if db is already open
db.on("ready", (vipDb) => {
console.log("This will fire immediately if db is already open");
}, true); // bSticky = true
// Blocked event - database operations blocked by another connection
db.on("blocked", (event) => {
console.log("Database operations are blocked", event);
});
// Version change event - another connection wants to upgrade/delete
db.on("versionchange", (event) => {
console.log("Another connection is requesting version change", event);
if (event.newVersion === null) {
console.log("Database is being deleted");
} else {
console.log(`Upgrading to version ${event.newVersion}`);
}
// Default behavior: close database to allow upgrade
db.close({ disableAutoOpen: false });
});
// Close event - database connection closed
db.on("close", (event) => {
console.log("Database connection closed", event);
});
// Populate event - initial data setup during database creation
db.on("populate", (trans) => {
console.log("Populating initial data");
// Add initial data to tables
trans.friends.add({ name: "John", age: 30 });
trans.friends.add({ name: "Jane", age: 25 });
});
// Using once() for single-use subscriptions
db.once("ready", (vipDb) => {
console.log("This will only fire once");
});
db.once("blocked", (event) => {
console.log("This blocked handler will only fire once");
});
// Multiple event handlers
db.on("ready", () => console.log("Handler 1"));
db.on("ready", () => console.log("Handler 2"));
// Unsubscribe from events
const readyHandler = (vipDb) => console.log("Ready!");
db.on("ready", readyHandler);
db.on.ready.unsubscribe(readyHandler);Methods to check the current status and state of the database connection.
/**
* Check if the database is currently open
* @returns True if database is open and ready for use
*/
isOpen(): boolean;
/**
* Check if the database has been closed
* @returns True if database has been explicitly closed
*/
hasBeenClosed(): boolean;
/**
* Check if the database has failed to open
* @returns True if database opening failed
*/
hasFailed(): boolean;
/**
* Check if database was opened dynamically (without schema)
* @returns True if opened without predefined schema
*/
dynamicallyOpened(): boolean;
/**
* Get the native IndexedDB database instance
* @returns Native IDBDatabase instance or null if closed
*/
backendDB(): IDBDatabase | null;Usage Examples:
// Check database status before operations
if (db.isOpen()) {
await db.friends.add({ name: "Alice", age: 25 });
} else {
console.log("Database is not open, attempting to open...");
await db.open();
}
// Handle closed databases
if (db.hasBeenClosed()) {
console.log("Database was explicitly closed");
// Reopen if needed
await db.open();
}
// Handle failed database connections
if (db.hasFailed()) {
console.error("Database failed to open");
// Implement fallback logic or error handling
}
// Check if database was dynamically opened
if (db.dynamicallyOpened()) {
console.log("Database opened without predefined schema");
console.log("Available tables:", db.tables.map(t => t.name));
}
// Access native IndexedDB instance for advanced operations
const nativeDB = db.backendDB();
if (nativeDB) {
console.log("Native DB version:", nativeDB.version);
console.log("Object stores:", Array.from(nativeDB.objectStoreNames));
// You can use native IndexedDB APIs if needed
// (though this is rarely necessary with Dexie)
const transaction = nativeDB.transaction(["friends"], "readonly");
const store = transaction.objectStore("friends");
}Additional static utility methods available on the Dexie class.
/**
* Check if a database exists
* @param name - Database name to check
* @returns Promise resolving to true if database exists
*/
static exists(name: string): Promise<boolean>;
/**
* Delete a database by name
* @param name - Database name to delete
* @returns Promise that resolves when deletion is complete
*/
static delete(databaseName: string): Promise<void>;
/**
* Ignore current transaction context
* @param scopeFunc - Function to execute outside current transaction
* @returns Result of the scope function
*/
static ignoreTransaction<T>(scopeFunc: () => T): T;
/**
* Wait for a promise or function result
* @param promiseOrFunction - Promise or function to wait for
* @param optionalTimeout - Optional timeout in milliseconds
* @returns Promise that resolves to the result
*/
static waitFor<T>(promiseOrFunction: T | (() => T), optionalTimeout?: number): Promise<T>;
/**
* Current active transaction
* @returns Current transaction or null if none active
*/
static currentTransaction: Transaction | null;Usage Examples:
// Check if database exists before operations
const exists = await Dexie.exists("MyDatabase");
if (exists) {
console.log("Database exists");
} else {
console.log("Database does not exist");
}
// Static delete (without creating instance)
await Dexie.delete("OldDatabase");
console.log("Database deleted");
// Ignore current transaction for logging
await db.transaction("rw", ["friends"], async () => {
await db.friends.add({ name: "Alice", age: 25 });
// Log this operation in a separate transaction
Dexie.ignoreTransaction(() => {
db.logs.add({ action: "user_added", timestamp: Date.now() });
});
});
// Wait for external promise within transaction
await db.transaction("rw", ["friends"], async () => {
const externalData = await Dexie.waitFor(
fetch("/api/user-data").then(r => r.json()),
5000 // 5 second timeout
);
await db.friends.add(externalData);
});
// Check current transaction
if (Dexie.currentTransaction) {
console.log("Currently in transaction:", Dexie.currentTransaction.mode);
} else {
console.log("No active transaction");
}