forked from trent_larson/crowd-funder-for-time-pwa
fix(database): improve SQLite connection handling and initialization
- 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.
This commit is contained in:
116
src/services/database/ConnectionPool.ts
Normal file
116
src/services/database/ConnectionPool.ts
Normal file
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
SQLiteDBConnection,
|
SQLiteDBConnection,
|
||||||
} from "@capacitor-community/sqlite";
|
} from "@capacitor-community/sqlite";
|
||||||
import { DEFAULT_ENDORSER_API_SERVER } from "@/constants/app";
|
import { DEFAULT_ENDORSER_API_SERVER } from "@/constants/app";
|
||||||
|
import { DatabaseConnectionPool } from "../database/ConnectionPool";
|
||||||
|
|
||||||
interface Migration {
|
interface Migration {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -25,160 +26,112 @@ interface Migration {
|
|||||||
* - System-level features (TODO)
|
* - System-level features (TODO)
|
||||||
*/
|
*/
|
||||||
export class ElectronPlatformService implements PlatformService {
|
export class ElectronPlatformService implements PlatformService {
|
||||||
private sqlite: SQLiteConnection;
|
private sqlite: any;
|
||||||
private db: SQLiteDBConnection | null = null;
|
private connection: SQLiteDBConnection | null = null;
|
||||||
private dbName = "timesafari.db";
|
private connectionPool: DatabaseConnectionPool;
|
||||||
private initialized = false;
|
|
||||||
private initializationPromise: Promise<void> | null = null;
|
private initializationPromise: Promise<void> | null = null;
|
||||||
|
private dbName = "timesafari";
|
||||||
private readonly MAX_RETRIES = 3;
|
private readonly MAX_RETRIES = 3;
|
||||||
private readonly RETRY_DELAY = 1000; // 1 second
|
private readonly RETRY_DELAY = 1000; // 1 second
|
||||||
private dbConnectionErrorLogged = false;
|
|
||||||
private dbFatalError = false;
|
private dbFatalError = false;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
this.connectionPool = DatabaseConnectionPool.getInstance();
|
||||||
if (!window.CapacitorSQLite) {
|
if (!window.CapacitorSQLite) {
|
||||||
throw new Error("CapacitorSQLite not initialized in Electron");
|
throw new Error("CapacitorSQLite not initialized in Electron");
|
||||||
}
|
}
|
||||||
this.sqlite = new SQLiteConnection(window.CapacitorSQLite);
|
this.sqlite = window.CapacitorSQLite;
|
||||||
}
|
|
||||||
|
|
||||||
private async resetConnection(): Promise<void> {
|
|
||||||
try {
|
|
||||||
// Try to close any existing connection
|
|
||||||
if (this.db) {
|
|
||||||
try {
|
|
||||||
await this.db.close();
|
|
||||||
} catch (e) {
|
|
||||||
logger.warn("Error closing existing connection:", e);
|
|
||||||
}
|
|
||||||
this.db = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset state
|
|
||||||
this.initialized = false;
|
|
||||||
this.initializationPromise = null;
|
|
||||||
this.dbFatalError = false;
|
|
||||||
this.dbConnectionErrorLogged = false;
|
|
||||||
|
|
||||||
// Wait a moment for cleanup
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
||||||
} catch (error) {
|
|
||||||
logger.error("Error resetting connection:", error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async initializeDatabase(): Promise<void> {
|
private async initializeDatabase(): Promise<void> {
|
||||||
// If we have a fatal error, try to recover
|
// If we already have a connection, return immediately
|
||||||
if (this.dbFatalError) {
|
if (this.connection) {
|
||||||
logger.info("Attempting to recover from fatal error state...");
|
|
||||||
await this.resetConnection();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.initialized) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If initialization is in progress, wait for it
|
||||||
if (this.initializationPromise) {
|
if (this.initializationPromise) {
|
||||||
return this.initializationPromise;
|
return this.initializationPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start initialization
|
||||||
this.initializationPromise = (async () => {
|
this.initializationPromise = (async () => {
|
||||||
let retryCount = 0;
|
|
||||||
let lastError: Error | null = null;
|
|
||||||
|
|
||||||
while (retryCount < this.MAX_RETRIES) {
|
|
||||||
try {
|
try {
|
||||||
// Test SQLite availability
|
if (!this.sqlite) {
|
||||||
const isAvailable = await window.CapacitorSQLite.isAvailable();
|
logger.debug("[ElectronPlatformService] SQLite plugin not available, checking...");
|
||||||
if (!isAvailable) {
|
this.sqlite = await import("@capacitor-community/sqlite");
|
||||||
throw new Error("SQLite is not available in the main process");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log the connection parameters
|
if (!this.sqlite) {
|
||||||
logger.info("Calling createConnection with:", {
|
throw new Error("SQLite plugin not available");
|
||||||
dbName: this.dbName,
|
}
|
||||||
readOnly: false,
|
|
||||||
encryption: "no-encryption",
|
// Get connection from pool
|
||||||
version: 1,
|
this.connection = await this.connectionPool.getConnection("timesafari", async () => {
|
||||||
useNative: true,
|
// Create the connection
|
||||||
|
const connection = await this.sqlite.createConnection({
|
||||||
|
database: "timesafari",
|
||||||
|
encrypted: false,
|
||||||
|
mode: "no-encryption",
|
||||||
|
readonly: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create connection
|
// Wait for the connection to be fully initialized
|
||||||
this.db = await this.sqlite.createConnection(
|
await new Promise<void>((resolve, reject) => {
|
||||||
this.dbName, // database name
|
const checkConnection = async () => {
|
||||||
false, // readOnly
|
try {
|
||||||
"no-encryption", // encryption
|
// Try a simple query to verify the connection is ready
|
||||||
1, // version
|
const result = await connection.query("SELECT 1");
|
||||||
true, // useNative
|
if (result && result.values) {
|
||||||
);
|
resolve();
|
||||||
|
} else {
|
||||||
|
reject(new Error("Connection query returned invalid result"));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// If the error is that query is not a function, the connection isn't ready yet
|
||||||
|
if (error instanceof Error && error.message.includes("query is not a function")) {
|
||||||
|
setTimeout(checkConnection, 100);
|
||||||
|
} else {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
checkConnection();
|
||||||
|
});
|
||||||
|
|
||||||
logger.info("createConnection result:", this.db);
|
// Verify write access
|
||||||
|
const result = await connection.query("PRAGMA journal_mode");
|
||||||
if (!this.db || typeof this.db.execute !== "function") {
|
const journalMode = result.values?.[0]?.journal_mode;
|
||||||
throw new Error("Failed to create a valid database connection");
|
if (journalMode !== "wal") {
|
||||||
|
throw new Error(`Database is not writable. Journal mode: ${journalMode}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify connection is not read-only
|
return connection;
|
||||||
const journalMode = await this.db.query("PRAGMA journal_mode;");
|
});
|
||||||
if (journalMode?.values?.[0]?.journal_mode === "off") {
|
|
||||||
throw new Error(
|
|
||||||
"Database opened in read-only mode despite options",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run migrations
|
// Run migrations if needed
|
||||||
await this.runMigrations();
|
await this.runMigrations();
|
||||||
|
|
||||||
// Success! Clear any error state
|
logger.info("[ElectronPlatformService] Database initialized successfully");
|
||||||
this.dbFatalError = false;
|
|
||||||
this.dbConnectionErrorLogged = false;
|
|
||||||
this.initialized = true;
|
|
||||||
return;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
lastError = error instanceof Error ? error : new Error(String(error));
|
logger.error("[ElectronPlatformService] Database initialization failed:", error);
|
||||||
retryCount++;
|
this.connection = null;
|
||||||
|
throw error;
|
||||||
if (retryCount < this.MAX_RETRIES) {
|
} finally {
|
||||||
logger.warn(
|
|
||||||
`Database initialization attempt ${retryCount}/${this.MAX_RETRIES} failed:`,
|
|
||||||
error,
|
|
||||||
);
|
|
||||||
await new Promise((resolve) =>
|
|
||||||
setTimeout(resolve, this.RETRY_DELAY),
|
|
||||||
);
|
|
||||||
await this.resetConnection();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we get here, all retries failed
|
|
||||||
this.dbFatalError = true;
|
|
||||||
if (!this.dbConnectionErrorLogged) {
|
|
||||||
logger.error(
|
|
||||||
"[Electron] Error initializing SQLite database after all retries:",
|
|
||||||
lastError,
|
|
||||||
);
|
|
||||||
this.dbConnectionErrorLogged = true;
|
|
||||||
}
|
|
||||||
this.initialized = false;
|
|
||||||
this.initializationPromise = null;
|
this.initializationPromise = null;
|
||||||
throw (
|
}
|
||||||
lastError ||
|
|
||||||
new Error("Failed to initialize database after all retries")
|
|
||||||
);
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
return this.initializationPromise;
|
return this.initializationPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async runMigrations(): Promise<void> {
|
private async runMigrations(): Promise<void> {
|
||||||
if (!this.db) {
|
if (!this.connection) {
|
||||||
throw new Error("Database not initialized");
|
throw new Error("Database not initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create migrations table if it doesn't exist
|
// Create migrations table if it doesn't exist
|
||||||
await this.db.execute(`
|
await this.connection.execute(`
|
||||||
CREATE TABLE IF NOT EXISTS migrations (
|
CREATE TABLE IF NOT EXISTS migrations (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
name TEXT NOT NULL UNIQUE,
|
name TEXT NOT NULL UNIQUE,
|
||||||
@@ -187,7 +140,7 @@ export class ElectronPlatformService implements PlatformService {
|
|||||||
`);
|
`);
|
||||||
|
|
||||||
// Get list of executed migrations
|
// Get list of executed migrations
|
||||||
const result = await this.db.query("SELECT name FROM migrations;");
|
const result = await this.connection.query("SELECT name FROM migrations;");
|
||||||
const executedMigrations = new Set(
|
const executedMigrations = new Set(
|
||||||
result.values?.map((row) => row[0]) || [],
|
result.values?.map((row) => row[0]) || [],
|
||||||
);
|
);
|
||||||
@@ -282,8 +235,8 @@ export class ElectronPlatformService implements PlatformService {
|
|||||||
|
|
||||||
for (const migration of migrations) {
|
for (const migration of migrations) {
|
||||||
if (!executedMigrations.has(migration.name)) {
|
if (!executedMigrations.has(migration.name)) {
|
||||||
await this.db.execute(migration.sql);
|
await this.connection.execute(migration.sql);
|
||||||
await this.db.run("INSERT INTO migrations (name) VALUES (?)", [
|
await this.connection.run("INSERT INTO migrations (name) VALUES (?)", [
|
||||||
migration.name,
|
migration.name,
|
||||||
]);
|
]);
|
||||||
logger.log(`Migration ${migration.name} executed successfully`);
|
logger.log(`Migration ${migration.name} executed successfully`);
|
||||||
@@ -400,21 +353,20 @@ export class ElectronPlatformService implements PlatformService {
|
|||||||
sql: string,
|
sql: string,
|
||||||
params?: unknown[],
|
params?: unknown[],
|
||||||
): Promise<QueryExecResult | undefined> {
|
): Promise<QueryExecResult | undefined> {
|
||||||
try {
|
if (this.dbFatalError) {
|
||||||
|
throw new Error("Database is in a fatal error state. Please restart the app.");
|
||||||
|
}
|
||||||
|
|
||||||
await this.initializeDatabase();
|
await this.initializeDatabase();
|
||||||
if (!this.db) {
|
if (!this.connection) {
|
||||||
throw new Error("Database not initialized");
|
throw new Error("Database not initialized");
|
||||||
}
|
}
|
||||||
const result = await this.db.query(sql, params);
|
|
||||||
// Convert SQLite plugin result to QueryExecResult format
|
const result = await this.connection.query(sql, params);
|
||||||
return {
|
return {
|
||||||
columns: [], // SQLite plugin doesn't provide column names
|
columns: [], // SQLite plugin doesn't provide column names
|
||||||
values: result.values || [],
|
values: result.values || [],
|
||||||
};
|
};
|
||||||
} catch (error) {
|
|
||||||
logger.error("[Electron] Database query error:", error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -425,89 +377,66 @@ export class ElectronPlatformService implements PlatformService {
|
|||||||
params?: unknown[],
|
params?: unknown[],
|
||||||
): Promise<{ changes: number; lastId?: number }> {
|
): Promise<{ changes: number; lastId?: number }> {
|
||||||
if (this.dbFatalError) {
|
if (this.dbFatalError) {
|
||||||
throw new Error(
|
throw new Error("Database is in a fatal error state. Please restart the app.");
|
||||||
"Database is in a fatal error state. Please restart the app.",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
await this.initializeDatabase();
|
await this.initializeDatabase();
|
||||||
if (!this.db) {
|
if (!this.connection) {
|
||||||
throw new Error("Database not initialized");
|
throw new Error("Database not initialized");
|
||||||
}
|
}
|
||||||
const result = await this.db.run(sql, params);
|
|
||||||
// Convert SQLite plugin result to expected format
|
const result = await this.connection.run(sql, params);
|
||||||
return {
|
return {
|
||||||
changes: result.changes?.changes || 0,
|
changes: result.changes?.changes || 0,
|
||||||
lastId: result.changes?.lastId,
|
lastId: result.changes?.lastId,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
|
||||||
logger.error("[Electron] Database execution error:", error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async initialize(): Promise<void> {
|
async initialize(): Promise<void> {
|
||||||
if (this.initialized) {
|
if (this.dbFatalError) {
|
||||||
return;
|
throw new Error("Database is in a fatal error state. Please restart the app.");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
await this.initializeDatabase();
|
await this.initializeDatabase();
|
||||||
} catch (error) {
|
|
||||||
logger.error("Failed to initialize database:", error);
|
|
||||||
throw new Error(
|
|
||||||
`Database initialization failed: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async query<T>(sql: string, params: any[] = []): Promise<T[]> {
|
async query<T>(sql: string, params: any[] = []): Promise<T[]> {
|
||||||
if (this.dbFatalError) {
|
if (this.dbFatalError) {
|
||||||
throw new Error(
|
throw new Error("Database is in a fatal error state. Please restart the app.");
|
||||||
"Database is in a fatal error state. Please restart the app.",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!this.initialized) {
|
|
||||||
throw new Error("Database not initialized. Call initialize() first.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.initializeDatabase().then(() => {
|
await this.initializeDatabase();
|
||||||
if (!this.db) {
|
if (!this.connection) {
|
||||||
throw new Error("Database not initialized after initialization");
|
throw new Error("Database not initialized");
|
||||||
}
|
}
|
||||||
return this.db.query(sql, params).then((result) => {
|
|
||||||
if (!result?.values) {
|
const result = await this.connection.query(sql, params);
|
||||||
return [] as T[];
|
return (result.values || []) as T[];
|
||||||
}
|
|
||||||
return result.values as T[];
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async execute(sql: string, params: any[] = []): Promise<void> {
|
async execute(sql: string, params: any[] = []): Promise<void> {
|
||||||
if (!this.initialized) {
|
if (this.dbFatalError) {
|
||||||
throw new Error("Database not initialized. Call initialize() first.");
|
throw new Error("Database is in a fatal error state. Please restart the app.");
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.initializeDatabase().then(() => {
|
await this.initializeDatabase();
|
||||||
return this.db?.run(sql, params);
|
if (!this.connection) {
|
||||||
});
|
throw new Error("Database not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.connection.run(sql, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
async close(): Promise<void> {
|
async close(): Promise<void> {
|
||||||
if (!this.initialized) {
|
if (!this.connection) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.db?.close();
|
await this.connectionPool.releaseConnection("timesafari");
|
||||||
this.initialized = false;
|
this.connection = null;
|
||||||
this.db = null;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Failed to close database:", error);
|
logger.error("Failed to close database:", error);
|
||||||
throw new Error(
|
throw error;
|
||||||
`Failed to close database: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user