use-idb-storage 0.1.1 → 0.1.3

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/README.md CHANGED
@@ -1,39 +1,25 @@
1
- # React IDB Storage
1
+ # useIDBStorage
2
+
2
3
  ![npm version](https://img.shields.io/npm/v/use-idb-storage)
3
4
  ![npm downloads](https://img.shields.io/npm/dm/use-idb-storage)
4
5
  ![License](https://img.shields.io/npm/l/use-idb-storage)
5
6
  ![Tests](https://github.com/sohanemon/idb/actions/workflows/test.yml/badge.svg)
6
7
 
7
- A modern, developer-friendly IndexedDB library for React and vanilla JavaScript with both hook-based and class-based APIs.
8
-
9
- ## Features
10
-
11
- - 🚀 **Simple API**: Easy-to-use hooks and classes
12
- - 🔄 **Reactive**: React hooks with automatic re-renders
13
- - 🏪 **Multi-store**: Support for multiple object stores
14
- - 📦 **Batch operations**: Efficient bulk operations
15
- - 🔧 **TypeScript**: Full type safety
16
- - 🎯 **Promise-based**: Modern async/await support
8
+ A React hook for IndexedDB state management with automatic persistence, similar to `useState` but with data persistence across sessions.
17
9
 
18
10
  ## Performance
19
11
 
20
- useIDBStorage offers near-native performance with minimal overhead. Benchmark results show only 1.5ms difference compared to useState for 400 forced unbatched updates:
12
+ Near-native performance with minimal overhead. Benchmark shows only 1.5ms difference vs `useState` for 400 forced updates:
21
13
 
22
14
  ```json
23
15
  {
24
- "useState": {
25
- "renders": 400,
26
- "time": 3317.9
27
- },
28
- "useIDBStorage": {
29
- "renders": 400,
30
- "time": 3316.4
31
- },
16
+ "useState": { "renders": 400, "time": 3317.9 },
17
+ "useIDBStorage": { "renders": 400, "time": 3316.4 },
32
18
  "differenceMs": 1.5
33
19
  }
34
20
  ```
35
21
 
36
- For detailed performance testing guide, see [PERFORMANCE.md](./PERFORMANCE.md).
22
+ See [PERFORMANCE.md](./PERFORMANCE.md) for detailed benchmarks.
37
23
 
38
24
  ## Installation
39
25
 
@@ -43,32 +29,10 @@ npm install use-idb-storage
43
29
 
44
30
  ## Quick Start
45
31
 
46
- ### Default Instance (Simplest)
47
-
48
- ```typescript
49
- import { idb } from 'use-idb-storage';
50
-
51
- // Use default store immediately
52
- await idb.store.set('user', { name: 'John' });
53
- const user = await idb.store.get('user');
54
-
55
- // Use custom store
56
- const customStore = await idb.get('settings');
57
- await customStore.set('theme', 'dark');
58
- ```
59
-
60
- ### React Hook (Recommended for React apps)
32
+ ### Basic Usage
61
33
 
62
34
  ```tsx
63
- import { useIDBStorage, IDBConfig } from 'use-idb-storage';
64
-
65
- function App() {
66
- return (
67
- <IDBConfig database="my-app" version={1} store="data">
68
- <MyComponent />
69
- </IDBConfig>
70
- );
71
- }
35
+ import { useIDBStorage } from 'use-idb-storage';
72
36
 
73
37
  function MyComponent() {
74
38
  const [user, setUser, removeUser] = useIDBStorage({
@@ -83,41 +47,84 @@ function MyComponent() {
83
47
  onChange={e => setUser({ ...user, name: e.target.value })}
84
48
  />
85
49
  <button onClick={() => removeUser()}>Clear</button>
50
+ <p>Data persists across browser sessions.</p>
86
51
  </div>
87
52
  );
88
53
  }
89
54
  ```
90
55
 
91
- ### Class-based API (For advanced use cases)
56
+ ### Advanced Usage
57
+
58
+ Use object destructuring for additional features:
59
+
60
+ ```tsx
61
+ function AdvancedComponent() {
62
+ const {
63
+ data: user,
64
+ update: setUser,
65
+ reset: clearUser,
66
+ loading,
67
+ persisted,
68
+ error,
69
+ lastUpdated,
70
+ refresh
71
+ } = useIDBStorage({
72
+ key: 'user-profile',
73
+ defaultValue: { name: '', email: '', avatar: null }
74
+ });
75
+
76
+ if (loading) return <div>Loading...</div>;
77
+ if (error) return <div>Error: {error.message}</div>;
78
+
79
+ return (
80
+ <div>
81
+ <h2>{user.name || 'Anonymous'}</h2>
82
+ <button onClick={() => setUser(prev => ({
83
+ ...prev,
84
+ loginCount: (prev.loginCount || 0) + 1
85
+ }))}>
86
+ Increment Login Count
87
+ </button>
88
+ <button onClick={() => setUser({ ...user, name: 'John' })}>
89
+ Set Name
90
+ </button>
91
+ <button onClick={clearUser}>Reset</button>
92
+ <button onClick={() => refresh()}>Refresh from DB</button>
93
+ <div>
94
+ Status: {persisted ? 'Saved' : 'Saving...'}
95
+ {lastUpdated && (
96
+ <span>Last saved: {lastUpdated.toLocaleTimeString()}</span>
97
+ )}
98
+ </div>
99
+ </div>
100
+ );
101
+ }
102
+ ```
103
+
104
+ ### Class-based API
92
105
 
93
106
  ```tsx
94
107
  import { IDBStorage } from 'use-idb-storage';
95
108
 
96
- // Create storage instance
97
109
  const db = new IDBStorage({
98
110
  database: 'my-app',
99
111
  version: 1,
100
112
  store: 'data'
101
113
  });
102
114
 
103
- // Get a store
104
115
  const store = await db.get('my-store');
105
- // Or use default store
106
- const defaultStore = await db.store; // Convenience getter
116
+ const defaultStore = await db.store;
107
117
 
108
- // Basic operations
109
118
  await store.set('user', { name: 'John', age: 30 });
110
119
  const user = await store.get('user');
111
120
  await store.delete('user');
112
121
 
113
- // Batch operations
114
122
  await store.setMany([
115
123
  ['key1', 'value1'],
116
124
  ['key2', 'value2']
117
125
  ]);
118
126
  const values = await store.getMany(['key1', 'key2']);
119
127
 
120
- // Advanced operations
121
128
  await store.update('counter', (val) => (val || 0) + 1);
122
129
  const allKeys = await store.keys();
123
130
  await store.clear();
@@ -184,29 +191,71 @@ Provides operations for a specific object store.
184
191
 
185
192
  ### useIDBStorage Hook
186
193
 
187
- React hook for reactive IndexedDB state management.
194
+ Supports both simple `useState`-style usage and advanced object destructuring.
188
195
 
189
- ```typescript
190
- const [value, setValue, removeValue] = useIDBStorage(options);
196
+ #### Basic Usage
197
+
198
+ ```tsx
199
+ const [value, setValue, removeValue] = useIDBStorage({
200
+ key: 'my-key',
201
+ defaultValue: 'default value'
202
+ });
203
+
204
+ setValue('new value');
205
+ setValue(prev => prev + ' updated');
206
+ removeValue();
207
+ ```
208
+
209
+ #### Advanced Usage
210
+
211
+ ```tsx
212
+ const {
213
+ data,
214
+ update,
215
+ reset,
216
+ loading,
217
+ persisted,
218
+ error,
219
+ lastUpdated,
220
+ refresh
221
+ } = useIDBStorage({
222
+ key: 'my-key',
223
+ defaultValue: 'default value'
224
+ });
191
225
  ```
192
226
 
193
227
  #### Parameters
194
228
 
195
229
  ```typescript
196
230
  interface IDBStorageOptions<T> {
197
- key: string;
198
- defaultValue: T;
199
- database?: string;
200
- version?: number;
201
- store?: string;
231
+ key: string; // Unique key for the stored value
232
+ defaultValue: T; // Default value if none exists in IndexedDB
233
+ database?: string; // Database name (uses context default)
234
+ version?: number; // Database version (uses context default)
235
+ store?: string; // Object store name (uses context default)
202
236
  }
203
237
  ```
204
238
 
205
- #### Returns
239
+ #### Return Types
206
240
 
207
- - `value: T` - Current stored value
208
- - `setValue: (value: T | ((prev: T) => T)) => Promise<void>` - Update function
209
- - `removeValue: () => Promise<void>` - Remove function
241
+ **Tuple Destructuring** (useState-compatible):
242
+ ```typescript
243
+ [value: T, setValue: (value: T | ((prev: T) => T)) => void, removeValue: () => void]
244
+ ```
245
+
246
+ **Object Destructuring** (Full-featured):
247
+ ```typescript
248
+ {
249
+ data: T, // The stored data
250
+ update: (value: T | ((prev: T) => T)) => void, // Update function
251
+ reset: () => void, // Reset to default
252
+ loading: boolean, // Loading state
253
+ persisted: boolean, // Persistence status
254
+ error: Error | null, // Error state
255
+ lastUpdated: Date | null, // Last update timestamp
256
+ refresh: () => Promise<void> // Force refresh from DB
257
+ }
258
+ ```
210
259
 
211
260
  ### IDBConfig Provider
212
261
 
@@ -228,72 +277,243 @@ interface IDBConfigValues {
228
277
  }
229
278
  ```
230
279
 
231
- ## Advanced Usage
280
+ ## Examples
232
281
 
233
- ### Custom Stores
282
+ ### User Authentication
234
283
 
235
- ```typescript
236
- const db = new IDBStorage({ database: 'my-app', store: 'default' });
284
+ ```tsx
285
+ function AuthProvider({ children }) {
286
+ const { data: user, update: setUser, reset: logout, loading } = useIDBStorage({
287
+ key: 'auth-user',
288
+ defaultValue: null
289
+ });
290
+
291
+ const login = async (credentials) => {
292
+ try {
293
+ const userData = await api.login(credentials);
294
+ setUser(userData);
295
+ } catch (error) {
296
+ console.error('Login failed:', error);
297
+ }
298
+ };
237
299
 
238
- // Different stores for different data types
239
- const users = await db.get('users');
240
- const settings = await db.get('settings');
300
+ if (loading) return <div>Loading...</div>;
241
301
 
242
- await users.set('user-1', { name: 'John' });
243
- await settings.set('theme', 'dark');
302
+ return (
303
+ <AuthContext.Provider value={{ user, login, logout }}>
304
+ {children}
305
+ </AuthContext.Provider>
306
+ );
307
+ }
244
308
  ```
245
309
 
246
- ### Batch Operations
310
+ ### Shopping Cart
247
311
 
248
- ```typescript
249
- const store = await db.getStore();
312
+ ```tsx
313
+ function ShoppingCart() {
314
+ const {
315
+ data: cart,
316
+ update: updateCart,
317
+ reset: clearCart,
318
+ persisted,
319
+ lastUpdated
320
+ } = useIDBStorage({
321
+ key: 'shopping-cart',
322
+ defaultValue: { items: [], total: 0 }
323
+ });
250
324
 
251
- // Set multiple values
252
- await store.setMany([
253
- ['user1', { name: 'Alice' }],
254
- ['user2', { name: 'Bob' }],
255
- ['user3', { name: 'Charlie' }]
256
- ]);
325
+ const addItem = (item) => {
326
+ updateCart(prev => ({
327
+ items: [...prev.items, item],
328
+ total: prev.total + item.price
329
+ }));
330
+ };
257
331
 
258
- // Get multiple values
259
- const users = await store.getMany(['user1', 'user2']);
332
+ return (
333
+ <div>
334
+ <h2>Cart ({cart.items.length} items)</h2>
335
+ {cart.items.map(item => (
336
+ <div key={item.id}>{item.name} - ${item.price}</div>
337
+ ))}
338
+ <p>Total: ${cart.total}</p>
339
+ <button onClick={clearCart}>Clear Cart</button>
340
+ <div>
341
+ Status: {persisted ? 'Saved' : 'Saving...'}
342
+ {lastUpdated && <span> • Saved {lastUpdated.toLocaleTimeString()}</span>}
343
+ </div>
344
+ </div>
345
+ );
346
+ }
260
347
  ```
261
348
 
262
- ### Reactive Updates
349
+ ### Auto-Save Form
263
350
 
264
- ```typescript
265
- const [counter, setCounter] = useIDBStorage({
266
- key: 'clicks',
267
- defaultValue: 0
268
- });
351
+ ```tsx
352
+ function AutoSaveForm() {
353
+ const {
354
+ data: formData,
355
+ update: updateForm,
356
+ loading,
357
+ error,
358
+ lastUpdated
359
+ } = useIDBStorage({
360
+ key: 'draft-form',
361
+ defaultValue: { title: '', content: '', tags: [] }
362
+ });
363
+
364
+ React.useEffect(() => {
365
+ const timer = setTimeout(() => {
366
+ console.log('Auto-saved at', new Date().toLocaleTimeString());
367
+ }, 1000);
368
+
369
+ return () => clearTimeout(timer);
370
+ }, [formData]);
269
371
 
270
- // This will automatically persist to IndexedDB
271
- const increment = () => setCounter(count => count + 1);
372
+ if (loading) return <div>Loading draft...</div>;
373
+ if (error) return <div>Failed to load draft: {error.message}</div>;
374
+
375
+ return (
376
+ <form>
377
+ <input
378
+ value={formData.title}
379
+ onChange={e => updateForm(prev => ({ ...prev, title: e.target.value }))}
380
+ placeholder="Title"
381
+ />
382
+ <textarea
383
+ value={formData.content}
384
+ onChange={e => updateForm(prev => ({ ...prev, content: e.target.value }))}
385
+ placeholder="Content"
386
+ />
387
+ <div>
388
+ {lastUpdated && (
389
+ <small>Last saved: {lastUpdated.toLocaleTimeString()}</small>
390
+ )}
391
+ </div>
392
+ </form>
393
+ );
394
+ }
272
395
  ```
273
396
 
274
- ### Migration Strategy
397
+ ### Error Handling
275
398
 
276
- For database migrations, increment the version number:
399
+ ```tsx
400
+ function RobustComponent() {
401
+ const {
402
+ data: settings,
403
+ update: updateSettings,
404
+ reset: resetSettings,
405
+ error,
406
+ refresh,
407
+ loading
408
+ } = useIDBStorage({
409
+ key: 'user-settings',
410
+ defaultValue: { theme: 'light', notifications: true }
411
+ });
277
412
 
278
- ```typescript
279
- // Version 1
413
+ if (error) {
414
+ return (
415
+ <div>
416
+ <p>Failed to load settings: {error.message}</p>
417
+ <button onClick={() => refresh()}>Retry</button>
418
+ <button onClick={resetSettings}>Reset to Defaults</button>
419
+ </div>
420
+ );
421
+ }
422
+
423
+ if (loading) return <div>Loading settings...</div>;
424
+
425
+ return (
426
+ <div>
427
+ <label>
428
+ Theme:
429
+ <select
430
+ value={settings.theme}
431
+ onChange={e => updateSettings(prev => ({
432
+ ...prev,
433
+ theme: e.target.value
434
+ }))}
435
+ >
436
+ <option value="light">Light</option>
437
+ <option value="dark">Dark</option>
438
+ </select>
439
+ </label>
440
+ </div>
441
+ );
442
+ }
443
+ ```
444
+
445
+ ## Advanced Usage
446
+
447
+ ### Custom Configuration
448
+
449
+ ```tsx
450
+ import { IDBConfig } from 'use-idb-storage';
451
+
452
+ function App() {
453
+ return (
454
+ <IDBConfig database="my-app" version={2} store="main">
455
+ <MyComponents />
456
+ </IDBConfig>
457
+ );
458
+ }
459
+
460
+ function MyComponents() {
461
+ const [user] = useIDBStorage({
462
+ key: 'user',
463
+ defaultValue: { name: '' }
464
+ });
465
+
466
+ const [cache] = useIDBStorage({
467
+ key: 'api-cache',
468
+ defaultValue: {},
469
+ store: 'cache'
470
+ });
471
+
472
+ return <div>...</div>;
473
+ }
474
+ ```
475
+
476
+ ### Migration
477
+
478
+ Increment version for schema changes:
479
+
480
+ ```tsx
280
481
  <IDBConfig database="my-app" version={1} store="data">
281
482
  <App />
282
483
  </IDBConfig>
283
484
 
284
- // Version 2 (with new features)
285
485
  <IDBConfig database="my-app" version={2} store="data">
286
486
  <App />
287
487
  </IDBConfig>
288
488
  ```
289
489
 
290
- ## Browser Support
490
+ ### Class-based API
491
+
492
+ ```tsx
493
+ import { IDBStorage } from 'use-idb-storage';
494
+
495
+ const db = new IDBStorage({ database: 'my-app' });
496
+ const store = await db.get('analytics');
497
+
498
+ await store.setMany([
499
+ ['pageviews', 1234],
500
+ ['sessions', 89],
501
+ ['bounce-rate', 0.45]
502
+ ]);
503
+
504
+ const allKeys = await store.keys();
505
+ const allData = await store.values();
506
+ ```
507
+
508
+
509
+
510
+ ## 🌐 Browser Support
291
511
 
292
512
  - Chrome 24+
293
513
  - Firefox 16+
294
514
  - Safari 10+
295
515
  - Edge 12+
296
516
 
297
- ## License
517
+ ## 📄 License
298
518
 
299
519
  ISC
package/dist/index.d.ts CHANGED
@@ -30,8 +30,37 @@ interface IDBConfigProps extends Partial<IDBConfigValues> {
30
30
  }
31
31
  /**
32
32
  * Return type of the useIDBStorage hook
33
+ * Supports both tuple destructuring: const [value, setValue, removeValue] = useIDBStorage()
34
+ * and object destructuring: const { value, update, reset, loading, persisted, ... } = useIDBStorage()
33
35
  */
34
- type UseIDBStorageReturn<T> = [T, (value: T | ((prevState: T) => T)) => void, () => void];
36
+ type UseIDBStorageReturn<T> = {
37
+ /** The current stored value */
38
+ 0: T;
39
+ /** Function to update the stored value */
40
+ 1: (value: T | ((prevState: T) => T)) => void;
41
+ /** Function to remove the stored value and reset to default */
42
+ 2: () => void;
43
+ /** The current stored data (alias for index 0) */
44
+ data: T;
45
+ /** Function to update the stored value (alias for index 1) */
46
+ update: (value: T | ((prevState: T) => T)) => void;
47
+ /** Function to remove the stored value and reset to default (alias for index 2) */
48
+ reset: () => void;
49
+ /** Whether the hook is still loading initial value from IndexedDB */
50
+ loading: boolean;
51
+ /** Whether the current value has been loaded/persisted to IndexedDB */
52
+ persisted: boolean;
53
+ /** Error that occurred during loading/persistence, if any */
54
+ error: Error | null;
55
+ /** Timestamp of last successful persistence */
56
+ lastUpdated: Date | null;
57
+ /** Force refresh value from IndexedDB */
58
+ refresh: () => Promise<void>;
59
+ /** Length for array-like behavior */
60
+ length: 3;
61
+ /** Iterator for array destructuring */
62
+ [Symbol.iterator](): Iterator<T | ((value: T | ((prevState: T) => T)) => void) | (() => void)>;
63
+ };
35
64
  //#endregion
36
65
  //#region src/idb-storage.d.ts
37
66
  /**
@@ -137,7 +166,9 @@ declare function getGlobalConfig(): IDBConfigValues;
137
166
  * Optimized for performance - synchronous updates like useState with background IDB persistence.
138
167
  *
139
168
  * @param options - Configuration object containing key, defaultValue, and optional database/store names
140
- * @returns A tuple of the stored value, an async updater function, and a remove function.
169
+ * @returns An object that supports both tuple and object destructuring:
170
+ * - Tuple: const [value, setValue, removeValue] = useIDBStorage()
171
+ * - Object: const { data, update, reset, loading, persisted, error, lastUpdated, refresh } = useIDBStorage()
141
172
  *
142
173
  * @example
143
174
  * ```tsx
@@ -149,6 +180,7 @@ declare function getGlobalConfig(): IDBConfigValues;
149
180
  * // Option 2: Global config
150
181
  * configureIDBStorage({ database: 'myApp', version: 2, store: 'data' });
151
182
  *
183
+ * // Tuple destructuring (like useState)
152
184
  * const [userData, setUserData, removeUserData] = useIDBStorage({
153
185
  * key: 'currentUser',
154
186
  * defaultValue: { name: '', email: '' },
@@ -157,6 +189,27 @@ declare function getGlobalConfig(): IDBConfigValues;
157
189
  * // store: 'users' // optional, uses context or global default
158
190
  * });
159
191
  *
192
+ * // Object destructuring (with powerful features)
193
+ * const {
194
+ * data: userData,
195
+ * update: updateUserData,
196
+ * reset: resetUserData,
197
+ * loading,
198
+ * persisted,
199
+ * error,
200
+ * lastUpdated,
201
+ * refresh
202
+ * } = useIDBStorage({
203
+ * key: 'currentUser',
204
+ * defaultValue: { name: '', email: '' },
205
+ * });
206
+ *
207
+ * // Use powerful features
208
+ * if (error) console.error('Storage error:', error);
209
+ * await refresh(); // Force reload from IndexedDB
210
+ * updateUserData({ name: 'John', email: 'john@example.com' }); // Direct update
211
+ * updateUserData(prev => ({ ...prev, lastLogin: new Date() })); // Functional update
212
+ *
160
213
  * // Update data
161
214
  * await setUserData({ name: 'John', email: 'john@example.com' });
162
215
  *
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import*as e from"react";import{jsx as t}from"react/jsx-runtime";const n=new Map;function r(){return typeof indexedDB<`u`}function i(e,t,i=1,o){if(!r())throw Error(`IndexedDB is not available in this environment`);let s=`${e}:${i}:${t}`;if(n.has(s))return n.get(s);let c=a(e,t,i,o);return n.set(s,c),c}function a(e,t,r,i){return new Promise((o,s)=>{let c=indexedDB.open(e,r);c.onerror=()=>{let l=`${e}:${r??`current`}:${t}`;if(n.delete(l),c.error?.name===`VersionError`&&c.error.message.includes(`less than`)){o(a(e,t,void 0,i));return}s(c.error)},c.onupgradeneeded=e=>{let n=e.target.result;n.objectStoreNames.contains(t)||n.createObjectStore(t)},c.onsuccess=()=>{let s=c.result,l=s.version;if(!s.objectStoreNames.contains(t)){s.close();let c=`${e}:${r??`current`}:${t}`;n.delete(c),o(a(e,t,l+1,i));return}s.onversionchange=()=>{s.close();for(let[t]of n)t.startsWith(`${e}:`)&&n.delete(t);i?.()},o(s)}})}function o(e,t,n){return new Promise((r,i)=>{try{let a=e.transaction([t],`readonly`).objectStore(t).get(n);a.onerror=()=>i(a.error),a.onsuccess=()=>r(a.result)}catch(e){i(e)}})}function s(e,t,n,r){return new Promise((i,a)=>{try{let o=e.transaction([t],`readwrite`).objectStore(t).put(r,n);o.onerror=()=>a(o.error),o.onsuccess=()=>i()}catch(e){a(e)}})}function c(e,t,n){return new Promise((r,i)=>{try{let a=e.transaction([t],`readwrite`).objectStore(t).delete(n);a.onerror=()=>i(a.error),a.onsuccess=()=>r()}catch(e){i(e)}})}var l=class{db;storeName;constructor(e,t){this.db=e,this.storeName=t}async get(e){try{return await o(this.db,this.storeName,e)}catch(t){throw console.error(`Failed to get value for key "${e}":`,t),t}}async set(e,t){try{await s(this.db,this.storeName,e,t)}catch(t){throw console.error(`Failed to set value for key "${e}":`,t),t}}async delete(e){try{await c(this.db,this.storeName,e)}catch(t){throw console.error(`Failed to delete key "${e}":`,t),t}}async getMany(e){try{let t=e.map(e=>this.get(e));return await Promise.all(t)}catch(e){throw console.error(`Failed to get multiple values:`,e),e}}async setMany(e){try{let t=this.db.transaction([this.storeName],`readwrite`).objectStore(this.storeName),n=e.map(([e,n])=>new Promise((r,i)=>{let a=t.put(n,e);a.onerror=()=>i(a.error),a.onsuccess=()=>r()}));await Promise.all(n)}catch(e){throw console.error(`Failed to set multiple values:`,e),e}}async deleteMany(e){try{let t=this.db.transaction([this.storeName],`readwrite`).objectStore(this.storeName),n=e.map(e=>new Promise((n,r)=>{let i=t.delete(e);i.onerror=()=>r(i.error),i.onsuccess=()=>n()}));await Promise.all(n)}catch(e){throw console.error(`Failed to delete multiple keys:`,e),e}}async update(e,t){try{let n=t(await this.get(e));await this.set(e,n)}catch(t){throw console.error(`Failed to update value for key "${e}":`,t),t}}async clear(){try{let e=this.db.transaction([this.storeName],`readwrite`).objectStore(this.storeName).clear();await new Promise((t,n)=>{e.onerror=()=>n(e.error),e.onsuccess=()=>t()})}catch(e){throw console.error(`Failed to clear store:`,e),e}}async keys(){try{let e=this.db.transaction([this.storeName],`readonly`).objectStore(this.storeName).getAllKeys();return await new Promise((t,n)=>{e.onerror=()=>n(e.error),e.onsuccess=()=>t(Array.from(e.result))})}catch(e){throw console.error(`Failed to get keys:`,e),e}}async values(){try{let e=this.db.transaction([this.storeName],`readonly`).objectStore(this.storeName).getAll();return await new Promise((t,n)=>{e.onerror=()=>n(e.error),e.onsuccess=()=>t(e.result)})}catch(e){throw console.error(`Failed to get values:`,e),e}}async entries(){try{let e=this.db.transaction([this.storeName],`readonly`).objectStore(this.storeName).openCursor(),t=[];return new Promise((n,r)=>{e.onerror=()=>r(e.error),e.onsuccess=()=>{let r=e.result;r?(t.push([r.key,r.value]),r.continue()):n(t)}})}catch(e){throw console.error(`Failed to get entries:`,e),e}}},u=class{config;db=null;dbPromise=null;constructor(e){this.config={database:`sohanemon-idb`,version:1,store:`default`,...e},this.config.version=Math.max(1,Math.floor(this.config.version))}async getDB(){return this.db?this.db:this.dbPromise?this.dbPromise:(this.dbPromise=i(this.config.database,this.config.store,this.config.version,()=>{this.db=null,this.dbPromise=null}),this.db=await this.dbPromise,this.dbPromise=null,this.db)}async get(e){return new l(await this.getDB(),e)}get store(){return this.get(this.config.store)}async drop(e){await(await this.get(e)).clear()}close(){this.db&&=(this.db.close(),null)}};let d={database:`sohanemon-idb`,version:1,store:`default`};function f(e){d={...d,...e}}function p(){return{...d}}const m=e.createContext(null);function h(){return e.useContext(m)||p()}function g({children:e,...n}){let r={database:n.database||`sohanemon-idb`,version:n.version||1,store:n.store||`default`};return t(m.Provider,{value:r,children:e})}function _(t){let{key:n,defaultValue:i,...a}=t,o=h(),s=a.database||o.database,c=Math.max(1,Math.floor(a.version||o.version||1)),l=a.store||o.store,[d,f]=e.useState(i),p=e.useRef(!1),m=e.useRef(null),g=e.useRef(null),_=e.useRef(null),v=e.useRef(null),y=e.useRef(!1);e.useEffect(()=>{if(typeof window>`u`)return;let e=!0;return(async()=>{try{if(!r()){console.warn(`IndexedDB is not available, using default values only`),p.current=!0,y.current=!0;return}m.current&&m.current.close();let t=new u({database:s,version:c,store:l}),i=await t.get(l);if(!e)return;m.current=t,g.current=i;let a=await i.get(n);e&&a!==void 0&&f(a),p.current=!0,y.current=!0}catch(e){console.error(`Failed to initialize IDBStorage:`,e),p.current=!0,y.current=!0}})(),()=>{e=!1,_.current&&(clearTimeout(_.current),v.current!==null&&g.current&&g.current.set(n,v.current).catch(console.error)),m.current&&(m.current.close(),m.current=null,g.current=null)}},[s,c,l,n]);let b=e.useCallback(e=>{!p.current||!g.current||!y.current||(v.current=e,_.current&&clearTimeout(_.current),_.current=setTimeout(()=>{let e=v.current;v.current=null,e!==null&&g.current&&g.current.set(n,e).catch(e=>{console.error(`Failed to save value to IndexedDB:`,e)})},0))},[n]);return[d,e.useCallback(e=>{f(t=>{let n=typeof e==`function`?e(t):e;return b(n),n})},[b]),e.useCallback(()=>{f(i),b(i)},[i,b])]}const v=new u;export{g as IDBConfig,u as IDBStorage,l as IDBStore,f as configureIDBStorage,p as getGlobalConfig,v as idb,h as useIDBConfig,_ as useIDBStorage};
1
+ import*as e from"react";import{jsx as t}from"react/jsx-runtime";const n=new Map;function r(){return typeof indexedDB<`u`}function i(e,t,i=1,o){if(!r())throw Error(`IndexedDB is not available in this environment`);let s=`${e}:${i}:${t}`;if(n.has(s))return n.get(s);let c=a(e,t,i,o);return n.set(s,c),c}function a(e,t,r,i){return new Promise((o,s)=>{let c=indexedDB.open(e,r);c.onerror=()=>{let l=`${e}:${r??`current`}:${t}`;if(n.delete(l),c.error?.name===`VersionError`&&c.error.message.includes(`less than`)){o(a(e,t,void 0,i));return}s(c.error)},c.onupgradeneeded=e=>{let n=e.target.result;n.objectStoreNames.contains(t)||n.createObjectStore(t)},c.onsuccess=()=>{let s=c.result,l=s.version;if(!s.objectStoreNames.contains(t)){s.close();let c=`${e}:${r??`current`}:${t}`;n.delete(c),o(a(e,t,l+1,i));return}s.onversionchange=()=>{s.close();for(let[t]of n)t.startsWith(`${e}:`)&&n.delete(t);i?.()},o(s)}})}function o(e,t,n){return new Promise((r,i)=>{try{let a=e.transaction([t],`readonly`).objectStore(t).get(n);a.onerror=()=>i(a.error),a.onsuccess=()=>r(a.result)}catch(e){i(e)}})}function s(e,t,n,r){return new Promise((i,a)=>{try{let o=e.transaction([t],`readwrite`).objectStore(t).put(r,n);o.onerror=()=>a(o.error),o.onsuccess=()=>i()}catch(e){a(e)}})}function c(e,t,n){return new Promise((r,i)=>{try{let a=e.transaction([t],`readwrite`).objectStore(t).delete(n);a.onerror=()=>i(a.error),a.onsuccess=()=>r()}catch(e){i(e)}})}var l=class{db;storeName;constructor(e,t){this.db=e,this.storeName=t}async get(e){try{return await o(this.db,this.storeName,e)}catch(t){throw console.error(`Failed to get value for key "${e}":`,t),t}}async set(e,t){try{await s(this.db,this.storeName,e,t)}catch(t){throw console.error(`Failed to set value for key "${e}":`,t),t}}async delete(e){try{await c(this.db,this.storeName,e)}catch(t){throw console.error(`Failed to delete key "${e}":`,t),t}}async getMany(e){try{let t=e.map(e=>this.get(e));return await Promise.all(t)}catch(e){throw console.error(`Failed to get multiple values:`,e),e}}async setMany(e){try{let t=this.db.transaction([this.storeName],`readwrite`).objectStore(this.storeName),n=e.map(([e,n])=>new Promise((r,i)=>{let a=t.put(n,e);a.onerror=()=>i(a.error),a.onsuccess=()=>r()}));await Promise.all(n)}catch(e){throw console.error(`Failed to set multiple values:`,e),e}}async deleteMany(e){try{let t=this.db.transaction([this.storeName],`readwrite`).objectStore(this.storeName),n=e.map(e=>new Promise((n,r)=>{let i=t.delete(e);i.onerror=()=>r(i.error),i.onsuccess=()=>n()}));await Promise.all(n)}catch(e){throw console.error(`Failed to delete multiple keys:`,e),e}}async update(e,t){try{let n=t(await this.get(e));await this.set(e,n)}catch(t){throw console.error(`Failed to update value for key "${e}":`,t),t}}async clear(){try{let e=this.db.transaction([this.storeName],`readwrite`).objectStore(this.storeName).clear();await new Promise((t,n)=>{e.onerror=()=>n(e.error),e.onsuccess=()=>t()})}catch(e){throw console.error(`Failed to clear store:`,e),e}}async keys(){try{let e=this.db.transaction([this.storeName],`readonly`).objectStore(this.storeName).getAllKeys();return await new Promise((t,n)=>{e.onerror=()=>n(e.error),e.onsuccess=()=>t(Array.from(e.result))})}catch(e){throw console.error(`Failed to get keys:`,e),e}}async values(){try{let e=this.db.transaction([this.storeName],`readonly`).objectStore(this.storeName).getAll();return await new Promise((t,n)=>{e.onerror=()=>n(e.error),e.onsuccess=()=>t(e.result)})}catch(e){throw console.error(`Failed to get values:`,e),e}}async entries(){try{let e=this.db.transaction([this.storeName],`readonly`).objectStore(this.storeName).openCursor(),t=[];return new Promise((n,r)=>{e.onerror=()=>r(e.error),e.onsuccess=()=>{let r=e.result;r?(t.push([r.key,r.value]),r.continue()):n(t)}})}catch(e){throw console.error(`Failed to get entries:`,e),e}}},u=class{config;db=null;dbPromise=null;constructor(e){this.config={database:`sohanemon-idb`,version:1,store:`default`,...e},this.config.version=Math.max(1,Math.floor(this.config.version))}async getDB(){return this.db?this.db:this.dbPromise?this.dbPromise:(this.dbPromise=i(this.config.database,this.config.store,this.config.version,()=>{this.db=null,this.dbPromise=null}),this.db=await this.dbPromise,this.dbPromise=null,this.db)}async get(e){return new l(await this.getDB(),e)}get store(){return this.get(this.config.store)}async drop(e){await(await this.get(e)).clear()}close(){this.db&&=(this.db.close(),null)}};let d={database:`sohanemon-idb`,version:1,store:`default`};function f(e){d={...d,...e}}function p(){return{...d}}const m=e.createContext(null);function h(){return e.useContext(m)||p()}function g({children:e,...n}){let r={database:n.database||`sohanemon-idb`,version:n.version||1,store:n.store||`default`};return t(m.Provider,{value:r,children:e})}function _(t){let{key:n,defaultValue:i,...a}=t,o=h(),s=a.database||o.database,c=Math.max(1,Math.floor(a.version||o.version||1)),l=a.store||o.store,[d,f]=e.useState(i),[p,m]=e.useState(null),[g,_]=e.useState(null),v=e.useRef(!1),y=e.useRef(null),b=e.useRef(null),x=e.useRef(null),S=e.useRef(null),C=e.useRef(!1),w=e.useRef(null);e.useEffect(()=>{if(typeof window>`u`)return;let e=!0;return(async()=>{try{if(m(null),!r()){console.warn(`IndexedDB is not available, using default values only`),w.current=i,v.current=!0,C.current=!0;return}y.current&&y.current.close();let t=new u({database:s,version:c,store:l}),a=await t.get(l);if(!e)return;y.current=t,b.current=a;let o=await a.get(n);e&&(o===void 0?w.current=i:(f(o),w.current=o,_(new Date))),v.current=!0,C.current=!0}catch(e){console.error(`Failed to initialize IDBStorage:`,e),m(e instanceof Error?e:Error(String(e))),w.current=i,v.current=!0,C.current=!0}})(),()=>{e=!1,x.current&&(clearTimeout(x.current),S.current!==null&&b.current&&b.current.set(n,S.current).catch(console.error)),y.current&&(y.current.close(),y.current=null,b.current=null)}},[s,c,l,n]);let T=e.useCallback(e=>{!v.current||!b.current||!C.current||(S.current=e,x.current&&clearTimeout(x.current),x.current=setTimeout(()=>{let e=S.current;S.current=null,e!==null&&b.current&&b.current.set(n,e).then(()=>{_(new Date),w.current=e}).catch(e=>{console.error(`Failed to save value to IndexedDB:`,e),m(e instanceof Error?e:Error(String(e)))})},0))},[n]),E=e.useCallback(e=>{f(t=>{let n=typeof e==`function`?e(t):e;return T(n),n})},[T]),D=e.useCallback(()=>{f(i),_(null),w.current=i,T(i)},[i,T]),O=e.useCallback(async()=>{if(!(!r()||!b.current))try{m(null);let e=await b.current.get(n);e===void 0?(f(i),w.current=i,_(null)):(f(e),w.current=e,_(new Date))}catch(e){m(e instanceof Error?e:Error(String(e)))}},[n,i]),k=e.useCallback(()=>{D()},[D]),A=e.useCallback(e=>{E(e)},[E]);return Object.assign({0:d,1:E,2:D,data:d,update:A,reset:k,loading:!C.current,persisted:C.current,error:p,lastUpdated:g,refresh:O,length:3},{[Symbol.iterator]:function*(){yield d,yield E,yield D}})}const v=new u;export{g as IDBConfig,u as IDBStorage,l as IDBStore,f as configureIDBStorage,p as getGlobalConfig,v as idb,h as useIDBConfig,_ as useIDBStorage};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "use-idb-storage",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "author": "Sohan Emon <sohanemon@outlook.com>",
5
5
  "description": "",
6
6
  "type": "module",