# Dexie to absurd-sql 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); // absurd-sql const result = await db.exec(` SELECT * FROM accounts WHERE did = ? `, [did]); const account = result[0]?.values[0]; ``` #### Get All Accounts ```typescript // Dexie const accounts = await db.accounts.toArray(); // absurd-sql const result = await db.exec(` SELECT * FROM accounts ORDER BY created_at DESC `); const accounts = result[0]?.values || []; ``` #### Add Account ```typescript // Dexie await db.accounts.add({ did, publicKeyHex, createdAt: Date.now(), updatedAt: Date.now() }); // absurd-sql await db.run(` 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() }); // absurd-sql await db.run(` 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); // absurd-sql const result = await db.exec(` SELECT * FROM settings WHERE key = ? `, [key]); const setting = result[0]?.values[0]; ``` #### Set Setting ```typescript // Dexie await db.settings.put({ key, value, updatedAt: Date.now() }); // absurd-sql await db.run(` 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(); // absurd-sql const result = await db.exec(` SELECT * FROM contacts WHERE did = ? ORDER BY created_at DESC `, [accountDid]); const contacts = result[0]?.values || []; ``` #### Add Contact ```typescript // Dexie await db.contacts.add({ id: generateId(), did: accountDid, name, createdAt: Date.now(), updatedAt: Date.now() }); // absurd-sql await db.run(` 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); }); // absurd-sql await db.exec('BEGIN TRANSACTION;'); try { await db.run(` 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 db.run(` INSERT INTO contacts (id, did, name, created_at, updated_at) VALUES (?, ?, ?, ?, ?) `, [contact.id, contact.did, contact.name, contact.createdAt, contact.updatedAt]); } await db.exec('COMMIT;'); } catch (error) { await db.exec('ROLLBACK;'); throw error; } ``` ## 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 absurd-sql) ```typescript async function importToAbsurdSql(data: MigrationData): Promise { await db.exec('BEGIN TRANSACTION;'); try { // Import accounts for (const account of data.accounts) { await db.run(` 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 db.run(` INSERT INTO settings (key, value, updated_at) VALUES (?, ?, ?) `, [setting.key, setting.value, setting.updatedAt]); } // Import contacts for (const contact of data.contacts) { await db.run(` INSERT INTO contacts (id, did, name, created_at, updated_at) VALUES (?, ?, ?, ?, ?) `, [contact.id, contact.did, contact.name, contact.createdAt, contact.updatedAt]); } await db.exec('COMMIT;'); } catch (error) { await db.exec('ROLLBACK;'); throw error; } } ``` ### 3. Verification ```typescript async function verifyMigration(dexieData: MigrationData): Promise { // Verify account count const accountResult = await db.exec('SELECT COUNT(*) as count FROM accounts'); const accountCount = accountResult[0].values[0][0]; if (accountCount !== dexieData.accounts.length) { return false; } // Verify settings count const settingsResult = await db.exec('SELECT COUNT(*) as count FROM settings'); const settingsCount = settingsResult[0].values[0][0]; if (settingsCount !== dexieData.settings.length) { return false; } // Verify contacts count const contactsResult = await db.exec('SELECT COUNT(*) as count FROM contacts'); const contactsCount = contactsResult[0].values[0][0]; if (contactsCount !== dexieData.contacts.length) { return false; } // Verify data integrity for (const account of dexieData.accounts) { const result = await db.exec( 'SELECT * FROM accounts WHERE did = ?', [account.did] ); const migratedAccount = result[0]?.values[0]; if (!migratedAccount || migratedAccount[1] !== account.publicKeyHex) { // public_key_hex is second column return false; } } return true; } ``` ## Performance Considerations ### 1. Indexing - Dexie automatically creates indexes based on the schema - absurd-sql requires explicit index creation - Added indexes for frequently queried fields - Use `PRAGMA journal_mode=MEMORY;` for better performance ### 2. Batch Operations - Dexie has built-in bulk operations - absurd-sql uses transactions for batch operations - Consider chunking large datasets - Use prepared statements for repeated queries ### 3. Query Optimization - Dexie uses IndexedDB's native indexing - absurd-sql requires explicit query optimization - Use prepared statements for repeated queries - Consider using `PRAGMA synchronous=NORMAL;` for better performance ## Error Handling ### 1. Common Errors ```typescript // Dexie errors try { await db.accounts.add(account); } catch (error) { if (error instanceof Dexie.ConstraintError) { // Handle duplicate key } } // absurd-sql errors try { await db.run(` INSERT INTO accounts (did, public_key_hex, created_at, updated_at) VALUES (?, ?, ?, ?) `, [account.did, account.publicKeyHex, account.createdAt, account.updatedAt]); } catch (error) { if (error.message.includes('UNIQUE constraint failed')) { // Handle duplicate key } } ``` ### 2. Transaction Recovery ```typescript // Dexie transaction try { await db.transaction('rw', db.accounts, async () => { // Operations }); } catch (error) { // Dexie automatically rolls back } // absurd-sql transaction try { await db.exec('BEGIN TRANSACTION;'); // Operations await db.exec('COMMIT;'); } catch (error) { await db.exec('ROLLBACK;'); throw error; } ``` ## 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