@@ -11,6 +11,7 @@ import * as SQLite from 'expo-sqlite';
1111 */
1212
1313let database : SQLite . SQLiteDatabase | null = null ;
14+ let initializationPromise : Promise < SQLite . SQLiteDatabase > | null = null ;
1415
1516const DB_NAME = 'typelets_mobile.db' ;
1617const DB_VERSION = 5 ;
@@ -164,7 +165,15 @@ async function migrateDatabase(db: SQLite.SQLiteDatabase): Promise<void> {
164165 // Clear notes cache so they get re-fetched with attachment counts
165166 console . log ( '[SQLite] Clearing notes cache to refresh with attachment counts...' ) ;
166167 await db . execAsync ( `DELETE FROM notes;` ) ;
167- await db . execAsync ( `DELETE FROM cache_metadata WHERE resource_type = 'notes';` ) ;
168+
169+ // Only clear cache_metadata if the table exists
170+ const cacheTableCheck = await db . getFirstAsync < { count : number } > (
171+ `SELECT COUNT(*) as count FROM sqlite_master WHERE type='table' AND name='cache_metadata'`
172+ ) ;
173+ if ( cacheTableCheck && cacheTableCheck . count > 0 ) {
174+ await db . execAsync ( `DELETE FROM cache_metadata WHERE resource_type = 'notes';` ) ;
175+ }
176+
168177 console . log ( '[SQLite] Notes cache cleared - will be refreshed on next load' ) ;
169178 }
170179 } catch ( error ) {
@@ -190,12 +199,75 @@ async function migrateDatabase(db: SQLite.SQLiteDatabase): Promise<void> {
190199 // Clear notes cache so they get re-fetched with public notes fields
191200 console . log ( '[SQLite] Clearing notes cache to refresh with public notes fields...' ) ;
192201 await db . execAsync ( `DELETE FROM notes;` ) ;
193- await db . execAsync ( `DELETE FROM cache_metadata WHERE resource_type = 'notes';` ) ;
202+
203+ // Only clear cache_metadata if the table exists
204+ const cacheTableCheck = await db . getFirstAsync < { count : number } > (
205+ `SELECT COUNT(*) as count FROM sqlite_master WHERE type='table' AND name='cache_metadata'`
206+ ) ;
207+ if ( cacheTableCheck && cacheTableCheck . count > 0 ) {
208+ await db . execAsync ( `DELETE FROM cache_metadata WHERE resource_type = 'notes';` ) ;
209+ }
210+
194211 console . log ( '[SQLite] Notes cache cleared - will be refreshed on next load' ) ;
195212 }
196213 } catch ( error ) {
197214 console . error ( '[SQLite] Failed to check/add public notes columns:' , error ) ;
198215 }
216+
217+ // Safety check: Ensure cache_metadata table exists (runs every time)
218+ try {
219+ const tableCheck = await db . getFirstAsync < { count : number } > (
220+ `SELECT COUNT(*) as count FROM sqlite_master WHERE type='table' AND name='cache_metadata'`
221+ ) ;
222+
223+ if ( tableCheck && tableCheck . count === 0 ) {
224+ console . log ( '[SQLite] cache_metadata table missing, creating it now...' ) ;
225+ await db . execAsync ( `
226+ CREATE TABLE cache_metadata (
227+ id TEXT PRIMARY KEY,
228+ resource_type TEXT NOT NULL,
229+ resource_id TEXT,
230+ e_tag TEXT,
231+ last_modified INTEGER,
232+ cached_at INTEGER NOT NULL,
233+ expires_at INTEGER NOT NULL
234+ );
235+ CREATE INDEX IF NOT EXISTS idx_cache_resource ON cache_metadata(resource_type, resource_id);
236+ ` ) ;
237+ console . log ( '[SQLite] cache_metadata table created successfully' ) ;
238+ }
239+ } catch ( error ) {
240+ console . error ( '[SQLite] Failed to check/create cache_metadata table:' , error ) ;
241+ }
242+
243+ // Safety check: Ensure sync_queue table exists (runs every time)
244+ try {
245+ const tableCheck = await db . getFirstAsync < { count : number } > (
246+ `SELECT COUNT(*) as count FROM sqlite_master WHERE type='table' AND name='sync_queue'`
247+ ) ;
248+
249+ if ( tableCheck && tableCheck . count === 0 ) {
250+ console . log ( '[SQLite] sync_queue table missing, creating it now...' ) ;
251+ await db . execAsync ( `
252+ CREATE TABLE sync_queue (
253+ id TEXT PRIMARY KEY,
254+ resource_type TEXT NOT NULL,
255+ resource_id TEXT NOT NULL,
256+ operation TEXT NOT NULL,
257+ payload TEXT NOT NULL,
258+ status TEXT DEFAULT 'pending',
259+ retry_count INTEGER DEFAULT 0,
260+ error_message TEXT,
261+ created_at INTEGER NOT NULL,
262+ synced_at INTEGER
263+ );
264+ CREATE INDEX IF NOT EXISTS idx_sync_status ON sync_queue(status);
265+ ` ) ;
266+ console . log ( '[SQLite] sync_queue table created successfully' ) ;
267+ }
268+ } catch ( error ) {
269+ console . error ( '[SQLite] Failed to check/create sync_queue table:' , error ) ;
270+ }
199271}
200272
201273/**
@@ -207,16 +279,22 @@ export async function initializeDatabase(): Promise<SQLite.SQLiteDatabase> {
207279 return database ;
208280 }
209281
282+ // If initialization is in progress, wait for it
283+ if ( initializationPromise ) {
284+ console . log ( '[SQLite] Database initialization already in progress, waiting...' ) ;
285+ return initializationPromise ;
286+ }
287+
210288 console . log ( '[SQLite] Initializing database...' ) ;
211289
290+ // Create initialization promise
291+ initializationPromise = ( async ( ) => {
292+
212293 try {
213294 // Open database
214295 database = await SQLite . openDatabaseAsync ( DB_NAME ) ;
215296
216- // Run migrations first
217- await migrateDatabase ( database ) ;
218-
219- // Create tables (if they don't exist)
297+ // Create base tables FIRST (if they don't exist) - this ensures migrations can reference them
220298 await database . execAsync ( `
221299 PRAGMA journal_mode = WAL;
222300
@@ -304,24 +382,47 @@ export async function initializeDatabase(): Promise<SQLite.SQLiteDatabase> {
304382 CREATE INDEX IF NOT EXISTS idx_notes_folder_status ON notes(folder_id, deleted, archived) WHERE folder_id IS NOT NULL;
305383 ` ) ;
306384
385+ console . log ( '[SQLite] Base tables created' ) ;
386+
387+ // Now run migrations - migrations can safely reference all tables
388+ await migrateDatabase ( database ) ;
389+
307390 console . log ( '[SQLite] Database initialized successfully' ) ;
308391 return database ;
309392 } catch ( error ) {
310393 console . error ( '[SQLite] Failed to initialize database:' , error ) ;
394+ database = null ;
395+ initializationPromise = null ;
311396 throw error ;
312397 }
398+ } ) ( ) ;
399+
400+ return initializationPromise ;
313401}
314402
315403/**
316404 * Get the database instance
317- * @throws Error if database hasn't been initialized yet
405+ * Automatically initializes if not yet initialized
318406 */
319407export function getDatabase ( ) : SQLite . SQLiteDatabase {
408+ if ( ! database && ! initializationPromise ) {
409+ // Auto-initialize if not already initializing
410+ console . warn ( '[SQLite] Database accessed before initialization, auto-initializing...' ) ;
411+ // We can't await here since this is a sync function, but we start the init
412+ initializeDatabase ( ) . catch ( err => {
413+ console . error ( '[SQLite] Auto-initialization failed:' , err ) ;
414+ } ) ;
415+ throw new Error (
416+ '[SQLite] Database not initialized yet. Initialization started automatically.'
417+ ) ;
418+ }
419+
320420 if ( ! database ) {
321421 throw new Error (
322- '[SQLite] Database not initialized. Call initializeDatabase() first .'
422+ '[SQLite] Database initialization in progress. Please wait .'
323423 ) ;
324424 }
425+
325426 return database ;
326427}
327428
@@ -362,6 +463,7 @@ export async function closeDatabase(): Promise<void> {
362463 if ( database ) {
363464 await database . closeAsync ( ) ;
364465 database = null ;
466+ initializationPromise = null ;
365467 console . log ( '[SQLite] Database closed' ) ;
366468 }
367469}
0 commit comments