CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-idb

A small wrapper that makes IndexedDB usable with promises and enhanced TypeScript support

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-store-operations.mddocs/

Object Store Operations

Enhanced object store interface with promise-based operations and async iteration support.

Capabilities

Store Properties

Access object store metadata and transaction information.

/**
 * The names of indexes in the store
 */
readonly indexNames: TypedDOMStringList<IndexNames<DBTypes, StoreName>>;

/**
 * The associated transaction
 */
readonly transaction: IDBPTransaction<DBTypes, TxStores, Mode>;

Usage Examples:

const tx = db.transaction("users", "readwrite");
const userStore = tx.store;

// Check available indexes
console.log("Available indexes:", [...userStore.indexNames]);

// Access parent transaction
console.log("Transaction mode:", userStore.transaction.mode);

// Check if specific index exists
if (userStore.indexNames.contains("email")) {
  const emailIndex = userStore.index("email");
  const userByEmail = await emailIndex.get("alice@example.com");
}

Data Modification Operations

Add, update, and delete records in the object store.

Add Operation

/**
 * Add a value to the store
 * Rejects if an item of a given key already exists in the store
 * Only available in readwrite and versionchange transactions
 * @param value - Value to add to the store
 * @param key - Optional key (if not using keyPath)
 * @returns Promise resolving to the key of the added item
 */
add: Mode extends 'readonly'
  ? undefined
  : (
      value: StoreValue<DBTypes, StoreName>,
      key?: StoreKey<DBTypes, StoreName> | IDBKeyRange
    ) => Promise<StoreKey<DBTypes, StoreName>>;

Put Operation

/**
 * Put an item in the store
 * Replaces any item with the same key
 * Only available in readwrite and versionchange transactions
 * @param value - Value to put in the store
 * @param key - Optional key (if not using keyPath)
 * @returns Promise resolving to the key of the stored item
 */
put: Mode extends 'readonly'
  ? undefined
  : (
      value: StoreValue<DBTypes, StoreName>,
      key?: StoreKey<DBTypes, StoreName> | IDBKeyRange
    ) => Promise<StoreKey<DBTypes, StoreName>>;

Delete Operation

/**
 * Deletes records in store matching the given query
 * Only available in readwrite and versionchange transactions
 * @param key - Key or key range to delete
 * @returns Promise that resolves when deletion is complete
 */
delete: Mode extends 'readonly'
  ? undefined
  : (key: StoreKey<DBTypes, StoreName> | IDBKeyRange) => Promise<void>;

Clear Operation

/**
 * Deletes all records in store
 * Only available in readwrite and versionchange transactions
 * @returns Promise that resolves when clearing is complete
 */
clear: Mode extends 'readonly' ? undefined : () => Promise<void>;

Data Modification Examples:

// Write operations (readwrite transaction required)
const tx = db.transaction("users", "readwrite");
const userStore = tx.store;

// Add new records (fails if key exists)
const userId1 = await userStore.add({ name: "Alice", email: "alice@example.com" });
const userId2 = await userStore.add({ name: "Bob", email: "bob@example.com" });

// Put records (replaces if key exists)
await userStore.put({ id: userId1, name: "Alice Smith", email: "alice@example.com" });

// Delete specific record
await userStore.delete(userId2);

// Delete multiple records with key range
await userStore.delete(IDBKeyRange.bound(100, 200));

// Clear all records
await userStore.clear();

await tx.done;

// Read operations (readonly transaction is sufficient)
const readTx = db.transaction("users", "readonly");
const readStore = readTx.store;

// These operations are not available in readonly mode:
// readStore.add - undefined
// readStore.put - undefined
// readStore.delete - undefined
// readStore.clear - undefined

Data Retrieval Operations

Query and retrieve records from the object store.

Get Operations

/**
 * Retrieves the value of the first record matching the query
 * Resolves with undefined if no match is found
 * @param query - Key or key range to match
 * @returns Promise resolving to the value or undefined
 */
get(
  query: StoreKey<DBTypes, StoreName> | IDBKeyRange
): Promise<StoreValue<DBTypes, StoreName> | undefined>;

/**
 * Retrieves all values that match the query
 * @param query - Key or key range to match (optional)
 * @param count - Maximum number of values to return (optional)
 * @returns Promise resolving to array of matching values
 */
