@firtoz/drizzle-indexeddb 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @firtoz/drizzle-indexeddb
2
2
 
3
+ ## 0.4.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [`58afa0a`](https://github.com/firtoz/fullstack-toolkit/commit/58afa0a5365f55f536e50194a73f847293102e7f) Thanks [@firtoz](https://github.com/firtoz)! - Hopefully this should work
8
+
9
+ ## 0.4.1
10
+
11
+ ### Patch Changes
12
+
13
+ - [`904019f`](https://github.com/firtoz/fullstack-toolkit/commit/904019f4d04bc02521206fbe0feaeecb67e38f87) Thanks [@firtoz](https://github.com/firtoz)! - Fix tsx exporting
14
+
3
15
  ## 0.4.0
4
16
 
5
17
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firtoz/drizzle-indexeddb",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "IndexedDB migrations powered by Drizzle ORM",
5
5
  "main": "./src/index.ts",
6
6
  "module": "./src/index.ts",
@@ -24,10 +24,16 @@
24
24
  "types": "./src/*.ts",
25
25
  "import": "./src/*.ts",
26
26
  "require": "./src/*.ts"
27
+ },
28
+ "./*.tsx": {
29
+ "types": "./src/*.tsx",
30
+ "import": "./src/*.tsx",
31
+ "require": "./src/*.tsx"
27
32
  }
28
33
  },
29
34
  "files": [
30
35
  "src/**/*.ts",
36
+ "src/**/*.tsx",
31
37
  "!src/**/*.test.ts",
32
38
  "README.md",
33
39
  "CHANGELOG.md"
@@ -36,9 +42,7 @@
36
42
  "typecheck": "tsc --noEmit -p ./tsconfig.json",
37
43
  "lint": "biome check --write src",
38
44
  "lint:ci": "biome ci src",
39
- "format": "biome format src --write",
40
- "test": "bun test --pass-with-no-tests",
41
- "test:watch": "bun test --watch"
45
+ "format": "biome format src --write"
42
46
  },
43
47
  "keywords": [
44
48
  "typescript",
@@ -63,17 +67,21 @@
63
67
  "publishConfig": {
64
68
  "access": "public"
65
69
  },
66
- "dependencies": {
67
- "@firtoz/drizzle-utils": "^0.3.0",
68
- "@tanstack/db": "^0.5.10",
69
- "drizzle-orm": "^0.44.7",
70
- "drizzle-valibot": "^0.4.2",
71
- "valibot": "^1.2.0"
72
- },
73
70
  "peerDependencies": {
74
- "react": "^19.2.0"
71
+ "@firtoz/drizzle-utils": ">=0.3.0",
72
+ "@tanstack/db": ">=0.5.0",
73
+ "drizzle-orm": ">=0.44.0",
74
+ "drizzle-valibot": ">=0.4.0",
75
+ "react": ">=18.0.0",
76
+ "valibot": ">=1.0.0"
75
77
  },
76
78
  "devDependencies": {
77
- "@types/react": "^19.2.7"
79
+ "@firtoz/drizzle-utils": "^0.3.0",
80
+ "@tanstack/db": "^0.5.11",
81
+ "@types/react": "^19.2.7",
82
+ "drizzle-orm": "^0.45.0",
83
+ "drizzle-valibot": "^0.4.2",
84
+ "react": "^19.2.1",
85
+ "valibot": "^1.2.0"
78
86
  }
79
87
  }
