diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index 5d4b499d..3e533fdc 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -26,6 +26,7 @@ export type { export type { // From records.ts PlanSummaryRecord, + GiveSummaryRecord, } from "./records"; export type { diff --git a/src/main.electron.ts b/src/main.electron.ts index 1392c7b2..f8ade261 100644 --- a/src/main.electron.ts +++ b/src/main.electron.ts @@ -13,53 +13,33 @@ if (pwa_enabled) { logger.warn("[Main Electron] PWA is enabled, but not supported in electron"); } -let appIsMounted = false; - // Initialize app and SQLite const app = initializeApp(); -/** - * SQLite initialization configuration - * Defines timeouts and retry settings for database operations - * - * @author Matthew Raymer - */ -const SQLITE_CONFIG = { - INITIALIZATION: { - TIMEOUT_MS: 10000, // 10 seconds for initial setup - RETRY_ATTEMPTS: 3, - RETRY_DELAY_MS: 1000 - }, - OPERATIONS: { - TIMEOUT_MS: 5000, // 5 seconds for regular operations - RETRY_ATTEMPTS: 2, - RETRY_DELAY_MS: 500 - }, - CONNECTION: { - MAX_CONNECTIONS: 5, - IDLE_TIMEOUT_MS: 30000 - } -}; - // Create a promise that resolves when SQLite is ready const sqliteReady = new Promise((resolve, reject) => { let retryCount = 0; let initializationTimeout: NodeJS.Timeout; - + const attemptInitialization = () => { // Clear any existing timeout if (initializationTimeout) { clearTimeout(initializationTimeout); } - + // Set timeout for this attempt initializationTimeout = setTimeout(() => { - if (retryCount < 3) { // Use same retry count as ElectronPlatformService + if (retryCount < 3) { + // Use same retry count as ElectronPlatformService retryCount++; - logger.warn(`[Main Electron] SQLite initialization attempt ${retryCount} timed out, retrying...`); + logger.warn( + `[Main Electron] SQLite initialization attempt ${retryCount} timed out, retrying...`, + ); setTimeout(attemptInitialization, 1000); // Use same delay as ElectronPlatformService } else { - logger.error("[Main Electron] SQLite initialization failed after all retries"); + logger.error( + "[Main Electron] SQLite initialization failed after all retries", + ); reject(new Error("SQLite initialization timeout after all retries")); } }, 10000); // Use same timeout as ElectronPlatformService @@ -71,134 +51,198 @@ const sqliteReady = new Promise((resolve, reject) => { setTimeout(checkElectronBridge, 100); return; } - + // At this point we know ipcRenderer exists const ipcRenderer = window.electron.ipcRenderer; - + logger.info("[Main Electron] [IPC:bridge] IPC renderer bridge available"); - + // Listen for SQLite ready signal - logger.debug("[Main Electron] [IPC:sqlite-ready] Registering listener for SQLite ready signal"); - ipcRenderer.once('sqlite-ready', () => { + logger.debug( + "[Main Electron] [IPC:sqlite-ready] Registering listener for SQLite ready signal", + ); + ipcRenderer.once("sqlite-ready", () => { clearTimeout(initializationTimeout); - logger.info("[Main Electron] [IPC:sqlite-ready] Received SQLite ready signal"); + logger.info( + "[Main Electron] [IPC:sqlite-ready] Received SQLite ready signal", + ); resolve(); }); // Also listen for database errors - logger.debug("[Main Electron] [IPC:database-status] Registering listener for database status"); - ipcRenderer.once('database-status', (...args: unknown[]) => { + logger.debug( + "[Main Electron] [IPC:database-status] Registering listener for database status", + ); + ipcRenderer.once("database-status", (...args: unknown[]) => { clearTimeout(initializationTimeout); const status = args[0] as { status: string; error?: string }; - if (status.status === 'error') { - logger.error("[Main Electron] [IPC:database-status] Database error:", { - error: status.error, - channel: 'database-status' - }); - reject(new Error(status.error || 'Database initialization failed')); + if (status.status === "error") { + logger.error( + "[Main Electron] [IPC:database-status] Database error:", + { + error: status.error, + channel: "database-status", + }, + ); + reject(new Error(status.error || "Database initialization failed")); } }); // Check if SQLite is already available - logger.debug("[Main Electron] [IPC:sqlite-is-available] Checking SQLite availability"); - ipcRenderer.invoke('sqlite-is-available') + logger.debug( + "[Main Electron] [IPC:sqlite-is-available] Checking SQLite availability", + ); + ipcRenderer + .invoke("sqlite-is-available") .then(async (result: unknown) => { const isAvailable = Boolean(result); if (isAvailable) { - logger.info("[Main Electron] [IPC:sqlite-is-available] SQLite is available"); - + logger.info( + "[Main Electron] [IPC:sqlite-is-available] SQLite is available", + ); + try { // First create a database connection - logger.debug("[Main Electron] [IPC:get-path] Requesting database path"); - const dbPath = await ipcRenderer.invoke('get-path'); - logger.info("[Main Electron] [IPC:get-path] Database path received:", { dbPath }); - + logger.debug( + "[Main Electron] [IPC:get-path] Requesting database path", + ); + const dbPath = await ipcRenderer.invoke("get-path"); + logger.info( + "[Main Electron] [IPC:get-path] Database path received:", + { dbPath }, + ); + // Create the database connection - logger.debug("[Main Electron] [IPC:sqlite-create-connection] Creating database connection"); - await ipcRenderer.invoke('sqlite-create-connection', { - database: 'timesafari', - version: 1 + logger.debug( + "[Main Electron] [IPC:sqlite-create-connection] Creating database connection", + ); + await ipcRenderer.invoke("sqlite-create-connection", { + database: "timesafari", + version: 1, }); - logger.info("[Main Electron] [IPC:sqlite-create-connection] Database connection created"); - + logger.info( + "[Main Electron] [IPC:sqlite-create-connection] Database connection created", + ); + // Explicitly open the database - logger.debug("[Main Electron] [IPC:sqlite-open] Opening database"); - await ipcRenderer.invoke('sqlite-open', { - database: 'timesafari' + logger.debug( + "[Main Electron] [IPC:sqlite-open] Opening database", + ); + await ipcRenderer.invoke("sqlite-open", { + database: "timesafari", }); - logger.info("[Main Electron] [IPC:sqlite-open] Database opened successfully"); - + logger.info( + "[Main Electron] [IPC:sqlite-open] Database opened successfully", + ); + // Verify the database is open - logger.debug("[Main Electron] [IPC:sqlite-is-db-open] Verifying database is open"); - const isOpen = await ipcRenderer.invoke('sqlite-is-db-open', { - database: 'timesafari' + logger.debug( + "[Main Electron] [IPC:sqlite-is-db-open] Verifying database is open", + ); + const isOpen = await ipcRenderer.invoke("sqlite-is-db-open", { + database: "timesafari", }); - logger.info("[Main Electron] [IPC:sqlite-is-db-open] Database open status:", { isOpen }); - + logger.info( + "[Main Electron] [IPC:sqlite-is-db-open] Database open status:", + { isOpen }, + ); + if (!isOpen) { - throw new Error('Database failed to open'); + throw new Error("Database failed to open"); } - + // Now execute the test query - logger.debug("[Main Electron] [IPC:sqlite-query] Executing test query"); - const testQuery = await ipcRenderer.invoke('sqlite-query', { - database: 'timesafari', - statement: 'SELECT 1 as test;' // Safe test query - }) as SQLiteQueryResult; - logger.info("[Main Electron] [IPC:sqlite-query] Test query successful:", { - hasResults: Boolean(testQuery?.values), - resultCount: testQuery?.values?.length - }); - + logger.debug( + "[Main Electron] [IPC:sqlite-query] Executing test query", + ); + const testQuery = (await ipcRenderer.invoke("sqlite-query", { + database: "timesafari", + statement: "SELECT 1 as test;", // Safe test query + })) as SQLiteQueryResult; + logger.info( + "[Main Electron] [IPC:sqlite-query] Test query successful:", + { + hasResults: Boolean(testQuery?.values), + resultCount: testQuery?.values?.length, + }, + ); + // Signal that SQLite is ready - database stays open - logger.debug("[Main Electron] [IPC:sqlite-status] Sending SQLite ready status"); - await ipcRenderer.invoke('sqlite-status', { - status: 'ready', - database: 'timesafari', - timestamp: Date.now() + logger.debug( + "[Main Electron] [IPC:sqlite-status] Sending SQLite ready status", + ); + await ipcRenderer.invoke("sqlite-status", { + status: "ready", + database: "timesafari", + timestamp: Date.now(), }); - logger.info("[Main Electron] SQLite ready status sent, database connection maintained"); + logger.info( + "[Main Electron] SQLite ready status sent, database connection maintained", + ); // Remove the close operations - database stays open for component use // Database will be closed during app shutdown } catch (error) { - logger.error("[Main Electron] [IPC:*] SQLite test operation failed:", { - error, - lastOperation: 'sqlite-test-query', - database: 'timesafari' - }); - + logger.error( + "[Main Electron] [IPC:*] SQLite test operation failed:", + { + error, + lastOperation: "sqlite-test-query", + database: "timesafari", + }, + ); + // Try to close everything if anything was opened try { - logger.debug("[Main Electron] [IPC:cleanup] Attempting database cleanup after error"); - await ipcRenderer.invoke('sqlite-close', { - database: 'timesafari' - }).catch((closeError) => { - logger.warn("[Main Electron] [IPC:sqlite-close] Failed to close database during cleanup:", closeError); - }); - - await ipcRenderer.invoke('sqlite-close-connection', { - database: 'timesafari' - }).catch((closeError) => { - logger.warn("[Main Electron] [IPC:sqlite-close-connection] Failed to close connection during cleanup:", closeError); - }); - - logger.info("[Main Electron] [IPC:cleanup] Database cleanup completed after error"); + logger.debug( + "[Main Electron] [IPC:cleanup] Attempting database cleanup after error", + ); + await ipcRenderer + .invoke("sqlite-close", { + database: "timesafari", + }) + .catch((closeError) => { + logger.warn( + "[Main Electron] [IPC:sqlite-close] Failed to close database during cleanup:", + closeError, + ); + }); + + await ipcRenderer + .invoke("sqlite-close-connection", { + database: "timesafari", + }) + .catch((closeError) => { + logger.warn( + "[Main Electron] [IPC:sqlite-close-connection] Failed to close connection during cleanup:", + closeError, + ); + }); + + logger.info( + "[Main Electron] [IPC:cleanup] Database cleanup completed after error", + ); } catch (closeError) { - logger.error("[Main Electron] [IPC:cleanup] Failed to cleanup database:", { - error: closeError, - database: 'timesafari' - }); + logger.error( + "[Main Electron] [IPC:cleanup] Failed to cleanup database:", + { + error: closeError, + database: "timesafari", + }, + ); } // Don't reject here - we still want to wait for the ready signal } } }) .catch((error: Error) => { - logger.error("[Main Electron] [IPC:sqlite-is-available] Failed to check SQLite availability:", { - error, - channel: 'sqlite-is-available' - }); + logger.error( + "[Main Electron] [IPC:sqlite-is-available] Failed to check SQLite availability:", + { + error, + channel: "sqlite-is-available", + }, + ); // Don't reject here - wait for either ready signal or timeout }); }; @@ -215,20 +259,22 @@ const sqliteReady = new Promise((resolve, reject) => { sqliteReady .then(async () => { logger.info("[Main Electron] SQLite ready, initializing router..."); - + // Initialize router after SQLite is ready - const router = await import('./router').then(m => m.default); + const router = await import("./router").then((m) => m.default); app.use(router); logger.info("[Main Electron] Router initialized"); - + // Now mount the app logger.info("[Main Electron] Mounting app..."); app.mount("#app"); - appIsMounted = true; logger.info("[Main Electron] App mounted successfully"); }) .catch((error) => { - logger.error("[Main Electron] Failed to initialize SQLite:", error instanceof Error ? error.message : 'Unknown error'); + logger.error( + "[Main Electron] Failed to initialize SQLite:", + error instanceof Error ? error.message : "Unknown error", + ); // Show error to user with retry option const errorDiv = document.createElement("div"); errorDiv.style.cssText = @@ -241,7 +287,7 @@ sqliteReady
  • Insufficient permissions to access the database
  • Database file is corrupted
  • -

    Error details: ${error instanceof Error ? error.message : 'Unknown error'}

    +

    Error details: ${error instanceof Error ? error.message : "Unknown error"}