A small wrapper that makes IndexedDB usable with promises and enhanced TypeScript support
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Enhanced object store interface with promise-based operations and async iteration support.
Access object store metadata and transaction information.
/**
* The names of indexes in the store
*/
readonly indexNames: TypedDOMStringList<IndexNames<DBTypes, StoreName>>;
/**
* The associated transaction
*/
readonly transaction: IDBPTransaction<DBTypes, TxStores, Mode>;Usage Examples:
const tx = db.transaction("users", "readwrite");
const userStore = tx.store;
// Check available indexes
console.log("Available indexes:", [...userStore.indexNames]);
// Access parent transaction
console.log("Transaction mode:", userStore.transaction.mode);
// Check if specific index exists
if (userStore.indexNames.contains("email")) {
const emailIndex = userStore.index("email");
const userByEmail = await emailIndex.get("alice@example.com");
}Add, update, and delete records in the object store.
/**
* Add a value to the store
* Rejects if an item of a given key already exists in the store
* Only available in readwrite and versionchange transactions
* @param value - Value to add to the store
* @param key - Optional key (if not using keyPath)
* @returns Promise resolving to the key of the added item
*/
add: Mode extends 'readonly'
? undefined
: (
value: StoreValue<DBTypes, StoreName>,
key?: StoreKey<DBTypes, StoreName> | IDBKeyRange
) => Promise<StoreKey<DBTypes, StoreName>>;/**
* Put an item in the store
* Replaces any item with the same key
* Only available in readwrite and versionchange transactions
* @param value - Value to put in the store
* @param key - Optional key (if not using keyPath)
* @returns Promise resolving to the key of the stored item
*/
put: Mode extends 'readonly'
? undefined
: (
value: StoreValue<DBTypes, StoreName>,
key?: StoreKey<DBTypes, StoreName> | IDBKeyRange
) => Promise<StoreKey<DBTypes, StoreName>>;/**
* Deletes records in store matching the given query
* Only available in readwrite and versionchange transactions
* @param key - Key or key range to delete
* @returns Promise that resolves when deletion is complete
*/
delete: Mode extends 'readonly'
? undefined
: (key: StoreKey<DBTypes, StoreName> | IDBKeyRange) => Promise<void>;/**
* Deletes all records in store
* Only available in readwrite and versionchange transactions
* @returns Promise that resolves when clearing is complete
*/
clear: Mode extends 'readonly' ? undefined : () => Promise<void>;Data Modification Examples:
// Write operations (readwrite transaction required)
const tx = db.transaction("users", "readwrite");
const userStore = tx.store;
// Add new records (fails if key exists)
const userId1 = await userStore.add({ name: "Alice", email: "alice@example.com" });
const userId2 = await userStore.add({ name: "Bob", email: "bob@example.com" });
// Put records (replaces if key exists)
await userStore.put({ id: userId1, name: "Alice Smith", email: "alice@example.com" });
// Delete specific record
await userStore.delete(userId2);
// Delete multiple records with key range
await userStore.delete(IDBKeyRange.bound(100, 200));
// Clear all records
await userStore.clear();
await tx.done;
// Read operations (readonly transaction is sufficient)
const readTx = db.transaction("users", "readonly");
const readStore = readTx.store;
// These operations are not available in readonly mode:
// readStore.add - undefined
// readStore.put - undefined
// readStore.delete - undefined
// readStore.clear - undefinedQuery and retrieve records from the object store.
/**
* Retrieves the value of the first record matching the query
* Resolves with undefined if no match is found
* @param query - Key or key range to match
* @returns Promise resolving to the value or undefined
*/
get(
query: StoreKey<DBTypes, StoreName> | IDBKeyRange
): Promise<StoreValue<DBTypes, StoreName> | undefined>;
/**
* Retrieves all values that match the query
* @param query - Key or key range to match (optional)
* @param count - Maximum number of values to return (optional)
* @returns Promise resolving to array of matching values
*/
getAll(
query?: StoreKey<DBTypes, StoreName> | IDBKeyRange | null,
count?: number
): Promise<StoreValue<DBTypes, StoreName>[]>;/**
* Retrieves the key of the first record that matches the query
* Resolves with undefined if no match is found
* @param query - Key or key range to match
* @returns Promise resolving to the key or undefined
*/
getKey(
query: StoreKey<DBTypes, StoreName> | IDBKeyRange
): Promise<StoreKey<DBTypes, StoreName> | undefined>;
/**
* Retrieves the keys of records matching the query
* @param query - Key or key range to match (optional)
* @param count - Maximum number of keys to return (optional)
* @returns Promise resolving to array of matching keys
*/
getAllKeys(
query?: StoreKey<DBTypes, StoreName> | IDBKeyRange | null,
count?: number
): Promise<StoreKey<DBTypes, StoreName>[]>;/**
* Retrieves the number of records matching the given query
* @param key - Key or key range to count (optional)
* @returns Promise resolving to the count
*/
count(
key?: StoreKey<DBTypes, StoreName> | IDBKeyRange | null
): Promise<number>;Data Retrieval Examples:
const tx = db.transaction("users", "readonly");
const userStore = tx.store;
// Get single record by key
const user = await userStore.get("user123");
if (user) {
console.log("Found user:", user.name);
}
// Get multiple records
const allUsers = await userStore.getAll();
const recentUsers = await userStore.getAll(IDBKeyRange.lowerBound(1000));
const firstTenUsers = await userStore.getAll(null, 10);
// Get keys only (more efficient when you don't need values)
const allUserKeys = await userStore.getAllKeys();
const recentUserKeys = await userStore.getAllKeys(IDBKeyRange.lowerBound(1000), 50);
// Get specific key
const foundKey = await userStore.getKey(IDBKeyRange.bound("user100", "user200"));
// Count records
const totalUsers = await userStore.count();
const activeUsers = await userStore.count(IDBKeyRange.only("active"));
await tx.done;Create and access indexes during database upgrades.
/**
* Creates a new index in store
* Throws "InvalidStateError" if not called within an upgrade transaction
* Only available in versionchange transactions
* @param name - Name of the index
* @param keyPath - Property path or array of paths for the index
* @param options - Index configuration options
* @returns Enhanced index interface
*/
createIndex: Mode extends 'versionchange'
? <IndexName extends IndexNames<DBTypes, StoreName>>(
name: IndexName,
keyPath: string | string[],
options?: IDBIndexParameters
) => IDBPIndex<DBTypes, TxStores, StoreName, IndexName, Mode>
: undefined;
/**
* Get an index by name
* @param name - Name of the index
* @returns Enhanced index interface
*/
index<IndexName extends IndexNames<DBTypes, StoreName>>(
name: IndexName
): IDBPIndex<DBTypes, TxStores, StoreName, IndexName, Mode>;Index Management Examples:
// Creating indexes during database upgrade
const db = await openDB("my-database", 2, {
upgrade(db, oldVersion) {
if (oldVersion < 1) {
const userStore = db.createObjectStore("users", {
keyPath: "id",
autoIncrement: true
});
// Create various types of indexes
userStore.createIndex("email", "email", { unique: true });
userStore.createIndex("name", "name");
userStore.createIndex("age", "age");
userStore.createIndex("fullName", ["firstName", "lastName"]);
userStore.createIndex("tags", "tags", { multiEntry: true });
}
}
});
// Using indexes for queries
const tx = db.transaction("users", "readonly");
const userStore = tx.store;
// Access specific indexes
const emailIndex = userStore.index("email");
const nameIndex = userStore.index("name");
const ageIndex = userStore.index("age");
// Query using indexes
const userByEmail = await emailIndex.get("alice@example.com");
const usersByName = await nameIndex.getAll("Alice");
const youngUsers = await ageIndex.getAll(IDBKeyRange.upperBound(25));Open cursors for efficient iteration over large datasets.
/**
* Opens a cursor over the records matching the query
* Resolves with null if no matches are found
* @param query - Key or key range to match (optional)
* @param direction - Cursor direction (next, nextunique, prev, prevunique)
* @returns Promise resolving to cursor or null
*/
openCursor(
query?: StoreKey<DBTypes, StoreName> | IDBKeyRange | null,
direction?: IDBCursorDirection
): Promise<IDBPCursorWithValue<DBTypes, TxStores, StoreName, unknown, Mode> | null>;
/**
* Opens a cursor over the keys matching the query
* Resolves with null if no matches are found
* @param query - Key or key range to match (optional)
* @param direction - Cursor direction (next, nextunique, prev, prevunique)
* @returns Promise resolving to cursor or null
*/
openKeyCursor(
query?: StoreKey<DBTypes, StoreName> | IDBKeyRange | null,
direction?: IDBCursorDirection
): Promise<IDBPCursor<DBTypes, TxStores, StoreName, unknown, Mode> | null>;Cursor Usage Examples:
const tx = db.transaction("users", "readonly");
const userStore = tx.store;
// Iterate with cursor manually
let cursor = await userStore.openCursor();
while (cursor) {
console.log("User:", cursor.key, cursor.value);
cursor = await cursor.continue();
}
// Iterate over keys only
let keyCursor = await userStore.openKeyCursor();
while (keyCursor) {
console.log("User key:", keyCursor.key);
keyCursor = await keyCursor.continue();
}
// Cursor with query and direction
let filteredCursor = await userStore.openCursor(
IDBKeyRange.bound("user100", "user200"),
"prev" // Reverse order
);
while (filteredCursor) {
console.log("Filtered user:", filteredCursor.value);
filteredCursor = await filteredCursor.continue();
}Use modern async iteration syntax for convenient data traversal.
/**
* Iterate over the store using async iteration
* @returns Async iterable iterator over cursor values
*/
[Symbol.asyncIterator](): AsyncIterableIterator<
IDBPCursorWithValueIteratorValue<DBTypes, TxStores, StoreName, unknown, Mode>
>;
/**
* Iterate over the records matching the query
* @param query - Key or key range to match (optional)
* @param direction - Cursor direction (optional)
* @returns Async iterable iterator over cursor values
*/
iterate(
query?: StoreKey<DBTypes, StoreName> | IDBKeyRange | null,
direction?: IDBCursorDirection
): AsyncIterableIterator<
IDBPCursorWithValueIteratorValue<DBTypes, TxStores, StoreName, unknown, Mode>
>;Async Iteration Examples:
const tx = db.transaction("users", "readonly");
const userStore = tx.store;
// Iterate over all records
for await (const cursor of userStore) {
console.log("User:", cursor.key, cursor.value);
// Control iteration
if (cursor.value.age < 18) {
cursor.continue(); // Skip to next
}
}
// Iterate with query
for await (const cursor of userStore.iterate(IDBKeyRange.bound(100, 200))) {
console.log("User in range:", cursor.value);
// Early termination
if (cursor.value.name === "Target User") {
break;
}
}
// Iterate in reverse order
for await (const cursor of userStore.iterate(null, "prev")) {
console.log("User (reverse):", cursor.value);
}
// Collect results with async iteration
const activeUsers = [];
for await (const cursor of userStore.iterate()) {
if (cursor.value.status === "active") {
activeUsers.push(cursor.value);
}
}
await tx.done;Strategies for efficient object store operations.
Batch Operations:
async function batchAddUsers(users: User[]) {
const tx = db.transaction("users", "readwrite");
const userStore = tx.store;
// Start all operations without awaiting individually
const promises = users.map(user => userStore.add(user));
// Wait for all operations and transaction completion
await Promise.all([...promises, tx.done]);
}Efficient Counting:
// Use count() instead of getAll().length for better performance
const userCount = await userStore.count();
// Count with filters using key ranges
const adultCount = await userStore.index("age").count(IDBKeyRange.lowerBound(18));Key-Only Queries:
// More efficient when you only need keys
const userKeys = await userStore.getAllKeys();
// Instead of
const users = await userStore.getAll();
const keys = users.map(user => user.id); // Less efficient