Web IndexedDB With Dexie
This shape gives a PWA a versioned local database, an offline mutation queue, and export/import hooks.
Database
import Dexie, { type Table } from "dexie";
export type Todo = {
id: string;
title: string;
completed: boolean;
updatedAt: string;
};
export type PendingMutation = {
id: string;
operation: "create" | "update" | "delete";
entityId: string;
payload: unknown;
idempotencyKey: string;
attemptCount: number;
createdAt: string;
};
class AppDb extends Dexie {
todos!: Table<Todo, string>;
pendingMutations!: Table<PendingMutation, string>;
constructor() {
super("offline-demo");
this.version(1).stores({
todos: "id, updatedAt, completed",
pendingMutations: "id, entityId, idempotencyKey, createdAt",
});
}
}
export const db = new AppDb();
Local Write
export async function createTodo(title: string) {
const id = crypto.randomUUID();
const now = new Date().toISOString();
await db.transaction("rw", db.todos, db.pendingMutations, async () => {
await db.todos.add({ id, title, completed: false, updatedAt: now });
await db.pendingMutations.add({
id: crypto.randomUUID(),
operation: "create",
entityId: id,
payload: { id, title },
idempotencyKey: `todo:create:${id}`,
attemptCount: 0,
createdAt: now,
});
});
}
Export
Use a tested export helper for real apps. For small demos, export JSON from tables:
export async function exportJson() {
return {
formatVersion: 1,
exportedAt: new Date().toISOString(),
todos: await db.todos.toArray(),
};
}
Browser Gate
- test quota exceeded handling
- test blocked version upgrades with two tabs open
- request persistent storage for critical data
- run large exports in a worker
- verify import on a fresh browser profile