Claude Code के साथ IndexedDB: local data और offline sync guide
Claude Code से IndexedDB बनाएं: schema, migration, index, transaction, quota, offline queue और tests.
IndexedDB browser के अंदर चलने वाला structured database है। जब localStorage सिर्फ छोटे string values के लिए ठीक रहता है, IndexedDB objects, indexes, Blob, async writes और transactions संभाल सकता है। Draft autosave, searchable cache, PWA offline queue, media metadata और AI tool के बड़े intermediate results के लिए यह ज्यादा practical option है।
चुनौती demo बनाने में नहीं है। असली काम schema design, version upgrade, transaction boundary, QuotaExceededError, storage eviction और sync conflict को संभालना है। अगर आप Claude Code को बस “IndexedDB add करो” बोलते हैं, तो वह happy path code बना सकता है, लेकिन migration, quota error, multi-tab upgrade और tests छूट सकते हैं।
इस guide में TypeScript और lightweight idb wrapper का इस्तेमाल है। Official references के लिए MDN Using IndexedDB, MDN storage quotas and eviction criteria, web.dev Storage for the web, idb README और Dexie.js docs देखें। Claude Code prompts को safer बनाने के लिए Claude Code productivity tips और testing strategies भी useful हैं।
IndexedDB कब localStorage से बेहतर है
localStorage theme, language, last open tab या छोटी preference के लिए अच्छा है। यह simple है, लेकिन synchronous है, main thread block कर सकता है, indexes नहीं देता और बड़े JSON array को हर change पर पूरा rewrite करने लगता है।
IndexedDB को browser-side छोटा database समझें। object store records रखता है। key record की identity है। index किसी दूसरे field से search करने का रास्ता है। transaction कई writes को एक unit बनाता है ताकि आधा save न हो। यह server database को replace नहीं करता, लेकिन offline और weak network में user experience बचाता है।
| काम | localStorage | IndexedDB |
|---|---|---|
| Theme, UI mode, छोटी setting | अच्छा | अक्सर overkill |
| लंबा draft, form recovery | fragile और blocking | अच्छा |
| Searchable article/product cache | बड़ा JSON blob बनता है | index से query |
| Blob, attachment metadata | कमजोर | practical |
| Offline action queue | order/retry मुश्किल | transaction के साथ अच्छा |
तीन से ज्यादा real use cases हैं। पहला, CMS या blog editor draft को updatedAt और syncStatus के साथ save करता है। दूसरा, SaaS dashboard products, plans या help articles cache करता है और category या freshness से पढ़ता है। तीसरा, PWA comments, contact forms या admin changes को offline queue में डालता है और online होने पर भेजता है। चौथा, Claude Code analysis जैसे बड़े results को expiry के साथ local रखता है ताकि हर navigation पर वही API call न करनी पड़े।
Schema, version और index पहले तय करें
IndexedDB में पहला सवाल “क्या store करना है?” नहीं, बल्कि “बाद में कैसे पढ़ना है?” होना चाहिए। सिर्फ id से पढ़ना आसान है। लेकिन real app में “dirty drafts”, “old cache entries”, “createdAt order में jobs” या “कम attempts वाले jobs” चाहिए होते हैं। इनके लिए indexes चाहिए। बाद में index जोड़ना हो तो DB version बढ़ेगा और upgrade migration लिखनी होगी।
Common failure यह है कि पहले सिर्फ notes store बना, फिर syncStatus index न होने से app सारे notes पढ़कर JavaScript में filter करने लगा। 50 records पर ठीक दिखता है, हजारों records पर startup slow हो जाता है। दूसरा failure decimal version जैसे 1.1 का use है। IndexedDB version को integer steps में रखें: 1, 2, 3.
npm i idb
यह module src/lib/local-db.ts में रखा जा सकता है। idb IndexedDB model को visible रखता है। अगर richer query helpers, live queries या higher-level data layer चाहिए, तो Dexie भी option है; इस guide में beginner path छोटा रखने के लिए idb रखा गया है।
import {
openDB,
type DBSchema,
type IDBPDatabase,
} from "idb";
type SyncStatus = "clean" | "dirty" | "syncing" | "failed";
export interface DraftNote {
id: string;
title: string;
body: string;
updatedAt: number;
baseVersion: number;
syncStatus: SyncStatus;
}
export interface SyncJob {
id: string;
noteId: string;
operation: "upsert-note" | "delete-note";
payload: unknown;
createdAt: number;
attempts: number;
lastError?: string;
}
interface AppDB extends DBSchema {
notes: {
key: string;
value: DraftNote;
indexes: {
"by-updated-at": number;
"by-sync-status": SyncStatus;
};
};
syncQueue: {
key: string;
value: SyncJob;
indexes: {
"by-created-at": number;
"by-attempts": number;
};
};
}
const DB_NAME = "claude-code-indexeddb-demo";
const DB_VERSION = 2;
let dbPromise: Promise<IDBPDatabase<AppDB>> | undefined;
export function getDb() {
dbPromise ??= openDB<AppDB>(DB_NAME, DB_VERSION, {
upgrade(db, oldVersion, _newVersion, tx) {
if (oldVersion < 1) {
const notes = db.createObjectStore("notes", {
keyPath: "id",
});
notes.createIndex("by-updated-at", "updatedAt");
const queue = db.createObjectStore("syncQueue", {
keyPath: "id",
});
queue.createIndex("by-created-at", "createdAt");
queue.createIndex("by-attempts", "attempts");
}
if (oldVersion < 2) {
const notes = tx.objectStore("notes");
if (!notes.indexNames.contains("by-sync-status")) {
notes.createIndex("by-sync-status", "syncStatus");
}
}
},
blocked() {
console.warn("Close other tabs to finish the DB upgrade.");
},
blocking() {
dbPromise?.then((db) => db.close()).catch(() => {});
},
});
return dbPromise;
}
Schema changes हमेशा upgrade में रखें। Normal read/write function में store या index create न करें। अगर कोई दूसरा tab old DB version open रखता है, new version block हो सकता है। blocked में user को tabs बंद करने की बात बताएं, और blocking current connection close करे।
Draft और sync queue को एक transaction में save करें
Draft save और sync job insert एक ही business action है। अगर note save हो गया पर queue insert fail हो गया, UI “saved” दिखाएगा लेकिन server को change नहीं मिलेगा। दोनों writes को same readwrite transaction में रखें।
const createId = () =>
globalThis.crypto?.randomUUID?.() ??
Math.random().toString(36).slice(2);
export async function saveDraft(input: {
id?: string;
title: string;
body: string;
baseVersion?: number;
}) {
const db = await getDb();
const now = Date.now();
const noteId = input.id ?? createId();
const note: DraftNote = {
id: noteId,
title: input.title,
body: input.body,
updatedAt: now,
baseVersion: input.baseVersion ?? 0,
syncStatus: "dirty",
};
const job: SyncJob = {
id: createId(),
noteId,
operation: "upsert-note",
payload: note,
createdAt: now,
attempts: 0,
};
const tx = db.transaction(["notes", "syncQueue"], "readwrite");
await tx.objectStore("notes").put(note);
await tx.objectStore("syncQueue").put(job);
await tx.done;
return note;
}
export async function getDirtyNotes() {
const db = await getDb();
return db.getAllFromIndex("notes", "by-sync-status", "dirty");
}
Active transaction के बीच await fetch() न करें। जब browser को pending DB work नहीं दिखता, transaction close हो सकती है। पहले DB write पूरा करें, फिर network call करें, और result को new transaction में save करें।
Quota, eviction और readable errors
IndexedDB unlimited storage नहीं है। Browser origin quota, device free space, private mode और storage pressure के आधार पर write reject कर सकता है या best effort data हटा सकता है। इसलिए errors catch करें, old cache cleanup रखें और important data server पर sync करें।
दो concrete failures common हैं। पहला, QuotaExceededError सिर्फ console में आता है और UI “saved” दिखाता रहता है। दूसरा, payment, contract, inventory या support data सिर्फ IndexedDB में रखा जाता है। IndexedDB offline workspace है; critical record का durable source server होना चाहिए।
export async function estimateStorage() {
if (!navigator.storage?.estimate) {
return { usage: undefined, quota: undefined };
}
const { usage, quota } = await navigator.storage.estimate();
return { usage, quota };
}
export async function requestPersistentStorage() {
if (!navigator.storage?.persist) {
return false;
}
return navigator.storage.persist();
}
export function isQuotaError(error: unknown) {
return (
error instanceof DOMException &&
error.name === "QuotaExceededError"
);
}
export async function saveDraftWithErrorMessage(
input: Parameters<typeof saveDraft>[0],
) {
try {
return await saveDraft(input);
} catch (error) {
if (isQuotaError(error)) {
throw new Error(
"Browser storage full है। पुराने drafts delete करके फिर try करें।",
);
}
throw error;
}
}
persist() guarantee नहीं है। Claude Code से UI message, cache cleanup और tests भी बनवाएं। सिर्फ silent try/catch काफी नहीं है।
Offline queue और sync conflict
Offline queue में order, retry, duplicate handling और conflict rule चाहिए। अगर एक note offline laptop पर edit हुआ और वही note दूसरी device से online update हुआ, तो पुराना offline job newer server data overwrite कर सकता है।
type SendJob = (job: SyncJob) => Promise<void>;
export async function flushSyncQueue(sendJob: SendJob) {
if (!navigator.onLine) return;
const db = await getDb();
const jobs = await db.getAllFromIndex(
"syncQueue",
"by-created-at",
);
for (const job of jobs) {
try {
await sendJob(job);
await db.delete("syncQueue", job.id);
} catch (error) {
const message =
error instanceof Error ? error.message : "Unknown sync error";
await db.put("syncQueue", {
...job,
attempts: job.attempts + 1,
lastError: message,
});
}
}
}
window.addEventListener("online", () => {
void flushSyncQueue(async (job) => {
const response = await fetch("/api/sync", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(job),
});
if (!response.ok) {
throw new Error(`Sync failed: ${response.status}`);
}
});
});
Personal note में local और remote version दिखाकर user से चुनवाना ठीक हो सकता है। Inventory, booking, invoice या payment event में server को version, ETag या idempotency key से stale write reject करना चाहिए।
export function detectConflict(input: {
local: DraftNote;
remoteVersion: number;
}) {
const { local, remoteVersion } = input;
return (
local.syncStatus === "dirty" &&
remoteVersion > local.baseVersion
);
}
Testing और mock की सीमा
fake-indexeddb Vitest में Node environment के अंदर IndexedDB जैसी API देता है। इससे save function, index lookup, queue insert और migration test कर सकते हैं। लेकिन quota, eviction, private mode, multi-tab blocking और mobile storage pressure real browser में ही verify होंगे।
npm i -D vitest fake-indexeddb
import "fake-indexeddb/auto";
import { beforeEach, expect, test } from "vitest";
import { deleteDB } from "idb";
import {
getDirtyNotes,
saveDraft,
} from "./local-db";
beforeEach(async () => {
await deleteDB("claude-code-indexeddb-demo");
});
test("saves a draft and finds it by sync status", async () => {
await saveDraft({
title: "IndexedDB note",
body: "Saved while offline",
});
const dirtyNotes = await getDirtyNotes();
expect(dirtyNotes).toHaveLength(1);
expect(dirtyNotes[0]?.syncStatus).toBe("dirty");
});
Release से पहले check करें: first load DB बनाता है, version 2 syncStatus index जोड़ता है, दूसरे tab पर upgrade warning आती है, offline draft reload के बाद बचता है, online होते ही queue send होती है और storage error readable है। Monetized page में CTA, product links, consultation links और analytics events भी टूटने नहीं चाहिए।
Claude Code के लिए safe prompt
Prompt में local data contract लिखें। “Offline बना दो” IndexedDB के लिए बहुत vague है।
claude <<'PROMPT'
Scope:
- Edit only src/lib/local-db.ts and src/lib/local-db.test.ts.
- Do not change API routes, pricing copy, analytics, or CTA links.
Build:
- Use IndexedDB through the idb package.
- Database name: claude-code-indexeddb-demo.
- Version 2 must create notes and syncQueue stores.
- Add indexes for updatedAt, syncStatus, createdAt, and attempts.
Reliability:
- Use readwrite transactions for note + queue writes.
- Do not await fetch inside an active IndexedDB transaction.
- Handle QuotaExceededError with a user-facing message.
- Explain blocked upgrades from another open tab.
Testing:
- Use vitest and fake-indexeddb.
- Test draft save, dirty-note lookup, and queue insertion.
- Return findings, patch summary, commands, and residual risk.
PROMPT
इस prompt से Claude Code browser storage snippet नहीं, बल्कि reviewable local data layer बनाता है। अगर site monetized है, तो offline draft, cache और queue CTA, purchase links, consultation form या analytics को नहीं तोड़ने चाहिए। Team rollout के लिए Claude Code training और consultation देखें। Solo builders free cheatsheet और Claude Code templates से शुरू कर सकते हैं।
Hands-on verification note
Masa ने यह pattern एक छोटे note editor में test किया। सबसे बड़ा improvement यह था कि syncStatus और updatedAt indexes शुरुआत में तय कर दिए गए। पहली version सभी notes पढ़कर JavaScript में filter करती थी; test data बढ़ने पर startup heavy लगा। idb पर जाने और note + queue को एक transaction में रखने के बाद failure state review करना आसान हुआ। fake-indexeddb ने save और lookup test cover किए, लेकिन quota और tab blocking prove नहीं किया। Final check Chrome DevTools में storage clear करके, network Offline करके, save, reload, Online recovery और queue request confirm करके किया गया।
मुफ़्त PDF: Claude Code cheatsheet
Email डालें और commands, review habits तथा safe workflow वाली एक-page PDF पाएँ.
हम आपका data सुरक्षित रखते हैं और spam नहीं भेजते.
लेखक के बारे में
Masa
Claude Code workflow और team adoption पर काम करने वाला engineer.
संबंधित लेख
Claude Code permission safety ladder: access धीरे-धीरे बढ़ाएं
read-only से limited edits, proof commands और deploy checks तक permission बढ़ाने की सुरक्षित ladder.
Claude Code Small PR Proof Pack: छोटे PR को review-ready बनाना
Claude Code PR के लिए diff, checks, public URL, CTA path और rollback वाला practical proof pack.
Claude Code Review Gate Before Commit: diff, test, public URL और CTA जांच
Claude Code से commit से पहले review gate बनाएं: diff, build, public URL, Gumroad, consultation, tests और unrelated files।