@@ -0,0 +1,365 @@
1
+ import type { PropsWithChildren } from "react";
2
+ import {
3
+ createContext,
4
+ useMemo,
5
+ useCallback,
6
+ useEffect,
7
+ useState,
8
+ useRef,
9
+ } from "react";
10
+ import {
11
+ createCollection,
12
+ type InferSchemaInput,
13
+ type UtilsRecord,
14
+ type Collection,
15
+ type InferSchemaOutput,
16
+ type SyncMode,
17
+ } from "@tanstack/db";
18
+ import { getTableName, type Table } from "drizzle-orm";
19
+ import {
20
+ indexedDBCollectionOptions,
21
+ type IndexedDBCollectionConfig,
22
+ } from "@firtoz/drizzle-indexeddb";
23
+ import type {
24
+ IdOf,
25
+ InsertSchema,
26
+ SelectSchema,
27
+ GetTableFromSchema,
28
+ InferCollectionFromTable,
29
+ ExternalSyncHandler,
30
+ } from "@firtoz/drizzle-utils";
31
+ import {
32
+ type Migration,
33
+ migrateIndexedDBWithFunctions,
34
+ } from "../function-migrator";
35
+ import type { IDBCreator, IDBDatabaseLike } from "../idb-types";
36
+ import { openIndexedDb } from "../idb-operations";
37
+ import type { IDBProxySyncMessage } from "../proxy/idb-proxy-types";
38
+
39
+ interface CollectionCacheEntry {
40
+ // biome-ignore lint/suspicious/noExplicitAny: Cache needs to store collections of various types
41
+ collection: Collection<any, string>;
42
+ refCount: number;
43
+ // biome-ignore lint/suspicious/noExplicitAny: External sync needs to accept any item type
44
+ pushExternalSync: ExternalSyncHandler<any>;
45
+ }
46
+
47
+ // Type for migration functions (generated by Drizzle)
48
+
49
+ type IndexedDbCollection<
50
+ TSchema extends Record<string, unknown>,
51
+ TTableName extends keyof TSchema & string,
52
+ > = Collection<
53
+ InferSchemaOutput<SelectSchema<GetTableFromSchema<TSchema, TTableName>>>,
54
+ IdOf<GetTableFromSchema<TSchema, TTableName>>,
55
+ UtilsRecord,
56
+ SelectSchema<GetTableFromSchema<TSchema, TTableName>>,
57
+ InferSchemaInput<InsertSchema<GetTableFromSchema<TSchema, TTableName>>>
58
+ >;
59
+
60
+ export type DrizzleIndexedDBContextValue<
61
+ TSchema extends Record<string, unknown>,
62
+ > = {
63
+ indexedDB: IDBDatabaseLike | null;
64
+ getCollection: <TTableName extends keyof TSchema & string>(
65
+ tableName: TTableName,
66
+ ) => IndexedDbCollection<TSchema, TTableName>;
67
+ incrementRefCount: (tableName: string) => void;
68
+ decrementRefCount: (tableName: string) => void;
69
+ /**
70
+ * Handle a sync message from a proxy server.
71
+ * Routes the message to the appropriate collection's external sync handler.
72
+ */
73
+ handleProxySync: (message: IDBProxySyncMessage) => void;
74
+ };
75
+
76
+ export const DrizzleIndexedDBContext =
77
+ // biome-ignore lint/suspicious/noExplicitAny: Context needs to accept any schema type
78
+ createContext<DrizzleIndexedDBContextValue<any> | null>(null);
79
+
80
+ type DrizzleIndexedDBProviderProps<TSchema extends Record<string, unknown>> =
81
+ PropsWithChildren<{
82
+ dbName: string;
83
+ schema: TSchema;
84
+ migrations?: Migration[];
85
+ migrateFunction?: (
86
+ dbName: string,
87
+ migrations: Migration[],
88
+ debug?: boolean,
89
+ dbCreator?: IDBCreator,
90
+ ) => Promise<IDBDatabaseLike>;
91
+ debug?: boolean;
92
+ syncMode?: SyncMode;
93
+ /**
94
+ * Optional custom database creator for testing/mocking.
95
+ * Use createInstrumentedDbCreator() to track IndexedDB operations.
96
+ */
97
+ dbCreator?: IDBCreator;
98
+ /**
99
+ * Called when the sync handler is ready.
100
+ * Use this to connect proxy sync messages to the provider.
101
+ *
102
+ * @example
103
+ * const proxyClient = ...;
104
+ * <DrizzleIndexedDBProvider
105
+ * onSyncReady={(handleSync) => proxyClient.onSync(handleSync)}
106
+ * ...
107
+ * />
108
+ */
109
+ onSyncReady?: (handleSync: (message: IDBProxySyncMessage) => void) => void;
110
+ }>;
111
+
112
+ export function DrizzleIndexedDBProvider<
113
+ TSchema extends Record<string, unknown>,
114
+ >({
115
+ children,
116
+ dbName,
117
+ schema,
118
+ migrations = [],
119
+ migrateFunction = migrateIndexedDBWithFunctions,
120
+ debug = false,
121
+ syncMode = "eager",
122
+ dbCreator,
123
+ onSyncReady,
124
+ }: DrizzleIndexedDBProviderProps<TSchema>) {
125
+ const [indexedDB, setIndexedDB] = useState<IDBDatabaseLike | null>(null);
126
+ const indexedDBRef = useRef<IDBDatabaseLike | null>(null);
127
+ const [readyPromise] = useState(() => {
128
+ let resolveReady: () => void;
129
+ const promise = new Promise<void>((resolve) => {
130
+ resolveReady = resolve;
131
+ });
132
+ // biome-ignore lint/style/noNonNullAssertion: resolveReady is guaranteed to be set
133
+ return { promise, resolve: resolveReady! };
134
+ });
135
+
136
+ useEffect(() => {
137
+ let db: IDBDatabaseLike | null = null;
138
+ const initDB = async () => {
139
+ try {
140
+ if (migrations.length === 0) {
141
+ // Open database directly without migration logic
142
+ db = await openIndexedDb(dbName, dbCreator);
143
+ } else {
144
+ db = await migrateFunction(dbName, migrations, debug, dbCreator);
145
+ }
146
+
147
+ indexedDBRef.current = db;
148
+ setIndexedDB(db);
149
+ readyPromise.resolve();
150
+ } catch (error) {
151
+ console.error(
152
+ `[DrizzleIndexedDBProvider] Failed to initialize database ${dbName}:`,
153
+ error,
154
+ );
155
+ throw error;
156
+ }
157
+ };
158
+
159
+ initDB();
160
+
161
+ // Cleanup on unmount
162
+ return () => {
163
+ if (db) {
164
+ db.close();
165
+ }
166
+ };
167
+ }, [dbName, migrations, migrateFunction, debug, readyPromise, dbCreator]);
168
+
169
+ // Collection cache with ref counting
170
+ const collections = useMemo<Map<string, CollectionCacheEntry>>(
171
+ () => new Map(),
172
+ [],
173
+ );
174
+
175
+ const getCollection = useCallback<
176
+ DrizzleIndexedDBContextValue<TSchema>["getCollection"]
177
+ >(
178
+ <TTableName extends keyof TSchema & string>(tableName: TTableName) => {
179
+ const cacheKey = tableName;
180
+
181
+ // Check if collection already exists in cache
182
+ if (!collections.has(cacheKey)) {
183
+ // Get the table definition from schema
184
+ const table = schema[tableName] as Table;
185
+
186
+ if (!table) {
187
+ throw new Error(
188
+ `Table "${tableName}" not found in schema. Available tables: ${Object.keys(schema).join(", ")}`,
189
+ );
190
+ }
191
+
192
+ // Extract the actual store/table name from the table definition
193
+ const actualTableName = getTableName(table);
194
+
195
+ // Create collection options
196
+ const collectionConfig = indexedDBCollectionOptions({
197
+ indexedDBRef,
198
+ dbName,
199
+ table,
200
+ storeName: actualTableName,
201
+ readyPromise: readyPromise.promise,
202
+ debug,
203
+ syncMode,
204
+ } as IndexedDBCollectionConfig<Table>);
205
+
206
+ // Create new collection and cache it with ref count 0
207
+ // The collection will wait for readyPromise before accessing the database
208
+ const collection = createCollection(collectionConfig);
209
+
210
+ collections.set(cacheKey, {
211
+ collection,
212
+ refCount: 0,
213
+ pushExternalSync: collectionConfig.utils.pushExternalSync,
214
+ });
215
+ }
216
+
217
+ // biome-ignore lint/style/noNonNullAssertion: We just ensured the collection exists
218
+ return collections.get(cacheKey)!
219
+ .collection as unknown as IndexedDbCollection<TSchema, TTableName>;
220
+ },
221
+ [collections, schema, readyPromise.promise, debug, dbName, syncMode],
222
+ );
223
+
224
+ const incrementRefCount: DrizzleIndexedDBContextValue<TSchema>["incrementRefCount"] =
225
+ useCallback(
226
+ (tableName: string) => {
227
+ const entry = collections.get(tableName);
228
+ if (entry) {
229
+ entry.refCount++;
230
+ }
231
+ },
232
+ [collections],
233
+ );
234
+
235
+ const decrementRefCount: DrizzleIndexedDBContextValue<TSchema>["decrementRefCount"] =
236
+ useCallback(
237
+ (tableName: string) => {
238
+ const entry = collections.get(tableName);
239
+ if (entry) {
240
+ entry.refCount--;
241
+
242
+ // If ref count reaches 0, remove from cache
243
+ if (entry.refCount <= 0) {
244
+ collections.delete(tableName);
245
+ }
246
+ }
247
+ },
248
+ [collections],
249
+ );
250
+
251
+ // Handle proxy sync messages by routing to the appropriate collection
252
+ const handleProxySync: DrizzleIndexedDBContextValue<TSchema>["handleProxySync"] =
253
+ useCallback(
254
+ (message: IDBProxySyncMessage) => {
255
+ // Find the collection for this store by checking the schema
256
+ for (const [tableName, table] of Object.entries(schema)) {
257
+ const actualStoreName = getTableName(table as Table);
258
+ if (actualStoreName === message.storeName) {
259
+ const entry = collections.get(tableName);
260
+ if (entry?.pushExternalSync) {
261
+ // Route sync message to collection
262
+ switch (message.type) {
263
+ case "sync:add":
264
+ entry.pushExternalSync({
265
+ type: "insert",
266
+ items: message.items,
267
+ });
268
+ break;
269
+ case "sync:put":
270
+ entry.pushExternalSync({
271
+ type: "update",
272
+ items: message.items,
273
+ });
274
+ break;
275
+ case "sync:delete":
276
+ // For delete, construct items with id
277
+ entry.pushExternalSync({
278
+ type: "delete",
279
+ items: message.keys.map((key) => ({ id: key })),
280
+ });
281
+ break;
282
+ case "sync:clear":
283
+ entry.pushExternalSync({
284
+ type: "truncate",
285
+ });
286
+ break;
287
+ }
288
+ }
289
+ return;
290
+ }
291
+ }
292
+
293
+ if (debug) {
294
+ console.warn(
295
+ `[DrizzleIndexedDBProvider] No collection found for store: ${message.storeName}`,
296
+ );
297
+ }
298
+ },
299
+ [collections, schema, debug],
300
+ );
301
+
302
+ // Call onSyncReady when handleProxySync is available
303
+ useEffect(() => {
304
+ if (onSyncReady) {
305
+ onSyncReady(handleProxySync);
306
+ }
307
+ }, [onSyncReady, handleProxySync]);
308
+
309
+ const contextValue: DrizzleIndexedDBContextValue<TSchema> = useMemo(
310
+ () => ({
311
+ indexedDB,
312
+ getCollection,
313
+ incrementRefCount,
314
+ decrementRefCount,
315
+ handleProxySync,
316
+ }),
317
+ [
318
+ indexedDB,
319
+ getCollection,
320
+ incrementRefCount,
321
+ decrementRefCount,
322
+ handleProxySync,
323
+ ],
324
+ );
325
+
326
+ return (
327
+ <DrizzleIndexedDBContext.Provider value={contextValue}>
328
+ {children}
329
+ </DrizzleIndexedDBContext.Provider>
330
+ );
331
+ }
332
+
333
+ // Hook that components use to get a collection with automatic ref counting
334
+ export function useIndexedDBCollection<
335
+ TSchema extends Record<string, unknown>,
336
+ TTableName extends keyof TSchema & string,
337
+ >(
338
+ context: DrizzleIndexedDBContextValue<TSchema>,
339
+ tableName: TTableName,
340
+ ): InferCollectionFromTable<GetTableFromSchema<TSchema, TTableName>> {
341
+ const { collection, unsubscribe } = useMemo(() => {
342
+ // Get the collection and increment ref count
343
+ const col = context.getCollection(tableName);
344
+ context.incrementRefCount(tableName);
345
+
346
+ // Return collection and unsubscribe function
347
+ return {
348
+ collection: col,
349
+ unsubscribe: () => {
350
+ context.decrementRefCount(tableName);
351
+ },
352
+ };
353
+ }, [context, tableName]);
354
+
355
+ // Cleanup on unmount
356
+ useEffect(() => {
357
+ return () => {
358
+ unsubscribe();
359
+ };
360
+ }, [unsubscribe]);
361
+
362
+ return collection as unknown as InferCollectionFromTable<
363
+ GetTableFromSchema<TSchema, TTableName>
364
+ >;
365
+ }
@@ -1,4 +1,4 @@
1
- import { useContext } from "react";
1
+ import { useCallback, useContext } from "react";
2
2
  import {
3
3
  DrizzleIndexedDBContext,
4
4
  useIndexedDBCollection,
@@ -28,10 +28,15 @@ export function useDrizzleIndexedDB<
28
28
  );
