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

events-utilities.mddocs/

Events and Utilities

Event handling, error management, and testing utilities for comprehensive IndexedDB simulation.

Capabilities

IDBRequest

Base request interface for all asynchronous IndexedDB operations.

interface IDBRequest extends EventTarget {
  /** Operation result (available when readyState is "done") */
  readonly result: any;
  
  /** Error that occurred (null if successful) */
  readonly error: DOMException | null;
  
  /** Source object that initiated the request */
  readonly source: IDBObjectStore | IDBIndex | IDBCursor | null;
  
  /** Transaction this request belongs to */
  readonly transaction: IDBTransaction | null;
  
  /** Current state of the request */
  readonly readyState: "pending" | "done";
  
  /** Success event handler */
  onsuccess: ((event: Event) => void) | null;
  
  /** Error event handler */
  onerror: ((event: Event) => void) | null;
}

Usage Examples:

import "fake-indexeddb/auto";

// Basic request handling
const tx = db.transaction("products", "readonly");
const store = tx.objectStore("products");
const request = store.get("LAP001");

request.onsuccess = (event) => {
  console.log("Product found:", event.target.result);
  console.log("Request state:", request.readyState); // "done"
};

request.onerror = (event) => {
  console.error("Request failed:", event.target.error);
};

// Using addEventListener for multiple handlers
request.addEventListener("success", (event) => {
  console.log("First success handler");
});

request.addEventListener("success", (event) => {
  console.log("Second success handler");
});

IDBOpenDBRequest

Extended request for database opening operations with upgrade and blocking events.

interface IDBOpenDBRequest extends IDBRequest {
  /** Database upgrade needed event handler */
  onupgradeneeded: ((event: IDBVersionChangeEvent) => void) | null;
  
  /** Database opening blocked event handler */
  onblocked: ((event: Event) => void) | null;
}

Usage Examples:

// Complete database opening with all events
const request = indexedDB.open("myapp", 3);

request.onupgradeneeded = (event) => {
  const db = event.target.result;
  console.log(`Upgrading from version ${event.oldVersion} to ${event.newVersion}`);
  
  // Handle schema changes
  if (event.oldVersion < 1) {
    db.createObjectStore("users", { keyPath: "id" });
  }
  
  if (event.oldVersion < 2) {
    const usersStore = event.target.transaction.objectStore("users");
    usersStore.createIndex("email", "email", { unique: true });
  }
  
  if (event.oldVersion < 3) {
    db.createObjectStore("settings", { keyPath: "key" });
  }
};

request.onblocked = (event) => {
  console.warn("Database upgrade blocked by other connections");
  // Notify user to close other tabs
  showUserMessage("Please close other tabs to continue");
};

request.onsuccess = (event) => {
  const db = event.target.result;
  console.log("Database opened successfully");
  
  // Set up global error handler
  db.onerror = (event) => {
    console.error("Database error:", event.target.error);
  };
  
  // Handle version changes from other connections
  db.onversionchange = (event) => {
    console.log("Database version changing, closing connection");
    db.close();
    showUserMessage("Database updated, please refresh the page");
  };
};

request.onerror = (event) => {
  console.error("Failed to open database:", event.target.error);
  handleDatabaseError(event.target.error);
};

IDBVersionChangeEvent

Event fired during database version changes and upgrades.

interface IDBVersionChangeEvent extends Event {
  /** Previous database version */
  readonly oldVersion: number;
  
  /** New database version (null for deletion) */
  readonly newVersion: number | null;
}

Usage Examples:

// Handle version change event details
request.onupgradeneeded = (event) => {
  const db = event.target.result;
  const { oldVersion, newVersion } = event;
  
  console.log(`Version change: ${oldVersion} → ${newVersion}`);
  
  // Progressive migration
  if (oldVersion === 0) {
    console.log("Creating initial database schema");
    setupInitialSchema(db);
  } else {
    console.log("Upgrading existing database");
    migrateSchema(db, oldVersion, newVersion);
  }
};

function setupInitialSchema(db) {
  const usersStore = db.createObjectStore("users", { 
    keyPath: "id",
    autoIncrement: true 
  });
  
  usersStore.createIndex("email", "email", { unique: true });
  usersStore.createIndex("created", "createdAt");
  
  const postsStore = db.createObjectStore("posts", {
    keyPath: "id",
    autoIncrement: true
  });
  
  postsStore.createIndex("author", "authorId");
  postsStore.createIndex("published", "publishedAt");
}

