use-idb-storage 0.1.1 → 0.1.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/README.md CHANGED
@@ -1,39 +1,46 @@
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
+ **The most powerful React hook for IndexedDB state management.** Drop-in replacement for `useState` with automatic persistence, error handling, and advanced features.
9
+
10
+ ## 🔥 What Makes It Special?
11
+
12
+ **useIDBStorage** combines the simplicity of `useState` with the power of IndexedDB. It provides:
13
+
14
+ - **Simple API**: `const [value, setValue] = useIDBStorage(...)` - just like `useState`!
15
+ - **Automatic Persistence**: Data survives browser refreshes, crashes, and sessions
16
+ - **Rich Features**: Loading states, error handling, timestamps, and more
17
+ - **High Performance**: Near-native speed with minimal overhead
18
+ - **Type Safety**: Full TypeScript support with inferred types
19
+ - **Flexible Usage**: Both simple tuple destructuring and powerful object destructuring
8
20
 
9
- ## Features
21
+ ## ✨ Why useIDBStorage?
10
22
 
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
23
+ - 🚀 **useState-compatible**: Same API, but with persistence
24
+ - 🔄 **Reactive & Powerful**: Loading states, error handling, timestamps
25
+ - 🏪 **Multi-store**: Multiple databases and object stores
14
26
  - 📦 **Batch operations**: Efficient bulk operations
15
27
  - 🔧 **TypeScript**: Full type safety
16
28
  - 🎯 **Promise-based**: Modern async/await support
29
+ - ⚡ **High Performance**: Near-native speed (1.5ms overhead vs useState)
17
30
 
18
- ## Performance
31
+ ## Performance
19
32
 
20
- useIDBStorage offers near-native performance with minimal overhead. Benchmark results show only 1.5ms difference compared to useState for 400 forced unbatched updates:
33
+ **Near-native performance** with minimal overhead. Benchmark shows only **1.5ms difference** vs `useState` for 400 forced updates:
21
34
 
22
35
  ```json
23
36
  {
24
- "useState": {
25
- "renders": 400,
26
- "time": 3317.9
27
- },
28
- "useIDBStorage": {
29
- "renders": 400,
30
- "time": 3316.4
31
- },
37
+ "useState": { "renders": 400, "time": 3317.9 },
38
+ "useIDBStorage": { "renders": 400, "time": 3316.4 },
32
39
  "differenceMs": 1.5
33
40
  }
34
41
  ```
35
42
 
36
- For detailed performance testing guide, see [PERFORMANCE.md](./PERFORMANCE.md).
43
+ See [PERFORMANCE.md](./PERFORMANCE.md) for detailed benchmarks.
37
44
 
38
45
  ## Installation
39
46
 
@@ -41,36 +48,15 @@ For detailed performance testing guide, see [PERFORMANCE.md](./PERFORMANCE.md).
41
48
  npm install use-idb-storage
42
49
  ```
43
50
 
44
- ## Quick Start
45
-
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
- ```
51
+ ## 🚀 Quick Start
59
52
 
60
- ### React Hook (Recommended for React apps)
53
+ ### Simple as useState (but with persistence!)
61
54
 
