CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-fake-indexeddb

A pure JS in-memory implementation of the IndexedDB API for testing IndexedDB-dependent code in Node.js environments

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

transactions-cursors.mddocs/

Transactions and Cursors

Transaction management and cursor-based iteration for efficient data processing and batch operations.

Capabilities

IDBTransaction

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 Lifecycle Management

// 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);

IDBCursor

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;
}

IDBCursorWithValue

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();
  }
};

Advanced Cursor Patterns

Batch Processing with Cursors

// 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");
});

Cursor Pagination

// 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);
}

Conditional Updates with Cursors

// 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");
};

Cursor with Index Iteration

// 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);
});

Cursor Direction Options

// 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
};

Transaction Error Handling

// 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();
};

docs

database-management.md

events-utilities.md

index.md

key-ranges.md

object-stores.md

transactions-cursors.md

tile.json