refactor-initialize #221
@@ -50,6 +50,8 @@ export class CapacitorPlatformService
|
||||
private sqlite: SQLiteConnection;
|
||||
private db: SQLiteDBConnection | null = null;
|
||||
private dbName = "timesafari.sqlite";
|
||||
private dbVersion: number = 1;
|
||||
private encryption: string = "no-encryption";
|
||||
private initialized = false;
|
||||
private initializationPromise: Promise<void> | null = null;
|
||||
private operationQueue: Array<QueuedOperation> = [];
|
||||
@@ -85,120 +87,396 @@ export class CapacitorPlatformService
|
||||
}
|
||||
}
|
||||
|
||||
private async _initialize(): Promise<void> {
|
||||
if (this.initialized) {
|
||||
/**
|
||||
* Extract error message from unknown error type.
|
||||
*
|
||||
* Provides consistent error text extraction for logging and
|
||||
* error handling across different error formats.
|
||||
*
|
||||
* @param e - Unknown error object
|
||||
* @returns String representation of error message
|
||||
* @private
|
||||
*/
|
||||
private _asMessage(e: unknown): string {
|
||||
if (e instanceof Error) {
|
||||
return e.message;
|
||||
}
|
||||
if (
|
||||
typeof e === "object" &&
|
||||
e !== null &&
|
||||
("errorMessage" in e || "message" in e)
|
||||
) {
|
||||
return (
|
||||
(e as { errorMessage?: string; message?: string }).errorMessage ||
|
||||
(e as { errorMessage?: string; message?: string }).message ||
|
||||
String(e)
|
||||
);
|
||||
}
|
||||
return String(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure database connection exists, handling JS/native map desyncs.
|
||||
*
|
||||
* Uses capability checks instead of error message parsing to
|
||||
* deterministically handle connection state across native/JS
|
||||
* boundaries.
|
||||
*
|
||||
* @returns Promise resolving when connection is established
|
||||
* @throws Error if connection cannot be established
|
||||
* @private
|
||||
*/
|
||||
private async _ensureConnection(): Promise<void> {
|
||||
const startTime = Date.now();
|
||||
|
||||
// Check if connection exists in JavaScript Map
|
||||
const isConnResult = await this.sqlite.isConnection(this.dbName, false);
|
||||
|
||||
if (isConnResult.result) {
|
||||
// Connection exists in Map, retrieve it
|
||||
logger.debug(
|
||||
`[CapacitorPlatformService] Connection exists in Map, retrieving (${Date.now() - startTime}ms)`,
|
||||
);
|
||||
this.db = await this.sqlite.retrieveConnection(this.dbName, false);
|
||||
logger.debug(
|
||||
`[CapacitorPlatformService] Successfully retrieved connection (${Date.now() - startTime}ms)`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Connection doesn't exist in Map, try to create
|
||||
try {
|
||||
// Try to create/Open database connection
|
||||
logger.debug(
|
||||
`[CapacitorPlatformService] Creating new connection (${Date.now() - startTime}ms)`,
|
||||
);
|
||||
this.db = await this.sqlite.createConnection(
|
||||
this.dbName,
|
||||
false,
|
||||
this.encryption,
|
||||
this.dbVersion,
|
||||
false,
|
||||
);
|
||||
logger.debug(
|
||||
`[CapacitorPlatformService] Successfully created connection (${Date.now() - startTime}ms)`,
|
||||
);
|
||||
} catch (createError: unknown) {
|
||||
// Connection might exist natively but not in JS Map
|
||||
// Best-effort cleanup then retry once
|
||||
logger.debug(
|
||||
`[CapacitorPlatformService] Create failed, attempting cleanup (${Date.now() - startTime}ms):`,
|
||||
this._asMessage(createError),
|
||||
);
|
||||
try {
|
||||
this.db = await this.sqlite.createConnection(
|
||||
this.dbName,
|
||||
false,
|
||||
"no-encryption",
|
||||
1,
|
||||
false,
|
||||
await this.sqlite.closeConnection(this.dbName, false);
|
||||
} catch (closeError) {
|
||||
// Ignore close errors - connection might not be properly tracked
|
||||
logger.debug(
|
||||
`[CapacitorPlatformService] Cleanup close error (may be expected):`,
|
||||
this._asMessage(closeError),
|
||||
);
|
||||
} catch (createError: unknown) {
|
||||
// If connection already exists, try to retrieve it or handle gracefully
|
||||
const errorMessage =
|
||||
createError instanceof Error
|
||||
? createError.message
|
||||
: String(createError);
|
||||
const errorObj =
|
||||
typeof createError === "object" && createError !== null
|
||||
? (createError as { errorMessage?: string; message?: string })
|
||||
: {};
|
||||
|
||||
const fullErrorMessage =
|
||||
errorObj.errorMessage || errorObj.message || errorMessage;
|
||||
|
||||
if (fullErrorMessage.includes("already exists")) {
|
||||
logger.debug(
|
||||
"[CapacitorPlatformService] Connection already exists on native side, attempting to retrieve",
|
||||
);
|
||||
// Check if connection exists in JavaScript Map
|
||||
const isConnResult = await this.sqlite.isConnection(
|
||||
this.dbName,
|
||||
false,
|
||||
);
|
||||
if (isConnResult.result) {
|
||||
// Connection exists in Map, retrieve it
|
||||
this.db = await this.sqlite.retrieveConnection(this.dbName, false);
|
||||
logger.debug(
|
||||
"[CapacitorPlatformService] Successfully retrieved existing connection from Map",
|
||||
);
|
||||
} else {
|
||||
// Connection exists on native side but not in JavaScript Map
|
||||
// This can happen when the app is restarted but native connections persist
|
||||
// Try to close the native connection first, then create a new one
|
||||
logger.debug(
|
||||
"[CapacitorPlatformService] Connection exists natively but not in Map, closing and recreating",
|
||||
);
|
||||
try {
|
||||
await this.sqlite.closeConnection(this.dbName, false);
|
||||
} catch (closeError) {
|
||||
// Ignore close errors - connection might not be properly tracked
|
||||
logger.debug(
|
||||
"[CapacitorPlatformService] Error closing connection (may be expected):",
|
||||
closeError,
|
||||
);
|
||||
}
|
||||
// Now try to create the connection again
|
||||
this.db = await this.sqlite.createConnection(
|
||||
this.dbName,
|
||||
false,
|
||||
"no-encryption",
|
||||
1,
|
||||
false,
|
||||
);
|
||||
logger.debug(
|
||||
"[CapacitorPlatformService] Successfully created connection after cleanup",
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Re-throw if it's a different error
|
||||
throw createError;
|
||||
}
|
||||
}
|
||||
|
||||
// Open the connection if it's not already open
|
||||
// Retry create once
|
||||
logger.debug(
|
||||
`[CapacitorPlatformService] Retrying connection creation (${Date.now() - startTime}ms)`,
|
||||
);
|
||||
this.db = await this.sqlite.createConnection(
|
||||
this.dbName,
|
||||
false,
|
||||
this.encryption,
|
||||
this.dbVersion,
|
||||
false,
|
||||
);
|
||||
logger.debug(
|
||||
`[CapacitorPlatformService] Successfully created connection after retry (${Date.now() - startTime}ms)`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply SQLite PRAGMAs with platform-aware configuration.
|
||||
*
|
||||
* Sets foreign keys, journal mode (WAL on native), synchronous
|
||||
* mode, and busy timeout. Handles platform differences gracefully.
|
||||
*
|
||||
* @param db - Database connection (non-null)
|
||||
* @returns Promise resolving when PRAGMAs are applied
|
||||
* @throws Error if PRAGMA application fails
|
||||
* @private
|
||||
*/
|
||||
private async _applyPragmas(db: SQLiteDBConnection): Promise<string> {
|
||||
const startTime = Date.now();
|
||||
const platform = Capacitor.getPlatform();
|
||||
let journalMode = "unknown";
|
||||
|
||||
try {
|
||||
// Always enable foreign keys
|
||||
await db.execute("PRAGMA foreign_keys = ON;");
|
||||
logger.debug(
|
||||
`[CapacitorPlatformService] Applied foreign_keys PRAGMA (${Date.now() - startTime}ms)`,
|
||||
);
|
||||
|
||||
// Set busy timeout before any operations
|
||||
await db.execute("PRAGMA busy_timeout = 5000;");
|
||||
logger.debug(
|
||||
`[CapacitorPlatformService] Applied busy_timeout PRAGMA (${Date.now() - startTime}ms)`,
|
||||
);
|
||||
|
||||
// Prefer WAL on native platforms
|
||||
if (platform !== "web") {
|
||||
try {
|
||||
await db.execute("PRAGMA journal_mode = WAL;");
|
||||
// Verify actual journal mode
|
||||
const modeResult = await db.query("PRAGMA journal_mode;");
|
||||
if (
|
||||
modeResult.values &&
|
||||
modeResult.values.length > 0 &&
|
||||
typeof modeResult.values[0] === "object" &&
|
||||
modeResult.values[0] !== null
|
||||
) {
|
||||
const row = modeResult.values[0] as Record<string, unknown>;
|
||||
journalMode =
|
||||
(row.journal_mode as string) ||
|
||||
(Array.isArray(modeResult.values[0])
|
||||
? (modeResult.values[0][0] as string)
|
||||
: "unknown");
|
||||
}
|
||||
logger.debug(
|
||||
`[CapacitorPlatformService] Applied WAL journal_mode: ${journalMode} (${Date.now() - startTime}ms)`,
|
||||
);
|
||||
|
||||
await db.execute("PRAGMA synchronous = NORMAL;");
|
||||
logger.debug(
|
||||
`[CapacitorPlatformService] Applied synchronous PRAGMA (${Date.now() - startTime}ms)`,
|
||||
);
|
||||
} catch (walError) {
|
||||
logger.warn(
|
||||
`[CapacitorPlatformService] WAL mode failed, continuing with default:`,
|
||||
this._asMessage(walError),
|
||||
);
|
||||
// Verify actual mode even if WAL failed
|
||||
try {
|
||||
const modeResult = await db.query("PRAGMA journal_mode;");
|
||||
if (
|
||||
modeResult.values &&
|
||||
modeResult.values.length > 0 &&
|
||||
typeof modeResult.values[0] === "object" &&
|
||||
modeResult.values[0] !== null
|
||||
) {
|
||||
const row = modeResult.values[0] as Record<string, unknown>;
|
||||
journalMode =
|
||||
(row.journal_mode as string) ||
|
||||
(Array.isArray(modeResult.values[0])
|
||||
? (modeResult.values[0][0] as string)
|
||||
: "unknown");
|
||||
}
|
||||
} catch {
|
||||
journalMode = "unknown";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.debug(
|
||||
`[CapacitorPlatformService] Skipping WAL on web platform (${Date.now() - startTime}ms)`,
|
||||
);
|
||||
journalMode = "default";
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`[CapacitorPlatformService] PRAGMAs applied successfully, journal_mode: ${journalMode} (${Date.now() - startTime}ms)`,
|
||||
);
|
||||
return journalMode;
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`[CapacitorPlatformService] Failed to apply PRAGMAs (${Date.now() - startTime}ms):`,
|
||||
this._asMessage(error),
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize database connection, apply PRAGMAs, run migrations,
|
||||
* and verify integrity.
|
||||
*
|
||||
* This method orchestrates the complete database initialization
|
||||
* process with proper error handling, transaction management, and
|
||||
* observability. It relies on `initializeDatabase()` for
|
||||
* single-flight concurrency control.
|
||||
*
|
||||
* ## Initialization Phases:
|
||||
*
|
||||
* 1. **Connection**: Establish connection using capability checks
|
||||
* 2. **Open**: Open connection with tolerant "already open" handling
|
||||
* 3. **PRAGMAs**: Apply platform-aware SQLite configuration
|
||||
* 4. **Migrations**: Execute migrations within transaction
|
||||
* 5. **Verification**: Run integrity checks post-migration
|
||||
* 6. **Queue Start**: Begin processing queued operations
|
||||
*
|
||||
* ## Error Handling:
|
||||
*
|
||||
* - All phases are timed and logged
|
||||
* - Failures trigger cleanup (close connection, reset state)
|
||||
* - Errors include original cause for upstream logging
|
||||
* - Single-flight promise is reset only on failure
|
||||
*
|
||||
* ## Transaction Management:
|
||||
*
|
||||
* Migrations run within `BEGIN IMMEDIATE` transaction with
|
||||
* automatic rollback on failure. This ensures database consistency
|
||||
* even if migrations fail mid-way.
|
||||
*
|
||||
* @throws Error if initialization fails at any phase
|
||||
* @private Internal method called only from initializeDatabase()
|
||||
*/
|
||||
private async _initialize(): Promise<void> {
|
||||
const initStartTime = Date.now();
|
||||
const phaseTimings: Record<string, number> = {};
|
||||
let connectionEstablished = false;
|
||||
let dbOpened = false;
|
||||
let journalMode = "unknown";
|
||||
|
||||
try {
|
||||
// Phase 1: Connection establishment
|
||||
const connectStart = Date.now();
|
||||
await this._ensureConnection();
|
||||
phaseTimings.connect = Date.now() - connectStart;
|
||||
connectionEstablished = true;
|
||||
|
||||
if (!this.db) {
|
||||
throw new Error("Connection established but db is null");
|
||||
}
|
||||
|
||||
// Narrow type for subsequent operations
|
||||
const db = this.db;
|
||||
|
||||
// Phase 2: Open connection
|
||||
const openStart = Date.now();
|
||||
try {
|
||||
await this.db.open();
|
||||
await db.open();
|
||||
dbOpened = true;
|
||||
} catch (openError: unknown) {
|
||||
const openErrorMessage =
|
||||
openError instanceof Error ? openError.message : String(openError);
|
||||
// If already open, that's fine - continue
|
||||
if (!openErrorMessage.includes("already open")) {
|
||||
const openMsg = this._asMessage(openError);
|
||||
// Swallow only "already open" errors
|
||||
if (!/already open/i.test(openMsg)) {
|
||||
throw openError;
|
||||
}
|
||||
logger.debug(
|
||||
"[CapacitorPlatformService] Database connection already open",
|
||||
`[CapacitorPlatformService] Database already open (${Date.now() - openStart}ms)`,
|
||||
);
|
||||
dbOpened = true;
|
||||
}
|
||||
phaseTimings.open = Date.now() - openStart;
|
||||
|
||||
// Phase 3: Apply PRAGMAs
|
||||
const pragmasStart = Date.now();
|
||||
journalMode = await this._applyPragmas(db);
|
||||
phaseTimings.pragmas = Date.now() - pragmasStart;
|
||||
|
||||
// Phase 4: Run migrations in transaction
|
||||
const migrateStart = Date.now();
|
||||
try {
|
||||
await db.execute("BEGIN IMMEDIATE;");
|
||||
logger.debug(
|
||||
`[CapacitorPlatformService] Migration transaction started (${Date.now() - migrateStart}ms)`,
|
||||
);
|
||||
|
||||
await this.runCapacitorMigrations();
|
||||
|
||||
await db.execute("COMMIT;");
|
||||
logger.debug(
|
||||
`[CapacitorPlatformService] Migration transaction committed (${Date.now() - migrateStart}ms)`,
|
||||
);
|
||||
} catch (migrateError) {
|
||||
// Rollback on any migration error
|
||||
try {
|
||||
await db.execute("ROLLBACK;");
|
||||
logger.debug(
|
||||
`[CapacitorPlatformService] Migration transaction rolled back (${Date.now() - migrateStart}ms)`,
|
||||
);
|
||||
} catch (rollbackError) {
|
||||
logger.error(
|
||||
`[CapacitorPlatformService] Rollback failed:`,
|
||||
this._asMessage(rollbackError),
|
||||
);
|
||||
}
|
||||
throw migrateError;
|
||||
}
|
||||
phaseTimings.migrate = Date.now() - migrateStart;
|
||||
|
||||
// Phase 5: Post-migration integrity verification
|
||||
const verifyStart = Date.now();
|
||||
await this.verifyDatabaseIntegrity();
|
||||
phaseTimings.verify = Date.now() - verifyStart;
|
||||
|
||||
// Mark as initialized
|
||||
this.initialized = true;
|
||||
|
||||
// Phase 6: Start queue processor (only once)
|
||||
if (!this.isProcessingQueue) {
|
||||
this.processQueue();
|
||||
}
|
||||
|
||||
// Set journal mode to WAL for better performance
|
||||
// await this.db.execute("PRAGMA journal_mode=WAL;");
|
||||
|
||||
// Run migrations
|
||||
await this.runCapacitorMigrations();
|
||||
|
||||
this.initialized = true;
|
||||
// Log success with all timing and config info
|
||||
const totalTime = Date.now() - initStartTime;
|
||||
const platform = Capacitor.getPlatform();
|
||||
logger.log(
|
||||
"[CapacitorPlatformService] SQLite database initialized successfully",
|
||||
`[CapacitorPlatformService] Database initialized successfully ` +
|
||||
`(total: ${totalTime}ms, connect: ${phaseTimings.connect}ms, ` +
|
||||
`open: ${phaseTimings.open}ms, pragmas: ${phaseTimings.pragmas}ms, ` +
|
||||
`migrate: ${phaseTimings.migrate}ms, verify: ${phaseTimings.verify}ms) ` +
|
||||
`[dbName: ${this.dbName}, dbVersion: ${this.dbVersion}, ` +
|
||||
`encryption: ${this.encryption}, platform: ${platform}, ` +
|
||||
`journal_mode: ${journalMode}]`,
|
||||
);
|
||||
} catch (error) {
|
||||
const phase =
|
||||
Object.keys(phaseTimings).length === 0
|
||||
? "connection"
|
||||
: Object.keys(phaseTimings).pop() || "unknown";
|
||||
const totalTime = Date.now() - initStartTime;
|
||||
|
||||
logger.error(
|
||||
`[CapacitorPlatformService] Initialization failed at phase '${phase}' ` +
|
||||
`(total: ${totalTime}ms):`,
|
||||
error instanceof Error ? error.message : String(error),
|
||||
);
|
||||
|
||||
// Start processing the queue after initialization
|
||||
this.processQueue();
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
"[CapacitorPlatformService] Error initializing SQLite database:",
|
||||
error,
|
||||
);
|
||||
throw new Error(
|
||||
"[CapacitorPlatformService] Failed to initialize database",
|
||||
// Cleanup: best-effort close and reset state
|
||||
if (connectionEstablished && this.db) {
|
||||
try {
|
||||
if (dbOpened) {
|
||||
await this.db.close();
|
||||
}
|
||||
} catch (closeError) {
|
||||
logger.debug(
|
||||
`[CapacitorPlatformService] Cleanup close error:`,
|
||||
this._asMessage(closeError),
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
await this.sqlite.closeConnection(this.dbName, false);
|
||||
} catch (closeConnError) {
|
||||
logger.debug(
|
||||
`[CapacitorPlatformService] Cleanup closeConnection error:`,
|
||||
this._asMessage(closeConnError),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.db = null;
|
||||
this.initialized = false;
|
||||
|
||||
// Re-throw with cause for upstream logging
|
||||
const initError = new Error(
|
||||
`[CapacitorPlatformService] Failed to initialize database at phase '${phase}'`,
|
||||
);
|
||||
if (error instanceof Error) {
|
||||
// Use standard Error properties for compatibility
|
||||
initError.message = `${initError.message}: ${error.message}`;
|
||||
initError.stack = error.stack;
|
||||
}
|
||||
throw initError;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -673,22 +951,13 @@ export class CapacitorPlatformService
|
||||
return new Set(names);
|
||||
};
|
||||
|
||||
try {
|
||||
// Execute the migration process
|
||||
await runMigrations(sqlExec, sqlQuery, extractMigrationNames);
|
||||
|
||||
// After migrations, run integrity check to verify database state
|
||||
await this.verifyDatabaseIntegrity();
|
||||
} catch (error) {
|
||||
logger.error(`❌ [CapacitorMigration] Migration failed:`, error);
|
||||
// Still try to verify what we have for debugging purposes
|
||||
await this.verifyDatabaseIntegrity();
|
||||
throw error;
|
||||
}
|
||||
// Execute the migration process
|
||||
// Note: Integrity verification is handled by _initialize() after transaction commit
|
||||
await runMigrations(sqlExec, sqlQuery, extractMigrationNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify database integrity and migration status
|
||||
* Verify database integrity and migration status.
|
||||
*
|
||||
* This method performs comprehensive validation of the database structure
|
||||
* and migration state. It's designed to help identify issues with the
|
||||
@@ -719,9 +988,9 @@ export class CapacitorPlatformService
|
||||
*
|
||||
* ## Error Handling:
|
||||
*
|
||||
* This method is designed to never throw errors - it captures and logs
|
||||
* all validation issues for debugging purposes. This ensures that even
|
||||
* if integrity checks fail, they don't prevent the application from starting.
|
||||
* This method throws errors for critical failures (missing core tables)
|
||||
* to ensure initialization fails when database integrity is compromised.
|
||||
* Non-critical validation issues are logged but do not cause failures.
|
||||
*
|
||||
* ## Logging Output:
|
||||
*
|
||||
@@ -731,6 +1000,7 @@ export class CapacitorPlatformService
|
||||
* - `📊` for data summaries
|
||||
* - `🔍` for investigation steps
|
||||
*
|
||||
* @throws Error if critical integrity checks fail (e.g., missing core tables)
|
||||
* @private Internal method called after migrations
|
||||
*
|
||||
* @example
|
||||
@@ -741,12 +1011,15 @@ export class CapacitorPlatformService
|
||||
*/
|
||||
private async verifyDatabaseIntegrity(): Promise<void> {
|
||||
if (!this.db) {
|
||||
logger.error(`❌ [DB-Integrity] Database not initialized`);
|
||||
return;
|
||||
const error = new Error("[DB-Integrity] Database not initialized");
|
||||
logger.error(`❌ ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
|
||||
logger.debug(`🔍 [DB-Integrity] Starting database integrity check...`);
|
||||
|
||||
const missingTables: string[] = [];
|
||||
|
||||
try {
|
||||
// Step 1: Check migrations table and applied migrations
|
||||
const migrationsResult = await this.db.query(
|
||||
@@ -763,7 +1036,6 @@ export class CapacitorPlatformService
|
||||
"logs",
|
||||
"temp",
|
||||
];
|
||||
const existingTables: string[] = [];
|
||||
|
||||
for (const tableName of coreTableNames) {
|
||||
try {
|
||||
@@ -771,58 +1043,66 @@ export class CapacitorPlatformService
|
||||
`SELECT name FROM sqlite_master WHERE type='table' AND name='${tableName}'`,
|
||||
);
|
||||
if (tableCheck.values && tableCheck.values.length > 0) {
|
||||
existingTables.push(tableName);
|
||||
logger.debug(`✅ [DB-Integrity] Table ${tableName} exists`);
|
||||
} else {
|
||||
logger.error(`❌ [DB-Integrity] Table ${tableName} missing`);
|
||||
missingTables.push(tableName);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`❌ [DB-Integrity] Error checking table ${tableName}:`,
|
||||
error,
|
||||
);
|
||||
missingTables.push(tableName);
|
||||
}
|
||||
}
|
||||
|
||||
// Fail init if critical tables are missing
|
||||
if (missingTables.length > 0) {
|
||||
const error = new Error(
|
||||
`[DB-Integrity] Critical tables missing: ${missingTables.join(", ")}`,
|
||||
);
|
||||
logger.error(`❌ ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Step 3: Check contacts table schema (including iViewContent column)
|
||||
if (existingTables.includes("contacts")) {
|
||||
try {
|
||||
const contactsSchema = await this.db.query(
|
||||
"PRAGMA table_info(contacts)",
|
||||
);
|
||||
try {
|
||||
const contactsSchema = await this.db.query(
|
||||
"PRAGMA table_info(contacts)",
|
||||
);
|
||||
logger.debug(
|
||||
`📊 [DB-Integrity] Contacts table schema:`,
|
||||
contactsSchema,
|
||||
);
|
||||
|
||||
// Check for iViewContent column specifically
|
||||
const hasIViewContent = contactsSchema.values?.some(
|
||||
(col: unknown) =>
|
||||
(typeof col === "object" &&
|
||||
col !== null &&
|
||||
"name" in col &&
|
||||
(col as { name: string }).name === "iViewContent") ||
|
||||
(Array.isArray(col) && col[1] === "iViewContent"),
|
||||
);
|
||||
|
||||
if (hasIViewContent) {
|
||||
logger.debug(
|
||||
`📊 [DB-Integrity] Contacts table schema:`,
|
||||
contactsSchema,
|
||||
`✅ [DB-Integrity] iViewContent column exists in contacts table`,
|
||||
);
|
||||
|
||||
// Check for iViewContent column specifically
|
||||
const hasIViewContent = contactsSchema.values?.some(
|
||||
(col: unknown) =>
|
||||
(typeof col === "object" &&
|
||||
col !== null &&
|
||||
"name" in col &&
|
||||
(col as { name: string }).name === "iViewContent") ||
|
||||
(Array.isArray(col) && col[1] === "iViewContent"),
|
||||
);
|
||||
|
||||
if (hasIViewContent) {
|
||||
logger.debug(
|
||||
`✅ [DB-Integrity] iViewContent column exists in contacts table`,
|
||||
);
|
||||
} else {
|
||||
logger.error(
|
||||
`❌ [DB-Integrity] iViewContent column missing from contacts table`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`❌ [DB-Integrity] Error checking contacts schema:`,
|
||||
error,
|
||||
} else {
|
||||
logger.warn(
|
||||
`⚠️ [DB-Integrity] iViewContent column missing from contacts table (non-critical)`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
`⚠️ [DB-Integrity] Error checking contacts schema (non-critical):`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
|
||||
// Step 4: Check for basic data integrity
|
||||
// Step 4: Check for basic data integrity (best-effort, non-critical)
|
||||
try {
|
||||
const accountCount = await this.db.query(
|
||||
"SELECT COUNT(*) as count FROM accounts",
|
||||
@@ -838,11 +1118,19 @@ export class CapacitorPlatformService
|
||||
`📊 [DB-Integrity] Data counts - Accounts: ${JSON.stringify(accountCount)}, Settings: ${JSON.stringify(settingsCount)}, Contacts: ${JSON.stringify(contactsCount)}`,
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error(`❌ [DB-Integrity] Error checking data counts:`, error);
|
||||
logger.warn(
|
||||
`⚠️ [DB-Integrity] Error checking data counts (non-critical):`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
|
||||
logger.log(`✅ [DB-Integrity] Database integrity check completed`);
|
||||
} catch (error) {
|
||||
// Re-throw if it's our own error (missing tables)
|
||||
if (error instanceof Error && error.message.includes("[DB-Integrity]")) {
|
||||
throw error;
|
||||
}
|
||||
// Log other errors but don't fail init
|
||||
logger.error(`❌ [DB-Integrity] Database integrity check failed:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user