29
29
  }
30
30
 
31
+ const useCollection = useCallback(
32
+ <TTableName extends keyof TSchema & string>(tableName: TTableName) =>
33
+ // biome-ignore lint/correctness/useHookAtTopLevel: This is on purpose.
34
+ useIndexedDBCollection(context, tableName),
35
+ [context],
36
+ );
37
+
31
38
  return {
32
- useCollection: <TTableName extends keyof TSchema & string>(
33
- tableName: TTableName,
34
- ) => useIndexedDBCollection(context, tableName),
39
+ useCollection,
35
40
  indexedDB: context.indexedDB,
36
41
  };
37
42
  }
@@ -23,9 +23,14 @@ export async function openIndexedDb(
23
23
  */
24
24
  const defaultIDBDeleter: IDBDeleter = (name: string): Promise<void> => {
25
25
  return new Promise((resolve, reject) => {
26
- const request = indexedDB.deleteDatabase(name);
27
- request.onerror = () => reject(request.error);
28
- request.onsuccess = () => resolve();
26
+ try {
27
+ const request = indexedDB.deleteDatabase(name);
28
+ request.onerror = () => reject(request.error);
29
+ request.onsuccess = () => resolve();
30
+ } catch (error) {
31
+ console.error("Error deleting database", error);
32
+ reject(error);
33
+ }
29
34
  });
30
35
  };
31
36
 
@@ -104,12 +104,17 @@ class NativeIDBDatabase implements IDBDatabaseLike {
104
104
  }
105
105
 
106
106
  return new Promise((resolve, reject) => {
107
- const transaction = this.db.transaction(storeName, "readonly");
108
- const store = transaction.objectStore(storeName);
109
- const request = store.getAll();
110
-
111
- request.onsuccess = () => resolve(request.result as T[]);
112
- request.onerror = () => reject(request.error);
107
+ try {
108
+ const transaction = this.db.transaction(storeName, "readonly");
109
+ const store = transaction.objectStore(storeName);
110
+ const request = store.getAll();
111
+
112
+ request.onsuccess = () => resolve(request.result as T[]);
113
+ request.onerror = () => reject(request.error);
114
+ } catch (error) {
115
+ console.error("Error getting all items", error);
116
+ reject(error);
117
+ }
113
118
  });
114
119
  }
