A pure JS in-memory implementation of the IndexedDB API for testing IndexedDB-dependent code in Node.js environments
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Data storage and retrieval operations with support for primary keys, indexes, and complex queries.
Primary storage interface for data records with key-based access and indexing support.
interface IDBObjectStore {
/** Object store name (can be changed during versionchange transaction) */
name: string;
/** Key path used for primary keys (readonly) */
readonly keyPath: string | string[] | null;
/** Whether keys are auto-generated (readonly) */
readonly autoIncrement: boolean;
/** List of index names in this store (readonly) */
readonly indexNames: DOMStringList;
/** Transaction this store belongs to (readonly) */
readonly transaction: IDBTransaction;
/**
* Stores a record, replacing if key exists
* @param value - Data to store
* @param key - Primary key (optional if keyPath or autoIncrement used)
* @returns Request completing with the stored key
*/
put(value: any, key?: IDBValidKey): IDBRequest;
/**
* Adds a record, failing if key exists
* @param value - Data to store
* @param key - Primary key (optional if keyPath or autoIncrement used)
* @returns Request completing with the stored key
*/
add(value: any, key?: IDBValidKey): IDBRequest;
/**
* Deletes records matching the key or key range
* @param key - Key or key range to delete
* @returns Request completing when deletion is done
*/
delete(key: IDBValidKey | IDBKeyRange): IDBRequest;
/**
* Retrieves a single record by key
* @param key - Key or key range to retrieve
* @returns Request completing with the record value
*/
get(key: IDBValidKey | IDBKeyRange): IDBRequest;
/**
* Retrieves multiple records
* @param query - Optional key or key range
* @param count - Maximum number of records to return
* @returns Request completing with array of values
*/
getAll(query?: IDBValidKey | IDBKeyRange, count?: number): IDBRequest;
/**
* Retrieves primary key without the full record
* @param key - Key or key range to find
* @returns Request completing with the primary key
*/
getKey(key: IDBValidKey | IDBKeyRange): IDBRequest;
/**
* Retrieves multiple primary keys
* @param query - Optional key or key range
* @param count - Maximum number of keys to return
* @returns Request completing with array of keys
*/
getAllKeys(query?: IDBValidKey | IDBKeyRange, count?: number): IDBRequest;
/**
* Counts records matching the key or key range
* @param key - Optional key or key range to count
* @returns Request completing with the count
*/
count(key?: IDBValidKey | IDBKeyRange): IDBRequest;
/**
* Removes all records from the store
* @returns Request completing when clearing is done
*/
clear(): IDBRequest;
/**
* Creates a new index (only during versionchange transaction)
* @param name - Index name
* @param keyPath - Property path(s) to index
* @param options - Index configuration
* @returns Created index
*/
createIndex(name: string, keyPath: string | string[], options?: {
unique?: boolean;
multiEntry?: boolean;
}): IDBIndex;
/**
* Gets an existing index
* @param name - Index name
* @returns The index object
*/
index(name: string): IDBIndex;
/**
* Deletes an index (only during versionchange transaction)
* @param name - Index name to delete
*/
deleteIndex(name: string): void;
/**
* Opens a cursor for iteration
* @param range - Optional key range to iterate
* @param direction - Cursor direction
* @returns Request completing with cursor or null
*/
openCursor(
range?: IDBValidKey | IDBKeyRange,
direction?: IDBCursorDirection
): IDBRequest;
/**
* Opens a key-only cursor for iteration
* @param range - Optional key range to iterate
* @param direction - Cursor direction
* @returns Request completing with cursor or null
*/
openKeyCursor(
range?: IDBValidKey | IDBKeyRange,
direction?: IDBCursorDirection
): IDBRequest;
}Usage Examples:
import "fake-indexeddb/auto";
// Open database and create transaction
const request = indexedDB.open("inventory", 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
// Create object store with compound key path
const productsStore = db.createObjectStore("products", {
keyPath: "sku" // Use product SKU as primary key
});
// Create object store with auto-increment
const ordersStore = db.createObjectStore("orders", {
keyPath: "id",
autoIncrement: true
});
// Create indexes for efficient querying
productsStore.createIndex("by_category", "category");
productsStore.createIndex("by_price", "price");
productsStore.createIndex("category_price", ["category", "price"]);
ordersStore.createIndex("by_customer", "customerId");
ordersStore.createIndex("by_date", "orderDate");
};
request.onsuccess = (event) => {
const db = event.target.result;
// Add data to store
const tx = db.transaction("products", "readwrite");
const store = tx.objectStore("products");
// Add records
store.add({
sku: "LAP001",
name: "Gaming Laptop",
category: "electronics",
price: 1299.99,
stock: 5
});
store.put({
sku: "LAP002",
name: "Business Laptop",
category: "electronics",
price: 899.99,
stock: 12
});
// Get single record
store.get("LAP001").onsuccess = (event) => {
const product = event.target.result;
console.log("Product:", product.name, "Price:", product.price);
};
// Get multiple records with limit
store.getAll(null, 10).onsuccess = (event) => {
const products = event.target.result;
console.log("First 10 products:", products);
};
// Count all records
store.count().onsuccess = (event) => {
console.log("Total products:", event.target.result);
};
tx.oncomplete = () => {
console.log("Transaction completed");
db.close();
};
};Secondary access path to object store data based on indexed properties.
interface IDBIndex {
/** Index name (can be changed during versionchange transaction) */
name: string;
/** Parent object store (readonly) */
readonly objectStore: IDBObjectStore;
/** Property path(s) this index covers (readonly) */
readonly keyPath: string | string[];
/** Whether index values must be unique (readonly) */
readonly unique: boolean;
/** Whether arrays are treated as multiple entries (readonly) */
readonly multiEntry: boolean;
/**
* Retrieves first record matching the index key
* @param key - Index key or key range to match
* @returns Request completing with the record value
*/
get(key: IDBValidKey | IDBKeyRange): IDBRequest;
/**
* Retrieves multiple records matching the index key
* @param query - Optional index key or key range
* @param count - Maximum number of records to return
* @returns Request completing with array of values
*/
getAll(query?: IDBValidKey | IDBKeyRange, count?: number): IDBRequest;
/**
* Retrieves primary key for first matching record
* @param key - Index key or key range to match
* @returns Request completing with the primary key
*/
getKey(key: IDBValidKey | IDBKeyRange): IDBRequest;
/**
* Retrieves multiple primary keys
* @param query - Optional index key or key range
* @param count - Maximum number of keys to return
* @returns Request completing with array of primary keys
*/
getAllKeys(query?: IDBValidKey | IDBKeyRange, count?: number): IDBRequest;
/**
* Counts records matching the index key
* @param key - Optional index key or key range to count
* @returns Request completing with the count
*/
count(key?: IDBValidKey | IDBKeyRange): IDBRequest;
/**
* Opens a cursor for iteration via index
* @param range - Optional key range to iterate
* @param direction - Cursor direction
* @returns Request completing with cursor or null
*/
openCursor(
range?: IDBValidKey | IDBKeyRange,
direction?: IDBCursorDirection
): IDBRequest;
/**
* Opens a key-only cursor for iteration via index
* @param range - Optional key range to iterate
* @param direction - Cursor direction
* @returns Request completing with cursor or null
*/
openKeyCursor(
range?: IDBValidKey | IDBKeyRange,
direction?: IDBCursorDirection
): IDBRequest;
}Usage Examples:
// Query by index
const tx = db.transaction("products", "readonly");
const store = tx.objectStore("products");
// Query by single index value
const categoryIndex = store.index("by_category");
categoryIndex.get("electronics").onsuccess = (event) => {
const product = event.target.result;
console.log("First electronics product:", product);
};
// Get all products in category
categoryIndex.getAll("electronics").onsuccess = (event) => {
const products = event.target.result;
console.log("All electronics:", products);
};
// Query by compound index (category + price)
const compoundIndex = store.index("category_price");
compoundIndex.getAll(["electronics", 1000]).onsuccess = (event) => {
const products = event.target.result;
console.log("Electronics costing $1000:", products);
};
// Count products in price range
const priceIndex = store.index("by_price");
const priceRange = IDBKeyRange.bound(500, 1500);
priceIndex.count(priceRange).onsuccess = (event) => {
console.log("Products in $500-$1500 range:", event.target.result);
};
// Iterate through index with cursor
priceIndex.openCursor().onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
const product = cursor.value;
console.log(`${product.name}: $${product.price}`);
cursor.continue();
}
};// Object store with auto-increment
const logsStore = db.createObjectStore("logs", {
keyPath: "id",
autoIncrement: true
});
// Records get automatically assigned sequential IDs
const tx = db.transaction("logs", "readwrite");
const store = tx.objectStore("logs");
store.add({
message: "User logged in",
timestamp: new Date(),
level: "info"
}); // Gets id: 1
store.add({
message: "Data processed",
timestamp: new Date(),
level: "debug"
}); // Gets id: 2// Object store without keyPath (out-of-line keys)
const cacheStore = db.createObjectStore("cache");
// Must provide keys explicitly
const tx = db.transaction("cache", "readwrite");
const store = tx.objectStore("cache");
store.put("cached data", "user:123");
store.put({ config: "settings" }, "app:config");
store.put([1, 2, 3], "numbers");// Create multi-entry index for arrays
const articlesStore = db.createObjectStore("articles", { keyPath: "id" });
articlesStore.createIndex("by_tags", "tags", { multiEntry: true });
// Add article with multiple tags
const tx = db.transaction("articles", "readwrite");
const store = tx.objectStore("articles");
store.add({
id: 1,
title: "IndexedDB Guide",
tags: ["javascript", "database", "tutorial"], // Array creates multiple index entries
content: "..."
});
// Query finds article by any tag
const tagIndex = store.index("by_tags");
tagIndex.get("javascript").onsuccess = (event) => {
// Finds the article above
console.log("Article about JavaScript:", event.target.result);
};// Efficient batch operations
const tx = db.transaction("products", "readwrite");
const store = tx.objectStore("products");
const products = [
{ sku: "A001", name: "Product A", price: 99.99 },
{ sku: "A002", name: "Product B", price: 149.99 },
{ sku: "A003", name: "Product C", price: 199.99 }
];
// Add all products in single transaction
products.forEach(product => {
store.add(product);
});
tx.oncomplete = () => {
console.log("All products added successfully");
};
tx.onerror = (event) => {
console.error("Batch operation failed:", event.target.error);
};// Validate data before storing
function addProduct(store, productData) {
// Validate required fields
if (!productData.sku || !productData.name || productData.price == null) {
throw new Error("Missing required product fields");
}
// Validate data types
if (typeof productData.price !== "number" || productData.price < 0) {
throw new Error("Invalid price");
}
// Sanitize data
const product = {
sku: productData.sku.toString().trim().toUpperCase(),
name: productData.name.toString().trim(),
price: Number(productData.price),
category: productData.category || "uncategorized",
createdAt: new Date()
};
return store.add(product);
}
// Usage
const tx = db.transaction("products", "readwrite");
const store = tx.objectStore("products");
try {
addProduct(store, {
sku: "lap001",
name: " Gaming Laptop ",
price: "1299.99",
category: "electronics"
});
} catch (error) {
console.error("Validation failed:", error.message);
}