62
55
  ```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
- }
56
+ import { useIDBStorage } from 'use-idb-storage';
72
57
 
73
58
  function MyComponent() {
59
+ // Just like useState - but persists to IndexedDB!
74
60
  const [user, setUser, removeUser] = useIDBStorage({
75
61
  key: 'current-user',
76
62
  defaultValue: { name: '', email: '' }
@@ -83,6 +69,68 @@ function MyComponent() {
83
69
  onChange={e => setUser({ ...user, name: e.target.value })}
84
70
  />
85
71
  <button onClick={() => removeUser()}>Clear</button>
72
+ <p>Data persists across browser sessions! 🎉</p>
73
+ </div>
74
+ );
75
+ }
76
+ ```
77
+
78
+ ### Powerful Object Destructuring
79
+
80
+ Unlock advanced features with object destructuring:
81
+
82
+ ```tsx
83
+ function AdvancedComponent() {
84
+ const {
85
+ data: user, // The stored data
86
+ update: setUser, // Update function (supports functions too!)
87
+ reset: clearUser, // Reset to default
88
+ loading, // Loading state
89
+ persisted, // Has data been loaded/persisted?
90
+ error, // Any errors?
91
+ lastUpdated, // When was it last saved?
92
+ refresh // Force reload from IndexedDB
93
+ } = useIDBStorage({
94
+ key: 'user-profile',
95
+ defaultValue: { name: '', email: '', avatar: null }
96
+ });
97
+
98
+ // Handle loading states
99
+ if (loading) return <div>Loading...</div>;
100
+
101
+ // Handle errors gracefully
102
+ if (error) return <div>Error: {error.message}</div>;
103
+
104
+ return (
105
+ <div>
106
+ <h2>{user.name || 'Anonymous'}</h2>
107
+
108
+ {/* Functional updates */}
109
+ <button onClick={() => setUser(prev => ({
110
+ ...prev,
111
+ loginCount: (prev.loginCount || 0) + 1
112
+ }))}>
113
+ Increment Login Count
114
+ </button>
115
+
116
+ {/* Direct updates */}
117
+ <button onClick={() => setUser({ ...user, name: 'John' })}>
118
+ Set Name
119
+ </button>
120
+
121
+ {/* Reset to default */}
122
+ <button onClick={clearUser}>Reset</button>
123
+
124
+ {/* Force refresh */}
125
+ <button onClick={() => refresh()}>Refresh from DB</button>
126
+
127
+ {/* Status indicators */}
128
+ <div>
129
+ Status: {persisted ? '✅ Saved' : '⏳ Saving...'}
130
+ {lastUpdated && (
131
+ <span>Last saved: {lastUpdated.toLocaleTimeString()}</span>
132
+ )}
133
+ </div>
86
134
  </div>
87
135
  );
88
136
  }
@@ -184,29 +232,72 @@ Provides operations for a specific object store.
184
232
 
185
233
  ### useIDBStorage Hook
186
234
 
187
- React hook for reactive IndexedDB state management.
235
+ **The most powerful React hook for IndexedDB state management.** Supports both simple `useState`-style usage and advanced object destructuring with rich features.
188
236
 
189
- ```typescript
190
- const [value, setValue, removeValue] = useIDBStorage(options);
237
+ #### Basic Usage (useState-compatible)
238
+
239
+ ```tsx
240
+ const [value, setValue, removeValue] = useIDBStorage({
241
+ key: 'my-key',
242
+ defaultValue: 'default value'
243
+ });
244
+
245
+ // Just like useState!
246
+ setValue('new value');
247
+ setValue(prev => prev + ' updated'); // Functional updates
248
+ removeValue(); // Reset to default
249
+ ```
250
+
251
+ #### Advanced Usage (Object Destructuring)
252
+
253
+ ```tsx
254
+ const {
255
+ data, // The stored data (renamed from 'value' for clarity)
256
+ update, // Update function (renamed from 'setValue')
257
+ reset, // Reset function (renamed from 'removeValue')
258
+ loading, // Boolean: Is data still loading?
259
+ persisted, // Boolean: Has data been loaded from IndexedDB?
260
+ error, // Error | null: Any errors during loading/saving?
261
+ lastUpdated, // Date | null: When was data last successfully saved?
262
+ refresh // Function: Force reload data from IndexedDB
263
+ } = useIDBStorage({
264
+ key: 'my-key',
265
+ defaultValue: 'default value'
266
+ });
191
267
  ```
192
268
 
193
269
  #### Parameters
194
270
 
195
271
  ```typescript
196
272
  interface IDBStorageOptions<T> {
197
- key: string;
198
- defaultValue: T;
199
- database?: string;
200
- version?: number;
201
- store?: string;
273
+ key: string; // Unique key for the stored value
274
+ defaultValue: T; // Default value if none exists in IndexedDB
275
+ database?: string; // Database name (uses context default)
276
+ version?: number; // Database version (uses context default)
277
+ store?: string; // Object store name (uses context default)
202
278
  }
203
279
  ```
