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 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({ key: 'my-key' });
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 || ''} onChange={(e) => setValue(e.target.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
- ### useStorage(binding)
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, required) - binding instance from bindValue
97
+ - `binding` (BindValue<T>, required) - binding instance from bindValue
80
98
 
81
99
  **Returns:** `{ value, setValue }`
82
100
 
83
- - `value` - `string | undefined` - current value from localStorage
84
- - `setValue` - `(value: string) => void` - function to update 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 || '(empty)'}</p>
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({ key: 'username' });
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({ key: 'theme' });
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, `getValue()` and `useStorage` return `undefined`:
197
+ When a localStorage key doesn't exist, `defaultValue` is returned:
168
198
 
169
199
  ```typescript
170
- const binding = bindValue({ key: 'non-existent-key' });
171
- console.log(binding.getValue()); // undefined
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 || 'No value set'}</div>;
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({ key: 'theme', storage: localStorage });
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({ key: 'form-data', storage: sessionStorage });
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({ key: 'temp-state' });
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 || 'light'}</div>;
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({ key: 'form-data' });
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({ key: 'counter' });
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: {count}</p>
250
- <button onClick={() => setValue(String(count + 1))}>
310
+ <p>Count: {value}</p>
311
+ <button onClick={() => setValue(value + 1)}>
251
312
  Increment
252
313
  </button>
253
- <button onClick={() => setValue(String(count - 1))}>
314
+ <button onClick={() => setValue(value - 1)}>
254
315
  Decrement
255
316
  </button>
256
- <button onClick={() => setValue('0')}>
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: string | undefined;
278
- private subscribers: Set<(value: string | undefined) => void>;
338
+ class BindValue<T> {
339
+ private value: T;
340
+ private subscribers: Set<(value: T) => void>;
279
341
 
280
- getValue(): string | undefined;
281
- set(value: string): void;
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: string) => binding.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(): string | undefined;
12
- set(newValue: string): void;
13
- subscribe(callback: (value: string | undefined) => void): () => void;
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 value = this.storage.getItem(this.options.key);
54
- return value !== null ? value : void 0;
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
- this.storage.setItem(this.options.key, value);
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: string | undefined;\n private subscribers: Set<(value: string | undefined) => void>;\n private storage: Storage;\n\n constructor(private options: BindValueOptions) {\n this.storage = options.storage ?? inMemoryStorage;\n this.subscribers = new Set();\n this.value = this.loadFromStorage();\n }\n\n getValue(): string | undefined {\n return this.value;\n }\n\n set(newValue: string): void {\n this.value = newValue;\n this.saveToStorage(newValue);\n this.notifySubscribers();\n }\n\n subscribe(callback: (value: string | undefined) => void): () => void {\n this.subscribers.add(callback);\n return () => {\n this.subscribers.delete(callback);\n };\n }\n\n private loadFromStorage(): string | undefined {\n const value = this.storage.getItem(this.options.key);\n return value !== null ? value : undefined;\n }\n\n private saveToStorage(value: string): void {\n this.storage.setItem(this.options.key, value);\n }\n\n private notifySubscribers(): void {\n for (const subscriber of this.subscribers) {\n subscriber(this.value);\n }\n }\n}\n\nexport function bindValue(options: BindValueOptions): BindValue {\n return new BindValue(options);\n}\n"],"names":[],"mappings":";;;;;AAKA,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,UAAU;AAAA,EAKrB,YAAoB,SAA2B;AAJvC;AACA;AACA;AAEY,SAAA,UAAA;AAClB,SAAK,UAAU,QAAQ,WAAW;AAClC,SAAK,kCAAkB,IAAA;AACvB,SAAK,QAAQ,KAAK,gBAAA;AAAA,EACpB;AAAA,EAEA,WAA+B;AAC7B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,UAAwB;AAC1B,SAAK,QAAQ;AACb,SAAK,cAAc,QAAQ;AAC3B,SAAK,kBAAA;AAAA,EACP;AAAA,EAEA,UAAU,UAA2D;AACnE,SAAK,YAAY,IAAI,QAAQ;AAC7B,WAAO,MAAM;AACX,WAAK,YAAY,OAAO,QAAQ;AAAA,IAClC;AAAA,EACF;AAAA,EAEQ,kBAAsC;AAC5C,UAAM,QAAQ,KAAK,QAAQ,QAAQ,KAAK,QAAQ,GAAG;AACnD,WAAO,UAAU,OAAO,QAAQ;AAAA,EAClC;AAAA,EAEQ,cAAc,OAAqB;AACzC,SAAK,QAAQ,QAAQ,KAAK,QAAQ,KAAK,KAAK;AAAA,EAC9C;AAAA,EAEQ,oBAA0B;AAChC,eAAW,cAAc,KAAK,aAAa;AACzC,iBAAW,KAAK,KAAK;AAAA,IACvB;AAAA,EACF;AACF;AAEO,SAAS,UAAU,SAAsC;AAC9D,SAAO,IAAI,UAAU,OAAO;AAC9B;;;"}
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: string | undefined;
4
- setValue: (value: string) => void;
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: string | undefined;\n setValue: (value: string) => void;\n}\n\nexport function useStorage(binding: BindValue): UseStorageReturn {\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: string) => {\n binding.set(newValue);\n };\n\n return { value, setValue: set };\n}\n"],"names":["useState","useEffect"],"mappings":";;;AAQO,SAAS,WAAW,SAAsC;AAC/D,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,aAAqB;AAChC,YAAQ,IAAI,QAAQ;AAAA,EACtB;AAEA,SAAO,EAAE,OAAO,UAAU,IAAA;AAC5B;;"}
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(): string | undefined;
12
- set(newValue: string): void;
13
- subscribe(callback: (value: string | undefined) => void): () => void;
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 value = this.storage.getItem(this.options.key);
52
- return value !== null ? value : void 0;
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
- this.storage.setItem(this.options.key, value);
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: string | undefined;\n private subscribers: Set<(value: string | undefined) => void>;\n private storage: Storage;\n\n constructor(private options: BindValueOptions) {\n this.storage = options.storage ?? inMemoryStorage;\n this.subscribers = new Set();\n this.value = this.loadFromStorage();\n }\n\n getValue(): string | undefined {\n return this.value;\n }\n\n set(newValue: string): void {\n this.value = newValue;\n this.saveToStorage(newValue);\n this.notifySubscribers();\n }\n\n subscribe(callback: (value: string | undefined) => void): () => void {\n this.subscribers.add(callback);\n return () => {\n this.subscribers.delete(callback);\n };\n }\n\n private loadFromStorage(): string | undefined {\n const value = this.storage.getItem(this.options.key);\n return value !== null ? value : undefined;\n }\n\n private saveToStorage(value: string): void {\n this.storage.setItem(this.options.key, value);\n }\n\n private notifySubscribers(): void {\n for (const subscriber of this.subscribers) {\n subscriber(this.value);\n }\n }\n}\n\nexport function bindValue(options: BindValueOptions): BindValue {\n return new BindValue(options);\n}\n"],"names":[],"mappings":";;;AAKA,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,UAAU;AAAA,EAKrB,YAAoB,SAA2B;AAJvC;AACA;AACA;AAEY,SAAA,UAAA;AAClB,SAAK,UAAU,QAAQ,WAAW;AAClC,SAAK,kCAAkB,IAAA;AACvB,SAAK,QAAQ,KAAK,gBAAA;AAAA,EACpB;AAAA,EAEA,WAA+B;AAC7B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,UAAwB;AAC1B,SAAK,QAAQ;AACb,SAAK,cAAc,QAAQ;AAC3B,SAAK,kBAAA;AAAA,EACP;AAAA,EAEA,UAAU,UAA2D;AACnE,SAAK,YAAY,IAAI,QAAQ;AAC7B,WAAO,MAAM;AACX,WAAK,YAAY,OAAO,QAAQ;AAAA,IAClC;AAAA,EACF;AAAA,EAEQ,kBAAsC;AAC5C,UAAM,QAAQ,KAAK,QAAQ,QAAQ,KAAK,QAAQ,GAAG;AACnD,WAAO,UAAU,OAAO,QAAQ;AAAA,EAClC;AAAA,EAEQ,cAAc,OAAqB;AACzC,SAAK,QAAQ,QAAQ,KAAK,QAAQ,KAAK,KAAK;AAAA,EAC9C;AAAA,EAEQ,oBAA0B;AAChC,eAAW,cAAc,KAAK,aAAa;AACzC,iBAAW,KAAK,KAAK;AAAA,IACvB;AAAA,EACF;AACF;AAEO,SAAS,UAAU,SAAsC;AAC9D,SAAO,IAAI,UAAU,OAAO;AAC9B;"}
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: string | undefined;
4
- setValue: (value: string) => void;
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: string | undefined;\n setValue: (value: string) => void;\n}\n\nexport function useStorage(binding: BindValue): UseStorageReturn {\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: string) => {\n binding.set(newValue);\n };\n\n return { value, setValue: set };\n}\n"],"names":[],"mappings":";AAQO,SAAS,WAAW,SAAsC;AAC/D,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,aAAqB;AAChC,YAAQ,IAAI,QAAQ;AAAA,EACtB;AAEA,SAAO,EAAE,OAAO,UAAU,IAAA;AAC5B;"}
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;"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kvozy",
3
3
  "type": "commonjs",
4
- "version": "0.2.0",
4
+ "version": "0.3.0",
5
5
  "types": "./__compiled__/cjs/src/index.d.ts",
6
6
  "module": "./__compiled__/esm/src/index.mjs",
7
7
  "main": "./__compiled__/cjs/src/index.js",