{"record":{"id":"1ee35ea7ddeda217","repo":"Stirling-Tools/Stirling-PDF","slug":"database-not-initialized","errorCode":null,"errorMessage":"Database not initialized","messagePattern":"Database not initialized","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"frontend/editor/src/core/services/automationStorage.ts","lineNumber":54,"sourceCode":"      request.onupgradeneeded = (event) => {\n        const db = (event.target as IDBOpenDBRequest).result;\n\n        if (!db.objectStoreNames.contains(this.storeName)) {\n          const store = db.createObjectStore(this.storeName, { keyPath: \"id\" });\n          store.createIndex(\"name\", \"name\", { unique: false });\n          store.createIndex(\"createdAt\", \"createdAt\", { unique: false });\n        }\n      };\n    });\n  }\n\n  async ensureDB(): Promise<IDBDatabase> {\n    if (!this.db) {\n      await this.init();\n    }\n\n    if (!this.db) {\n      throw new Error(\"Database not initialized\");\n    }\n\n    return this.db;\n  }\n\n  async saveAutomation(\n    automation: Omit<AutomationConfig, \"id\" | \"createdAt\" | \"updatedAt\">,\n  ): Promise<AutomationConfig> {\n    const db = await this.ensureDB();\n    const timestamp = new Date().toISOString();\n\n    const automationWithMeta: AutomationConfig = {\n      id: `automation-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,\n      ...automation,\n      createdAt: timestamp,\n      updatedAt: timestamp,\n    };\n","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/Stirling-Tools/Stirling-PDF/blob/9ef20dcab80b85041912f045e17a6aea1d08f969/frontend/editor/src/core/services/automationStorage.ts#L36-L72","documentation":"`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`.","triggerScenarios":"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`.","commonSituations":"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.","solutions":["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.","Detect private/incognito mode at startup and disable the automation feature gracefully rather than letting `ensureDB` throw.","Wrap `onupgradeneeded` logic defensively (guard `db.objectStoreNames.contains` and index existence) so a version bump cannot abort the open.","Fall back to an in-memory store when IndexedDB is unavailable so reads/writes still work for the session."],"exampleFix":"// before\nasync ensureDB(): Promise<IDBDatabase> {\n  if (!this.db) {\n    await this.init();\n  }\n  if (!this.db) {\n    throw new Error(\"Database not initialized\");\n  }\n  return this.db;\n}\n\n// after\nasync ensureDB(): Promise<IDBDatabase> {\n  if (!this.db) await this.init();\n  if (!this.db) {\n    throw new Error(\"Database not initialized (IndexedDB may be disabled or in private mode)\");\n  }\n  return this.db;\n}","handlingStrategy":"validation","validationCode":"// Detect whether IndexedDB is usable before relying on automationStorage\nasync function indexedDBAvailable(): Promise<boolean> {\n  try {\n    if (typeof indexedDB === \"undefined\") return false;\n    const req = indexedDB.open(\"__probe__\");\n    return await new Promise<boolean>((resolve) => {\n      req.onsuccess = () => { req.result.close(); indexedDB.deleteDatabase(\"__probe__\"); resolve(true); };\n      req.onerror = () => resolve(false);\n    });\n  } catch { return false; }\n}","typeGuard":"function isOpenDB(db: unknown): db is IDBDatabase {\n  return !!db && typeof (db as IDBDatabase).transaction === \"function\";\n}","tryCatchPattern":"try {\n  const db = await automationStorage.ensureDB();\n  await automationStorage.saveAutomation(automation);\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith(\"Database not initialized\")) {\n    showBanner(\"Automations are unavailable in this browser mode (private/incognito or storage disabled).\");\n  } else { throw e; }\n}","preventionTips":["Initialise automationStorage at app startup and surface init failure immediately.","Detect private/incognito mode and disable the automations feature rather than failing on first use.","Defensively guard index creation in onupgradeneeded so a version bump can't abort the open.","Maintain an in-memory fallback store so the session still works when IndexedDB is unavailable."],"tags":["indexeddb","storage","browser","persistence","private-mode"],"backgroundTag":null,"analyzedSha":"9ef20dcab80b85041912f045e17a6aea1d08f969","analyzedAt":"2026-08-13T22:11:39.827Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}