Description
destroyJournalDatabase() in mobile/src/lib/db/database.ts is the mechanism behind the "Delete all my data" control that #116 tracks and ADR 0007 requires. It can report success while leaving the encrypted journal file on disk.
// database.ts:101-105
export async function destroyJournalDatabase(): Promise<void> {
await closeJournalDatabase();
await SQLite.deleteDatabaseAsync(DATABASE_NAME).catch(() => undefined);
await deleteDatabaseKey();
}
1. The blanket catch hides a real failure. expo-sqlite's iOS implementation throws two different errors from that call (node_modules/expo-sqlite/ios/SQLiteModule.swift, deleteDatabase(databasePath:)):
DatabaseNotFoundException — the file is already gone. Benign, and what the test at database.test.ts:159 intends to cover.
DeleteDatabaseException — thrown when the database is still in the module's connection cache, i.e. it was not closed. Not benign.
Both are swallowed identically, so a genuinely failed deletion resolves as success and the UI tells the user their data is gone when it is not.
2. A failure to close skips key deletion entirely. closeJournalDatabase() awaits db?.closeAsync() unguarded (database.ts:89). If that rejects, destroyJournalDatabase rejects before either deletion runs, leaving the file and the key — the one outcome where the user keeps a fully readable journal after asking for it to be destroyed.
3. The stated ordering rationale does not hold. The comment at database.ts:96-99, repeated at database.test.ts:153, says file-before-key means an interruption "leaves an unreadable database rather than a readable one with no key". A database with no key is unreadable by definition, so that sentence describes a state which cannot exist. Deleting the key first is the stronger order: it guarantees unreadability at the earliest possible moment, and it survives the case where file deletion then fails.
Also worth handling while in here: deleteDatabaseAsync removes only the main file, not the -wal / -shm sidecars. A clean close checkpoints and removes those, so it only matters on the path where the close failed — which is exactly the path in (2).
Nothing calls destroyJournalDatabase yet, so this is latent. It is probably best done as part of #116, so the error handling is designed alongside the UI that has to report it.
Acceptance Criteria
Additional Info and Resources
QA
Description
destroyJournalDatabase()inmobile/src/lib/db/database.tsis the mechanism behind the "Delete all my data" control that #116 tracks and ADR 0007 requires. It can report success while leaving the encrypted journal file on disk.1. The blanket catch hides a real failure. expo-sqlite's iOS implementation throws two different errors from that call (
node_modules/expo-sqlite/ios/SQLiteModule.swift,deleteDatabase(databasePath:)):DatabaseNotFoundException— the file is already gone. Benign, and what the test atdatabase.test.ts:159intends to cover.DeleteDatabaseException— thrown when the database is still in the module's connection cache, i.e. it was not closed. Not benign.Both are swallowed identically, so a genuinely failed deletion resolves as success and the UI tells the user their data is gone when it is not.
2. A failure to close skips key deletion entirely.
closeJournalDatabase()awaitsdb?.closeAsync()unguarded (database.ts:89). If that rejects,destroyJournalDatabaserejects before either deletion runs, leaving the file and the key — the one outcome where the user keeps a fully readable journal after asking for it to be destroyed.3. The stated ordering rationale does not hold. The comment at
database.ts:96-99, repeated atdatabase.test.ts:153, says file-before-key means an interruption "leaves an unreadable database rather than a readable one with no key". A database with no key is unreadable by definition, so that sentence describes a state which cannot exist. Deleting the key first is the stronger order: it guarantees unreadability at the earliest possible moment, and it survives the case where file deletion then fails.Also worth handling while in here:
deleteDatabaseAsyncremoves only the main file, not the-wal/-shmsidecars. A clean close checkpoints and removes those, so it only matters on the path where the close failed — which is exactly the path in (2).Nothing calls
destroyJournalDatabaseyet, so this is latent. It is probably best done as part of #116, so the error handling is designed alongside the UI that has to report it.Acceptance Criteria
database.tsand the assertion indatabase.test.tsagree with whichever order is chosenAdditional Info and Resources
QA
deleteDatabaseAsyncmocked to reject, confirm the caller can distinguish failure from successcloseAsyncmocked to reject, confirm the key is still deletedcd mobile && npm test— green