getAll(
  query?: StoreKey<DBTypes, StoreName> | IDBKeyRange | null,
  count?: number
): Promise<StoreValue<DBTypes, StoreName>[]>;

Key Operations

/**
 * Retrieves the key of the first record that matches the query
 * Resolves with undefined if no match is found
 * @param query - Key or key range to match
 * @returns Promise resolving to the key or undefined
 */
getKey(
  query: StoreKey<DBTypes, StoreName> | IDBKeyRange
): Promise<StoreKey<DBTypes, StoreName> | undefined>;

/**
 * Retrieves the keys of records matching the query
 * @param query - Key or key range to match (optional)
 * @param count - Maximum number of keys to return (optional)
 * @returns Promise resolving to array of matching keys
 */
getAllKeys(
  query?: StoreKey<DBTypes, StoreName> | IDBKeyRange | null,
  count?: number
): Promise<StoreKey<DBTypes, StoreName>[]>;

Count Operation

/**
 * Retrieves the number of records matching the given query
 * @param key - Key or key range to count (optional)
 * @returns Promise resolving to the count
 */
count(
  key?: StoreKey<DBTypes, StoreName> | IDBKeyRange | null
): Promise<number>;

Data Retrieval Examples:

const tx = db.transaction("users", "readonly");
const userStore = tx.store;

// Get single record by key
const user = await userStore.get("user123");
if (user) {
  console.log("Found user:", user.name);
}

// Get multiple records
const allUsers = await userStore.getAll();
const recentUsers = await userStore.getAll(IDBKeyRange.lowerBound(1000));
const firstTenUsers = await userStore.getAll(null, 10);

// Get keys only (more efficient when you don't need values)
const allUserKeys = await userStore.getAllKeys();
const recentUserKeys = await userStore.getAllKeys(IDBKeyRange.lowerBound(1000), 50);

// Get specific key
const foundKey = await userStore.getKey(IDBKeyRange.bound("user100", "user200"));

// Count records
const totalUsers = await userStore.count();
const activeUsers = await userStore.count(IDBKeyRange.only("active"));

await tx.done;

Index Management

Create and access indexes during database upgrades.

/**
 * Creates a new index in store
 * Throws "InvalidStateError" if not called within an upgrade transaction
 * Only available in versionchange transactions
 * @param name - Name of the index
 * @param keyPath - Property path or array of paths for the index
 * @param options - Index configuration options
 * @returns Enhanced index interface
 */
createIndex: Mode extends 'versionchange'
  ? <IndexName extends IndexNames<DBTypes, StoreName>>(
      name: IndexName,
      keyPath: string | string[],
      options?: IDBIndexParameters
    ) => IDBPIndex<DBTypes, TxStores, StoreName, IndexName, Mode>
  : undefined;

/**
 * Get an index by name
 * @param name - Name of the index
 * @returns Enhanced index interface
 */
index<IndexName extends IndexNames<DBTypes, StoreName>>(
  name: IndexName
): IDBPIndex<DBTypes, TxStores, StoreName, IndexName, Mode>;

Index Management Examples:

// Creating indexes during database upgrade
const db = await openDB("my-database", 2, {
  upgrade(db, oldVersion) {
    if (oldVersion < 1) {
      const userStore = db.createObjectStore("users", {
        keyPath: "id",
        autoIncrement: true
      });
      
      // Create various types of indexes
      userStore.createIndex("email", "email", { unique: true });
      userStore.createIndex("name", "name");
      userStore.createIndex("age", "age");
      userStore.createIndex("fullName", ["firstName", "lastName"]);
      userStore.createIndex("tags", "tags", { multiEntry: true });
    }
  }
});

// Using indexes for queries
const tx = db.transaction("users", "readonly");
const userStore = tx.store;

// Access specific indexes
const emailIndex = userStore.index("email");
const nameIndex = userStore.index("name");
const ageIndex = userStore.index("age");

// Query using indexes
const userByEmail = await emailIndex.get("alice@example.com");
const usersByName = await nameIndex.getAll("Alice");
const youngUsers = await ageIndex.getAll(IDBKeyRange.upperBound(25));

Cursor Operations

Open cursors for efficient iteration over large datasets.

/**
 * Opens a cursor over the records matching the query
 * Resolves with null if no matches are found
 * @param query - Key or key range to match (optional)
 * @param direction - Cursor direction (next, nextunique, prev, prevunique)
 * @returns Promise resolving to cursor or null
 */