204
280
 
205
- #### Returns
281
+ #### Return Types
206
282
 
207
- - `value: T` - Current stored value
208
- - `setValue: (value: T | ((prev: T) => T)) => Promise<void>` - Update function
209
- - `removeValue: () => Promise<void>` - Remove function
283
+ **Tuple Destructuring** (useState-compatible):
284
+ ```typescript
285
+ [value: T, setValue: (value: T | ((prev: T) => T)) => void, removeValue: () => void]
286
+ ```
287
+
288
+ **Object Destructuring** (Full-featured):
289
+ ```typescript
290
+ {
291
+ data: T, // The stored data
292
+ update: (value: T | ((prev: T) => T)) => void, // Update function
293
+ reset: () => void, // Reset to default
294
+ loading: boolean, // Loading state
295
+ persisted: boolean, // Persistence status
296
+ error: Error | null, // Error state
297
+ lastUpdated: Date | null, // Last update timestamp
298
+ refresh: () => Promise<void> // Force refresh from DB
299
+ }
300
+ ```
210
301
 
211
302
  ### IDBConfig Provider
212
303
 
@@ -228,72 +319,277 @@ interface IDBConfigValues {
228
319
  }
229
320
  ```
230
321
 
231
- ## Advanced Usage
322
+ ## 🎯 Real-World Examples
232
323
 
233
- ### Custom Stores
324
+ ### User Authentication State
234
325
 
235
- ```typescript
236
- const db = new IDBStorage({ database: 'my-app', store: 'default' });
326
+ ```tsx
327
+ function AuthProvider({ children }) {
328
+ const { data: user, update: setUser, reset: logout, loading } = useIDBStorage({
329
+ key: 'auth-user',
330
+ defaultValue: null
331
+ });
237
332
 
238
- // Different stores for different data types
239
- const users = await db.get('users');
240
- const settings = await db.get('settings');
333
+ const login = async (credentials) => {
334
+ try {
335
+ const userData = await api.login(credentials);
336
+ setUser(userData);
337
+ } catch (error) {
338
+ console.error('Login failed:', error);
339
+ }
340
+ };
241
341
 
242
- await users.set('user-1', { name: 'John' });
243
- await settings.set('theme', 'dark');
342
+ if (loading) return <div>Loading...</div>;
343
+
344
+ return (
345
+ <AuthContext.Provider value={{ user, login, logout }}>
346
+ {children}
347
+ </AuthContext.Provider>
348
+ );
349
+ }
244
350
  ```
245
351
 
246
- ### Batch Operations
352
+ ### Shopping Cart with Persistence
247
353
 
