Skip to content

Commit 071615d

Browse files
Merge pull request #12 from Atharva0506/feat/core-importer
feat: implement core importer with overwrite and merge strategies
2 parents 8ec6103 + c72513d commit 071615d

5 files changed

Lines changed: 736 additions & 55 deletions

File tree

src/core/exporter.ts

Lines changed: 152 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,152 @@
1-
// TODO: Implement exportDB() — dump IndexedDB stores to generic JSON format
2-
//
3-
// Responsibilities:
4-
// - Accept a database name (or IDBDatabase reference) and optional store filter
5-
// - Extract database schema (object store names, key paths, autoIncrement, indexes)
6-
// - Read all records from each store
7-
// - Serialize records using type-tagged serialization (bigint, Date, Uint8Array)
8-
// - Return the generic ExportFormat JSON object
1+
import type { ExportFormat, ExportOptions, IndexSchema, StoreSchema } from '../types/index.js';
2+
import { serialize, BACKUP_VERSION } from '../serialization/index.js';
3+
4+
/**
5+
* Extract the schema for a single object store, including all its indexes.
6+
*
7+
* @param store - The IDBObjectStore to extract the schema from.
8+
* @returns The store schema definition.
9+
*/
10+
function extractStoreSchema(store: IDBObjectStore): StoreSchema {
11+
const indexes: IndexSchema[] = [];
12+
13+
const indexNames = Array.from(store.indexNames);
14+
15+
for (const indexName of indexNames) {
16+
const index = store.index(indexName);
17+
indexes.push({
18+
name: index.name,
19+
keyPath: index.keyPath,
20+
unique: index.unique,
21+
multiEntry: index.multiEntry,
22+
});
23+
}
24+
25+
return {
26+
keyPath: store.keyPath,
27+
autoIncrement: store.autoIncrement,
28+
indexes,
29+
};
30+
}
31+
32+
/**
33+
* Read all records from an object store using a cursor.
34+
*
35+
* Each record is serialized using type-tagged serialization to ensure
36+
* non-JSON-safe types (Uint8Array, bigint, Date) are preserved.
37+
*
38+
* @param store - The IDBObjectStore to read records from.
39+
* @returns A promise that resolves to an array of serialized records.
40+
*/
41+
function readAllRecords(store: IDBObjectStore): Promise<Array<{ key: unknown; value: unknown }>> {
42+
return new Promise((resolve, reject) => {
43+
const records: Array<{ key: unknown; value: unknown }> = [];
44+
const request = store.openCursor();
45+
46+
request.onsuccess = () => {
47+
const cursor = request.result;
48+
if (cursor) {
49+
records.push({
50+
key: serialize(cursor.primaryKey),
51+
value: serialize(cursor.value),
52+
});
53+
cursor.continue();
54+
} else {
55+
resolve(records);
56+
}
57+
};
58+
59+
request.onerror = () => {
60+
reject(new Error(`Failed to read records from store "${store.name}": ${String(request.error)}`));
61+
};
62+
});
63+
}
64+
65+
/**
66+
* Open an IndexedDB database by name.
67+
*
68+
* @param dbName - The name of the database to open.
69+
* @returns A promise that resolves to the opened IDBDatabase instance.
70+
*/
71+
function openDatabase(dbName: string): Promise<IDBDatabase> {
72+
return new Promise((resolve, reject) => {
73+
const request = indexedDB.open(dbName);
74+
75+
request.onsuccess = () => {
76+
resolve(request.result);
77+
};
78+
79+
request.onerror = () => {
80+
reject(new Error(`Failed to open database "${dbName}": ${String(request.error)}`));
81+
};
82+
});
83+
}
84+
85+
/**
86+
* Export an IndexedDB database to the generic JSON backup format.
87+
*
88+
* Opens the specified database, extracts the schema for each object store,
89+
* reads all records (with type-tagged serialization), and returns the
90+
* complete ExportFormat envelope.
91+
*
92+
* @param options - Export configuration.
93+
* @param options.dbName - The name of the IndexedDB database to export.
94+
* @param options.storeNames - Optional list of store names to export. If omitted, all stores are exported.
95+
* @returns A promise that resolves to the ExportFormat JSON object.
96+
*
97+
* @example
98+
* ```typescript
99+
* const backup = await exportDB({ dbName: 'my-app-db' });
100+
* console.log(JSON.stringify(backup, null, 2));
101+
* ```
102+
*/
103+
export async function exportDB(options: ExportOptions): Promise<ExportFormat> {
104+
const { dbName, storeNames } = options;
105+
106+
const db = await openDatabase(dbName);
107+
108+
try {
109+
// Determine which stores to export
110+
const allStoreNames = Array.from(db.objectStoreNames);
111+
const targetStores = storeNames
112+
? [...new Set(storeNames.filter((name) => allStoreNames.includes(name)))]
113+
: allStoreNames;
114+
115+
if (targetStores.length === 0) {
116+
// Return an empty export if no stores match
117+
return {
118+
backupVersion: BACKUP_VERSION,
119+
databaseName: db.name,
120+
databaseVersion: db.version,
121+
exportedAt: new Date().toISOString(),
122+
schema: {},
123+
stores: {},
124+
};
125+
}
126+
127+
// Open a single read-only transaction across all target stores
128+
const transaction = db.transaction(targetStores, 'readonly');
129+
const schema: Record<string, StoreSchema> = {};
130+
const stores: Record<string, Array<{ key: unknown; value: unknown }>> = {};
131+
132+
// Process each store: extract schema and read records
133+
const storePromises = targetStores.map(async (storeName) => {
134+
const store = transaction.objectStore(storeName);
135+
schema[storeName] = extractStoreSchema(store);
136+
stores[storeName] = await readAllRecords(store);
137+
});
138+
139+
await Promise.all(storePromises);
140+
141+
return {
142+
backupVersion: BACKUP_VERSION,
143+
databaseName: db.name,
144+
databaseVersion: db.version,
145+
exportedAt: new Date().toISOString(),
146+
schema,
147+
stores,
148+
};
149+
} finally {
150+
db.close();
151+
}
152+
}

0 commit comments

Comments
 (0)