115
120
 
@@ -119,14 +124,19 @@ class NativeIDBDatabase implements IDBDatabaseLike {
119
124
  keyRange?: KeyRangeSpec,
120
125
  ): Promise<T[]> {
121
126
  return new Promise((resolve, reject) => {
122
- const transaction = this.db.transaction(storeName, "readonly");
123
- const store = transaction.objectStore(storeName);
124
- const index = store.index(indexName);
125
- const range = keyRange ? createKeyRange(keyRange) : undefined;
126
- const request = index.getAll(range);
127
-
128
- request.onsuccess = () => resolve(request.result as T[]);
129
- request.onerror = () => reject(request.error);
127
+ try {
128
+ const transaction = this.db.transaction(storeName, "readonly");
129
+ const store = transaction.objectStore(storeName);
130
+ const index = store.index(indexName);
131
+ const range = keyRange ? createKeyRange(keyRange) : undefined;
132
+ const request = index.getAll(range);
133
+
134
+ request.onsuccess = () => resolve(request.result as T[]);
135
+ request.onerror = () => reject(request.error);
136
+ } catch (error) {
137
+ console.error("Error getting all items by index", error);
138
+ reject(error);
139
+ }
130
140
  });
131
141
  }
132
142
 
@@ -135,72 +145,98 @@ class NativeIDBDatabase implements IDBDatabaseLike {
135
145
  key: IDBValidKey,
136
146
  ): Promise<T | undefined> {
137
147
  return new Promise((resolve, reject) => {
138
- const transaction = this.db.transaction(storeName, "readonly");
139
- const store = transaction.objectStore(storeName);
140
- const request = store.get(key);
141
-
142
- request.onsuccess = () => resolve(request.result as T | undefined);
143
- request.onerror = () => reject(request.error);
148
+ try {
149
+ const transaction = this.db.transaction(storeName, "readonly");
150
+ const store = transaction.objectStore(storeName);
151
+ const request = store.get(key);
152
+
153
+ request.onsuccess = () => resolve(request.result as T | undefined);
154
+ request.onerror = () => reject(request.error);
155
+ } catch (error) {
156
+ console.error("Error getting item", error);
157
+ reject(error);
158
+ }
144
159
  });
145
160
  }
