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