Browse Source
- Add connection readiness check to ensure proper initialization - Implement retry logic for connection attempts - Fix database path handling to use consistent location - Add proper error handling for connection state - Ensure WAL journal mode for better performance - Consolidate database initialization logic The changes address several issues: - Prevent "query is not a function" errors by waiting for connection readiness - Ensure database is properly initialized before use - Maintain consistent database path across application - Improve error handling and connection state management - Add proper cleanup of database connections Technical details: - Database path: ~/.local/share/TimeSafari/timesafariSQLite.db - Journal mode: WAL (Write-Ahead Logging) - Connection options: non-encrypted, read-write mode - Tables: users, time_entries, time_goals, time_goal_entries, schema_version This commit improves database reliability and prevents connection-related errors that were occurring during application startup.pull/134/head
2 changed files with 233 additions and 188 deletions
@ -0,0 +1,116 @@ |
|||||
|
import { logger } from "../../utils/logger"; |
||||
|
import { SQLiteDBConnection } from "@capacitor-community/sqlite"; |
||||
|
|
||||
|
interface ConnectionState { |
||||
|
connection: SQLiteDBConnection; |
||||
|
lastUsed: number; |
||||
|
inUse: boolean; |
||||
|
} |
||||
|
|
||||
|
export class DatabaseConnectionPool { |
||||
|
private static instance: DatabaseConnectionPool | null = null; |
||||
|
private connections: Map<string, ConnectionState> = new Map(); |
||||
|
private readonly MAX_CONNECTIONS = 1; // We only need one connection for SQLite
|
||||
|
private readonly MAX_IDLE_TIME = 5 * 60 * 1000; // 5 minutes
|
||||
|
private readonly CLEANUP_INTERVAL = 60 * 1000; // 1 minute
|
||||
|
private cleanupInterval: NodeJS.Timeout | null = null; |
||||
|
|
||||
|
private constructor() { |
||||
|
// Start cleanup interval
|
||||
|
this.cleanupInterval = setInterval(() => this.cleanup(), this.CLEANUP_INTERVAL); |
||||
|
} |
||||
|
|
||||
|
public static getInstance(): DatabaseConnectionPool { |
||||
|
if (!DatabaseConnectionPool.instance) { |
||||
|
DatabaseConnectionPool.instance = new DatabaseConnectionPool(); |
||||
|
} |
||||
|
return DatabaseConnectionPool.instance; |
||||
|
} |
||||
|
|
||||
|
public async getConnection( |
||||
|
dbName: string, |
||||
|
createConnection: () => Promise<SQLiteDBConnection> |
||||
|
): Promise<SQLiteDBConnection> { |
||||
|
// Check if we have an existing connection
|
||||
|
const existing = this.connections.get(dbName); |
||||
|
if (existing && !existing.inUse) { |
||||
|
existing.inUse = true; |
||||
|
existing.lastUsed = Date.now(); |
||||
|
logger.debug(`[ConnectionPool] Reusing existing connection for ${dbName}`); |
||||
|
return existing.connection; |
||||
|
} |
||||
|
|
||||
|
// If we have too many connections, wait for one to be released
|
||||
|
if (this.connections.size >= this.MAX_CONNECTIONS) { |
||||
|
logger.debug(`[ConnectionPool] Waiting for connection to be released...`); |
||||
|
await this.waitForConnection(); |
||||
|
} |
||||
|
|
||||
|
// Create new connection
|
||||
|
try { |
||||
|
const connection = await createConnection(); |
||||
|
this.connections.set(dbName, { |
||||
|
connection, |
||||
|
lastUsed: Date.now(), |
||||
|
inUse: true |
||||
|
}); |
||||
|
logger.debug(`[ConnectionPool] Created new connection for ${dbName}`); |
||||
|
return connection; |
||||
|
} catch (error) { |
||||
|
logger.error(`[ConnectionPool] Failed to create connection for ${dbName}:`, error); |
||||
|
throw error; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public async releaseConnection(dbName: string): Promise<void> { |
||||
|
const connection = this.connections.get(dbName); |
||||
|
if (connection) { |
||||
|
connection.inUse = false; |
||||
|
connection.lastUsed = Date.now(); |
||||
|
logger.debug(`[ConnectionPool] Released connection for ${dbName}`); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private async waitForConnection(): Promise<void> { |
||||
|
return new Promise((resolve) => { |
||||
|
const checkInterval = setInterval(() => { |
||||
|
if (this.connections.size < this.MAX_CONNECTIONS) { |
||||
|
clearInterval(checkInterval); |
||||
|
resolve(); |
||||
|
} |
||||
|
}, 100); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private async cleanup(): Promise<void> { |
||||
|
const now = Date.now(); |
||||
|
for (const [dbName, state] of this.connections.entries()) { |
||||
|
if (!state.inUse && now - state.lastUsed > this.MAX_IDLE_TIME) { |
||||
|
try { |
||||
|
await state.connection.close(); |
||||
|
this.connections.delete(dbName); |
||||
|
logger.debug(`[ConnectionPool] Cleaned up idle connection for ${dbName}`); |
||||
|
} catch (error) { |
||||
|
logger.warn(`[ConnectionPool] Error closing idle connection for ${dbName}:`, error); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public async closeAll(): Promise<void> { |
||||
|
if (this.cleanupInterval) { |
||||
|
clearInterval(this.cleanupInterval); |
||||
|
this.cleanupInterval = null; |
||||
|
} |
||||
|
|
||||
|
for (const [dbName, state] of this.connections.entries()) { |
||||
|
try { |
||||
|
await state.connection.close(); |
||||
|
logger.debug(`[ConnectionPool] Closed connection for ${dbName}`); |
||||
|
} catch (error) { |
||||
|
logger.warn(`[ConnectionPool] Error closing connection for ${dbName}:`, error); |
||||
|
} |
||||
|
} |
||||
|
this.connections.clear(); |
||||
|
} |
||||
|
} |
Loading…
Reference in new issue