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

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