IndexedDB Local Data

Use IndexedDB for offline records and outbox queues.

Minimal Helper

type LedgerEntry = {
  id: string;
  amount: number;
  note: string;
  synced: boolean;
  createdAt: string;
};

function openDb(): Promise<IDBDatabase> {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open('ledger', 1);

    request.onupgradeneeded = () => {
      const db = request.result;
      db.createObjectStore('entries', { keyPath: 'id' });
      db.createObjectStore('outbox', { keyPath: 'id' });
    };

    request.onsuccess = () => resolve(request.result);
    request.onerror = () => reject(request.error);
  });
}

async function saveEntry(entry: LedgerEntry) {
  const db = await openDb();
  const tx = db.transaction(['entries', 'outbox'], 'readwrite');
  tx.objectStore('entries').put(entry);
  tx.objectStore('outbox').put({ id: entry.id, type: 'entry.create', payload: entry });
}

Sync-Safe Model

FieldPurpose
idClient-generated idempotency key
syncedUI can show pending state
createdAtConflict and ordering context
outbox typeReplay operation type
outbox payloadData needed for retry

Add schema migrations before users have real data. Add export before you promise offline-first safety.