@ -10,10 +10,9 @@ import {
SQLiteConnection ,
SQLiteDBConnection ,
CapacitorSQLite ,
Changes ,
} from "@capacitor-community/sqlite" ;
import { logger } from "../../utils/logger" ;
import { QueryExecResult , SqlValue } from "@/interfaces/database" ;
import { QueryExecResult } from "@/interfaces/database" ;
import { DEFAULT_ENDORSER_API_SERVER } from "@/constants/app" ;
interface Migration {
@ -21,6 +20,14 @@ interface Migration {
sql : string ;
}
interface QueuedOperation {
type : "run" | "query" | "getOneRow" | "getAll" ;
sql : string ;
params : unknown [ ] ;
resolve : ( value : unknown ) = > void ;
reject : ( reason : unknown ) = > void ;
}
/ * *
* Platform service implementation for Capacitor ( mobile ) platform .
* Provides native mobile functionality through Capacitor plugins for :
@ -34,12 +41,40 @@ export class CapacitorPlatformService implements PlatformService {
private db : SQLiteDBConnection | null = null ;
private dbName = "timesafari.db" ;
private initialized = false ;
private initializationPromise : Promise < void > | null = null ;
private operationQueue : Array < QueuedOperation > = [ ] ;
private isProcessingQueue : boolean = false ;
constructor ( ) {
this . sqlite = new SQLiteConnection ( CapacitorSQLite ) ;
}
private async initializeDatabase ( ) : Promise < void > {
// If already initialized, return immediately
if ( this . initialized ) {
return ;
}
// If initialization is in progress, wait for it
if ( this . initializationPromise ) {
return this . initializationPromise ;
}
// Start initialization
this . initializationPromise = this . _initialize ( ) ;
try {
await this . initializationPromise ;
} catch ( error ) {
logger . error (
"[CapacitorPlatformService] Initialize method failed:" ,
error ,
) ;
this . initializationPromise = null ; // Reset on failure
throw error ;
}
}
private async _initialize ( ) : Promise < void > {
if ( this . initialized ) {
return ;
}
@ -57,16 +92,142 @@ export class CapacitorPlatformService implements PlatformService {
await this . db . open ( ) ;
// Set journal mode to WAL for better performance
await this . db . execute ( "PRAGMA journal_mode=WAL;" ) ;
// await this.db.execute("PRAGMA journal_mode=WAL;");
// Run migrations
await this . runMigrations ( ) ;
this . initialized = true ;
logger . log ( "SQLite database initialized successfully" ) ;
logger . log (
"[CapacitorPlatformService] SQLite database initialized successfully" ,
) ;
// Start processing the queue after initialization
this . processQueue ( ) ;
} catch ( error ) {
logger . error ( "Error initializing SQLite database:" , error ) ;
throw new Error ( "Failed to initialize database" ) ;
logger . error (
"[CapacitorPlatformService] Error initializing SQLite database:" ,
error ,
) ;
throw new Error (
"[CapacitorPlatformService] Failed to initialize database" ,
) ;
}
}
private async processQueue ( ) : Promise < void > {
if ( this . isProcessingQueue || ! this . initialized || ! this . db ) {
return ;
}
this . isProcessingQueue = true ;
while ( this . operationQueue . length > 0 ) {
const operation = this . operationQueue . shift ( ) ;
if ( ! operation ) continue ;
try {
let result : unknown ;
switch ( operation . type ) {
case "run" : {
const runResult = await this . db . run (
operation . sql ,
operation . params ,
) ;
result = {
changes : runResult.changes?.changes || 0 ,
lastId : runResult.changes?.lastId ,
} ;
break ;
}
case "query" : {
const queryResult = await this . db . query (
operation . sql ,
operation . params ,
) ;
result = {
columns : [ ] , // SQLite plugin doesn't provide column names
values : queryResult.values || [ ] ,
} ;
break ;
}
case "getOneRow" : {
const oneRowResult = await this . db . query (
operation . sql ,
operation . params ,
) ;
result = oneRowResult . values ? . [ 0 ] ;
break ;
}
case "getAll" : {
const allResult = await this . db . query (
operation . sql ,
operation . params ,
) ;
result = allResult . values || [ ] ;
break ;
}
}
operation . resolve ( result ) ;
} catch ( error ) {
logger . error (
"[CapacitorPlatformService] Error while processing SQL queue:" ,
error ,
" ... for sql:" ,
operation . sql ,
" ... with params:" ,
operation . params ,
) ;
operation . reject ( error ) ;
}
}
this . isProcessingQueue = false ;
}
private async queueOperation < R > (
type : QueuedOperation [ "type" ] ,
sql : string ,
params : unknown [ ] = [ ] ,
) : Promise < R > {
return new Promise < R > ( ( resolve , reject ) = > {
const operation : QueuedOperation = {
type ,
sql ,
params ,
resolve : ( value : unknown ) = > resolve ( value as R ) ,
reject ,
} ;
this . operationQueue . push ( operation ) ;
// If we're already initialized, start processing the queue
if ( this . initialized && this . db ) {
this . processQueue ( ) ;
}
} ) ;
}
private async waitForInitialization ( ) : Promise < void > {
// If we have an initialization promise, wait for it
if ( this . initializationPromise ) {
await this . initializationPromise ;
return ;
}
// If not initialized and no promise, start initialization
if ( ! this . initialized ) {
await this . initializeDatabase ( ) ;
return ;
}
// If initialized but no db, something went wrong
if ( ! this . db ) {
logger . error (
"[CapacitorPlatformService] Database not properly initialized after await waitForInitialization() - initialized flag is true but db is null" ,
) ;
throw new Error (
"[CapacitorPlatformService] The database could not be initialized. We recommend you restart or reinstall." ,
) ;
}
}
@ -166,7 +327,7 @@ export class CapacitorPlatformService implements PlatformService {
CREATE INDEX IF NOT EXISTS idx_contacts_name ON contacts ( name ) ;
CREATE TABLE IF NOT EXISTS logs (
date TEXT PRIMARY KEY ,
date TEXT ,
message TEXT NOT NULL
) ;
@ -660,24 +821,8 @@ export class CapacitorPlatformService implements PlatformService {
* @see PlatformService . dbQuery
* /
async dbQuery ( sql : string , params? : unknown [ ] ) : Promise < QueryExecResult > {
await this . initializeDatabase ( ) ;
if ( ! this . db ) {
throw new Error ( "Database not initialized" ) ;
}
try {
const result = await this . db . query ( sql , params || [ ] ) ;
const values = result . values || [ ] ;
return {
columns : [ ] , // SQLite plugin doesn't provide column names in query result
values : values as SqlValue [ ] [ ] ,
} ;
} catch ( error ) {
logger . error ( "Error executing query:" , error ) ;
throw new Error (
` Database query failed: ${ error instanceof Error ? error.message : String ( error ) } ` ,
) ;
}
await this . waitForInitialization ( ) ;
return this . queueOperation < QueryExecResult > ( "query" , sql , params || [ ] ) ;
}
/ * *
@ -687,23 +832,11 @@ export class CapacitorPlatformService implements PlatformService {
sql : string ,
params? : unknown [ ] ,
) : Promise < { changes : number ; lastId? : number } > {
await this . initializeDatabase ( ) ;
if ( ! this . db ) {
throw new Error ( "Database not initialized" ) ;
}
try {
const result = await this . db . run ( sql , params || [ ] ) ;
const changes = result . changes as Changes ;
return {
changes : changes?.changes || 0 ,
lastId : changes?.lastId ,
} ;
} catch ( error ) {
logger . error ( "Error executing statement:" , error ) ;
throw new Error (
` Database execution failed: ${ error instanceof Error ? error.message : String ( error ) } ` ,
) ;
}
await this . waitForInitialization ( ) ;
return this . queueOperation < { changes : number ; lastId? : number } > (
"run" ,
sql ,
params || [ ] ,
) ;
}
}