146
161
 
147
162
  async add(storeName: string, items: unknown[]): Promise<void> {
148
163
  return new Promise((resolve, reject) => {
149
- const transaction = this.db.transaction(storeName, "readwrite");
150
- const store = transaction.objectStore(storeName);
164
+ try {
165
+ const transaction = this.db.transaction(storeName, "readwrite");
166
+ const store = transaction.objectStore(storeName);
151
167
 
152
- for (const item of items) {
153
- store.add(item);
154
- }
168
+ for (const item of items) {
169
+ store.add(item);
170
+ }
155
171
 
156
- transaction.oncomplete = () => resolve();
157
- transaction.onerror = () => reject(transaction.error);
158
- transaction.onabort = () => reject(new Error("Transaction aborted"));
172
+ transaction.oncomplete = () => resolve();
173
+ transaction.onerror = () => reject(transaction.error);
174
+ transaction.onabort = () => reject(new Error("Transaction aborted"));
175
+ } catch (error) {
176
+ console.error("Error adding items", error);
177
+ reject(error);
178
+ }
159
179
  });
160
180
  }
161
181
 
162
182
  async put(storeName: string, items: unknown[]): Promise<void> {
163
183
  return new Promise((resolve, reject) => {
164
- const transaction = this.db.transaction(storeName, "readwrite");
165
- const store = transaction.objectStore(storeName);
184
+ try {
185
+ const transaction = this.db.transaction(storeName, "readwrite");
186
+ const store = transaction.objectStore(storeName);
166
187
 
167
- for (const item of items) {
168
- store.put(item);
169
- }
188
+ for (const item of items) {
189
+ store.put(item);
190
+ }
170
191
 
171
- transaction.oncomplete = () => resolve();
172
- transaction.onerror = () => reject(transaction.error);
173
- transaction.onabort = () => reject(new Error("Transaction aborted"));
192
+ transaction.oncomplete = () => resolve();
193
+ transaction.onerror = () => reject(transaction.error);
194
+ transaction.onabort = () => reject(new Error("Transaction aborted"));
195
+ } catch (error) {
196
+ console.error("Error putting items", error);
197
+ reject(error);
198
+ }
174
199
  });
175
200
  }
