use-idb-storage 0.1.0 → 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 +393 -77
- package/dist/index.d.ts +55 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,56 +1,62 @@
|
|
|
1
|
-
#
|
|
1
|
+
# useIDBStorage
|
|
2
|
+
|
|
2
3
|

|
|
3
4
|

|
|
4
5
|

|
|
5
6
|

|
|
6
7
|
|
|
7
|
-
|
|
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
|
-
##
|
|
21
|
+
## ✨ Why useIDBStorage?
|
|
10
22
|
|
|
11
|
-
- 🚀 **
|
|
12
|
-
- 🔄 **Reactive**:
|
|
13
|
-
- 🏪 **Multi-store**:
|
|
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
|
-
##
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
npm install use-idb-storage
|
|
22
|
-
```
|
|
31
|
+
## ⚡ Performance
|
|
23
32
|
|
|
24
|
-
|
|
33
|
+
**Near-native performance** with minimal overhead. Benchmark shows only **1.5ms difference** vs `useState` for 400 forced updates:
|
|
25
34
|
|
|
26
|
-
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"useState": { "renders": 400, "time": 3317.9 },
|
|
38
|
+
"useIDBStorage": { "renders": 400, "time": 3316.4 },
|
|
39
|
+
"differenceMs": 1.5
|
|
40
|
+
}
|
|
41
|
+
```
|
|
27
42
|
|
|
28
|
-
|
|
29
|
-
import { idb } from 'use-idb-storage';
|
|
43
|
+
See [PERFORMANCE.md](./PERFORMANCE.md) for detailed benchmarks.
|
|
30
44
|
|
|
31
|
-
|
|
32
|
-
await idb.store.set('user', { name: 'John' });
|
|
33
|
-
const user = await idb.store.get('user');
|
|
45
|
+
## Installation
|
|
34
46
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
await customStore.set('theme', 'dark');
|
|
47
|
+
```bash
|
|
48
|
+
npm install use-idb-storage
|
|
38
49
|
```
|
|
39
50
|
|
|
40
|
-
|
|
51
|
+
## 🚀 Quick Start
|
|
41
52
|
|
|
42
|
-
|
|
43
|
-
import { useIDBStorage, IDBConfig } from 'use-idb-storage';
|
|
53
|
+
### Simple as useState (but with persistence!)
|
|
44
54
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
<IDBConfig database="my-app" version={1} store="data">
|
|
48
|
-
<MyComponent />
|
|
49
|
-
</IDBConfig>
|
|
50
|
-
);
|
|
51
|
-
}
|
|
55
|
+
```tsx
|
|
56
|
+
import { useIDBStorage } from 'use-idb-storage';
|
|
52
57
|
|
|
53
58
|
function MyComponent() {
|
|
59
|
+
// Just like useState - but persists to IndexedDB!
|
|
54
60
|
const [user, setUser, removeUser] = useIDBStorage({
|
|
55
61
|
key: 'current-user',
|
|
56
62
|
defaultValue: { name: '', email: '' }
|
|
@@ -63,6 +69,68 @@ function MyComponent() {
|
|
|
63
69
|
onChange={e => setUser({ ...user, name: e.target.value })}
|
|
64
70
|
/>
|
|
65
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>
|
|
66
134
|
</div>
|
|
67
135
|
);
|
|
68
136
|
}
|
|
@@ -164,29 +232,72 @@ Provides operations for a specific object store.
|
|
|
164
232
|
|
|
165
233
|
### useIDBStorage Hook
|
|
166
234
|
|
|
167
|
-
React hook for
|
|
235
|
+
**The most powerful React hook for IndexedDB state management.** Supports both simple `useState`-style usage and advanced object destructuring with rich features.
|
|
168
236
|
|
|
169
|
-
|
|
170
|
-
|
|
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
|
+
});
|
|
171
267
|
```
|
|
172
268
|
|
|
173
269
|
#### Parameters
|
|
174
270
|
|
|
175
271
|
```typescript
|
|
176
272
|
interface IDBStorageOptions<T> {
|
|
177
|
-
key: string;
|
|
178
|
-
defaultValue: T;
|
|
179
|
-
database?: string;
|
|
180
|
-
version?: number;
|
|
181
|
-
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)
|
|
182
278
|
}
|
|
183
279
|
```
|
|
184
280
|
|
|
185
|
-
####
|
|
281
|
+
#### Return Types
|
|
282
|
+
|
|
283
|
+
**Tuple Destructuring** (useState-compatible):
|
|
284
|
+
```typescript
|
|
285
|
+
[value: T, setValue: (value: T | ((prev: T) => T)) => void, removeValue: () => void]
|
|
286
|
+
```
|
|
186
287
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
+
```
|
|
190
301
|
|
|
191
302
|
### IDBConfig Provider
|
|
192
303
|
|
|
@@ -208,72 +319,277 @@ interface IDBConfigValues {
|
|
|
208
319
|
}
|
|
209
320
|
```
|
|
210
321
|
|
|
211
|
-
##
|
|
322
|
+
## 🎯 Real-World Examples
|
|
212
323
|
|
|
213
|
-
###
|
|
324
|
+
### User Authentication State
|
|
214
325
|
|
|
215
|
-
```
|
|
216
|
-
|
|
326
|
+
```tsx
|
|
327
|
+
function AuthProvider({ children }) {
|
|
328
|
+
const { data: user, update: setUser, reset: logout, loading } = useIDBStorage({
|
|
329
|
+
key: 'auth-user',
|
|
330
|
+
defaultValue: null
|
|
331
|
+
});
|
|
217
332
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const
|
|
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
|
+
};
|
|
221
341
|
|
|
222
|
-
|
|
223
|
-
|
|
342
|
+
if (loading) return <div>Loading...</div>;
|
|
343
|
+
|
|
344
|
+
return (
|
|
345
|
+
<AuthContext.Provider value={{ user, login, logout }}>
|
|
346
|
+
{children}
|
|
347
|
+
</AuthContext.Provider>
|
|
348
|
+
);
|
|
349
|
+
}
|
|
224
350
|
```
|
|
225
351
|
|
|
226
|
-
###
|
|
352
|
+
### Shopping Cart with Persistence
|
|
227
353
|
|
|
228
|
-
```
|
|
229
|
-
|
|
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
|
+
});
|
|
230
366
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
367
|
+
const addItem = (item) => {
|
|
368
|
+
updateCart(prev => ({
|
|
369
|
+
items: [...prev.items, item],
|
|
370
|
+
total: prev.total + item.price
|
|
371
|
+
}));
|
|
372
|
+
};
|
|
237
373
|
|
|
238
|
-
|
|
239
|
-
|
|
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
|
+
}
|
|
240
389
|
```
|
|
241
390
|
|
|
242
|
-
###
|
|
391
|
+
### Form with Auto-Save
|
|
243
392
|
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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);
|
|
412
|
+
|
|
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
|
+
});
|
|
249
518
|
|
|
250
|
-
|
|
251
|
-
|
|
519
|
+
return <div>...</div>;
|
|
520
|
+
}
|
|
252
521
|
```
|
|
253
522
|
|
|
254
523
|
### Migration Strategy
|
|
255
524
|
|
|
256
|
-
|
|
525
|
+
Increment version for schema changes:
|
|
257
526
|
|
|
258
|
-
```
|
|
259
|
-
// Version 1
|
|
527
|
+
```tsx
|
|
528
|
+
// Version 1: Basic user data
|
|
260
529
|
<IDBConfig database="my-app" version={1} store="data">
|
|
261
530
|
<App />
|
|
262
531
|
</IDBConfig>
|
|
263
532
|
|
|
264
|
-
// Version 2
|
|
533
|
+
// Version 2: Added preferences
|
|
265
534
|
<IDBConfig database="my-app" version={2} store="data">
|
|
266
535
|
<App />
|
|
267
536
|
</IDBConfig>
|
|
268
537
|
```
|
|
269
538
|
|
|
270
|
-
|
|
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
|
|
271
587
|
|
|
272
588
|
- Chrome 24+
|
|
273
589
|
- Firefox 16+
|
|
274
590
|
- Safari 10+
|
|
275
591
|
- Edge 12+
|
|
276
592
|
|
|
277
|
-
## License
|
|
593
|
+
## 📄 License
|
|
278
594
|
|
|
279
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> =
|
|
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
|
|
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=()=>{
|
|
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};
|