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

object-stores.mddocs/

Object Stores and Indexes

Data storage and retrieval operations with support for primary keys, indexes, and complex queries.

Capabilities

IDBObjectStore

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

IDBIndex

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

Advanced Object Store Patterns

Auto-incrementing Keys

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

Out-of-line Keys

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

Multi-entry Indexes

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

Batch Operations

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

Data Validation

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

docs

database-management.md

events-utilities.md

index.md

key-ranges.md

object-stores.md

transactions-cursors.md

tile.json