or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

database-management.mderror-handling.mdevents.mdindex.mdlive-queries.mdquery-building.mdschema-management.mdtable-operations.mdutility-functions.md
tile.json

error-handling.mddocs/

Error Handling

Comprehensive guide to Dexie's error system, error types, and handling strategies for robust database operations.

Overview

Dexie provides a sophisticated error handling system built around the DexieError base class and specialized error types for different database operations. The system includes:

  • Hierarchical error types extending from DexieError
  • IndexedDB error mapping to Dexie-specific errors
  • Enhanced promise catching with error type filtering
  • Bulk operation errors with detailed failure information
  • Transaction error handling with automatic rollback

Error Architecture

Base Error Class

The foundation of Dexie's error system is the DexieError class:

/**
 * Common base class for all errors originating from Dexie.js except TypeError,
 * SyntaxError and RangeError.
 */
interface DexieError extends Error {
  name: string;
  message: string;
  stack: string;
  inner: any;
  toString(): string;
}

interface DexieErrorConstructor {
  new(msg?: string, inner?: Object): DexieError;
  new(inner: Object): DexieError;
  prototype: DexieError;
}

Error Types

Core Dexie Errors

Database lifecycle and schema errors:

/**
 * Database operation and lifecycle error types
 */
type DexieErrors = {
  // Database Opening and Connection
  OpenFailed: 'OpenFailedError';
  VersionChange: 'VersionChangeError';
  DatabaseClosed: 'DatabaseClosedError';
  MissingAPI: 'MissingAPIError';
  NoSuchDatabase: 'NoSuchDatabaseError';
  
  // Schema and Structure
  Schema: 'SchemaError';
  Upgrade: 'UpgradeError';
  InvalidTable: 'InvalidTableError';
  
  // Transaction Management
  SubTransaction: 'SubTransactionError';
  TransactionInactive: 'TransactionInactiveError';
  PrematureCommit: 'PrematureCommitError';
  ForeignAwait: 'ForeignAwaitError';
  
  // Data Operations
  InvalidArgument: 'InvalidArgumentError';
  ReadOnly: 'ReadOnlyError';
  
  // System Errors  
  Unsupported: 'UnsupportedError';
  Internal: 'InternalError';
}

IndexedDB Mapped Errors

Errors mapped from underlying IndexedDB operations:

/**
 * IndexedDB errors mapped to Dexie error types
 */
type IndexedDBErrors = {
  // Data Integrity
  Constraint: 'ConstraintError';
  Data: 'DataError';
  DataClone: 'DataCloneError';
  
  // Access and State
  InvalidState: 'InvalidStateError';
  InvalidAccess: 'InvalidAccessError';
  NotFound: 'NotFoundError';
  Version: 'VersionError';
  
  // Transaction and Storage
  Abort: 'AbortError';
  Timeout: 'TimeoutError';
  QuotaExceeded: 'QuotaExceededError';
  
  // General
  Unknown: 'UnknownError';
}

Specialized Error Classes

ModifyError

Thrown during bulk modify operations with detailed failure information:

/**
 * Error thrown during Collection.modify() operations containing
 * information about which operations succeeded and failed
 */
interface ModifyError extends DexieError {
  failures: Array<any>;
  failedKeys: IndexableTypeArrayReadonly;
  successCount: number;
}

interface ModifyErrorConstructor {
  new (
    msg?: string,
    failures?: any[],
    successCount?: number,
    failedKeys?: IndexableTypeArrayReadonly
  ): ModifyError;
  prototype: ModifyError;
}

BulkError

Thrown during bulk operations like bulkAdd, bulkPut, or bulkDelete:

/**
 * Error thrown during bulk operations containing information
 * about which specific operations failed
 */
interface BulkError extends DexieError {
  failures: Error[];
  failuresByPos: {[operationNumber: number]: Error};
}

interface BulkErrorConstructor {
  new (
    msg?: string, 
    failures?: {[operationNumber: number]: Error}
  ): BulkError;
  prototype: BulkError;
}

Error Handling Patterns

Basic Error Catching

Standard promise-based error handling:

import Dexie from 'dexie';

const db = new Dexie('MyDatabase');
db.version(1).stores({
  users: '++id, name, email'
});

// Basic error handling
try {
  await db.users.add({ name: 'John', email: 'john@example.com' });
} catch (error) {
  if (error instanceof Dexie.ConstraintError) {
    console.log('Constraint violation:', error.message);
  } else if (error instanceof Dexie.DatabaseClosedError) {
    console.log('Database is closed');
  } else {
    console.log('Unexpected error:', error);
  }
}

