@ -183,6 +183,27 @@ const MIGRATIONS = [
DELETE FROM settings WHERE accountDid IS NULL ;
DELETE FROM settings WHERE accountDid IS NULL ;
UPDATE settings SET activeDid = NULL ;
UPDATE settings SET activeDid = NULL ;
` ,
` ,
// Split into individual statements for better error handling
statements : [
"PRAGMA foreign_keys = ON" ,
"CREATE UNIQUE INDEX IF NOT EXISTS idx_accounts_did_unique ON accounts(did)" ,
` CREATE TABLE IF NOT EXISTS active_identity (
id INTEGER PRIMARY KEY CHECK ( id = 1 ) ,
activeDid TEXT REFERENCES accounts ( did ) ON DELETE RESTRICT ,
lastUpdated TEXT NOT NULL DEFAULT ( datetime ( 'now' ) )
) ` ,
"CREATE UNIQUE INDEX IF NOT EXISTS idx_active_identity_single_record ON active_identity(id)" ,
` INSERT INTO active_identity (id, activeDid, lastUpdated)
SELECT 1 , NULL , datetime ( 'now' )
WHERE NOT EXISTS ( SELECT 1 FROM active_identity WHERE id = 1 ) ` ,
` UPDATE active_identity
SET activeDid = ( SELECT activeDid FROM settings WHERE id = 1 ) ,
lastUpdated = datetime ( 'now' )
WHERE id = 1
AND EXISTS ( SELECT 1 FROM settings WHERE id = 1 AND activeDid IS NOT NULL AND activeDid != '' ) ` ,
"DELETE FROM settings WHERE accountDid IS NULL" ,
"UPDATE settings SET activeDid = NULL" ,
] ,
} ,
} ,
] ;
] ;
@ -216,13 +237,86 @@ export async function runMigrations<T>(
? ( ( accountsResult as DatabaseResult ) . values ? . [ 0 ] ? . [ 0 ] as number )
? ( ( accountsResult as DatabaseResult ) . values ? . [ 0 ] ? . [ 0 ] as number )
: 0 ;
: 0 ;
// Check if active_identity table exists, and if not, try to recover
let activeDid : string | null = null ;
try {
const activeResult = await sqlQuery (
"SELECT activeDid FROM active_identity WHERE id = 1" ,
) ;
activeDid =
activeResult && ( activeResult as DatabaseResult ) . values
? ( ( activeResult as DatabaseResult ) . values ? . [ 0 ] ? . [ 0 ] as string )
: null ;
} catch ( error ) {
// Table doesn't exist - this means migration 004 failed but was marked as applied
logger . warn (
"[Migration] active_identity table missing, attempting recovery" ,
) ;
// Check if migration 004 is marked as applied
const migrationResult = await sqlQuery (
"SELECT name FROM migrations WHERE name = '004_active_identity_management'" ,
) ;
const isMigrationMarked =
migrationResult && ( migrationResult as DatabaseResult ) . values
? ( ( migrationResult as DatabaseResult ) . values ? . length ? ? 0 ) > 0
: false ;
if ( isMigrationMarked ) {
logger . warn (
"[Migration] Migration 004 marked as applied but table missing - recreating table" ,
) ;
// Recreate the active_identity table using the individual statements
const statements = [
"PRAGMA foreign_keys = ON" ,
"CREATE UNIQUE INDEX IF NOT EXISTS idx_accounts_did_unique ON accounts(did)" ,
` CREATE TABLE IF NOT EXISTS active_identity (
id INTEGER PRIMARY KEY CHECK ( id = 1 ) ,
activeDid TEXT REFERENCES accounts ( did ) ON DELETE RESTRICT ,
lastUpdated TEXT NOT NULL DEFAULT ( datetime ( 'now' ) )
) ` ,
"CREATE UNIQUE INDEX IF NOT EXISTS idx_active_identity_single_record ON active_identity(id)" ,
` INSERT INTO active_identity (id, activeDid, lastUpdated)
SELECT 1 , NULL , datetime ( 'now' )
WHERE NOT EXISTS ( SELECT 1 FROM active_identity WHERE id = 1 ) ` ,
` UPDATE active_identity
SET activeDid = ( SELECT activeDid FROM settings WHERE id = 1 ) ,
lastUpdated = datetime ( 'now' )
WHERE id = 1
AND EXISTS ( SELECT 1 FROM settings WHERE id = 1 AND activeDid IS NOT NULL AND activeDid != '' ) ` ,
"DELETE FROM settings WHERE accountDid IS NULL" ,
"UPDATE settings SET activeDid = NULL" ,
] ;
for ( const statement of statements ) {
try {
await sqlExec ( statement ) ;
} catch ( stmtError ) {
logger . warn (
` [Migration] Recovery statement failed: ${ statement } ` ,
stmtError ,
) ;
}
}
// Try to get activeDid again after recovery
try {
const activeResult = await sqlQuery (
const activeResult = await sqlQuery (
"SELECT activeDid FROM active_identity WHERE id = 1" ,
"SELECT activeDid FROM active_identity WHERE id = 1" ,
) ;
) ;
const activeDid =
activeDid =
activeResult && ( activeResult as DatabaseResult ) . values
activeResult && ( activeResult as DatabaseResult ) . values
? ( ( activeResult as DatabaseResult ) . values ? . [ 0 ] ? . [ 0 ] as string )
? ( ( activeResult as DatabaseResult ) . values ? . [ 0 ] ? . [ 0 ] as string )
: null ;
: null ;
} catch ( recoveryError ) {
logger . error (
"[Migration] Recovery failed - active_identity table still not accessible" ,
recoveryError ,
) ;
}
}
}
if ( accountsCount > 0 && ( ! activeDid || activeDid === "" ) ) {
if ( accountsCount > 0 && ( ! activeDid || activeDid === "" ) ) {
logger . debug ( "[Migration] Auto-selecting first account as active" ) ;
logger . debug ( "[Migration] Auto-selecting first account as active" ) ;