kvozy 0.2.0 → 0.3.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 +99 -36
- package/__compiled__/cjs/src/bindValue.d.ts +10 -7
- package/__compiled__/cjs/src/bindValue.js +14 -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 +10 -7
- package/__compiled__/esm/src/bindValue.mjs +14 -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,14 @@ 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
|
|
24
25
|
- Easy to extend to other frameworks
|
|
25
26
|
|
|
26
27
|
## Installation
|
|
@@ -36,14 +37,19 @@ npm install kvozy
|
|
|
36
37
|
```typescript
|
|
37
38
|
import { bindValue, useStorage } from 'kvozy';
|
|
38
39
|
|
|
39
|
-
// Define the binding
|
|
40
|
-
const myValue = bindValue({
|
|
40
|
+
// Define the binding with type, serialize, deserialize, and defaultValue
|
|
41
|
+
const myValue = bindValue<string>({
|
|
42
|
+
key: 'my-key',
|
|
43
|
+
defaultValue: '',
|
|
44
|
+
serialize: (v) => v,
|
|
45
|
+
deserialize: (s) => s,
|
|
46
|
+
});
|
|
41
47
|
|
|
42
48
|
// Use in component
|
|
43
49
|
const Component = () => {
|
|
44
50
|
const { value, setValue } = useStorage(myValue);
|
|
45
51
|
|
|
46
|
-
return <input value={value
|
|
52
|
+
return <input value={value} onChange={(e) => setValue(e.target.value)} />;
|
|
47
53
|
};
|
|
48
54
|
```
|
|
49
55
|
|
|
@@ -70,23 +76,37 @@ The in-memory storage ensures your application remains functional, maintaining s
|
|
|
70
76
|
|
|
71
77
|
> ⚠️ **Note:** `bindValue` is an internal API. Use `useStorage` for React components. Direct usage of `bindValue` is not recommended.
|
|
72
78
|
|
|
73
|
-
###
|
|
79
|
+
### BindValueOptions<T>
|
|
80
|
+
|
|
81
|
+
Options for creating a BindValue instance.
|
|
82
|
+
|
|
83
|
+
**Parameters:**
|
|
84
|
+
|
|
85
|
+
- `key` (string, required) - Storage key
|
|
86
|
+
- `defaultValue` (T, required) - Default value when key doesn't exist or deserialize fails
|
|
87
|
+
- `serialize` (function, required) - Convert value to string: `(value: T) => string`
|
|
88
|
+
- `deserialize` (function, required) - Convert string to value: `(serialized: string) => T`
|
|
89
|
+
- `storage` (Storage, optional) - localStorage, sessionStorage, or undefined for in-memory
|
|
90
|
+
|
|
91
|
+
### useStorage<T>(binding)
|
|
74
92
|
|
|
75
93
|
React hook that connects a BindValue instance to React state.
|
|
76
94
|
|
|
77
95
|
**Parameters:**
|
|
78
96
|
|
|
79
|
-
- `binding` (BindValue
|
|
97
|
+
- `binding` (BindValue<T>, required) - binding instance from bindValue
|
|
80
98
|
|
|
81
99
|
**Returns:** `{ value, setValue }`
|
|
82
100
|
|
|
83
|
-
- `value` - `
|
|
84
|
-
- `setValue` - `(value:
|
|
101
|
+
- `value` - `T` - current value from storage
|
|
102
|
+
- `setValue` - `(value: T) => void` - function to update value
|
|
85
103
|
|
|
86
104
|
**Behavior:**
|
|
87
105
|
|
|
88
106
|
- `subscribe()` does NOT call the callback immediately when subscribing
|
|
89
107
|
- Callbacks are only invoked when the value changes via `set()`
|
|
108
|
+
- If `serialize()` fails, in-memory value is kept but storage is NOT updated
|
|
109
|
+
- If `deserialize()` fails, `defaultValue` is returned
|
|
90
110
|
|
|
91
111
|
**Example:**
|
|
92
112
|
|
|
@@ -95,7 +115,7 @@ const Component = () => {
|
|
|
95
115
|
const { value, setValue } = useStorage(myBinding);
|
|
96
116
|
|
|
97
117
|
return <div>
|
|
98
|
-
<p>Current value: {value
|
|
118
|
+
<p>Current value: {value}</p>
|
|
99
119
|
<button onClick={() => setValue('new value')}>
|
|
100
120
|
Update Value
|
|
101
121
|
</button>
|
|
@@ -110,7 +130,12 @@ const Component = () => {
|
|
|
110
130
|
```typescript
|
|
111
131
|
import { bindValue, useStorage } from 'kvozy';
|
|
112
132
|
|
|
113
|
-
const usernameBinding = bindValue({
|
|
133
|
+
const usernameBinding = bindValue<string>({
|
|
134
|
+
key: 'username',
|
|
135
|
+
defaultValue: '',
|
|
136
|
+
serialize: (v) => v,
|
|
137
|
+
deserialize: (s) => s,
|
|
138
|
+
});
|
|
114
139
|
|
|
115
140
|
const LoginForm = () => {
|
|
116
141
|
const { value, setValue } = useStorage(usernameBinding);
|
|
@@ -120,7 +145,7 @@ const LoginForm = () => {
|
|
|
120
145
|
<label>
|
|
121
146
|
Username:
|
|
122
147
|
<input
|
|
123
|
-
value={value
|
|
148
|
+
value={value}
|
|
124
149
|
onChange={(e) => setValue(e.target.value)}
|
|
125
150
|
/>
|
|
126
151
|
</label>
|
|
@@ -134,7 +159,12 @@ const LoginForm = () => {
|
|
|
134
159
|
```typescript
|
|
135
160
|
import { bindValue, useStorage } from 'kvozy';
|
|
136
161
|
|
|
137
|
-
const themeBinding = bindValue({
|
|
162
|
+
const themeBinding = bindValue<string>({
|
|
163
|
+
key: 'theme',
|
|
164
|
+
defaultValue: 'light',
|
|
165
|
+
serialize: (v) => v,
|
|
166
|
+
deserialize: (s) => s,
|
|
167
|
+
});
|
|
138
168
|
|
|
139
169
|
const ThemeToggle = () => {
|
|
140
170
|
const { value, setValue } = useStorage(themeBinding);
|
|
@@ -164,15 +194,20 @@ Both components stay in sync automatically!
|
|
|
164
194
|
|
|
165
195
|
### Handling Undefined Values
|
|
166
196
|
|
|
167
|
-
When a localStorage key doesn't exist, `
|
|
197
|
+
When a localStorage key doesn't exist, `defaultValue` is returned:
|
|
168
198
|
|
|
169
199
|
```typescript
|
|
170
|
-
const binding = bindValue({
|
|
171
|
-
|
|
200
|
+
const binding = bindValue<string>({
|
|
201
|
+
key: 'non-existent-key',
|
|
202
|
+
defaultValue: 'default value',
|
|
203
|
+
serialize: (v) => v,
|
|
204
|
+
deserialize: (s) => s,
|
|
205
|
+
});
|
|
206
|
+
console.log(binding.getValue()); // 'default value'
|
|
172
207
|
|
|
173
208
|
const Component = () => {
|
|
174
209
|
const { value } = useStorage(binding);
|
|
175
|
-
return <div>{value
|
|
210
|
+
return <div>{value}</div>;
|
|
176
211
|
};
|
|
177
212
|
```
|
|
178
213
|
|
|
@@ -184,17 +219,34 @@ Kvozy supports localStorage, sessionStorage, and in-memory storage:
|
|
|
184
219
|
import { bindValue, useStorage } from 'kvozy';
|
|
185
220
|
|
|
186
221
|
// localStorage - persists across browser sessions
|
|
187
|
-
const localBinding = bindValue({
|
|
222
|
+
const localBinding = bindValue<string>({
|
|
223
|
+
key: 'theme',
|
|
224
|
+
defaultValue: 'light',
|
|
225
|
+
serialize: (v) => v,
|
|
226
|
+
deserialize: (s) => s,
|
|
227
|
+
storage: localStorage,
|
|
228
|
+
});
|
|
188
229
|
|
|
189
230
|
// sessionStorage - persists within the same tab
|
|
190
|
-
const sessionBinding = bindValue({
|
|
231
|
+
const sessionBinding = bindValue<string>({
|
|
232
|
+
key: 'form-data',
|
|
233
|
+
defaultValue: '',
|
|
234
|
+
serialize: (v) => v,
|
|
235
|
+
deserialize: (s) => s,
|
|
236
|
+
storage: sessionStorage,
|
|
237
|
+
});
|
|
191
238
|
|
|
192
239
|
// In-memory - no persistence, graceful fallback
|
|
193
|
-
const memoryBinding = bindValue({
|
|
240
|
+
const memoryBinding = bindValue<string>({
|
|
241
|
+
key: 'temp-state',
|
|
242
|
+
defaultValue: '',
|
|
243
|
+
serialize: (v) => v,
|
|
244
|
+
deserialize: (s) => s,
|
|
245
|
+
});
|
|
194
246
|
|
|
195
247
|
const LocalComponent = () => {
|
|
196
248
|
const { value, setValue } = useStorage(localBinding);
|
|
197
|
-
return <div>Theme: {value
|
|
249
|
+
return <div>Theme: {value}</div>;
|
|
198
250
|
};
|
|
199
251
|
|
|
200
252
|
const SessionComponent = () => {
|
|
@@ -211,7 +263,12 @@ const MemoryComponent = () => {
|
|
|
211
263
|
### Persisting Form Data
|
|
212
264
|
|
|
213
265
|
```typescript
|
|
214
|
-
const formBinding = bindValue({
|
|
266
|
+
const formBinding = bindValue<string>({
|
|
267
|
+
key: 'form-data',
|
|
268
|
+
defaultValue: '',
|
|
269
|
+
serialize: (v) => v,
|
|
270
|
+
deserialize: (s) => s,
|
|
271
|
+
});
|
|
215
272
|
|
|
216
273
|
const Form = () => {
|
|
217
274
|
const { value, setValue } = useStorage(formBinding);
|
|
@@ -238,22 +295,26 @@ const Form = () => {
|
|
|
238
295
|
### Counter Example
|
|
239
296
|
|
|
240
297
|
```typescript
|
|
241
|
-
const counterBinding = bindValue({
|
|
298
|
+
const counterBinding = bindValue<number>({
|
|
299
|
+
key: 'counter',
|
|
300
|
+
defaultValue: 0,
|
|
301
|
+
serialize: (v) => String(v),
|
|
302
|
+
deserialize: (s) => Number(s),
|
|
303
|
+
});
|
|
242
304
|
|
|
243
305
|
const Counter = () => {
|
|
244
306
|
const { value, setValue } = useStorage(counterBinding);
|
|
245
|
-
const count = parseInt(value || '0', 10);
|
|
246
307
|
|
|
247
308
|
return (
|
|
248
309
|
<div>
|
|
249
|
-
<p>Count: {
|
|
250
|
-
<button onClick={() => setValue(
|
|
310
|
+
<p>Count: {value}</p>
|
|
311
|
+
<button onClick={() => setValue(value + 1)}>
|
|
251
312
|
Increment
|
|
252
313
|
</button>
|
|
253
|
-
<button onClick={() => setValue(
|
|
314
|
+
<button onClick={() => setValue(value - 1)}>
|
|
254
315
|
Decrement
|
|
255
316
|
</button>
|
|
256
|
-
<button onClick={() => setValue(
|
|
317
|
+
<button onClick={() => setValue(0)}>
|
|
257
318
|
Reset
|
|
258
319
|
</button>
|
|
259
320
|
</div>
|
|
@@ -268,18 +329,19 @@ const Counter = () => {
|
|
|
268
329
|
All storage logic lives in `bindValue` class:
|
|
269
330
|
|
|
270
331
|
- Framework-agnostic
|
|
332
|
+
- Type-safe generic API
|
|
271
333
|
- Manages localStorage operations
|
|
272
334
|
- Handles subscriptions
|
|
273
335
|
- Can be used with any UI framework
|
|
274
336
|
|
|
275
337
|
```typescript
|
|
276
|
-
class BindValue {
|
|
277
|
-
private value:
|
|
278
|
-
private subscribers: Set<(value:
|
|
338
|
+
class BindValue<T> {
|
|
339
|
+
private value: T;
|
|
340
|
+
private subscribers: Set<(value: T) => void>;
|
|
279
341
|
|
|
280
|
-
getValue():
|
|
281
|
-
set(value:
|
|
282
|
-
subscribe(callback): () => void;
|
|
342
|
+
getValue(): T;
|
|
343
|
+
set(value: T): void;
|
|
344
|
+
subscribe(callback: (value: T) => void): () => void;
|
|
283
345
|
}
|
|
284
346
|
```
|
|
285
347
|
|
|
@@ -293,7 +355,7 @@ Thin wrapper that connects `bindValue` to React:
|
|
|
293
355
|
- Returns `{ value, setValue }`
|
|
294
356
|
|
|
295
357
|
```typescript
|
|
296
|
-
function useStorage(binding: BindValue): UseStorageReturn {
|
|
358
|
+
function useStorage<T>(binding: BindValue<T>): UseStorageReturn<T> {
|
|
297
359
|
const [value, setValue] = useState(binding.getValue());
|
|
298
360
|
|
|
299
361
|
useEffect(() => {
|
|
@@ -301,7 +363,7 @@ function useStorage(binding: BindValue): UseStorageReturn {
|
|
|
301
363
|
return unsubscribe;
|
|
302
364
|
}, [binding]);
|
|
303
365
|
|
|
304
|
-
const set = (newValue:
|
|
366
|
+
const set = (newValue: T) => binding.set(newValue);
|
|
305
367
|
|
|
306
368
|
return { value, setValue: set };
|
|
307
369
|
}
|
|
@@ -309,9 +371,9 @@ function useStorage(binding: BindValue): UseStorageReturn {
|
|
|
309
371
|
|
|
310
372
|
## Limitations
|
|
311
373
|
|
|
312
|
-
- **String-only values**: Kvozy only supports string values. For objects or arrays, serialize them as JSON.
|
|
313
374
|
- **No SSR support**: Currently designed for client-side only (requires `window.localStorage`).
|
|
314
375
|
- **No cross-tab sync**: Changes in one tab don't update other tabs automatically.
|
|
376
|
+
- **Serialize failures**: If `serialize()` fails, the value is kept in memory but not persisted to storage (graceful degradation).
|
|
315
377
|
|
|
316
378
|
## Future Plans
|
|
317
379
|
|
|
@@ -320,3 +382,4 @@ function useStorage(binding: BindValue): UseStorageReturn {
|
|
|
320
382
|
- [ ] Angular connector
|
|
321
383
|
- [ ] SSR support
|
|
322
384
|
- [ ] Cross-tab synchronization
|
|
385
|
+
- [ ] Console warnings on serialization/deserialization errors for debugging
|
|
@@ -1,18 +1,21 @@
|
|
|
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;
|
|
4
7
|
}
|
|
5
|
-
export declare class BindValue {
|
|
8
|
+
export declare class BindValue<T> {
|
|
6
9
|
private options;
|
|
7
10
|
private value;
|
|
8
11
|
private subscribers;
|
|
9
12
|
private storage;
|
|
10
|
-
constructor(options: BindValueOptions);
|
|
11
|
-
getValue():
|
|
12
|
-
set(newValue:
|
|
13
|
-
subscribe(callback: (value:
|
|
13
|
+
constructor(options: BindValueOptions<T>);
|
|
14
|
+
getValue(): T;
|
|
15
|
+
set(newValue: T): void;
|
|
16
|
+
subscribe(callback: (value: T) => void): () => void;
|
|
14
17
|
private loadFromStorage;
|
|
15
18
|
private saveToStorage;
|
|
16
19
|
private notifySubscribers;
|
|
17
20
|
}
|
|
18
|
-
export declare function bindValue(options: BindValueOptions): BindValue
|
|
21
|
+
export declare function bindValue<T>(options: BindValueOptions<T>): BindValue<T>;
|
|
@@ -50,11 +50,22 @@ 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
|
+
try {
|
|
58
|
+
return this.options.deserialize(rawValue);
|
|
59
|
+
} catch {
|
|
60
|
+
return this.options.defaultValue;
|
|
61
|
+
}
|
|
55
62
|
}
|
|
56
63
|
saveToStorage(value) {
|
|
57
|
-
|
|
64
|
+
try {
|
|
65
|
+
const serialized = this.options.serialize(value);
|
|
66
|
+
this.storage.setItem(this.options.key, serialized);
|
|
67
|
+
} catch {
|
|
68
|
+
}
|
|
58
69
|
}
|
|
59
70
|
notifySubscribers() {
|
|
60
71
|
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}\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 try {\n return this.options.deserialize(rawValue);\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 this.storage.setItem(this.options.key, serialized);\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":";;;;;AAQA,MAAM,oCAAoB,IAAA;AAE1B,MAAM,kBAA2B;AAAA,EAC/B,IAAI,SAAS;AACX,WAAO,cAAc;AAAA,EACvB;AAAA,EACA,QAAQ;AACN,kBAAc,MAAA;AAAA,EAChB;AAAA,EACA,QAAQ,KAAa;AACnB,WAAO,cAAc,IAAI,GAAG,KAAK;AAAA,EACnC;AAAA,EACA,IAAI,OAAe;AACjB,UAAM,OAAO,MAAM,KAAK,cAAc,MAAM;AAC5C,WAAO,KAAK,KAAK,KAAK;AAAA,EACxB;AAAA,EACA,WAAW,KAAa;AACtB,kBAAc,OAAO,GAAG;AAAA,EAC1B;AAAA,EACA,QAAQ,KAAa,OAAe;AAClC,kBAAc,IAAI,KAAK,KAAK;AAAA,EAC9B;AACF;AAEO,MAAM,UAAa;AAAA,EAKxB,YAAoB,SAA8B;AAJ1C;AACA;AACA;AAEY,SAAA,UAAA;AAClB,SAAK,UAAU,QAAQ,WAAW;AAClC,SAAK,kCAAkB,IAAA;AACvB,SAAK,QAAQ,KAAK,gBAAA;AAAA,EACpB;AAAA,EAEA,WAAc;AACZ,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,UAAmB;AACrB,SAAK,QAAQ;AACb,SAAK,cAAc,QAAQ;AAC3B,SAAK,kBAAA;AAAA,EACP;AAAA,EAEA,UAAU,UAA0C;AAClD,SAAK,YAAY,IAAI,QAAQ;AAC7B,WAAO,MAAM;AACX,WAAK,YAAY,OAAO,QAAQ;AAAA,IAClC;AAAA,EACF;AAAA,EAEQ,kBAAqB;AAC3B,UAAM,WAAW,KAAK,QAAQ,QAAQ,KAAK,QAAQ,GAAG;AAEtD,QAAI,aAAa,MAAM;AACrB,aAAO,KAAK,QAAQ;AAAA,IACtB;AAEA,QAAI;AACF,aAAO,KAAK,QAAQ,YAAY,QAAQ;AAAA,IAC1C,QAAQ;AACN,aAAO,KAAK,QAAQ;AAAA,IACtB;AAAA,EACF;AAAA,EAEQ,cAAc,OAAgB;AACpC,QAAI;AACF,YAAM,aAAa,KAAK,QAAQ,UAAU,KAAK;AAC/C,WAAK,QAAQ,QAAQ,KAAK,QAAQ,KAAK,UAAU;AAAA,IACnD,QAAQ;AAAA,IAAC;AAAA,EACX;AAAA,EAEQ,oBAA0B;AAChC,eAAW,cAAc,KAAK,aAAa;AACzC,iBAAW,KAAK,KAAK;AAAA,IACvB;AAAA,EACF;AACF;AAEO,SAAS,UAAa,SAA4C;AACvE,SAAO,IAAI,UAAU,OAAO;AAC9B;;;"}
|
|
@@ -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,21 @@
|
|
|
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;
|
|
4
7
|
}
|
|
5
|
-
export declare class BindValue {
|
|
8
|
+
export declare class BindValue<T> {
|
|
6
9
|
private options;
|
|
7
10
|
private value;
|
|
8
11
|
private subscribers;
|
|
9
12
|
private storage;
|
|
10
|
-
constructor(options: BindValueOptions);
|
|
11
|
-
getValue():
|
|
12
|
-
set(newValue:
|
|
13
|
-
subscribe(callback: (value:
|
|
13
|
+
constructor(options: BindValueOptions<T>);
|
|
14
|
+
getValue(): T;
|
|
15
|
+
set(newValue: T): void;
|
|
16
|
+
subscribe(callback: (value: T) => void): () => void;
|
|
14
17
|
private loadFromStorage;
|
|
15
18
|
private saveToStorage;
|
|
16
19
|
private notifySubscribers;
|
|
17
20
|
}
|
|
18
|
-
export declare function bindValue(options: BindValueOptions): BindValue
|
|
21
|
+
export declare function bindValue<T>(options: BindValueOptions<T>): BindValue<T>;
|
|
@@ -48,11 +48,22 @@ 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
|
+
try {
|
|
56
|
+
return this.options.deserialize(rawValue);
|
|
57
|
+
} catch {
|
|
58
|
+
return this.options.defaultValue;
|
|
59
|
+
}
|
|
53
60
|
}
|
|
54
61
|
saveToStorage(value) {
|
|
55
|
-
|
|
62
|
+
try {
|
|
63
|
+
const serialized = this.options.serialize(value);
|
|
64
|
+
this.storage.setItem(this.options.key, serialized);
|
|
65
|
+
} catch {
|
|
66
|
+
}
|
|
56
67
|
}
|
|
57
68
|
notifySubscribers() {
|
|
58
69
|
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}\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 try {\n return this.options.deserialize(rawValue);\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 this.storage.setItem(this.options.key, serialized);\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":";;;AAQA,MAAM,oCAAoB,IAAA;AAE1B,MAAM,kBAA2B;AAAA,EAC/B,IAAI,SAAS;AACX,WAAO,cAAc;AAAA,EACvB;AAAA,EACA,QAAQ;AACN,kBAAc,MAAA;AAAA,EAChB;AAAA,EACA,QAAQ,KAAa;AACnB,WAAO,cAAc,IAAI,GAAG,KAAK;AAAA,EACnC;AAAA,EACA,IAAI,OAAe;AACjB,UAAM,OAAO,MAAM,KAAK,cAAc,MAAM;AAC5C,WAAO,KAAK,KAAK,KAAK;AAAA,EACxB;AAAA,EACA,WAAW,KAAa;AACtB,kBAAc,OAAO,GAAG;AAAA,EAC1B;AAAA,EACA,QAAQ,KAAa,OAAe;AAClC,kBAAc,IAAI,KAAK,KAAK;AAAA,EAC9B;AACF;AAEO,MAAM,UAAa;AAAA,EAKxB,YAAoB,SAA8B;AAJ1C;AACA;AACA;AAEY,SAAA,UAAA;AAClB,SAAK,UAAU,QAAQ,WAAW;AAClC,SAAK,kCAAkB,IAAA;AACvB,SAAK,QAAQ,KAAK,gBAAA;AAAA,EACpB;AAAA,EAEA,WAAc;AACZ,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,UAAmB;AACrB,SAAK,QAAQ;AACb,SAAK,cAAc,QAAQ;AAC3B,SAAK,kBAAA;AAAA,EACP;AAAA,EAEA,UAAU,UAA0C;AAClD,SAAK,YAAY,IAAI,QAAQ;AAC7B,WAAO,MAAM;AACX,WAAK,YAAY,OAAO,QAAQ;AAAA,IAClC;AAAA,EACF;AAAA,EAEQ,kBAAqB;AAC3B,UAAM,WAAW,KAAK,QAAQ,QAAQ,KAAK,QAAQ,GAAG;AAEtD,QAAI,aAAa,MAAM;AACrB,aAAO,KAAK,QAAQ;AAAA,IACtB;AAEA,QAAI;AACF,aAAO,KAAK,QAAQ,YAAY,QAAQ;AAAA,IAC1C,QAAQ;AACN,aAAO,KAAK,QAAQ;AAAA,IACtB;AAAA,EACF;AAAA,EAEQ,cAAc,OAAgB;AACpC,QAAI;AACF,YAAM,aAAa,KAAK,QAAQ,UAAU,KAAK;AAC/C,WAAK,QAAQ,QAAQ,KAAK,QAAQ,KAAK,UAAU;AAAA,IACnD,QAAQ;AAAA,IAAC;AAAA,EACX;AAAA,EAEQ,oBAA0B;AAChC,eAAW,cAAc,KAAK,aAAa;AACzC,iBAAW,KAAK,KAAK;AAAA,IACvB;AAAA,EACF;AACF;AAEO,SAAS,UAAa,SAA4C;AACvE,SAAO,IAAI,UAAU,OAAO;AAC9B;"}
|
|
@@ -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;"}
|