Enhanced Promise Catching

Dexie promises support enhanced catch methods with error type filtering:

// Catch specific error types
db.users.add({ name: 'John', email: 'existing@email.com' })
  .catch('ConstraintError', error => {
    console.log('Email already exists');
    // Only catch ConstraintError, others bubble up
  })
  .catch(Dexie.InvalidArgumentError, error => {
    console.log('Invalid data provided');
    // Catch by constructor
  })
  .catch(error => {
    console.log('Other error:', error);
    // Catch all remaining errors
  });

Transaction Error Handling

Transactions automatically roll back on any error:

// Transaction with error handling
try {
  await db.transaction('rw', [db.users, db.orders], async () => {
    const userId = await db.users.add({ 
      name: 'John', 
      email: 'john@example.com' 
    });
    
    await db.orders.add({
      userId: userId,
      product: 'Widget',
      amount: 99.99
    });
    
    // If any operation fails, entire transaction rolls back
  });
} catch (error) {
  if (error instanceof Dexie.ConstraintError) {
    console.log('Data constraint violation - transaction rolled back');
  } else if (error instanceof Dexie.TransactionInactiveError) {
    console.log('Transaction became inactive');
  } else {
    console.log('Transaction failed:', error.message);
  }
}

Bulk Operation Error Handling

Handle partial failures in bulk operations:

const users = [
  { name: 'Alice', email: 'alice@example.com' },
  { name: 'Bob', email: 'alice@example.com' }, // Duplicate email
  { name: 'Charlie', email: 'charlie@example.com' }
];

try {
  await db.users.bulkAdd(users);
} catch (error) {
  if (error instanceof Dexie.BulkError) {
    console.log(`${error.failures.length} operations failed`);
    
    // Check specific failures
    error.failures.forEach((failure, index) => {
      console.log(`Item ${index} failed:`, failure.message);
    });
    
    // Access failures by position
    const failureAtPos1 = error.failuresByPos[1];
    if (failureAtPos1) {
      console.log('Second item failed:', failureAtPos1.message);
    }
  }
}

Collection Modify Error Handling

Handle modify operations with partial success:

try {
  const modifyCount = await db.users
    .where('age').above(18)
    .modify({ status: 'adult' });
  
  console.log(`Modified ${modifyCount} users`);
} catch (error) {
  if (error instanceof Dexie.ModifyError) {
    console.log(`Successfully modified ${error.successCount} items`);
    console.log(`Failed to modify ${error.failures.length} items`);
    
    // Access failed keys
    console.log('Failed keys:', error.failedKeys);
    
    // Process individual failures
    error.failures.forEach(failure => {
      console.log('Individual failure:', failure.message);
    });
  }
}

Database Connection Errors

Opening and Version Errors

const db = new Dexie('MyDatabase');
db.version(1).stores({ users: '++id, name, email' });

try {
  await db.open();
} catch (error) {
  if (error instanceof Dexie.OpenFailedError) {
    console.log('Failed to open database:', error.message);
    
    if (error.inner) {
      console.log('Underlying cause:', error.inner);
    }
  } else if (error instanceof Dexie.MissingAPIError) {
    console.log('IndexedDB not supported in this environment');
  } else if (error instanceof Dexie.VersionChangeError) {
    console.log('Database version changed by another connection');
    // Consider retrying after a delay
  }
}

Database Existence and Deletion

// Check if database exists
try {
  const exists = await Dexie.exists('MyDatabase');
  console.log('Database exists:', exists);
} catch (error) {
  if (error instanceof Dexie.MissingAPIError) {
    console.log('Cannot check database existence - IndexedDB not available');
  }
}

// Delete database with error handling
try {
  await Dexie.delete('MyDatabase');
  console.log('Database deleted successfully');
} catch (error) {
  if (error instanceof Dexie.DatabaseClosedError) {
    console.log('Database was already closed');
  } else if (error instanceof Dexie.NoSuchDatabaseError) {
    console.log('Database does not exist');
  }
}

Error Recovery Strategies

Retry with Backoff

async function withRetry<T>(
  operation: () => Promise<T>, 
  maxRetries: number = 3
): Promise<T> {
  let lastError: Error;
  
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await operation();
    } catch (error) {
      lastError = error;
      
      // Don't retry on certain error types
      if (error instanceof Dexie.InvalidArgumentError ||
          error instanceof Dexie.ConstraintError) {
        throw error;
      }
      
      if (attempt < maxRetries) {
        // Exponential backoff
        const delay = Math.pow(2, attempt) * 100;
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
  }
  
  throw lastError;
}