176
201
 
177
202
  async delete(storeName: string, keys: IDBValidKey[]): Promise<void> {
178
203
  return new Promise((resolve, reject) => {
179
- const transaction = this.db.transaction(storeName, "readwrite");
180
- const store = transaction.objectStore(storeName);
204
+ try {
205
+ const transaction = this.db.transaction(storeName, "readwrite");
206
+ const store = transaction.objectStore(storeName);
181
207
 
182
- for (const key of keys) {
183
- store.delete(key);
184
- }
208
+ for (const key of keys) {
209
+ store.delete(key);
210
+ }
185
211
 
186
- transaction.oncomplete = () => resolve();
187
- transaction.onerror = () => reject(transaction.error);
188
- transaction.onabort = () => reject(new Error("Transaction aborted"));
212
+ transaction.oncomplete = () => resolve();
213
+ transaction.onerror = () => reject(transaction.error);
214
+ transaction.onabort = () => reject(new Error("Transaction aborted"));
215
+ } catch (error) {
216
+ console.error("Error deleting items", error);
217
+ reject(error);
218
+ }
189
219
  });
190
220
  }
191
221
 
192
222
  async clear(storeName: string): Promise<void> {
193
223
  return new Promise((resolve, reject) => {
194
- const transaction = this.db.transaction(storeName, "readwrite");
195
- const store = transaction.objectStore(storeName);
196
- const request = store.clear();
197
-
198
- request.onsuccess = () => resolve();
199
- request.onerror = () => reject(request.error);
224
+ try {
225
+ const transaction = this.db.transaction(storeName, "readwrite");
226
+ const store = transaction.objectStore(storeName);
227
+ const request = store.clear();
228
+
229
+ request.onsuccess = () => resolve();
230
+ request.onerror = () => reject(request.error);
231
+ } catch (error) {
232
+ console.error("Error clearing store", error);
233
+ reject(error);
234
+ }
200
235
  });
201
236
  }