248
- ```typescript
249
- const store = await db.getStore();
354
+ ```tsx
355
+ function ShoppingCart() {
356
+ const {
357
+ data: cart,
358
+ update: updateCart,
359
+ reset: clearCart,
360
+ persisted,
361
+ lastUpdated
362
+ } = useIDBStorage({
363
+ key: 'shopping-cart',
364
+ defaultValue: { items: [], total: 0 }
365
+ });
250
366
 
251
- // Set multiple values
252
- await store.setMany([
253
- ['user1', { name: 'Alice' }],
254
- ['user2', { name: 'Bob' }],
255
- ['user3', { name: 'Charlie' }]
256
- ]);
367
+ const addItem = (item) => {
368
+ updateCart(prev => ({
369
+ items: [...prev.items, item],
370
+ total: prev.total + item.price
371
+ }));
372
+ };
257
373
 
258
- // Get multiple values
259
- const users = await store.getMany(['user1', 'user2']);
374
+ return (
375
+ <div>
376
+ <h2>Cart ({cart.items.length} items)</h2>
377
+ {cart.items.map(item => (
378
+ <div key={item.id}>{item.name} - ${item.price}</div>
379
+ ))}
380
+ <p>Total: ${cart.total}</p>
381
+ <button onClick={clearCart}>Clear Cart</button>
382
+ <div>
383
+ Status: {persisted ? '✅ Saved' : '💾 Saving...'}
384
+ {lastUpdated && <span> • Saved {lastUpdated.toLocaleTimeString()}</span>}
385
+ </div>
386
+ </div>
387
+ );
388
+ }
260
389
  ```
261
390
 
262
- ### Reactive Updates
391
+ ### Form with Auto-Save
263
392
 
264
- ```typescript
265
- const [counter, setCounter] = useIDBStorage({
266
- key: 'clicks',
267
- defaultValue: 0
268
- });
393
+ ```tsx
394
+ function AutoSaveForm() {
395
+ const {
396
+ data: formData,
397
+ update: updateForm,
398
+ loading,
399
+ error,
400
+ lastUpdated
401
+ } = useIDBStorage({
402
+ key: 'draft-form',
403
+ defaultValue: { title: '', content: '', tags: [] }
404
+ });
405
+
406
+ // Auto-save on changes
407
+ React.useEffect(() => {
408
+ const timer = setTimeout(() => {
409
+ // Form data is automatically persisted!
410
+ console.log('Auto-saved at', new Date().toLocaleTimeString());
411
+ }, 1000);
269
412
 
270
- // This will automatically persist to IndexedDB
271
- const increment = () => setCounter(count => count + 1);
413
+ return () => clearTimeout(timer);
414
+ }, [formData]);
415
+
416
+ if (loading) return <div>Loading draft...</div>;
417
+ if (error) return <div>Failed to load draft: {error.message}</div>;
418
+
419
+ return (
420
+ <form>
421
+ <input
422
+ value={formData.title}
423
+ onChange={e => updateForm(prev => ({ ...prev, title: e.target.value }))}
424
+ placeholder="Title"
425
+ />
426
+ <textarea
427
+ value={formData.content}
428
+ onChange={e => updateForm(prev => ({ ...prev, content: e.target.value }))}
429
+ placeholder="Content"
430
+ />
431
+ <div>
432
+ {lastUpdated && (
433
+ <small>Last saved: {lastUpdated.toLocaleTimeString()}</small>
434
+ )}
435
+ </div>
436
+ </form>
437
+ );
438
+ }
439
+ ```
440
+
441
+ ### Error Handling & Recovery
442
+
443
+ ```tsx
444
+ function RobustComponent() {
445
+ const {
446
+ data: settings,
447
+ update: updateSettings,
448
+ reset: resetSettings,
449
+ error,
450
+ refresh,
451
+ loading
452
+ } = useIDBStorage({
453
+ key: 'user-settings',
454
+ defaultValue: { theme: 'light', notifications: true }
455
+ });
456
+
457
+ // Handle errors gracefully
458
+ if (error) {
459
+ return (
460
+ <div>
461
+ <p>Failed to load settings: {error.message}</p>
462
+ <button onClick={() => refresh()}>Retry</button>
463
+ <button onClick={resetSettings}>Reset to Defaults</button>
464
+ </div>
465
+ );
466
+ }
467
+
468
+ if (loading) return <div>Loading settings...</div>;
469
+
470
+ return (
471
+ <div>
472
+ <label>
473
+ Theme:
474
+ <select
475
+ value={settings.theme}
476
+ onChange={e => updateSettings(prev => ({
477
+ ...prev,
478
+ theme: e.target.value
479
+ }))}
480
+ >
481
+ <option value="light">Light</option>
482
+ <option value="dark">Dark</option>
483
+ </select>
484
+ </label>
485
+ </div>
486
+ );
487
+ }
488
+ ```
489
+
490
+ ## 🔧 Advanced Usage
491
+
492
+ ### Custom Stores & Configuration
493
+
494
+ ```tsx
495
+ import { IDBConfig } from 'use-idb-storage';
496
+
497
+ function App() {
498
+ return (
499
+ <IDBConfig database="my-app" version={2} store="main">
500
+ <MyComponents />
501
+ </IDBConfig>
502
+ );
503
+ }
504
+
505
+ function MyComponents() {
506
+ // Uses context config
507
+ const [user] = useIDBStorage({
508
+ key: 'user',
509
+ defaultValue: { name: '' }
510
+ });
511
+
512
+ // Override config for specific data
513
+ const [cache] = useIDBStorage({
514
+ key: 'api-cache',
515
+ defaultValue: {},
516
+ store: 'cache' // Different store
517
+ });
518
+
519
+ return <div>...</div>;
520
+ }
272
521
  ```