openCursor(
  query?: StoreKey<DBTypes, StoreName> | IDBKeyRange | null,
  direction?: IDBCursorDirection
): Promise<IDBPCursorWithValue<DBTypes, TxStores, StoreName, unknown, Mode> | null>;

/**
 * Opens a cursor over the keys matching the query
 * Resolves with null if no matches are found
 * @param query - Key or key range to match (optional)
 * @param direction - Cursor direction (next, nextunique, prev, prevunique)
 * @returns Promise resolving to cursor or null
 */
openKeyCursor(
  query?: StoreKey<DBTypes, StoreName> | IDBKeyRange | null,
  direction?: IDBCursorDirection
): Promise<IDBPCursor<DBTypes, TxStores, StoreName, unknown, Mode> | null>;

Cursor Usage Examples:

const tx = db.transaction("users", "readonly");
const userStore = tx.store;

// Iterate with cursor manually
let cursor = await userStore.openCursor();
while (cursor) {
  console.log("User:", cursor.key, cursor.value);
  cursor = await cursor.continue();
}

// Iterate over keys only
let keyCursor = await userStore.openKeyCursor();
while (keyCursor) {
  console.log("User key:", keyCursor.key);
  keyCursor = await keyCursor.continue();
}

// Cursor with query and direction
let filteredCursor = await userStore.openCursor(
  IDBKeyRange.bound("user100", "user200"),
  "prev" // Reverse order
);
while (filteredCursor) {
  console.log("Filtered user:", filteredCursor.value);
  filteredCursor = await filteredCursor.continue();
}

Async Iteration

Use modern async iteration syntax for convenient data traversal.

/**
 * Iterate over the store using async iteration
 * @returns Async iterable iterator over cursor values
 */
[Symbol.asyncIterator](): AsyncIterableIterator<
  IDBPCursorWithValueIteratorValue<DBTypes, TxStores, StoreName, unknown, Mode>
>;

/**
 * Iterate over the records matching the query
 * @param query - Key or key range to match (optional)
 * @param direction - Cursor direction (optional)
 * @returns Async iterable iterator over cursor values
 */
iterate(
  query?: StoreKey<DBTypes, StoreName> | IDBKeyRange | null,
  direction?: IDBCursorDirection
): AsyncIterableIterator<
  IDBPCursorWithValueIteratorValue<DBTypes, TxStores, StoreName, unknown, Mode>
>;

Async Iteration Examples:

const tx = db.transaction("users", "readonly");
const userStore = tx.store;

// Iterate over all records
for await (const cursor of userStore) {
  console.log("User:", cursor.key, cursor.value);
  
  // Control iteration
  if (cursor.value.age < 18) {
    cursor.continue(); // Skip to next
  }
}

// Iterate with query
for await (const cursor of userStore.iterate(IDBKeyRange.bound(100, 200))) {
  console.log("User in range:", cursor.value);
  
  // Early termination
  if (cursor.value.name === "Target User") {
    break;
  }
}

// Iterate in reverse order
for await (const cursor of userStore.iterate(null, "prev")) {
  console.log("User (reverse):", cursor.value);
}

// Collect results with async iteration
const activeUsers = [];
for await (const cursor of userStore.iterate()) {
  if (cursor.value.status === "active") {
    activeUsers.push(cursor.value);
  }
}

await tx.done;

Performance Optimization

Strategies for efficient object store operations.

Batch Operations:

async function batchAddUsers(users: User[]) {
  const tx = db.transaction("users", "readwrite");
  const userStore = tx.store;
  
  // Start all operations without awaiting individually
  const promises = users.map(user => userStore.add(user));
  
  // Wait for all operations and transaction completion
  await Promise.all([...promises, tx.done]);
}

Efficient Counting:

// Use count() instead of getAll().length for better performance
const userCount = await userStore.count();

// Count with filters using key ranges
const adultCount = await userStore.index("age").count(IDBKeyRange.lowerBound(18));

Key-Only Queries:

// More efficient when you only need keys
const userKeys = await userStore.getAllKeys();

// Instead of
const users = await userStore.getAll();
const keys = users.map(user => user.id); // Less efficient

docs

cursor-navigation.md

database-operations.md

enhanced-database.md

index-operations.md

index.md

object-store-operations.md

promise-wrapping.md

transaction-management.md

tile.json