function migrateSchema(db, fromVersion, toVersion) {
  const transaction = event.target.transaction;
  
  for (let version = fromVersion + 1; version <= toVersion; version++) {
    switch (version) {
      case 2:
        // Add comments store
        const commentsStore = db.createObjectStore("comments", {
          keyPath: "id",
          autoIncrement: true
        });
        commentsStore.createIndex("post", "postId");
        break;
        
      case 3:
        // Add tags index to posts
        const postsStore = transaction.objectStore("posts");
        postsStore.createIndex("tags", "tags", { multiEntry: true });
        break;
        
      case 4:
        // Data migration
        migrateUserData(transaction.objectStore("users"));
        break;
    }
  }
}

Event Propagation and Error Handling

// Comprehensive event handling with propagation
const tx = db.transaction(["products", "orders"], "readwrite");

// Transaction-level error handler (catches all operation errors)
tx.onerror = (event) => {
  console.error("Transaction error:", event.target.error);
  
  // Check if error was handled at operation level
  if (event.defaultPrevented) {
    console.log("Error was handled by operation");
  } else {
    console.log("Unhandled operation error, transaction will abort");
  }
};

tx.onabort = (event) => {
  console.log("Transaction aborted");
  // Cleanup or retry logic
  handleTransactionAbort();
};

tx.oncomplete = (event) => {
  console.log("Transaction completed successfully");
  // Success notifications
  showSuccessMessage("Data saved successfully");
};

// Operation-level error handling
const productsStore = tx.objectStore("products");
const addRequest = productsStore.add(product);

addRequest.onerror = (event) => {
  const error = event.target.error;
  
  if (error.name === "ConstraintError") {
    console.log("Product already exists, using put instead");
    
    // Prevent transaction abort
    event.preventDefault();
    
    // Retry with put
    productsStore.put(product);
  } else {
    console.error("Unexpected error:", error);
    // Let transaction handle the error
  }
};

Testing Utilities

forceCloseDatabase

Utility function for testing abnormal database closure scenarios.

/**
 * Forces abnormal database closure for testing scenarios
 * @param db - Database instance to forcibly close
 */
declare function forceCloseDatabase(db: IDBDatabase): void;

Usage Examples:

import { forceCloseDatabase } from "fake-indexeddb";

// Test database close event handling
const db = await openDatabase();

db.onclose = (event) => {
  console.log("Database was forcibly closed");
  // Handle unexpected closure
  handleUnexpectedClose();
};

// Simulate abnormal closure (e.g., user clearing browser data)
forceCloseDatabase(db);

// Test application resilience
function testDatabaseResilience(db) {
  // Set up close handler
  db.addEventListener("close", () => {
    console.log("Detected database closure");
    
    // Attempt to reconnect
    setTimeout(() => {
      reopenDatabase();
    }, 1000);
  });
  
  // Simulate closure after random delay
  setTimeout(() => {
    console.log("Simulating unexpected database closure");
    forceCloseDatabase(db);
  }, Math.random() * 5000 + 1000);
}

Error Types and Handling

// Handle specific IndexedDB errors
function handleIndexedDBError(error) {
  switch (error.name) {
    case "AbortError":
      console.log("Operation was aborted");
      // Transaction was aborted, possibly due to tab close
      break;
      
    case "ConstraintError":
      console.log("Constraint violation (unique index, etc.)");
      // Handle duplicate key or constraint violation
      break;
      
    case "DataError":
      console.log("Invalid key or key range");
      // Validate and sanitize key data
      break;
      
    case "DataCloneError":
      console.log("Value cannot be cloned");
      // Handle non-serializable objects
      break;
      
    case "InvalidAccessError":
      console.log("Invalid operation for current state");
      // Check transaction state and object store availability
      break;
      
    case "InvalidStateError":
      console.log("Operation not allowed in current state");
      // Verify database and transaction state
      break;
      
    case "NotFoundError":
      console.log("Object store or index not found");
      // Handle missing object stores or indexes
      break;
      
    case "ReadOnlyError":
      console.log("Attempted write on readonly transaction");
      // Create readwrite transaction for modifications
      break;
      
    case "TransactionInactiveError":
      console.log("Transaction is no longer active");
      // Create new transaction for the operation
      break;
      
    case "VersionError":
      console.log("Invalid version number");
      // Handle version conflicts
      break;
      
    default:
      console.error("Unknown IndexedDB error:", error.name, error.message);
  }
}

// Global error handler setup
function setupGlobalErrorHandling(db) {
  // Database-level error handler
  db.onerror = (event) => {
    handleIndexedDBError(event.target.error);
  };
  
  // Version change handler
  db.onversionchange = (event) => {
    console.log("Database version changed by another connection");
    
    // Close current connection
    db.close();
    
    // Notify user
    showVersionChangeNotification();
  };
  
  // Close handler
  db.onclose = (event) => {
    console.log("Database connection closed");
    
    // Update application state
    updateConnectionStatus(false);
    
    // Attempt reconnection if unexpected
    if (!event.wasClean) {
      attemptReconnection();
    }
  };
}

