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
Advanced querying capabilities with key ranges, bounds, and filtering for precise data selection.
Factory methods for creating key ranges to filter queries and cursor iterations.
interface IDBKeyRangeStatic {
/**
* Creates a range containing only the specified key
* @param value - The single key value to match
* @returns Key range matching exactly the given value
*/
only(value: IDBValidKey): IDBKeyRange;
/**
* Creates a range with a lower bound
* @param lower - The lower bound key
* @param open - Whether lower bound is exclusive (default: false)
* @returns Key range from lower bound to end
*/
lowerBound(lower: IDBValidKey, open?: boolean): IDBKeyRange;
/**
* Creates a range with an upper bound
* @param upper - The upper bound key
* @param open - Whether upper bound is exclusive (default: false)
* @returns Key range from start to upper bound
*/
upperBound(upper: IDBValidKey, open?: boolean): IDBKeyRange;
/**
* Creates a range with both lower and upper bounds
* @param lower - The lower bound key
* @param upper - The upper bound key
* @param lowerOpen - Whether lower bound is exclusive (default: false)
* @param upperOpen - Whether upper bound is exclusive (default: false)
* @returns Key range between the bounds
*/
bound(
lower: IDBValidKey,
upper: IDBValidKey,
lowerOpen?: boolean,
upperOpen?: boolean
): IDBKeyRange;
}Represents a range of keys for querying and filtering operations.
interface IDBKeyRange {
/** Lower bound value (readonly) */
readonly lower: IDBValidKey | undefined;
/** Upper bound value (readonly) */
readonly upper: IDBValidKey | undefined;
/** Whether lower bound is exclusive (readonly) */
readonly lowerOpen: boolean;
/** Whether upper bound is exclusive (readonly) */
readonly upperOpen: boolean;
/**
* Tests whether a key is included in this range
* @param key - Key to test
* @returns True if key is within the range
*/
includes(key: IDBValidKey): boolean;
}Usage Examples:
import "fake-indexeddb/auto";
// Single value range
const exactRange = IDBKeyRange.only("electronics");
store.index("by_category").getAll(exactRange);
// Lower bound range (>= 100)
const minPriceRange = IDBKeyRange.lowerBound(100);
store.index("by_price").getAll(minPriceRange);
// Lower bound range (> 100, exclusive)
const aboveMinRange = IDBKeyRange.lowerBound(100, true);
store.index("by_price").getAll(aboveMinRange);
// Upper bound range (<= 500)
const maxPriceRange = IDBKeyRange.upperBound(500);
store.index("by_price").getAll(maxPriceRange);
// Upper bound range (< 500, exclusive)
const belowMaxRange = IDBKeyRange.upperBound(500, true);
store.index("by_price").getAll(belowMaxRange);
// Bounded range (100 <= price <= 500)
const priceRange = IDBKeyRange.bound(100, 500);
store.index("by_price").getAll(priceRange);
// Bounded range (100 < price < 500, both exclusive)
const exclusiveRange = IDBKeyRange.bound(100, 500, true, true);
store.index("by_price").getAll(exclusiveRange);// Query records within date range
const startDate = new Date("2024-01-01");
const endDate = new Date("2024-12-31");
const dateRange = IDBKeyRange.bound(startDate, endDate);
const tx = db.transaction("orders", "readonly");
const store = tx.objectStore("orders");
const dateIndex = store.index("by_date");
dateIndex.getAll(dateRange).onsuccess = (event) => {
const orders = event.target.result;
console.log(`Orders from 2024: ${orders.length}`);
};
// Last 30 days
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
const recentRange = IDBKeyRange.lowerBound(thirtyDaysAgo);
dateIndex.getAll(recentRange).onsuccess = (event) => {
const recentOrders = event.target.result;
console.log(`Recent orders: ${recentOrders.length}`);
};// Query with compound keys [category, price]
const categoryPriceIndex = store.index("category_price");
// Electronics under $1000
const electronicsRange = IDBKeyRange.bound(
["electronics", 0],
["electronics", 1000]
);
categoryPriceIndex.getAll(electronicsRange).onsuccess = (event) => {
const affordableElectronics = event.target.result;
console.log("Affordable electronics:", affordableElectronics);
};
// All books (any price)
const booksRange = IDBKeyRange.bound(
["books", Number.NEGATIVE_INFINITY],
["books", Number.POSITIVE_INFINITY]
);
// Or more simply for all books:
const allBooksRange = IDBKeyRange.bound(
["books"],
["books", []] // Empty array is greater than any other value
);// String prefix matching
const nameIndex = store.index("by_name");
// Products starting with "Lap" (like "Laptop")
const prefixRange = IDBKeyRange.bound("Lap", "Lap\uffff");
nameIndex.getAll(prefixRange).onsuccess = (event) => {
const laptops = event.target.result;
console.log("Products starting with 'Lap':", laptops);
};
// Case-insensitive range (if data is normalized to lowercase)
const searchTerm = "gaming";
const caseInsensitiveRange = IDBKeyRange.bound(
searchTerm,
searchTerm + "\uffff"
);
nameIndex.getAll(caseInsensitiveRange).onsuccess = (event) => {
const gamingProducts = event.target.result;
console.log("Gaming products:", gamingProducts);
};// Price ranges for different budgets
const priceIndex = store.index("by_price");
const budgetRanges = {
budget: IDBKeyRange.upperBound(100), // <= $100
midRange: IDBKeyRange.bound(100, 500, true), // $100 < price <= $500
premium: IDBKeyRange.bound(500, 2000, true), // $500 < price <= $2000
luxury: IDBKeyRange.lowerBound(2000, true) // > $2000
};
Object.entries(budgetRanges).forEach(([category, range]) => {
priceIndex.count(range).onsuccess = (event) => {
console.log(`${category} products: ${event.target.result}`);
};
});// Test if key is in range
const priceRange = IDBKeyRange.bound(100, 500);
console.log(priceRange.includes(150)); // true
console.log(priceRange.includes(50)); // false
console.log(priceRange.includes(600)); // false
console.log(priceRange.includes(100)); // true (inclusive by default)
console.log(priceRange.includes(500)); // true (inclusive by default)
// Test with exclusive bounds
const exclusiveRange = IDBKeyRange.bound(100, 500, true, true);
console.log(exclusiveRange.includes(100)); // false (exclusive)
console.log(exclusiveRange.includes(500)); // false (exclusive)
console.log(exclusiveRange.includes(300)); // true// Complex filtering using cursor + key ranges
function findProductsWithConditions(store, conditions) {
return new Promise((resolve) => {
const results = [];
const { category, minPrice, maxPrice, inStock } = conditions;
// Use compound index for category + price
const categoryPriceIndex = store.index("category_price");
const range = IDBKeyRange.bound(
[category, minPrice],
[category, maxPrice]
);
categoryPriceIndex.openCursor(range).onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
const product = cursor.value;
// Additional filtering in memory
if (!inStock || product.stock > 0) {
results.push(product);
}
cursor.continue();
} else {
resolve(results);
}
};
});
}
// Usage
const tx = db.transaction("products", "readonly");
const store = tx.objectStore("products");
findProductsWithConditions(store, {
category: "electronics",
minPrice: 200,
maxPrice: 1000,
inStock: true
}).then((products) => {
console.log("Filtered products:", products);
});// Paginate within a key range
function getPaginatedRange(index, keyRange, pageSize, lastKey = null) {
return new Promise((resolve) => {
const results = [];
let count = 0;
// Adjust range to start after last key
let effectiveRange = keyRange;
if (lastKey) {
if (keyRange.upper !== undefined) {
effectiveRange = IDBKeyRange.bound(
lastKey,
keyRange.upper,
true, // Exclude lastKey
keyRange.upperOpen
);
} else {
effectiveRange = IDBKeyRange.lowerBound(lastKey, true);
}
}
index.openCursor(effectiveRange).onsuccess = (event) => {
const cursor = event.target.result;
if (cursor && count < pageSize) {
results.push({
key: cursor.key,
value: cursor.value
});
count++;
cursor.continue();
} else {
resolve({
results,
hasMore: cursor !== null,
lastKey: results.length > 0 ? results[results.length - 1].key : null
});
}
};
});
}
// Usage - paginate expensive products
const priceIndex = store.index("by_price");
const expensiveRange = IDBKeyRange.lowerBound(1000);
async function paginateExpensiveProducts() {
let lastKey = null;
let page = 1;
do {
const result = await getPaginatedRange(priceIndex, expensiveRange, 10, lastKey);
console.log(`Page ${page}:`, result.results.map(r =>
`${r.value.name} - $${r.key}`
));
lastKey = result.lastKey;
page++;
} while (result.hasMore);
}// Simulate OR queries by combining multiple ranges
async function getMultipleRanges(index, ranges) {
const allResults = [];
for (const range of ranges) {
const results = await new Promise((resolve) => {
const rangeResults = [];
index.openCursor(range).onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
rangeResults.push(cursor.value);
cursor.continue();
} else {
resolve(rangeResults);
}
};
});
allResults.push(...results);
}
// Remove duplicates based on primary key
const uniqueResults = allResults.filter((product, index, array) =>
array.findIndex(p => p.id === product.id) === index
);
return uniqueResults;
}
// Usage - get both budget and luxury products
const priceIndex = store.index("by_price");
const ranges = [
IDBKeyRange.upperBound(100), // Budget: <= $100
IDBKeyRange.lowerBound(2000) // Luxury: >= $2000
];
getMultipleRanges(priceIndex, ranges).then((products) => {
console.log("Budget and luxury products:", products);
});// Efficient key range usage
const priceIndex = store.index("by_price");
// Good - use specific range
const specificRange = IDBKeyRange.bound(100, 500);
priceIndex.getAll(specificRange);
// Better - use count() first for large datasets
priceIndex.count(specificRange).onsuccess = (event) => {
const count = event.target.result;
if (count < 1000) {
// Small result set - get all
priceIndex.getAll(specificRange);
} else {
// Large result set - use cursor with pagination
priceIndex.openCursor(specificRange);
}
};
// Best - combine with limits
priceIndex.getAll(specificRange, 50).onsuccess = (event) => {
const firstFifty = event.target.result;
// Process first 50 results
};// Handle invalid key ranges
try {
// This will throw DataError - lower > upper
const invalidRange = IDBKeyRange.bound(500, 100);
} catch (error) {
if (error.name === "DataError") {
console.error("Invalid key range - lower bound greater than upper bound");
}
}
// Validate keys before creating ranges
function createSafeRange(lower, upper) {
try {
// Normalize and validate keys
if (lower != null && upper != null) {
// Simple comparison for same types
if (typeof lower === typeof upper && lower > upper) {
throw new Error("Lower bound cannot be greater than upper bound");
}
}
return IDBKeyRange.bound(lower, upper);
} catch (error) {
console.error("Failed to create key range:", error.message);
return null;
}
}
// Usage
const range = createSafeRange(userInputMin, userInputMax);
if (range) {
priceIndex.getAll(range);
}