202
237
 
203
238
  close(): void {
239
+ console.log("Closing database");
204
240
  this.db.close();
205
241
  }
206
242
  }
@@ -319,37 +355,42 @@ export const defaultIDBCreator: IDBCreator = (
319
355
  options?: IDBOpenOptions,
320
356
  ): Promise<IDBDatabaseLike> => {
321
357
  return new Promise((resolve, reject) => {
322
- const request = options?.version
323
- ? indexedDB.open(name, options.version)
324
- : indexedDB.open(name);
325
-
326
- request.onerror = () => reject(request.error);
358
+ try {
359
+ const request = options?.version
360
+ ? indexedDB.open(name, options.version)
361
+ : indexedDB.open(name);
327
362
 
328
- request.onblocked = () => {
329
- setTimeout(() => {
330
- reject(new Error("Database upgrade blocked - close other tabs"));
331
- }, 3000);
332
- };
363
+ request.onerror = () => reject(request.error);
333
364
 
334
- request.onupgradeneeded = (event) => {
335
- if (options?.onUpgrade) {
336
- const db = request.result;
337
- const transaction = (event.target as IDBOpenDBRequest).transaction;
338
- if (!transaction) {
339
- reject(new Error("No transaction during upgrade"));
340
- return;
365
+ request.onblocked = () => {
366
+ setTimeout(() => {
367
+ reject(new Error("Database upgrade blocked - close other tabs"));
368
+ }, 3000);
369
+ };
370
+
371
+ request.onupgradeneeded = (event) => {
372
+ if (options?.onUpgrade) {
373
+ const db = request.result;
374
+ const transaction = (event.target as IDBOpenDBRequest).transaction;
375
+ if (!transaction) {
376
+ reject(new Error("No transaction during upgrade"));
377
+ return;
378
+ }
379
+ // Create an upgrade-mode database wrapper
380
+ const upgradeDb = new UpgradeModeDatabase(db, transaction);
381
+ try {
382
+ options.onUpgrade(upgradeDb);
383
+ } catch (error) {
384
+ transaction.abort();
385
+ reject(error);
386
+ }
341
387
  }
342
- // Create an upgrade-mode database wrapper
343
- const upgradeDb = new UpgradeModeDatabase(db, transaction);
344
- try {
345
- options.onUpgrade(upgradeDb);
346
- } catch (error) {
347
- transaction.abort();
348
- reject(error);
349
- }
350
- }
351
- };
388
+ };
352
389
 
353
- request.onsuccess = () => resolve(new NativeIDBDatabase(request.result));
390
+ request.onsuccess = () => resolve(new NativeIDBDatabase(request.result));
391
+ } catch (error) {
392
+ console.error("Error creating database", error);
393
+ reject(error);
394
+ }
354
395
  });
355
396
  };