Stirling-Tools/Stirling-PDF · error · Error

Database not initialized

Error message

Database not initialized

What it means

`ensureDB()` lazily calls `init()` (which opens IndexedDB via `indexedDB.open`) and then re-checks `this.db`. This throw means `init()` resolved without ever setting `this.db` — the `onsuccess` callback never ran. IndexedDB open can fail outright (private/incognito mode, quota exceeded, the browser disabling storage) and `request.onerror` rejects, but if `init()` is rejected the rejection propagates before this line; reaching the throw means `init()` settled to undefined without assigning `this.db`.

Source

Thrown at frontend/editor/src/core/services/automationStorage.ts:54

      request.onupgradeneeded = (event) => {
        const db = (event.target as IDBOpenDBRequest).result;

        if (!db.objectStoreNames.contains(this.storeName)) {
          const store = db.createObjectStore(this.storeName, { keyPath: "id" });
          store.createIndex("name", "name", { unique: false });
          store.createIndex("createdAt", "createdAt", { unique: false });
        }
      };
    });
  }

  async ensureDB(): Promise<IDBDatabase> {
    if (!this.db) {
      await this.init();
    }

    if (!this.db) {
      throw new Error("Database not initialized");
    }

    return this.db;
  }

  async saveAutomation(
    automation: Omit<AutomationConfig, "id" | "createdAt" | "updatedAt">,
  ): Promise<AutomationConfig> {
    const db = await this.ensureDB();
    const timestamp = new Date().toISOString();

    const automationWithMeta: AutomationConfig = {
      id: `automation-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
      ...automation,
      createdAt: timestamp,
      updatedAt: timestamp,
    };

View on GitHub (pinned to 9ef20dcab8)

Solutions

  1. Call `automationStorage.init()` once at app startup and surface its failure to the user (banner: 'automations unavailable in this browser mode') instead of deferring to first-use.
  2. Detect private/incognito mode at startup and disable the automation feature gracefully rather than letting `ensureDB` throw.
  3. Wrap `onupgradeneeded` logic defensively (guard `db.objectStoreNames.contains` and index existence) so a version bump cannot abort the open.
  4. Fall back to an in-memory store when IndexedDB is unavailable so reads/writes still work for the session.

Example fix

// before
async ensureDB(): Promise<IDBDatabase> {
  if (!this.db) {
    await this.init();
  }
  if (!this.db) {
    throw new Error("Database not initialized");
  }
  return this.db;
}

// after
async ensureDB(): Promise<IDBDatabase> {
  if (!this.db) await this.init();
  if (!this.db) {
    throw new Error("Database not initialized (IndexedDB may be disabled or in private mode)");
  }
  return this.db;
}
Defensive patterns

Strategy: validation

Validate before calling

// Detect whether IndexedDB is usable before relying on automationStorage
async function indexedDBAvailable(): Promise<boolean> {
  try {
    if (typeof indexedDB === "undefined") return false;
    const req = indexedDB.open("__probe__");
    return await new Promise<boolean>((resolve) => {
      req.onsuccess = () => { req.result.close(); indexedDB.deleteDatabase("__probe__"); resolve(true); };
      req.onerror = () => resolve(false);
    });
  } catch { return false; }
}

Type guard

function isOpenDB(db: unknown): db is IDBDatabase {
  return !!db && typeof (db as IDBDatabase).transaction === "function";
}

Try / catch

try {
  const db = await automationStorage.ensureDB();
  await automationStorage.saveAutomation(automation);
} catch (e) {
  if (e instanceof Error && e.message.startsWith("Database not initialized")) {
    showBanner("Automations are unavailable in this browser mode (private/incognito or storage disabled).");
  } else { throw e; }
}

Prevention

When it happens

Trigger: Calling `saveAutomation`/`getAllAutomations` (any method going through `ensureDB`) when the user is in private browsing where IndexedDB is blocked or ephemeral; when `onupgradeneeded` throws (e.g. `createIndex` on an existing index name) aborts the upgrade; or when `init()` was overwritten/monkeypatched to resolve without assigning `this.db`.

Common situations: Safari private mode (IndexedDB open silently fails or the DB evaporates on tab close); a `dbVersion` bump that makes `onupgradeneeded` fail mid-migration leaving no usable handle; Firefox/Chrome with site data blocked by the user; running under a test runner that stubs `indexedDB` incorrectly.

Related errors


AI-assisted analysis of Stirling-Tools/Stirling-PDF@9ef20dcab8 (2026-08-13). Data as JSON: /api/errors/1ee35ea7ddeda217. Report an issue: GitHub.