// Usage
try {
  const result = await withRetry(() => 
    db.users.add({ name: 'John', email: 'john@example.com' })
  );
} catch (error) {
  console.log('Operation failed after retries:', error.message);
}

Graceful Degradation

class DatabaseService {
  private db: Dexie;
  private fallbackStorage = new Map();
  
  constructor() {
    this.db = new Dexie('MyApp');
    this.db.version(1).stores({ users: '++id, name, email' });
  }
  
  async addUser(user: { name: string; email: string }) {
    try {
      return await this.db.users.add(user);
    } catch (error) {
      if (error instanceof Dexie.MissingAPIError ||
          error instanceof Dexie.DatabaseClosedError) {
        // Fall back to in-memory storage
        const id = Date.now();
        this.fallbackStorage.set(id, { id, ...user });
        console.warn('Using fallback storage due to DB error');
        return id;
      }
      throw error;
    }
  }
  
  async getUser(id: number) {
    try {
      return await this.db.users.get(id);
    } catch (error) {
      if (error instanceof Dexie.DatabaseClosedError) {
        return this.fallbackStorage.get(id);
      }
      throw error;
    }
  }
}

Transaction Recovery

async function robustTransaction<T>(
  db: Dexie,
  tables: Table[],
  operation: () => Promise<T>
): Promise<T> {
  let retries = 3;
  
  while (retries > 0) {
    try {
      return await db.transaction('rw', tables, operation);
    } catch (error) {
      if (error instanceof Dexie.TransactionInactiveError && retries > 1) {
        retries--;
        // Wait before retry
        await new Promise(resolve => setTimeout(resolve, 100));
        continue;
      } else if (error instanceof Dexie.AbortError) {
        console.log('Transaction was aborted');
        throw error;
      } else {
        throw error;
      }
    }
  }
}

Best Practices

Error Classification

function classifyError(error: Error): 'recoverable' | 'user-error' | 'fatal' {
  if (error instanceof Dexie.TransactionInactiveError ||
      error instanceof Dexie.AbortError ||
      error instanceof Dexie.TimeoutError) {
    return 'recoverable';
  }
  
  if (error instanceof Dexie.InvalidArgumentError ||
      error instanceof Dexie.ConstraintError ||
      error instanceof Dexie.DataError) {
    return 'user-error';
  }
  
  return 'fatal';
}

async function handleDatabaseOperation<T>(operation: () => Promise<T>): Promise<T> {
  try {
    return await operation();
  } catch (error) {
    const errorType = classifyError(error);
    
    switch (errorType) {
      case 'recoverable':
        // Log and potentially retry
        console.warn('Recoverable database error:', error.message);
        break;
      case 'user-error':
        // Show user-friendly message
        console.error('Invalid operation:', error.message);
        break;
      case 'fatal':
        // Report to error service
        console.error('Fatal database error:', error);
        break;
    }
    
    throw error;
  }
}

Error Logging

class DatabaseErrorLogger {
  static logError(error: Error, context?: string) {
    const errorInfo = {
      name: error.name,
      message: error.message,
      context,
      timestamp: new Date().toISOString(),
      isDexieError: error instanceof Dexie.DexieError,
      stack: error.stack
    };
    
    if (error instanceof Dexie.DexieError && error.inner) {
      errorInfo['innerError'] = {
        name: error.inner.name,
        message: error.inner.message
      };
    }
    
    console.error('Database Error:', errorInfo);
    
    // Send to error reporting service
    // errorReporter.report(errorInfo);
  }
}

// Usage
try {
  await db.users.add(userData);
} catch (error) {
  DatabaseErrorLogger.logError(error, 'Adding new user');
  throw error;
}

Accessing Error Classes

Import and use error classes for type checking and throwing:

import Dexie, { 
  DexieError, 
  ModifyError, 
  BulkError 
} from 'dexie';

// Access via Dexie static properties
const dbError = new Dexie.InvalidTableError('Table does not exist');
const constraintError = new Dexie.ConstraintError('Unique constraint violated');

// Direct constructor usage
const modifyError = new ModifyError(
  'Modify operation failed',
  [new Error('Individual failure')],
  5, // successCount
  ['key1', 'key2'] // failedKeys
);

// Type checking
function isRecoverableError(error: Error): boolean {
  return error instanceof Dexie.TransactionInactiveError ||
         error instanceof Dexie.AbortError ||
         error instanceof Dexie.TimeoutError;
}

This comprehensive error handling system ensures robust database operations with clear error classification, appropriate recovery strategies, and detailed failure information for debugging and user feedback.