273
522
 
274
523
  ### Migration Strategy
275
524
 
276
- For database migrations, increment the version number:
525
+ Increment version for schema changes:
277
526
 
278
- ```typescript
279
- // Version 1
527
+ ```tsx
528
+ // Version 1: Basic user data
280
529
  <IDBConfig database="my-app" version={1} store="data">
281
530
  <App />
282
531
  </IDBConfig>
283
532
 
284
- // Version 2 (with new features)
533
+ // Version 2: Added preferences
285
534
  <IDBConfig database="my-app" version={2} store="data">
286
535
  <App />
287
536
  </IDBConfig>
288
537
  ```
289
538
 
290
- ## Browser Support
539
+ ### Class-based API (Advanced)
540
+
541
+ For complex operations, use the class-based API:
542
+
543
+ ```tsx
544
+ import { IDBStorage } from 'use-idb-storage';
545
+
546
+ const db = new IDBStorage({ database: 'my-app' });
547
+ const store = await db.get('analytics');
548
+
549
+ // Batch operations
550
+ await store.setMany([
551
+ ['pageviews', 1234],
552
+ ['sessions', 89],
553
+ ['bounce-rate', 0.45]
554
+ ]);
555
+
556
+ // Advanced queries
557
+ const allKeys = await store.keys();
558
+ const allData = await store.values();
559
+ ```
560
+
561
+ ## 🆚 Why useIDBStorage vs Alternatives?
562
+
563
+ | Feature | useIDBStorage | localStorage | useState | Redux Persist | SWR |
564
+ |---------|---------------|--------------|----------|---------------|-----|
565
+ | **API Simplicity** | `useState`-like ✅ | Basic key-value | `useState`-like | Complex setup | Complex setup |
566
+ | **TypeScript** | Full type safety ✅ | Manual typing | Full types | Complex types | Complex types |
567
+ | **Error Handling** | Built-in ✅ | None | None | Limited | Built-in |
568
+ | **Loading States** | ✅ | None | None | Limited | ✅ |
569
+ | **Persistence** | IndexedDB ✅ | localStorage | None | Configurable | Cache only |
570
+ | **Performance** | Near-native ✅ | Fast | Fast | Overhead | Network calls |
571
+ | **Storage Limits** | ~1GB+ ✅ | ~5-10MB | None | ~5-10MB | Memory only |
572
+ | **Structured Data** | ✅ | JSON strings | Any | Any | Any |
573
+ | **Batch Operations** | ✅ | Manual | N/A | Manual | Limited |
574
+ | **React Integration** | Seamless ✅ | Manual effects | Built-in | Complex | Built-in |
575
+
576
+ ## 🎉 Key Benefits
577
+
578
+ - **🚀 Drop-in Replacement**: Use exactly like `useState` but with persistence
579
+ - **⚡ High Performance**: Minimal overhead, near-native speed
580
+ - **🛡️ Error Resilient**: Built-in error handling and recovery
581
+ - **🔄 Reactive**: Automatic re-renders with loading/error states
582
+ - **📱 Offline-First**: Works without network connectivity
583
+ - **🏗️ Scalable**: Handles complex data structures and large datasets
584
+ - **🔧 Developer Experience**: Rich debugging with status indicators
585
+
586
+ ## 🌐 Browser Support
291
587
 
292
588
  - Chrome 24+
293
589
  - Firefox 16+
294
590
  - Safari 10+
295
591
  - Edge 12+
296
592
 
297
- ## License
593
+ ## 📄 License
298
594
 
299
595
  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.2",
4
4
  "author": "Sohan Emon <sohanemon@outlook.com>",
5
5
  "description": "",
6
6
  "type": "module",