# Dexie to SQLite Mapping Guide ## Schema Mapping ### Current Dexie Schema ```typescript // Current Dexie schema const db = new Dexie('TimeSafariDB'); db.version(1).stores({ accounts: 'did, publicKeyHex, createdAt, updatedAt', settings: 'key, value, updatedAt', contacts: 'id, did, name, createdAt, updatedAt' }); ``` ### New SQLite Schema ```sql -- New SQLite schema CREATE TABLE accounts ( did TEXT PRIMARY KEY, public_key_hex TEXT NOT NULL, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL ); CREATE TABLE settings ( key TEXT PRIMARY KEY, value TEXT NOT NULL, updated_at INTEGER NOT NULL ); CREATE TABLE contacts ( id TEXT PRIMARY KEY, did TEXT NOT NULL, name TEXT, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL, FOREIGN KEY (did) REFERENCES accounts(did) ); -- Indexes for performance CREATE INDEX idx_accounts_created_at ON accounts(created_at); CREATE INDEX idx_contacts_did ON contacts(did); CREATE INDEX idx_settings_updated_at ON settings(updated_at); ``` ## Query Mapping ### 1. Account Operations #### Get Account by DID ```typescript // Dexie const account = await db.accounts.get(did); // SQLite const account = await db.selectOne(` SELECT * FROM accounts WHERE did = ? `, [did]); ``` #### Get All Accounts ```typescript // Dexie const accounts = await db.accounts.toArray(); // SQLite const accounts = await db.selectAll(` SELECT * FROM accounts ORDER BY created_at DESC `); ``` #### Add Account ```typescript // Dexie await db.accounts.add({ did, publicKeyHex, createdAt: Date.now(), updatedAt: Date.now() }); // SQLite await db.execute(` INSERT INTO accounts (did, public_key_hex, created_at, updated_at) VALUES (?, ?, ?, ?) `, [did, publicKeyHex, Date.now(), Date.now()]); ``` #### Update Account ```typescript // Dexie await db.accounts.update(did, { publicKeyHex, updatedAt: Date.now() }); // SQLite await db.execute(` UPDATE accounts SET public_key_hex = ?, updated_at = ? WHERE did = ? `, [publicKeyHex, Date.now(), did]); ``` ### 2. Settings Operations #### Get Setting ```typescript // Dexie const setting = await db.settings.get(key); // SQLite const setting = await db.selectOne(` SELECT * FROM settings WHERE key = ? `, [key]); ``` #### Set Setting ```typescript // Dexie await db.settings.put({ key, value, updatedAt: Date.now() }); // SQLite await db.execute(` INSERT INTO settings (key, value, updated_at) VALUES (?, ?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at `, [key, value, Date.now()]); ``` ### 3. Contact Operations #### Get Contacts by Account ```typescript // Dexie const contacts = await db.contacts .where('did') .equals(accountDid) .toArray(); // SQLite const contacts = await db.selectAll(` SELECT * FROM contacts WHERE did = ? ORDER BY created_at DESC `, [accountDid]); ``` #### Add Contact ```typescript // Dexie await db.contacts.add({ id: generateId(), did: accountDid, name, createdAt: Date.now(), updatedAt: Date.now() }); // SQLite await db.execute(` INSERT INTO contacts (id, did, name, created_at, updated_at) VALUES (?, ?, ?, ?, ?) `, [generateId(), accountDid, name, Date.now(), Date.now()]); ``` ## Transaction Mapping ### Batch Operations ```typescript // Dexie await db.transaction('rw', [db.accounts, db.contacts], async () => { await db.accounts.add(account); await db.contacts.bulkAdd(contacts); }); // SQLite await db.transaction(async (tx) => { await tx.execute(` INSERT INTO accounts (did, public_key_hex, created_at, updated_at) VALUES (?, ?, ?, ?) `, [account.did, account.publicKeyHex, account.createdAt, account.updatedAt]); for (const contact of contacts) { await tx.execute(` INSERT INTO contacts (id, did, name, created_at, updated_at) VALUES (?, ?, ?, ?, ?) `, [contact.id, contact.did, contact.name, contact.createdAt, contact.updatedAt]); } }); ``` ## Migration Helper Functions ### 1. Data Export (Dexie to JSON) ```typescript async function exportDexieData(): Promise { const db = new Dexie('TimeSafariDB'); return { accounts: await db.accounts.toArray(), settings: await db.settings.toArray(), contacts: await db.contacts.toArray(), metadata: { version: '1.0.0', timestamp: Date.now(), dexieVersion: Dexie.version } }; } ``` ### 2. Data Import (JSON to SQLite) ```typescript async function importToSQLite(data: MigrationData): Promise { const db = await getSQLiteConnection(); await db.transaction(async (tx) => { // Import accounts for (const account of data.accounts) { await tx.execute(` INSERT INTO accounts (did, public_key_hex, created_at, updated_at) VALUES (?, ?, ?, ?) `, [account.did, account.publicKeyHex, account.createdAt, account.updatedAt]); } // Import settings for (const setting of data.settings) { await tx.execute(` INSERT INTO settings (key, value, updated_at) VALUES (?, ?, ?) `, [setting.key, setting.value, setting.updatedAt]); } // Import contacts for (const contact of data.contacts) { await tx.execute(` INSERT INTO contacts (id, did, name, created_at, updated_at) VALUES (?, ?, ?, ?, ?) `, [contact.id, contact.did, contact.name, contact.createdAt, contact.updatedAt]); } }); } ``` ### 3. Verification ```typescript async function verifyMigration(dexieData: MigrationData): Promise { const db = await getSQLiteConnection(); // Verify account count const accountCount = await db.selectValue( 'SELECT COUNT(*) FROM accounts' ); if (accountCount !== dexieData.accounts.length) { return false; } // Verify settings count const settingsCount = await db.selectValue( 'SELECT COUNT(*) FROM settings' ); if (settingsCount !== dexieData.settings.length) { return false; } // Verify contacts count const contactsCount = await db.selectValue( 'SELECT COUNT(*) FROM contacts' ); if (contactsCount !== dexieData.contacts.length) { return false; } // Verify data integrity for (const account of dexieData.accounts) { const migratedAccount = await db.selectOne( 'SELECT * FROM accounts WHERE did = ?', [account.did] ); if (!migratedAccount || migratedAccount.public_key_hex !== account.publicKeyHex) { return false; } } return true; } ``` ## Performance Considerations ### 1. Indexing - Dexie automatically creates indexes based on the schema - SQLite requires explicit index creation - Added indexes for frequently queried fields ### 2. Batch Operations - Dexie has built-in bulk operations - SQLite uses transactions for batch operations - Consider chunking large datasets ### 3. Query Optimization - Dexie uses IndexedDB's native indexing - SQLite requires explicit query optimization - Use prepared statements for repeated queries ## Error Handling ### 1. Common Errors ```typescript // Dexie errors try { await db.accounts.add(account); } catch (error) { if (error instanceof Dexie.ConstraintError) { // Handle duplicate key } } // SQLite errors try { await db.execute(` INSERT INTO accounts (did, public_key_hex, created_at, updated_at) VALUES (?, ?, ?, ?) `, [account.did, account.publicKeyHex, account.createdAt, account.updatedAt]); } catch (error) { if (error.code === 'SQLITE_CONSTRAINT') { // Handle duplicate key } } ``` ### 2. Transaction Recovery ```typescript // Dexie transaction try { await db.transaction('rw', db.accounts, async () => { // Operations }); } catch (error) { // Dexie automatically rolls back } // SQLite transaction const db = await getSQLiteConnection(); try { await db.transaction(async (tx) => { // Operations }); } catch (error) { // SQLite automatically rolls back await db.execute('ROLLBACK'); } ``` ## Migration Strategy 1. **Preparation** - Export all Dexie data - Verify data integrity - Create SQLite schema - Setup indexes 2. **Migration** - Import data in transactions - Verify each batch - Handle errors gracefully - Maintain backup 3. **Verification** - Compare record counts - Verify data integrity - Test common queries - Validate relationships 4. **Cleanup** - Remove Dexie database - Clear IndexedDB storage - Update application code - Remove old dependencies