Event-Driven Patterns

Promise-based Request Handling

// Convert IDBRequest to Promise
function requestToPromise(request) {
  return new Promise((resolve, reject) => {
    request.onsuccess = (event) => {
      resolve(event.target.result);
    };
    
    request.onerror = (event) => {
      reject(event.target.error);
    };
  });
}

// Usage with async/await
async function getProduct(store, id) {
  try {
    const product = await requestToPromise(store.get(id));
    return product;
  } catch (error) {
    console.error("Failed to get product:", error);
    throw error;
  }
}

// Transaction as Promise
function transactionToPromise(transaction) {
  return new Promise((resolve, reject) => {
    transaction.oncomplete = () => resolve();
    transaction.onerror = (event) => reject(event.target.error);
    transaction.onabort = () => reject(new Error("Transaction aborted"));
  });
}

// Complete async transaction
async function performAsyncTransaction(db) {
  const tx = db.transaction(["products", "orders"], "readwrite");
  const productsStore = tx.objectStore("products");
  const ordersStore = tx.objectStore("orders");
  
  try {
    // Perform operations
    await requestToPromise(productsStore.put(product));
    await requestToPromise(ordersStore.add(order));
    
    // Wait for transaction completion
    await transactionToPromise(tx);
    
    console.log("Transaction completed successfully");
  } catch (error) {
    console.error("Transaction failed:", error);
    throw error;
  }
}

Event Delegation and Management

// Centralized event management
class IndexedDBEventManager {
  constructor(db) {
    this.db = db;
    this.listeners = new Map();
    this.setupGlobalHandlers();
  }
  
  setupGlobalHandlers() {
    this.db.onerror = (event) => {
      this.emit("database-error", event.target.error);
    };
    
    this.db.onversionchange = (event) => {
      this.emit("version-change", event);
    };
    
    this.db.onclose = (event) => {
      this.emit("database-close", event);
    };
  }
  
  on(eventType, callback) {
    if (!this.listeners.has(eventType)) {
      this.listeners.set(eventType, []);
    }
    this.listeners.get(eventType).push(callback);
  }
  
  off(eventType, callback) {
    const callbacks = this.listeners.get(eventType);
    if (callbacks) {
      const index = callbacks.indexOf(callback);
      if (index > -1) {
        callbacks.splice(index, 1);
      }
    }
  }
  
  emit(eventType, data) {
    const callbacks = this.listeners.get(eventType);
    if (callbacks) {
      callbacks.forEach(callback => {
        try {
          callback(data);
        } catch (error) {
          console.error("Event handler error:", error);
        }
      });
    }
  }
}

// Usage
const eventManager = new IndexedDBEventManager(db);

eventManager.on("database-error", (error) => {
  console.error("Database error:", error);
  showErrorNotification(error.message);
});

eventManager.on("version-change", () => {
  console.log("Database version changed");
  showVersionChangeDialog();
});

eventManager.on("database-close", () => {
  console.log("Database closed");
  updateUIState("disconnected");
});

Development and Debugging Utilities

// Development helper for monitoring requests
function monitorRequest(request, label = "Request") {
  console.log(`${label} started`);
  
  const startTime = performance.now();
  
  request.onsuccess = (event) => {
    const duration = performance.now() - startTime;
    console.log(`${label} completed in ${duration.toFixed(2)}ms`);
    console.log("Result:", event.target.result);
  };
  
  request.onerror = (event) => {
    const duration = performance.now() - startTime;
    console.error(`${label} failed after ${duration.toFixed(2)}ms`);
    console.error("Error:", event.target.error);
  };
  
  return request;
}

// Usage
const request = store.get("product-123");
monitorRequest(request, "Get Product");

// Transaction monitoring
function monitorTransaction(transaction, label = "Transaction") {
  console.log(`${label} started`);
  
  const startTime = performance.now();
  
  transaction.oncomplete = () => {
    const duration = performance.now() - startTime;
    console.log(`${label} completed in ${duration.toFixed(2)}ms`);
  };
  
  transaction.onerror = (event) => {
    const duration = performance.now() - startTime;
    console.error(`${label} failed after ${duration.toFixed(2)}ms`);
    console.error("Error:", event.target.error);
  };
  
  transaction.onabort = () => {
    const duration = performance.now() - startTime;
    console.warn(`${label} aborted after ${duration.toFixed(2)}ms`);
  };
  
  return transaction;
}

docs

database-management.md

events-utilities.md

index.md

key-ranges.md

object-stores.md

transactions-cursors.md

tile.json