or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

database-management.mdevents-utilities.mdindex.mdkey-ranges.mdobject-stores.mdtransactions-cursors.md
tile.json

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