kvozy 0.0.1 → 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 +144 -119
- package/__compiled__/cjs/src/bindValue.d.ts +12 -7
- package/__compiled__/cjs/src/bindValue.js +38 -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 +38 -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,11 +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
|
|
18
|
+
- Flexible storage support (localStorage, sessionStorage, in-memory)
|
|
19
|
+
- Graceful fallback to in-memory storage when storage is unavailable
|
|
17
20
|
- Thin React integration (useStorage)
|
|
18
|
-
-
|
|
19
|
-
- Real localStorage backend
|
|
21
|
+
- Real localStorage/backend storage
|
|
20
22
|
- Subscription-based reactivity
|
|
21
23
|
- TypeScript support
|
|
24
|
+
- Required default values for safety
|
|
22
25
|
- Easy to extend to other frameworks
|
|
23
26
|
|
|
24
27
|
## Installation
|
|
@@ -29,81 +32,81 @@ npm install kvozy
|
|
|
29
32
|
|
|
30
33
|
## Quick Start
|
|
31
34
|
|
|
35
|
+
> ⚠️ **Note:** `bindValue` is an internal API. Use `useStorage` for React components. Direct usage of `bindValue` is not recommended.
|
|
36
|
+
|
|
32
37
|
```typescript
|
|
33
38
|
import { bindValue, useStorage } from 'kvozy';
|
|
34
39
|
|
|
35
|
-
// Define the binding
|
|
36
|
-
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
|
+
});
|
|
37
47
|
|
|
38
48
|
// Use in component
|
|
39
49
|
const Component = () => {
|
|
40
50
|
const { value, setValue } = useStorage(myValue);
|
|
41
51
|
|
|
42
|
-
return <input value={value
|
|
52
|
+
return <input value={value} onChange={(e) => setValue(e.target.value)} />;
|
|
43
53
|
};
|
|
44
54
|
```
|
|
45
55
|
|
|
46
|
-
##
|
|
47
|
-
|
|
48
|
-
### bindValue(options)
|
|
49
|
-
|
|
50
|
-
Creates a storage binding to localStorage.
|
|
51
|
-
|
|
52
|
-
**Parameters:**
|
|
53
|
-
|
|
54
|
-
- `options` (object, required)
|
|
55
|
-
- `key` (string, required) - localStorage key
|
|
56
|
-
|
|
57
|
-
**Returns:** `BindValue` instance
|
|
56
|
+
## Why In-Memory Storage?
|
|
58
57
|
|
|
59
|
-
|
|
58
|
+
In-memory storage provides a graceful fallback when persistent storage is unavailable, such as:
|
|
60
59
|
|
|
61
|
-
-
|
|
62
|
-
-
|
|
63
|
-
-
|
|
60
|
+
- **Incognito/Private Mode**: Some browsers disable localStorage in private browsing
|
|
61
|
+
- **Storage Quota Exceeded**: When storage limits are reached
|
|
62
|
+
- **Cookie/Storage Disabled**: When users have disabled cookies/storage
|
|
64
63
|
|
|
65
|
-
|
|
64
|
+
```typescript
|
|
65
|
+
// This code works even if localStorage is unavailable (e.g., incognito mode)
|
|
66
|
+
const bindedValue = bindValue({
|
|
67
|
+
key: "some-key",
|
|
68
|
+
storage: localStorage ?? undefined,
|
|
69
|
+
});
|
|
70
|
+
// Falls back to in-memory storage, so your code continues to work
|
|
71
|
+
```
|
|
66
72
|
|
|
67
|
-
-
|
|
68
|
-
- Callbacks are only invoked when value changes via `set()`
|
|
73
|
+
The in-memory storage ensures your application remains functional, maintaining session state without throwing errors or breaking your user experience.
|
|
69
74
|
|
|
70
|
-
|
|
75
|
+
## API Reference
|
|
71
76
|
|
|
72
|
-
|
|
73
|
-
const binding = bindValue({ key: "user-name" });
|
|
77
|
+
> ⚠️ **Note:** `bindValue` is an internal API. Use `useStorage` for React components. Direct usage of `bindValue` is not recommended.
|
|
74
78
|
|
|
75
|
-
|
|
76
|
-
const currentValue = binding.getValue();
|
|
79
|
+
### BindValueOptions<T>
|
|
77
80
|
|
|
78
|
-
|
|
79
|
-
binding.set("Alice");
|
|
81
|
+
Options for creating a BindValue instance.
|
|
80
82
|
|
|
81
|
-
|
|
82
|
-
const unsubscribe = binding.subscribe((newValue) => {
|
|
83
|
-
console.log("Value changed:", newValue);
|
|
84
|
-
});
|
|
83
|
+
**Parameters:**
|
|
85
84
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
|
89
90
|
|
|
90
|
-
### useStorage(binding)
|
|
91
|
+
### useStorage<T>(binding)
|
|
91
92
|
|
|
92
93
|
React hook that connects a BindValue instance to React state.
|
|
93
94
|
|
|
94
95
|
**Parameters:**
|
|
95
96
|
|
|
96
|
-
- `binding` (BindValue
|
|
97
|
+
- `binding` (BindValue<T>, required) - binding instance from bindValue
|
|
97
98
|
|
|
98
99
|
**Returns:** `{ value, setValue }`
|
|
99
100
|
|
|
100
|
-
- `value` - `
|
|
101
|
-
- `setValue` - `(value:
|
|
101
|
+
- `value` - `T` - current value from storage
|
|
102
|
+
- `setValue` - `(value: T) => void` - function to update value
|
|
102
103
|
|
|
103
104
|
**Behavior:**
|
|
104
105
|
|
|
105
106
|
- `subscribe()` does NOT call the callback immediately when subscribing
|
|
106
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
|
|
107
110
|
|
|
108
111
|
**Example:**
|
|
109
112
|
|
|
@@ -112,7 +115,7 @@ const Component = () => {
|
|
|
112
115
|
const { value, setValue } = useStorage(myBinding);
|
|
113
116
|
|
|
114
117
|
return <div>
|
|
115
|
-
<p>Current value: {value
|
|
118
|
+
<p>Current value: {value}</p>
|
|
116
119
|
<button onClick={() => setValue('new value')}>
|
|
117
120
|
Update Value
|
|
118
121
|
</button>
|
|
@@ -127,7 +130,12 @@ const Component = () => {
|
|
|
127
130
|
```typescript
|
|
128
131
|
import { bindValue, useStorage } from 'kvozy';
|
|
129
132
|
|
|
130
|
-
const usernameBinding = bindValue({
|
|
133
|
+
const usernameBinding = bindValue<string>({
|
|
134
|
+
key: 'username',
|
|
135
|
+
defaultValue: '',
|
|
136
|
+
serialize: (v) => v,
|
|
137
|
+
deserialize: (s) => s,
|
|
138
|
+
});
|
|
131
139
|
|
|
132
140
|
const LoginForm = () => {
|
|
133
141
|
const { value, setValue } = useStorage(usernameBinding);
|
|
@@ -137,7 +145,7 @@ const LoginForm = () => {
|
|
|
137
145
|
<label>
|
|
138
146
|
Username:
|
|
139
147
|
<input
|
|
140
|
-
value={value
|
|
148
|
+
value={value}
|
|
141
149
|
onChange={(e) => setValue(e.target.value)}
|
|
142
150
|
/>
|
|
143
151
|
</label>
|
|
@@ -151,7 +159,12 @@ const LoginForm = () => {
|
|
|
151
159
|
```typescript
|
|
152
160
|
import { bindValue, useStorage } from 'kvozy';
|
|
153
161
|
|
|
154
|
-
const themeBinding = bindValue({
|
|
162
|
+
const themeBinding = bindValue<string>({
|
|
163
|
+
key: 'theme',
|
|
164
|
+
defaultValue: 'light',
|
|
165
|
+
serialize: (v) => v,
|
|
166
|
+
deserialize: (s) => s,
|
|
167
|
+
});
|
|
155
168
|
|
|
156
169
|
const ThemeToggle = () => {
|
|
157
170
|
const { value, setValue } = useStorage(themeBinding);
|
|
@@ -181,22 +194,81 @@ Both components stay in sync automatically!
|
|
|
181
194
|
|
|
182
195
|
### Handling Undefined Values
|
|
183
196
|
|
|
184
|
-
When a localStorage key doesn't exist, `
|
|
197
|
+
When a localStorage key doesn't exist, `defaultValue` is returned:
|
|
185
198
|
|
|
186
199
|
```typescript
|
|
187
|
-
const binding = bindValue({
|
|
188
|
-
|
|
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'
|
|
189
207
|
|
|
190
208
|
const Component = () => {
|
|
191
209
|
const { value } = useStorage(binding);
|
|
192
|
-
return <div>{value
|
|
210
|
+
return <div>{value}</div>;
|
|
211
|
+
};
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Using Different Storage Types
|
|
215
|
+
|
|
216
|
+
Kvozy supports localStorage, sessionStorage, and in-memory storage:
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
import { bindValue, useStorage } from 'kvozy';
|
|
220
|
+
|
|
221
|
+
// localStorage - persists across browser sessions
|
|
222
|
+
const localBinding = bindValue<string>({
|
|
223
|
+
key: 'theme',
|
|
224
|
+
defaultValue: 'light',
|
|
225
|
+
serialize: (v) => v,
|
|
226
|
+
deserialize: (s) => s,
|
|
227
|
+
storage: localStorage,
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// sessionStorage - persists within the same tab
|
|
231
|
+
const sessionBinding = bindValue<string>({
|
|
232
|
+
key: 'form-data',
|
|
233
|
+
defaultValue: '',
|
|
234
|
+
serialize: (v) => v,
|
|
235
|
+
deserialize: (s) => s,
|
|
236
|
+
storage: sessionStorage,
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// In-memory - no persistence, graceful fallback
|
|
240
|
+
const memoryBinding = bindValue<string>({
|
|
241
|
+
key: 'temp-state',
|
|
242
|
+
defaultValue: '',
|
|
243
|
+
serialize: (v) => v,
|
|
244
|
+
deserialize: (s) => s,
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const LocalComponent = () => {
|
|
248
|
+
const { value, setValue } = useStorage(localBinding);
|
|
249
|
+
return <div>Theme: {value}</div>;
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const SessionComponent = () => {
|
|
253
|
+
const { value, setValue } = useStorage(sessionBinding);
|
|
254
|
+
return <div>Form data: {value || 'empty'}</div>;
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const MemoryComponent = () => {
|
|
258
|
+
const { value, setValue } = useStorage(memoryBinding);
|
|
259
|
+
return <div>Temp: {value || 'empty'}</div>;
|
|
193
260
|
};
|
|
194
261
|
```
|
|
195
262
|
|
|
196
263
|
### Persisting Form Data
|
|
197
264
|
|
|
198
265
|
```typescript
|
|
199
|
-
const formBinding = bindValue({
|
|
266
|
+
const formBinding = bindValue<string>({
|
|
267
|
+
key: 'form-data',
|
|
268
|
+
defaultValue: '',
|
|
269
|
+
serialize: (v) => v,
|
|
270
|
+
deserialize: (s) => s,
|
|
271
|
+
});
|
|
200
272
|
|
|
201
273
|
const Form = () => {
|
|
202
274
|
const { value, setValue } = useStorage(formBinding);
|
|
@@ -223,22 +295,26 @@ const Form = () => {
|
|
|
223
295
|
### Counter Example
|
|
224
296
|
|
|
225
297
|
```typescript
|
|
226
|
-
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
|
+
});
|
|
227
304
|
|
|
228
305
|
const Counter = () => {
|
|
229
306
|
const { value, setValue } = useStorage(counterBinding);
|
|
230
|
-
const count = parseInt(value || '0', 10);
|
|
231
307
|
|
|
232
308
|
return (
|
|
233
309
|
<div>
|
|
234
|
-
<p>Count: {
|
|
235
|
-
<button onClick={() => setValue(
|
|
310
|
+
<p>Count: {value}</p>
|
|
311
|
+
<button onClick={() => setValue(value + 1)}>
|
|
236
312
|
Increment
|
|
237
313
|
</button>
|
|
238
|
-
<button onClick={() => setValue(
|
|
314
|
+
<button onClick={() => setValue(value - 1)}>
|
|
239
315
|
Decrement
|
|
240
316
|
</button>
|
|
241
|
-
<button onClick={() => setValue(
|
|
317
|
+
<button onClick={() => setValue(0)}>
|
|
242
318
|
Reset
|
|
243
319
|
</button>
|
|
244
320
|
</div>
|
|
@@ -253,18 +329,19 @@ const Counter = () => {
|
|
|
253
329
|
All storage logic lives in `bindValue` class:
|
|
254
330
|
|
|
255
331
|
- Framework-agnostic
|
|
332
|
+
- Type-safe generic API
|
|
256
333
|
- Manages localStorage operations
|
|
257
334
|
- Handles subscriptions
|
|
258
335
|
- Can be used with any UI framework
|
|
259
336
|
|
|
260
337
|
```typescript
|
|
261
|
-
class BindValue {
|
|
262
|
-
private value:
|
|
263
|
-
private subscribers: Set<(value:
|
|
338
|
+
class BindValue<T> {
|
|
339
|
+
private value: T;
|
|
340
|
+
private subscribers: Set<(value: T) => void>;
|
|
264
341
|
|
|
265
|
-
getValue():
|
|
266
|
-
set(value:
|
|
267
|
-
subscribe(callback): () => void;
|
|
342
|
+
getValue(): T;
|
|
343
|
+
set(value: T): void;
|
|
344
|
+
subscribe(callback: (value: T) => void): () => void;
|
|
268
345
|
}
|
|
269
346
|
```
|
|
270
347
|
|
|
@@ -278,7 +355,7 @@ Thin wrapper that connects `bindValue` to React:
|
|
|
278
355
|
- Returns `{ value, setValue }`
|
|
279
356
|
|
|
280
357
|
```typescript
|
|
281
|
-
function useStorage(binding: BindValue): UseStorageReturn {
|
|
358
|
+
function useStorage<T>(binding: BindValue<T>): UseStorageReturn<T> {
|
|
282
359
|
const [value, setValue] = useState(binding.getValue());
|
|
283
360
|
|
|
284
361
|
useEffect(() => {
|
|
@@ -286,62 +363,17 @@ function useStorage(binding: BindValue): UseStorageReturn {
|
|
|
286
363
|
return unsubscribe;
|
|
287
364
|
}, [binding]);
|
|
288
365
|
|
|
289
|
-
const set = (newValue:
|
|
366
|
+
const set = (newValue: T) => binding.set(newValue);
|
|
290
367
|
|
|
291
368
|
return { value, setValue: set };
|
|
292
369
|
}
|
|
293
370
|
```
|
|
294
371
|
|
|
295
|
-
## Testing
|
|
296
|
-
|
|
297
|
-
### Unit Tests (Node)
|
|
298
|
-
|
|
299
|
-
Run unit tests for `bindValue`:
|
|
300
|
-
|
|
301
|
-
```bash
|
|
302
|
-
npm test
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
Tests are located in `unitTests/bindValue.test.ts` and use mocked localStorage.
|
|
306
|
-
|
|
307
|
-
### Browser Tests (Playwright)
|
|
308
|
-
|
|
309
|
-
Run browser tests for React integration:
|
|
310
|
-
|
|
311
|
-
```bash
|
|
312
|
-
npm run test:browser
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
Tests are located in `browserTests/useStorage.test.ts` and run on Chrome, Firefox, and Webkit.
|
|
316
|
-
|
|
317
|
-
## Browser Support
|
|
318
|
-
|
|
319
|
-
- Chrome >= 87
|
|
320
|
-
- Firefox >= 78
|
|
321
|
-
- Safari >= 15.4
|
|
322
|
-
- Edge >= 88
|
|
323
|
-
|
|
324
|
-
## TypeScript Support
|
|
325
|
-
|
|
326
|
-
Kvozy is written in TypeScript and provides full type definitions:
|
|
327
|
-
|
|
328
|
-
```typescript
|
|
329
|
-
import {
|
|
330
|
-
bindValue,
|
|
331
|
-
useStorage,
|
|
332
|
-
type BindValue,
|
|
333
|
-
type UseStorageReturn,
|
|
334
|
-
} from "kvozy";
|
|
335
|
-
|
|
336
|
-
const binding: BindValue = bindValue({ key: "test" });
|
|
337
|
-
const { value, setValue }: UseStorageReturn = useStorage(binding);
|
|
338
|
-
```
|
|
339
|
-
|
|
340
372
|
## Limitations
|
|
341
373
|
|
|
342
|
-
- **String-only values**: Kvozy only supports string values. For objects or arrays, serialize them as JSON.
|
|
343
374
|
- **No SSR support**: Currently designed for client-side only (requires `window.localStorage`).
|
|
344
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).
|
|
345
377
|
|
|
346
378
|
## Future Plans
|
|
347
379
|
|
|
@@ -350,11 +382,4 @@ const { value, setValue }: UseStorageReturn = useStorage(binding);
|
|
|
350
382
|
- [ ] Angular connector
|
|
351
383
|
- [ ] SSR support
|
|
352
384
|
- [ ] Cross-tab synchronization
|
|
353
|
-
|
|
354
|
-
## License
|
|
355
|
-
|
|
356
|
-
ISC
|
|
357
|
-
|
|
358
|
-
## Contributing
|
|
359
|
-
|
|
360
|
-
Contributions are welcome! Please read `src/AGENTS.md` for development guidelines.
|
|
385
|
+
- [ ] Console warnings on serialization/deserialization errors for debugging
|
|
@@ -1,16 +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;
|
|
6
|
+
storage?: Storage;
|
|
3
7
|
}
|
|
4
|
-
export declare class BindValue {
|
|
8
|
+
export declare class BindValue<T> {
|
|
5
9
|
private options;
|
|
6
10
|
private value;
|
|
7
11
|
private subscribers;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
private storage;
|
|
13
|
+
constructor(options: BindValueOptions<T>);
|
|
14
|
+
getValue(): T;
|
|
15
|
+
set(newValue: T): void;
|
|
16
|
+
subscribe(callback: (value: T) => void): () => void;
|
|
12
17
|
private loadFromStorage;
|
|
13
18
|
private saveToStorage;
|
|
14
19
|
private notifySubscribers;
|
|
15
20
|
}
|
|
16
|
-
export declare function bindValue(options: BindValueOptions): BindValue
|
|
21
|
+
export declare function bindValue<T>(options: BindValueOptions<T>): BindValue<T>;
|
|
@@ -3,11 +3,35 @@ var __defProp = Object.defineProperty;
|
|
|
3
3
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
4
4
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
5
5
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
6
|
+
const memoryStorage = /* @__PURE__ */ new Map();
|
|
7
|
+
const inMemoryStorage = {
|
|
8
|
+
get length() {
|
|
9
|
+
return memoryStorage.size;
|
|
10
|
+
},
|
|
11
|
+
clear() {
|
|
12
|
+
memoryStorage.clear();
|
|
13
|
+
},
|
|
14
|
+
getItem(key) {
|
|
15
|
+
return memoryStorage.get(key) ?? null;
|
|
16
|
+
},
|
|
17
|
+
key(index) {
|
|
18
|
+
const keys = Array.from(memoryStorage.keys());
|
|
19
|
+
return keys[index] ?? null;
|
|
20
|
+
},
|
|
21
|
+
removeItem(key) {
|
|
22
|
+
memoryStorage.delete(key);
|
|
23
|
+
},
|
|
24
|
+
setItem(key, value) {
|
|
25
|
+
memoryStorage.set(key, value);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
6
28
|
class BindValue {
|
|
7
29
|
constructor(options) {
|
|
8
30
|
__publicField(this, "value");
|
|
9
31
|
__publicField(this, "subscribers");
|
|
32
|
+
__publicField(this, "storage");
|
|
10
33
|
this.options = options;
|
|
34
|
+
this.storage = options.storage ?? inMemoryStorage;
|
|
11
35
|
this.subscribers = /* @__PURE__ */ new Set();
|
|
12
36
|
this.value = this.loadFromStorage();
|
|
13
37
|
}
|
|
@@ -26,11 +50,22 @@ class BindValue {
|
|
|
26
50
|
};
|
|
27
51
|
}
|
|
28
52
|
loadFromStorage() {
|
|
29
|
-
const
|
|
30
|
-
|
|
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
|
+
}
|
|
31
62
|
}
|
|
32
63
|
saveToStorage(value) {
|
|
33
|
-
|
|
64
|
+
try {
|
|
65
|
+
const serialized = this.options.serialize(value);
|
|
66
|
+
this.storage.setItem(this.options.key, serialized);
|
|
67
|
+
} catch {
|
|
68
|
+
}
|
|
34
69
|
}
|
|
35
70
|
notifySubscribers() {
|
|
36
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}\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,16 +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;
|
|
6
|
+
storage?: Storage;
|
|
3
7
|
}
|
|
4
|
-
export declare class BindValue {
|
|
8
|
+
export declare class BindValue<T> {
|
|
5
9
|
private options;
|
|
6
10
|
private value;
|
|
7
11
|
private subscribers;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
private storage;
|
|
13
|
+
constructor(options: BindValueOptions<T>);
|
|
14
|
+
getValue(): T;
|
|
15
|
+
set(newValue: T): void;
|
|
16
|
+
subscribe(callback: (value: T) => void): () => void;
|
|
12
17
|
private loadFromStorage;
|
|
13
18
|
private saveToStorage;
|
|
14
19
|
private notifySubscribers;
|
|
15
20
|
}
|
|
16
|
-
export declare function bindValue(options: BindValueOptions): BindValue
|
|
21
|
+
export declare function bindValue<T>(options: BindValueOptions<T>): BindValue<T>;
|
|
@@ -1,11 +1,35 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
const memoryStorage = /* @__PURE__ */ new Map();
|
|
5
|
+
const inMemoryStorage = {
|
|
6
|
+
get length() {
|
|
7
|
+
return memoryStorage.size;
|
|
8
|
+
},
|
|
9
|
+
clear() {
|
|
10
|
+
memoryStorage.clear();
|
|
11
|
+
},
|
|
12
|
+
getItem(key) {
|
|
13
|
+
return memoryStorage.get(key) ?? null;
|
|
14
|
+
},
|
|
15
|
+
key(index) {
|
|
16
|
+
const keys = Array.from(memoryStorage.keys());
|
|
17
|
+
return keys[index] ?? null;
|
|
18
|
+
},
|
|
19
|
+
removeItem(key) {
|
|
20
|
+
memoryStorage.delete(key);
|
|
21
|
+
},
|
|
22
|
+
setItem(key, value) {
|
|
23
|
+
memoryStorage.set(key, value);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
4
26
|
class BindValue {
|
|
5
27
|
constructor(options) {
|
|
6
28
|
__publicField(this, "value");
|
|
7
29
|
__publicField(this, "subscribers");
|
|
30
|
+
__publicField(this, "storage");
|
|
8
31
|
this.options = options;
|
|
32
|
+
this.storage = options.storage ?? inMemoryStorage;
|
|
9
33
|
this.subscribers = /* @__PURE__ */ new Set();
|
|
10
34
|
this.value = this.loadFromStorage();
|
|
11
35
|
}
|
|
@@ -24,11 +48,22 @@ class BindValue {
|
|
|
24
48
|
};
|
|
25
49
|
}
|
|
26
50
|
loadFromStorage() {
|
|
27
|
-
const
|
|
28
|
-
|
|
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
|
+
}
|
|
29
60
|
}
|
|
30
61
|
saveToStorage(value) {
|
|
31
|
-
|
|
62
|
+
try {
|
|
63
|
+
const serialized = this.options.serialize(value);
|
|
64
|
+
this.storage.setItem(this.options.key, serialized);
|
|
65
|
+
} catch {
|
|
66
|
+
}
|
|
32
67
|
}
|
|
33
68
|
notifySubscribers() {
|
|
34
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}\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;"}
|