Transaction management and cursor-based iteration for efficient data processing and batch operations.
Provides atomic operations across one or more object stores with ACID properties.
interface IDBTransaction extends EventTarget {
/** List of object stores in this transaction scope (readonly) */
readonly objectStoreNames: DOMStringList;
/** Transaction access mode (readonly) */
readonly mode: "readonly" | "readwrite" | "versionchange";
/** Durability setting for the transaction (readonly) */
readonly durability: "default" | "strict" | "relaxed";
/** Database this transaction belongs to (readonly) */
readonly db: IDBDatabase;
/** Error that caused transaction to abort (readonly) */
readonly error: DOMException | null;
/**
* Gets an object store within this transaction
* @param name - Object store name
* @returns Object store instance bound to this transaction
*/
objectStore(name: string): IDBObjectStore;
/**
* Explicitly aborts the transaction
*/
abort(): void;
/**
* Explicitly commits the transaction (auto-commits when no pending operations)
*/
commit(): void;
// Event handlers
onabort: ((event: Event) => void) | null;
oncomplete: ((event: Event) => void) | null;
onerror: ((event: Event) => void) | null;
}Usage Examples:
import "fake-indexeddb/auto";
// Single store transaction
const tx1 = db.transaction("products", "readonly");
const productsStore = tx1.objectStore("products");
productsStore.get("LAP001").onsuccess = (event) => {
console.log("Product:", event.target.result);
};
tx1.oncomplete = () => {
console.log("Read transaction completed");
};
// Multi-store transaction
const tx2 = db.transaction(["products", "orders"], "readwrite");
const productsStore2 = tx2.objectStore("products");
const ordersStore = tx2.objectStore("orders");
// Atomic operation across multiple stores
const productRequest = productsStore2.get("LAP001");
productRequest.onsuccess = (event) => {
const product = event.target.result;
if (product.stock > 0) {
// Decrease stock
product.stock--;
productsStore2.put(product);
// Create order
ordersStore.add({
productSku: product.sku,
quantity: 1,
price: product.price,
timestamp: new Date()
});
} else {
// Abort if out of stock
tx2.abort();
}
};
tx2.oncomplete = () => {
console.log("Order transaction completed successfully");
};
tx2.onabort = () => {
console.log("Order transaction aborted - insufficient stock");
};
tx2.onerror = (event) => {
console.error("Transaction error:", event.target.error);
};// Transaction with durability settings
const tx = db.transaction("criticalData", "readwrite", {
durability: "strict" // Ensures data is written to persistent storage
});
const store = tx.objectStore("criticalData");
// Queue multiple operations
store.put({ id: 1, data: "important" });
store.put({ id: 2, data: "critical" });
store.delete("old-record");
// Manual commit for control
tx.commit();
tx.oncomplete = () => {
console.log("Critical data saved with strict durability");
};
// Handle transaction timeout/abort
setTimeout(() => {
if (tx.error === null) {
console.log("Transaction taking too long, aborting");
tx.abort();
}
}, 5000);Enables efficient iteration over records in object stores or indexes.
interface IDBCursor {
/** Source object store or index (readonly) */
readonly source: IDBObjectStore | IDBIndex;
/** Iteration direction (readonly) */
readonly direction: IDBCursorDirection;
/** Current key (readonly) */
readonly key: IDBValidKey;
/** Current primary key (readonly) */
readonly primaryKey: IDBValidKey;
/**
* Advances cursor by specified number of records
* @param count - Number of records to skip
*/
advance(count: number): void;
/**
* Continues cursor to next record or specific key
* @param key - Optional key to continue to
*/
continue(key?: IDBValidKey): void;
/**
* Continues cursor to specific key and primary key combination
* @param key - Index key to continue to
* @param primaryKey - Primary key to continue to
*/
continuePrimaryKey(key: IDBValidKey, primaryKey: IDBValidKey): void;
/**
* Deletes the current record
* @returns Request completing when deletion is done
*/
delete(): IDBRequest;
/**
* Updates the current record
* @param value - New value for the record
* @returns Request completing when update is done
*/
update(value: any): IDBRequest;
}Extended cursor that also provides access to record values.
interface IDBCursorWithValue extends IDBCursor {
/** Current record value (readonly) */
readonly value: any;
}Usage Examples:
// Basic cursor iteration
const tx = db.transaction("products", "readonly");
const store = tx.objectStore("products");
store.openCursor().onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
const product = cursor.value;
console.log(`${product.name}: $${product.price}`);
cursor.continue();
} else {
console.log("No more products");
}
};
// Cursor with key range and direction
const priceIndex = store.index("by_price");
const range = IDBKeyRange.bound(100, 500); // $100-$500 range
priceIndex.openCursor(range, "prev").onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
console.log(`Expensive: ${cursor.value.name} - $${cursor.key}`);
cursor.continue();
}
};
// Key-only cursor for better performance
store.openKeyCursor().onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
console.log("Product SKU:", cursor.key);
cursor.continue();
}
};// Process records in batches
function processBatch(store, batchSize = 100) {
return new Promise((resolve, reject) => {
const results = [];
let processed = 0;
store.openCursor().onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
const record = cursor.value;
// Process record
const processedRecord = {
...record,
processedAt: new Date(),
status: 'processed'
};
results.push(processedRecord);
processed++;
if (processed >= batchSize) {
// Process batch and continue
console.log(`Processed batch of ${results.length} records`);
results.length = 0; // Clear batch
processed = 0;
}
cursor.continue();
} else {
// Process final batch
if (results.length > 0) {
console.log(`Processed final batch of ${results.length} records`);
}
resolve();
}
};
store.openCursor().onerror = (event) => {
reject(event.target.error);
};
});
}
// Usage
const tx = db.transaction("largeDataset", "readonly");
const store = tx.objectStore("largeDataset");
processBatch(store, 50).then(() => {
console.log("All records processed");
});// Paginate through results using cursors
function getPage(store, pageSize = 20, lastKey = null) {
return new Promise((resolve) => {
const results = [];
let count = 0;
const range = lastKey ? IDBKeyRange.lowerBound(lastKey, true) : null;
store.openCursor(range).onsuccess = (event) => {
const cursor = event.target.result;
if (cursor && count < pageSize) {
results.push({
key: cursor.key,
value: cursor.value
});
count++;
cursor.continue();
} else {
const hasMore = cursor !== null;
const nextKey = hasMore ? cursor.key : null;
resolve({
results,
hasMore,
nextKey
});
}
};
});
}
// Usage - paginate through products
async function paginateProducts() {
const tx = db.transaction("products", "readonly");
const store = tx.objectStore("products");
let page = 1;
let lastKey = null;
do {
const result = await getPage(store, 10, lastKey);
console.log(`Page ${page}:`, result.results.map(r => r.value.name));
lastKey = result.nextKey;
page++;
if (!result.hasMore) break;
} while (lastKey);
}// Update records based on conditions
function updatePrices(store, priceIncrease = 0.1) {
return new Promise((resolve, reject) => {
let updated = 0;
const tx = store.transaction;
store.openCursor().onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
const product = cursor.value;
// Apply conditional logic
if (product.category === "electronics" && product.price < 1000) {
product.price = Math.round(product.price * (1 + priceIncrease) * 100) / 100;
product.lastUpdated = new Date();
cursor.update(product).onsuccess = () => {
updated++;
};
}
cursor.continue();
} else {
resolve(updated);
}
};
store.openCursor().onerror = (event) => {
reject(event.target.error);
};
});
}
// Usage
const tx = db.transaction("products", "readwrite");
const store = tx.objectStore("products");
updatePrices(store, 0.05).then((count) => {
console.log(`Updated prices for ${count} electronics products`);
});
tx.oncomplete = () => {
console.log("Price update transaction completed");
};// Iterate through index with cursor for complex queries
function findProductsByPriceRange(categoryIndex, category, minPrice, maxPrice) {
return new Promise((resolve) => {
const results = [];
// Use compound index for efficient querying
const range = IDBKeyRange.bound([category, minPrice], [category, maxPrice]);
categoryIndex.openCursor(range).onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
results.push(cursor.value);
cursor.continue();
} else {
resolve(results);
}
};
});
}
// Usage
const tx = db.transaction("products", "readonly");
const store = tx.objectStore("products");
const categoryIndex = store.index("category_price");
findProductsByPriceRange(categoryIndex, "electronics", 500, 1500).then((products) => {
console.log("Electronics in $500-$1500 range:", products);
});// Different cursor directions
const store = tx.objectStore("products");
const priceIndex = store.index("by_price");
// Forward iteration (default)
priceIndex.openCursor(null, "next").onsuccess = (event) => {
// Cheapest to most expensive
};
// Reverse iteration
priceIndex.openCursor(null, "prev").onsuccess = (event) => {
// Most expensive to cheapest
};
// Unique values only (forward)
priceIndex.openCursor(null, "nextunique").onsuccess = (event) => {
// Skip duplicate prices, cheapest to expensive
};
// Unique values only (reverse)
priceIndex.openCursor(null, "prevunique").onsuccess = (event) => {
// Skip duplicate prices, expensive to cheapest
};// Comprehensive error handling
const tx = db.transaction(["products", "inventory"], "readwrite");
tx.onerror = (event) => {
const error = event.target.error;
console.error("Transaction failed:", error.name, error.message);
// Handle specific error types
switch (error.name) {
case "AbortError":
console.log("Transaction was aborted");
break;
case "ConstraintError":
console.log("Constraint violation occurred");
break;
case "ReadOnlyError":
console.log("Attempted write on readonly transaction");
break;
default:
console.log("Unknown transaction error");
}
};
tx.onabort = (event) => {
console.log("Transaction aborted");
// Cleanup or retry logic
};
// Operation-level error handling
const store = tx.objectStore("products");
const request = store.add(product);
request.onerror = (event) => {
console.error("Add operation failed:", event.target.error);
// Don't prevent transaction from continuing
event.preventDefault();
};