kvozy 0.2.0 → 0.4.0
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 +220 -36
- package/__compiled__/cjs/src/bindValue.d.ts +12 -7
- package/__compiled__/cjs/src/bindValue.js +54 -3
- package/__compiled__/cjs/src/bindValue.js.map +1 -1
- package/__compiled__/cjs/src/useStorage.d.ts +4 -4
- package/__compiled__/cjs/src/useStorage.js.map +1 -1
- package/__compiled__/esm/src/bindValue.d.mts +12 -7
- package/__compiled__/esm/src/bindValue.mjs +54 -3
- package/__compiled__/esm/src/bindValue.mjs.map +1 -1
- package/__compiled__/esm/src/useStorage.d.mts +4 -4
- package/__compiled__/esm/src/useStorage.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,13 +14,15 @@ This architecture makes it easy to add connectors for other frameworks (Vue, Sve
|
|
|
14
14
|
## Features
|
|
15
15
|
|
|
16
16
|
- Framework-agnostic core (bindValue)
|
|
17
|
+
- Type-safe generic API with custom serialization/deserialization
|
|
17
18
|
- Flexible storage support (localStorage, sessionStorage, in-memory)
|
|
18
19
|
- Graceful fallback to in-memory storage when storage is unavailable
|
|
19
20
|
- Thin React integration (useStorage)
|
|
20
|
-
- String-only values (simple, predictable)
|
|
21
21
|
- Real localStorage/backend storage
|
|
22
22
|
- Subscription-based reactivity
|
|
23
23
|
- TypeScript support
|
|
24
|
+
- Required default values for safety
|
|
25
|
+
- Schema versioning and migration support
|
|
24
26
|
- Easy to extend to other frameworks
|
|
25
27
|
|
|
26
28
|
## Installation
|
|
@@ -36,14 +38,19 @@ npm install kvozy
|
|
|
36
38
|
```typescript
|
|
37
39
|
import { bindValue, useStorage } from 'kvozy';
|
|
38
40
|
|
|
39
|
-
// Define the binding
|
|
40
|
-
const myValue = bindValue({
|
|
41
|
+
// Define the binding with type, serialize, deserialize, and defaultValue
|
|
42
|
+
const myValue = bindValue<string>({
|
|
43
|
+
key: 'my-key',
|
|
44
|
+
defaultValue: '',
|
|
45
|
+
serialize: (v) => v,
|
|
46
|
+
deserialize: (s) => s,
|
|
47
|
+
});
|
|
41
48
|
|
|
42
49
|
// Use in component
|
|
43
50
|
const Component = () => {
|
|
44
51
|
const { value, setValue } = useStorage(myValue);
|
|
45
52
|
|
|
46
|
-
return <input value={value
|
|
53
|
+
return <input value={value} onChange={(e) => setValue(e.target.value)} />;
|
|
47
54
|
};
|
|
48
55
|
```
|
|
49
56
|
|
|
@@ -70,23 +77,39 @@ The in-memory storage ensures your application remains functional, maintaining s
|
|
|
70
77
|
|
|
71
78
|
> ⚠️ **Note:** `bindValue` is an internal API. Use `useStorage` for React components. Direct usage of `bindValue` is not recommended.
|
|
72
79
|
|
|
73
|
-
###
|
|
80
|
+
### BindValueOptions<T>
|
|
81
|
+
|
|
82
|
+
Options for creating a BindValue instance.
|
|
83
|
+
|
|
84
|
+
**Parameters:**
|
|
85
|
+
|
|
86
|
+
- `key` (string, required) - Storage key
|
|
87
|
+
- `defaultValue` (T, required) - Default value when key doesn't exist or deserialize fails
|
|
88
|
+
- `serialize` (function, required) - Convert value to string: `(value: T) => string`
|
|
89
|
+
- `deserialize` (function, required) - Convert string to value: `(serialized: string) => T`
|
|
90
|
+
- `storage` (Storage, optional) - localStorage, sessionStorage, or undefined for in-memory
|
|
91
|
+
- `version` (string, optional) - Schema version for migration support
|
|
92
|
+
- `migrate` (function, optional) - Migration function: `(oldSerialized: string, oldVersion: string | undefined) => T`
|
|
93
|
+
|
|
94
|
+
### useStorage<T>(binding)
|
|
74
95
|
|
|
75
96
|
React hook that connects a BindValue instance to React state.
|
|
76
97
|
|
|
77
98
|
**Parameters:**
|
|
78
99
|
|
|
79
|
-
- `binding` (BindValue
|
|
100
|
+
- `binding` (BindValue<T>, required) - binding instance from bindValue
|
|
80
101
|
|
|
81
102
|
**Returns:** `{ value, setValue }`
|
|
82
103
|
|
|
83
|
-
- `value` - `
|
|
84
|
-
- `setValue` - `(value:
|
|
104
|
+
- `value` - `T` - current value from storage
|
|
105
|
+
- `setValue` - `(value: T) => void` - function to update value
|
|
85
106
|
|
|
86
107
|
**Behavior:**
|
|
87
108
|
|
|
88
109
|
- `subscribe()` does NOT call the callback immediately when subscribing
|
|
89
110
|
- Callbacks are only invoked when the value changes via `set()`
|
|
111
|
+
- If `serialize()` fails, in-memory value is kept but storage is NOT updated
|
|
112
|
+
- If `deserialize()` fails, `defaultValue` is returned
|
|
90
113
|
|
|
91
114
|
**Example:**
|
|
92
115
|
|
|
@@ -95,7 +118,7 @@ const Component = () => {
|
|
|
95
118
|
const { value, setValue } = useStorage(myBinding);
|
|
96
119
|
|
|
97
120
|
return <div>
|
|
98
|
-
<p>Current value: {value
|
|
121
|
+
<p>Current value: {value}</p>
|
|
99
122
|
<button onClick={() => setValue('new value')}>
|
|
100
123
|
Update Value
|
|
101
124
|
</button>
|
|
@@ -110,7 +133,12 @@ const Component = () => {
|
|
|
110
133
|
```typescript
|
|
111
134
|
import { bindValue, useStorage } from 'kvozy';
|
|
112
135
|
|
|
113
|
-
const usernameBinding = bindValue({
|
|
136
|
+
const usernameBinding = bindValue<string>({
|
|
137
|
+
key: 'username',
|
|
138
|
+
defaultValue: '',
|
|
139
|
+
serialize: (v) => v,
|
|
140
|
+
deserialize: (s) => s,
|
|
141
|
+
});
|
|
114
142
|
|
|
115
143
|
const LoginForm = () => {
|
|
116
144
|
const { value, setValue } = useStorage(usernameBinding);
|
|
@@ -120,7 +148,7 @@ const LoginForm = () => {
|
|
|
120
148
|
<label>
|
|
121
149
|
Username:
|
|
122
150
|
<input
|
|
123
|
-
value={value
|
|
151
|
+
value={value}
|
|
124
152
|
onChange={(e) => setValue(e.target.value)}
|
|
125
153
|
/>
|
|
126
154
|
</label>
|
|
@@ -134,7 +162,12 @@ const LoginForm = () => {
|
|
|
134
162
|
```typescript
|
|
135
163
|
import { bindValue, useStorage } from 'kvozy';
|
|
136
164
|
|
|
137
|
-
const themeBinding = bindValue({
|
|
165
|
+
const themeBinding = bindValue<string>({
|
|
166
|
+
key: 'theme',
|
|
167
|
+
defaultValue: 'light',
|
|
168
|
+
serialize: (v) => v,
|
|
169
|
+
deserialize: (s) => s,
|
|
170
|
+
});
|
|
138
171
|
|
|
139
172
|
const ThemeToggle = () => {
|
|
140
173
|
const { value, setValue } = useStorage(themeBinding);
|
|
@@ -164,15 +197,20 @@ Both components stay in sync automatically!
|
|
|
164
197
|
|
|
165
198
|
### Handling Undefined Values
|
|
166
199
|
|
|
167
|
-
When a localStorage key doesn't exist, `
|
|
200
|
+
When a localStorage key doesn't exist, `defaultValue` is returned:
|
|
168
201
|
|
|
169
202
|
```typescript
|
|
170
|
-
const binding = bindValue({
|
|
171
|
-
|
|
203
|
+
const binding = bindValue<string>({
|
|
204
|
+
key: 'non-existent-key',
|
|
205
|
+
defaultValue: 'default value',
|
|
206
|
+
serialize: (v) => v,
|
|
207
|
+
deserialize: (s) => s,
|
|
208
|
+
});
|
|
209
|
+
console.log(binding.getValue()); // 'default value'
|
|
172
210
|
|
|
173
211
|
const Component = () => {
|
|
174
212
|
const { value } = useStorage(binding);
|
|
175
|
-
return <div>{value
|
|
213
|
+
return <div>{value}</div>;
|
|
176
214
|
};
|
|
177
215
|
```
|
|
178
216
|
|
|
@@ -184,17 +222,34 @@ Kvozy supports localStorage, sessionStorage, and in-memory storage:
|
|
|
184
222
|
import { bindValue, useStorage } from 'kvozy';
|
|
185
223
|
|
|
186
224
|
// localStorage - persists across browser sessions
|
|
187
|
-
const localBinding = bindValue({
|
|
225
|
+
const localBinding = bindValue<string>({
|
|
226
|
+
key: 'theme',
|
|
227
|
+
defaultValue: 'light',
|
|
228
|
+
serialize: (v) => v,
|
|
229
|
+
deserialize: (s) => s,
|
|
230
|
+
storage: localStorage,
|
|
231
|
+
});
|
|
188
232
|
|
|
189
233
|
// sessionStorage - persists within the same tab
|
|
190
|
-
const sessionBinding = bindValue({
|
|
234
|
+
const sessionBinding = bindValue<string>({
|
|
235
|
+
key: 'form-data',
|
|
236
|
+
defaultValue: '',
|
|
237
|
+
serialize: (v) => v,
|
|
238
|
+
deserialize: (s) => s,
|
|
239
|
+
storage: sessionStorage,
|
|
240
|
+
});
|
|
191
241
|
|
|
192
242
|
// In-memory - no persistence, graceful fallback
|
|
193
|
-
const memoryBinding = bindValue({
|
|
243
|
+
const memoryBinding = bindValue<string>({
|
|
244
|
+
key: 'temp-state',
|
|
245
|
+
defaultValue: '',
|
|
246
|
+
serialize: (v) => v,
|
|
247
|
+
deserialize: (s) => s,
|
|
248
|
+
});
|
|
194
249
|
|
|
195
250
|
const LocalComponent = () => {
|
|
196
251
|
const { value, setValue } = useStorage(localBinding);
|
|
197
|
-
return <div>Theme: {value
|
|
252
|
+
return <div>Theme: {value}</div>;
|
|
198
253
|
};
|
|
199
254
|
|
|
200
255
|
const SessionComponent = () => {
|
|
@@ -211,7 +266,12 @@ const MemoryComponent = () => {
|
|
|
211
266
|
### Persisting Form Data
|
|
212
267
|
|
|
213
268
|
```typescript
|
|
214
|
-
const formBinding = bindValue({
|
|
269
|
+
const formBinding = bindValue<string>({
|
|
270
|
+
key: 'form-data',
|
|
271
|
+
defaultValue: '',
|
|
272
|
+
serialize: (v) => v,
|
|
273
|
+
deserialize: (s) => s,
|
|
274
|
+
});
|
|
215
275
|
|
|
216
276
|
const Form = () => {
|
|
217
277
|
const { value, setValue } = useStorage(formBinding);
|
|
@@ -238,22 +298,144 @@ const Form = () => {
|
|
|
238
298
|
### Counter Example
|
|
239
299
|
|
|
240
300
|
```typescript
|
|
241
|
-
const counterBinding = bindValue({
|
|
301
|
+
const counterBinding = bindValue<number>({
|
|
302
|
+
key: 'counter',
|
|
303
|
+
defaultValue: 0,
|
|
304
|
+
serialize: (v) => String(v),
|
|
305
|
+
deserialize: (s) => Number(s),
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
const Counter = () => {
|
|
309
|
+
const { value, setValue } = useStorage(counterBinding);
|
|
310
|
+
|
|
311
|
+
return (
|
|
312
|
+
<div>
|
|
313
|
+
<p>Count: {value}</p>
|
|
314
|
+
<button onClick={() => setValue(value + 1)}>
|
|
315
|
+
Increment
|
|
316
|
+
</button>
|
|
317
|
+
<button onClick={() => setValue(value - 1)}>
|
|
318
|
+
Decrement
|
|
319
|
+
</button>
|
|
320
|
+
<button onClick={() => setValue(0)}>
|
|
321
|
+
Reset
|
|
322
|
+
</button>
|
|
323
|
+
</div>
|
|
324
|
+
);
|
|
325
|
+
};
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## Schema Versioning and Migration
|
|
329
|
+
|
|
330
|
+
Kvozy supports schema evolution through optional versioning and migration functions. This allows you to safely update your data structure without breaking existing users' stored data.
|
|
331
|
+
|
|
332
|
+
### Basic Versioning
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
const userBinding = bindValue<User>({
|
|
336
|
+
key: "user",
|
|
337
|
+
defaultValue: { name: "", age: 0 },
|
|
338
|
+
serialize: (v) => JSON.stringify(v),
|
|
339
|
+
deserialize: (s) => JSON.parse(s),
|
|
340
|
+
version: "1.0.0",
|
|
341
|
+
});
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### Migration Example
|
|
345
|
+
|
|
346
|
+
When you change your data structure, provide a migration function:
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
// Version 1.0.0: stored as string
|
|
350
|
+
const themeBindingV1 = bindValue<string>({
|
|
351
|
+
key: "theme",
|
|
352
|
+
defaultValue: "light",
|
|
353
|
+
serialize: (v) => v,
|
|
354
|
+
deserialize: (s) => s,
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
// Version 2.0.0: store as object with additional metadata
|
|
358
|
+
const themeBindingV2 = bindValue<{ value: string; lastUpdated: number }>({
|
|
359
|
+
key: "theme",
|
|
360
|
+
defaultValue: { value: "light", lastUpdated: Date.now() },
|
|
361
|
+
serialize: (v) => JSON.stringify(v),
|
|
362
|
+
deserialize: (s) => JSON.parse(s),
|
|
363
|
+
version: "2.0.0",
|
|
364
|
+
migrate: (oldSerialized, oldVersion) => {
|
|
365
|
+
if (oldVersion === "1.0.0" || oldVersion === undefined) {
|
|
366
|
+
// Migrate from string to object
|
|
367
|
+
return {
|
|
368
|
+
value: oldSerialized,
|
|
369
|
+
lastUpdated: Date.now(),
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
// Fallback to default for unknown versions
|
|
373
|
+
return { value: "light", lastUpdated: Date.now() };
|
|
374
|
+
},
|
|
375
|
+
});
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### Common Migration Patterns
|
|
379
|
+
|
|
380
|
+
**Add new field:**
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
migrate: (oldSerialized, oldVersion) => {
|
|
384
|
+
const oldData = JSON.parse(oldSerialized);
|
|
385
|
+
return { ...oldData, newField: "default" };
|
|
386
|
+
};
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
**Rename field:**
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
migrate: (oldSerialized) => {
|
|
393
|
+
const oldData = JSON.parse(oldSerialized);
|
|
394
|
+
return { newName: oldData.oldName };
|
|
395
|
+
};
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
**Change data type:**
|
|
399
|
+
|
|
400
|
+
```typescript
|
|
401
|
+
migrate: (oldSerialized) => {
|
|
402
|
+
const dateString = oldSerialized;
|
|
403
|
+
return { date: new Date(dateString) };
|
|
404
|
+
};
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Migration Behavior
|
|
408
|
+
|
|
409
|
+
- When `version` is provided, values are stored with a version prefix
|
|
410
|
+
- On load, if versions mismatch, the `migrate` function is called
|
|
411
|
+
- If `migrate` is undefined or fails, the `defaultValue` is used
|
|
412
|
+
- Old data is automatically cleaned up when using default fallback
|
|
413
|
+
- Migration receives the raw serialized string (not deserialized)
|
|
414
|
+
- Migration failures are handled silently
|
|
415
|
+
|
|
416
|
+
This ensures your application works even when users have old data formats, and new users get the default structure.
|
|
417
|
+
|
|
418
|
+
```typescript
|
|
419
|
+
const counterBinding = bindValue<number>({
|
|
420
|
+
key: 'counter',
|
|
421
|
+
defaultValue: 0,
|
|
422
|
+
serialize: (v) => String(v),
|
|
423
|
+
deserialize: (s) => Number(s),
|
|
424
|
+
});
|
|
242
425
|
|
|
243
426
|
const Counter = () => {
|
|
244
427
|
const { value, setValue } = useStorage(counterBinding);
|
|
245
|
-
const count = parseInt(value || '0', 10);
|
|
246
428
|
|
|
247
429
|
return (
|
|
248
430
|
<div>
|
|
249
|
-
<p>Count: {
|
|
250
|
-
<button onClick={() => setValue(
|
|
431
|
+
<p>Count: {value}</p>
|
|
432
|
+
<button onClick={() => setValue(value + 1)}>
|
|
251
433
|
Increment
|
|
252
434
|
</button>
|
|
253
|
-
<button onClick={() => setValue(
|
|
435
|
+
<button onClick={() => setValue(value - 1)}>
|
|
254
436
|
Decrement
|
|
255
437
|
</button>
|
|
256
|
-
<button onClick={() => setValue(
|
|
438
|
+
<button onClick={() => setValue(0)}>
|
|
257
439
|
Reset
|
|
258
440
|
</button>
|
|
259
441
|
</div>
|
|
@@ -268,18 +450,19 @@ const Counter = () => {
|
|
|
268
450
|
All storage logic lives in `bindValue` class:
|
|
269
451
|
|
|
270
452
|
- Framework-agnostic
|
|
453
|
+
- Type-safe generic API
|
|
271
454
|
- Manages localStorage operations
|
|
272
455
|
- Handles subscriptions
|
|
273
456
|
- Can be used with any UI framework
|
|
274
457
|
|
|
275
458
|
```typescript
|
|
276
|
-
class BindValue {
|
|
277
|
-
private value:
|
|
278
|
-
private subscribers: Set<(value:
|
|
459
|
+
class BindValue<T> {
|
|
460
|
+
private value: T;
|
|
461
|
+
private subscribers: Set<(value: T) => void>;
|
|
279
462
|
|
|
280
|
-
getValue():
|
|
281
|
-
set(value:
|
|
282
|
-
subscribe(callback): () => void;
|
|
463
|
+
getValue(): T;
|
|
464
|
+
set(value: T): void;
|
|
465
|
+
subscribe(callback: (value: T) => void): () => void;
|
|
283
466
|
}
|
|
284
467
|
```
|
|
285
468
|
|
|
@@ -293,7 +476,7 @@ Thin wrapper that connects `bindValue` to React:
|
|
|
293
476
|
- Returns `{ value, setValue }`
|
|
294
477
|
|
|
295
478
|
```typescript
|
|
296
|
-
function useStorage(binding: BindValue): UseStorageReturn {
|
|
479
|
+
function useStorage<T>(binding: BindValue<T>): UseStorageReturn<T> {
|
|
297
480
|
const [value, setValue] = useState(binding.getValue());
|
|
298
481
|
|
|
299
482
|
useEffect(() => {
|
|
@@ -301,7 +484,7 @@ function useStorage(binding: BindValue): UseStorageReturn {
|
|
|
301
484
|
return unsubscribe;
|
|
302
485
|
}, [binding]);
|
|
303
486
|
|
|
304
|
-
const set = (newValue:
|
|
487
|
+
const set = (newValue: T) => binding.set(newValue);
|
|
305
488
|
|
|
306
489
|
return { value, setValue: set };
|
|
307
490
|
}
|
|
@@ -309,9 +492,9 @@ function useStorage(binding: BindValue): UseStorageReturn {
|
|
|
309
492
|
|
|
310
493
|
## Limitations
|
|
311
494
|
|
|
312
|
-
- **String-only values**: Kvozy only supports string values. For objects or arrays, serialize them as JSON.
|
|
313
495
|
- **No SSR support**: Currently designed for client-side only (requires `window.localStorage`).
|
|
314
496
|
- **No cross-tab sync**: Changes in one tab don't update other tabs automatically.
|
|
497
|
+
- **Serialize failures**: If `serialize()` fails, the value is kept in memory but not persisted to storage (graceful degradation).
|
|
315
498
|
|
|
316
499
|
## Future Plans
|
|
317
500
|
|
|
@@ -320,3 +503,4 @@ function useStorage(binding: BindValue): UseStorageReturn {
|
|
|
320
503
|
- [ ] Angular connector
|
|
321
504
|
- [ ] SSR support
|
|
322
505
|
- [ ] Cross-tab synchronization
|
|
506
|
+
- [ ] Console warnings on serialization/deserialization errors for debugging
|
|
@@ -1,18 +1,23 @@
|
|
|
1
|
-
export interface BindValueOptions {
|
|
1
|
+
export interface BindValueOptions<T> {
|
|
2
2
|
key: string;
|
|
3
|
+
defaultValue: T;
|
|
4
|
+
serialize: (value: T) => string;
|
|
5
|
+
deserialize: (serialized: string) => T;
|
|
3
6
|
storage?: Storage;
|
|
7
|
+
version?: string;
|
|
8
|
+
migrate?: (oldSerialized: string, oldVersion: string | undefined) => T;
|
|
4
9
|
}
|
|
5
|
-
export declare class BindValue {
|
|
10
|
+
export declare class BindValue<T> {
|
|
6
11
|
private options;
|
|
7
12
|
private value;
|
|
8
13
|
private subscribers;
|
|
9
14
|
private storage;
|
|
10
|
-
constructor(options: BindValueOptions);
|
|
11
|
-
getValue():
|
|
12
|
-
set(newValue:
|
|
13
|
-
subscribe(callback: (value:
|
|
15
|
+
constructor(options: BindValueOptions<T>);
|
|
16
|
+
getValue(): T;
|
|
17
|
+
set(newValue: T): void;
|
|
18
|
+
subscribe(callback: (value: T) => void): () => void;
|
|
14
19
|
private loadFromStorage;
|
|
15
20
|
private saveToStorage;
|
|
16
21
|
private notifySubscribers;
|
|
17
22
|
}
|
|
18
|
-
export declare function bindValue(options: BindValueOptions): BindValue
|
|
23
|
+
export declare function bindValue<T>(options: BindValueOptions<T>): BindValue<T>;
|
|
@@ -50,11 +50,62 @@ class BindValue {
|
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
52
|
loadFromStorage() {
|
|
53
|
-
const
|
|
54
|
-
|
|
53
|
+
const rawValue = this.storage.getItem(this.options.key);
|
|
54
|
+
if (rawValue === null) {
|
|
55
|
+
return this.options.defaultValue;
|
|
56
|
+
}
|
|
57
|
+
let oldVersion;
|
|
58
|
+
let serializedValue;
|
|
59
|
+
if (rawValue.startsWith("\0")) {
|
|
60
|
+
const parts = rawValue.split("\0");
|
|
61
|
+
if (parts.length >= 3) {
|
|
62
|
+
oldVersion = parts[1];
|
|
63
|
+
serializedValue = parts.slice(2).join("\0");
|
|
64
|
+
} else {
|
|
65
|
+
serializedValue = rawValue;
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
serializedValue = rawValue;
|
|
69
|
+
}
|
|
70
|
+
const currentVersion = this.options.version;
|
|
71
|
+
if (oldVersion !== currentVersion) {
|
|
72
|
+
if (this.options.migrate) {
|
|
73
|
+
try {
|
|
74
|
+
const migratedValue = this.options.migrate(
|
|
75
|
+
serializedValue,
|
|
76
|
+
oldVersion
|
|
77
|
+
);
|
|
78
|
+
this.saveToStorage(migratedValue);
|
|
79
|
+
return migratedValue;
|
|
80
|
+
} catch {
|
|
81
|
+
this.storage.removeItem(this.options.key);
|
|
82
|
+
return this.options.defaultValue;
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
this.storage.removeItem(this.options.key);
|
|
86
|
+
return this.options.defaultValue;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
return this.options.deserialize(serializedValue);
|
|
91
|
+
} catch {
|
|
92
|
+
return this.options.defaultValue;
|
|
93
|
+
}
|
|
55
94
|
}
|
|
56
95
|
saveToStorage(value) {
|
|
57
|
-
|
|
96
|
+
try {
|
|
97
|
+
const serialized = this.options.serialize(value);
|
|
98
|
+
const version = this.options.version;
|
|
99
|
+
if (version) {
|
|
100
|
+
this.storage.setItem(
|
|
101
|
+
this.options.key,
|
|
102
|
+
`\0${version}\0${serialized}`
|
|
103
|
+
);
|
|
104
|
+
} else {
|
|
105
|
+
this.storage.setItem(this.options.key, serialized);
|
|
106
|
+
}
|
|
107
|
+
} catch {
|
|
108
|
+
}
|
|
58
109
|
}
|
|
59
110
|
notifySubscribers() {
|
|
60
111
|
for (const subscriber of this.subscribers) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bindValue.js","sources":["../../../../src/bindValue.ts"],"sourcesContent":["export interface BindValueOptions {\n key: string;\n storage?: Storage;\n}\n\nconst memoryStorage = new Map<string, string>();\n\nconst inMemoryStorage: Storage = {\n get length() {\n return memoryStorage.size;\n },\n clear() {\n memoryStorage.clear();\n },\n getItem(key: string) {\n return memoryStorage.get(key) ?? null;\n },\n key(index: number) {\n const keys = Array.from(memoryStorage.keys());\n return keys[index] ?? null;\n },\n removeItem(key: string) {\n memoryStorage.delete(key);\n },\n setItem(key: string, value: string) {\n memoryStorage.set(key, value);\n },\n};\n\nexport class BindValue {\n private value:
|
|
1
|
+
{"version":3,"file":"bindValue.js","sources":["../../../../src/bindValue.ts"],"sourcesContent":["export interface BindValueOptions<T> {\n key: string;\n defaultValue: T;\n serialize: (value: T) => string;\n deserialize: (serialized: string) => T;\n storage?: Storage;\n version?: string;\n migrate?: (oldSerialized: string, oldVersion: string | undefined) => T;\n}\n\nconst memoryStorage = new Map<string, string>();\n\nconst inMemoryStorage: Storage = {\n get length() {\n return memoryStorage.size;\n },\n clear() {\n memoryStorage.clear();\n },\n getItem(key: string) {\n return memoryStorage.get(key) ?? null;\n },\n key(index: number) {\n const keys = Array.from(memoryStorage.keys());\n return keys[index] ?? null;\n },\n removeItem(key: string) {\n memoryStorage.delete(key);\n },\n setItem(key: string, value: string) {\n memoryStorage.set(key, value);\n },\n};\n\nexport class BindValue<T> {\n private value: T;\n private subscribers: Set<(value: T) => void>;\n private storage: Storage;\n\n constructor(private options: BindValueOptions<T>) {\n this.storage = options.storage ?? inMemoryStorage;\n this.subscribers = new Set();\n this.value = this.loadFromStorage();\n }\n\n getValue(): T {\n return this.value;\n }\n\n set(newValue: T): void {\n this.value = newValue;\n this.saveToStorage(newValue);\n this.notifySubscribers();\n }\n\n subscribe(callback: (value: T) => void): () => void {\n this.subscribers.add(callback);\n return () => {\n this.subscribers.delete(callback);\n };\n }\n\n private loadFromStorage(): T {\n const rawValue = this.storage.getItem(this.options.key);\n\n if (rawValue === null) {\n return this.options.defaultValue;\n }\n\n let oldVersion: string | undefined;\n let serializedValue: string;\n\n if (rawValue.startsWith(\"\\x00\")) {\n const parts = rawValue.split(\"\\x00\");\n if (parts.length >= 3) {\n oldVersion = parts[1];\n serializedValue = parts.slice(2).join(\"\\x00\");\n } else {\n serializedValue = rawValue;\n }\n } else {\n serializedValue = rawValue;\n }\n\n const currentVersion = this.options.version;\n\n if (oldVersion !== currentVersion) {\n if (this.options.migrate) {\n try {\n const migratedValue = this.options.migrate(\n serializedValue,\n oldVersion,\n );\n this.saveToStorage(migratedValue);\n return migratedValue;\n } catch {\n this.storage.removeItem(this.options.key);\n return this.options.defaultValue;\n }\n } else {\n this.storage.removeItem(this.options.key);\n return this.options.defaultValue;\n }\n }\n\n try {\n return this.options.deserialize(serializedValue);\n } catch {\n return this.options.defaultValue;\n }\n }\n\n private saveToStorage(value: T): void {\n try {\n const serialized = this.options.serialize(value);\n const version = this.options.version;\n\n if (version) {\n this.storage.setItem(\n this.options.key,\n `\\x00${version}\\x00${serialized}`,\n );\n } else {\n this.storage.setItem(this.options.key, serialized);\n }\n } catch {}\n }\n\n private notifySubscribers(): void {\n for (const subscriber of this.subscribers) {\n subscriber(this.value);\n }\n }\n}\n\nexport function bindValue<T>(options: BindValueOptions<T>): BindValue<T> {\n return new BindValue(options);\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type BindValue } from "./bindValue.js";
|
|
2
|
-
export interface UseStorageReturn {
|
|
3
|
-
value:
|
|
4
|
-
setValue: (value:
|
|
2
|
+
export interface UseStorageReturn<T> {
|
|
3
|
+
value: T;
|
|
4
|
+
setValue: (value: T) => void;
|
|
5
5
|
}
|
|
6
|
-
export declare function useStorage(binding: BindValue): UseStorageReturn
|
|
6
|
+
export declare function useStorage<T>(binding: BindValue<T>): UseStorageReturn<T>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useStorage.js","sources":["../../../../src/useStorage.ts"],"sourcesContent":["import { useState, useEffect } from \"react\";\nimport { type BindValue } from \"./bindValue.js\";\n\nexport interface UseStorageReturn {\n value:
|
|
1
|
+
{"version":3,"file":"useStorage.js","sources":["../../../../src/useStorage.ts"],"sourcesContent":["import { useState, useEffect } from \"react\";\nimport { type BindValue } from \"./bindValue.js\";\n\nexport interface UseStorageReturn<T> {\n value: T;\n setValue: (value: T) => void;\n}\n\nexport function useStorage<T>(binding: BindValue<T>): UseStorageReturn<T> {\n const [value, setValue] = useState(binding.getValue());\n\n useEffect(() => {\n const unsubscribe = binding.subscribe(setValue);\n return unsubscribe;\n }, [binding]);\n\n const set = (newValue: T) => {\n binding.set(newValue);\n };\n\n return { value, setValue: set };\n}\n"],"names":["useState","useEffect"],"mappings":";;;AAQO,SAAS,WAAc,SAA4C;AACxE,QAAM,CAAC,OAAO,QAAQ,IAAIA,MAAAA,SAAS,QAAQ,UAAU;AAErDC,QAAAA,UAAU,MAAM;AACd,UAAM,cAAc,QAAQ,UAAU,QAAQ;AAC9C,WAAO;AAAA,EACT,GAAG,CAAC,OAAO,CAAC;AAEZ,QAAM,MAAM,CAAC,aAAgB;AAC3B,YAAQ,IAAI,QAAQ;AAAA,EACtB;AAEA,SAAO,EAAE,OAAO,UAAU,IAAA;AAC5B;;"}
|
|
@@ -1,18 +1,23 @@
|
|
|
1
|
-
export interface BindValueOptions {
|
|
1
|
+
export interface BindValueOptions<T> {
|
|
2
2
|
key: string;
|
|
3
|
+
defaultValue: T;
|
|
4
|
+
serialize: (value: T) => string;
|
|
5
|
+
deserialize: (serialized: string) => T;
|
|
3
6
|
storage?: Storage;
|
|
7
|
+
version?: string;
|
|
8
|
+
migrate?: (oldSerialized: string, oldVersion: string | undefined) => T;
|
|
4
9
|
}
|
|
5
|
-
export declare class BindValue {
|
|
10
|
+
export declare class BindValue<T> {
|
|
6
11
|
private options;
|
|
7
12
|
private value;
|
|
8
13
|
private subscribers;
|
|
9
14
|
private storage;
|
|
10
|
-
constructor(options: BindValueOptions);
|
|
11
|
-
getValue():
|
|
12
|
-
set(newValue:
|
|
13
|
-
subscribe(callback: (value:
|
|
15
|
+
constructor(options: BindValueOptions<T>);
|
|
16
|
+
getValue(): T;
|
|
17
|
+
set(newValue: T): void;
|
|
18
|
+
subscribe(callback: (value: T) => void): () => void;
|
|
14
19
|
private loadFromStorage;
|
|
15
20
|
private saveToStorage;
|
|
16
21
|
private notifySubscribers;
|
|
17
22
|
}
|
|
18
|
-
export declare function bindValue(options: BindValueOptions): BindValue
|
|
23
|
+
export declare function bindValue<T>(options: BindValueOptions<T>): BindValue<T>;
|
|
@@ -48,11 +48,62 @@ class BindValue {
|
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
50
|
loadFromStorage() {
|
|
51
|
-
const
|
|
52
|
-
|
|
51
|
+
const rawValue = this.storage.getItem(this.options.key);
|
|
52
|
+
if (rawValue === null) {
|
|
53
|
+
return this.options.defaultValue;
|
|
54
|
+
}
|
|
55
|
+
let oldVersion;
|
|
56
|
+
let serializedValue;
|
|
57
|
+
if (rawValue.startsWith("\0")) {
|
|
58
|
+
const parts = rawValue.split("\0");
|
|
59
|
+
if (parts.length >= 3) {
|
|
60
|
+
oldVersion = parts[1];
|
|
61
|
+
serializedValue = parts.slice(2).join("\0");
|
|
62
|
+
} else {
|
|
63
|
+
serializedValue = rawValue;
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
serializedValue = rawValue;
|
|
67
|
+
}
|
|
68
|
+
const currentVersion = this.options.version;
|
|
69
|
+
if (oldVersion !== currentVersion) {
|
|
70
|
+
if (this.options.migrate) {
|
|
71
|
+
try {
|
|
72
|
+
const migratedValue = this.options.migrate(
|
|
73
|
+
serializedValue,
|
|
74
|
+
oldVersion
|
|
75
|
+
);
|
|
76
|
+
this.saveToStorage(migratedValue);
|
|
77
|
+
return migratedValue;
|
|
78
|
+
} catch {
|
|
79
|
+
this.storage.removeItem(this.options.key);
|
|
80
|
+
return this.options.defaultValue;
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
this.storage.removeItem(this.options.key);
|
|
84
|
+
return this.options.defaultValue;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
return this.options.deserialize(serializedValue);
|
|
89
|
+
} catch {
|
|
90
|
+
return this.options.defaultValue;
|
|
91
|
+
}
|
|
53
92
|
}
|
|
54
93
|
saveToStorage(value) {
|
|
55
|
-
|
|
94
|
+
try {
|
|
95
|
+
const serialized = this.options.serialize(value);
|
|
96
|
+
const version = this.options.version;
|
|
97
|
+
if (version) {
|
|
98
|
+
this.storage.setItem(
|
|
99
|
+
this.options.key,
|
|
100
|
+
`\0${version}\0${serialized}`
|
|
101
|
+
);
|
|
102
|
+
} else {
|
|
103
|
+
this.storage.setItem(this.options.key, serialized);
|
|
104
|
+
}
|
|
105
|
+
} catch {
|
|
106
|
+
}
|
|
56
107
|
}
|
|
57
108
|
notifySubscribers() {
|
|
58
109
|
for (const subscriber of this.subscribers) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bindValue.mjs","sources":["../../../../src/bindValue.ts"],"sourcesContent":["export interface BindValueOptions {\n key: string;\n storage?: Storage;\n}\n\nconst memoryStorage = new Map<string, string>();\n\nconst inMemoryStorage: Storage = {\n get length() {\n return memoryStorage.size;\n },\n clear() {\n memoryStorage.clear();\n },\n getItem(key: string) {\n return memoryStorage.get(key) ?? null;\n },\n key(index: number) {\n const keys = Array.from(memoryStorage.keys());\n return keys[index] ?? null;\n },\n removeItem(key: string) {\n memoryStorage.delete(key);\n },\n setItem(key: string, value: string) {\n memoryStorage.set(key, value);\n },\n};\n\nexport class BindValue {\n private value:
|
|
1
|
+
{"version":3,"file":"bindValue.mjs","sources":["../../../../src/bindValue.ts"],"sourcesContent":["export interface BindValueOptions<T> {\n key: string;\n defaultValue: T;\n serialize: (value: T) => string;\n deserialize: (serialized: string) => T;\n storage?: Storage;\n version?: string;\n migrate?: (oldSerialized: string, oldVersion: string | undefined) => T;\n}\n\nconst memoryStorage = new Map<string, string>();\n\nconst inMemoryStorage: Storage = {\n get length() {\n return memoryStorage.size;\n },\n clear() {\n memoryStorage.clear();\n },\n getItem(key: string) {\n return memoryStorage.get(key) ?? null;\n },\n key(index: number) {\n const keys = Array.from(memoryStorage.keys());\n return keys[index] ?? null;\n },\n removeItem(key: string) {\n memoryStorage.delete(key);\n },\n setItem(key: string, value: string) {\n memoryStorage.set(key, value);\n },\n};\n\nexport class BindValue<T> {\n private value: T;\n private subscribers: Set<(value: T) => void>;\n private storage: Storage;\n\n constructor(private options: BindValueOptions<T>) {\n this.storage = options.storage ?? inMemoryStorage;\n this.subscribers = new Set();\n this.value = this.loadFromStorage();\n }\n\n getValue(): T {\n return this.value;\n }\n\n set(newValue: T): void {\n this.value = newValue;\n this.saveToStorage(newValue);\n this.notifySubscribers();\n }\n\n subscribe(callback: (value: T) => void): () => void {\n this.subscribers.add(callback);\n return () => {\n this.subscribers.delete(callback);\n };\n }\n\n private loadFromStorage(): T {\n const rawValue = this.storage.getItem(this.options.key);\n\n if (rawValue === null) {\n return this.options.defaultValue;\n }\n\n let oldVersion: string | undefined;\n let serializedValue: string;\n\n if (rawValue.startsWith(\"\\x00\")) {\n const parts = rawValue.split(\"\\x00\");\n if (parts.length >= 3) {\n oldVersion = parts[1];\n serializedValue = parts.slice(2).join(\"\\x00\");\n } else {\n serializedValue = rawValue;\n }\n } else {\n serializedValue = rawValue;\n }\n\n const currentVersion = this.options.version;\n\n if (oldVersion !== currentVersion) {\n if (this.options.migrate) {\n try {\n const migratedValue = this.options.migrate(\n serializedValue,\n oldVersion,\n );\n this.saveToStorage(migratedValue);\n return migratedValue;\n } catch {\n this.storage.removeItem(this.options.key);\n return this.options.defaultValue;\n }\n } else {\n this.storage.removeItem(this.options.key);\n return this.options.defaultValue;\n }\n }\n\n try {\n return this.options.deserialize(serializedValue);\n } catch {\n return this.options.defaultValue;\n }\n }\n\n private saveToStorage(value: T): void {\n try {\n const serialized = this.options.serialize(value);\n const version = this.options.version;\n\n if (version) {\n this.storage.setItem(\n this.options.key,\n `\\x00${version}\\x00${serialized}`,\n );\n } else {\n this.storage.setItem(this.options.key, serialized);\n }\n } catch {}\n }\n\n private notifySubscribers(): void {\n for (const subscriber of this.subscribers) {\n subscriber(this.value);\n }\n }\n}\n\nexport function bindValue<T>(options: BindValueOptions<T>): BindValue<T> {\n return new BindValue(options);\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type BindValue } from "./bindValue.js";
|
|
2
|
-
export interface UseStorageReturn {
|
|
3
|
-
value:
|
|
4
|
-
setValue: (value:
|
|
2
|
+
export interface UseStorageReturn<T> {
|
|
3
|
+
value: T;
|
|
4
|
+
setValue: (value: T) => void;
|
|
5
5
|
}
|
|
6
|
-
export declare function useStorage(binding: BindValue): UseStorageReturn
|
|
6
|
+
export declare function useStorage<T>(binding: BindValue<T>): UseStorageReturn<T>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useStorage.mjs","sources":["../../../../src/useStorage.ts"],"sourcesContent":["import { useState, useEffect } from \"react\";\nimport { type BindValue } from \"./bindValue.js\";\n\nexport interface UseStorageReturn {\n value:
|
|
1
|
+
{"version":3,"file":"useStorage.mjs","sources":["../../../../src/useStorage.ts"],"sourcesContent":["import { useState, useEffect } from \"react\";\nimport { type BindValue } from \"./bindValue.js\";\n\nexport interface UseStorageReturn<T> {\n value: T;\n setValue: (value: T) => void;\n}\n\nexport function useStorage<T>(binding: BindValue<T>): UseStorageReturn<T> {\n const [value, setValue] = useState(binding.getValue());\n\n useEffect(() => {\n const unsubscribe = binding.subscribe(setValue);\n return unsubscribe;\n }, [binding]);\n\n const set = (newValue: T) => {\n binding.set(newValue);\n };\n\n return { value, setValue: set };\n}\n"],"names":[],"mappings":";AAQO,SAAS,WAAc,SAA4C;AACxE,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,QAAQ,UAAU;AAErD,YAAU,MAAM;AACd,UAAM,cAAc,QAAQ,UAAU,QAAQ;AAC9C,WAAO;AAAA,EACT,GAAG,CAAC,OAAO,CAAC;AAEZ,QAAM,MAAM,CAAC,aAAgB;AAC3B,YAAQ,IAAI,QAAQ;AAAA,EACtB;AAEA,SAAO,EAAE,OAAO,UAAU,IAAA;AAC5B;"}
|