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 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
- - String-only values (simple, predictable)
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({ 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
+ });
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 || ''} onChange={(e) => setValue(e.target.value)} />;
52
+ return <input value={value} onChange={(e) => setValue(e.target.value)} />;
43
53
  };
44
54
  ```
45
55
 
46
- ## API Reference
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
- **Methods:**
58
+ In-memory storage provides a graceful fallback when persistent storage is unavailable, such as:
60
59
 
61
- - `getValue()` - returns `string | undefined`
62
- - `set(value: string)` - updates localStorage and notifies subscribers
63
- - `subscribe(callback)` - subscribe to value changes, returns unsubscribe function
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
- **Behavior:**
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
- - `subscribe()` does NOT call the callback immediately when subscribing
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
- **Example:**
75
+ ## API Reference
71
76
 
72
- ```typescript
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
- // Get current value
76
- const currentValue = binding.getValue();
79
+ ### BindValueOptions<T>
77
80
 
78
- // Set new value
79
- binding.set("Alice");
81
+ Options for creating a BindValue instance.
80
82
 
81
- // Subscribe to changes
82
- const unsubscribe = binding.subscribe((newValue) => {
83
- console.log("Value changed:", newValue);
84
- });
83
+ **Parameters:**
85
84
 
86
- // Unsubscribe when done
87
- unsubscribe();
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, required) - binding instance from bindValue
97
+ - `binding` (BindValue<T>, required) - binding instance from bindValue
97
98
 
98
99
  **Returns:** `{ value, setValue }`
99
100
 
100
- - `value` - `string | undefined` - current value from localStorage
101
- - `setValue` - `(value: string) => void` - function to update 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 || '(empty)'}</p>
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({ key: 'username' });
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({ key: 'theme' });
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, `getValue()` and `useStorage` return `undefined`:
197
+ When a localStorage key doesn't exist, `defaultValue` is returned:
185
198
 
186
199
  ```typescript
187
- const binding = bindValue({ key: 'non-existent-key' });
188
- 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'
189
207
 
190
208
  const Component = () => {
191
209
  const { value } = useStorage(binding);
192
- return <div>{value || 'No value set'}</div>;
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({ key: 'form-data' });
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({ key: 'counter' });
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: {count}</p>
235
- <button onClick={() => setValue(String(count + 1))}>
310
+ <p>Count: {value}</p>
311
+ <button onClick={() => setValue(value + 1)}>
236
312
  Increment
237
313
  </button>
238
- <button onClick={() => setValue(String(count - 1))}>
314
+ <button onClick={() => setValue(value - 1)}>
239
315
  Decrement
240
316
  </button>
241
- <button onClick={() => setValue('0')}>
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: string | undefined;
263
- private subscribers: Set<(value: string | undefined) => void>;
338
+ class BindValue<T> {
339
+ private value: T;
340
+ private subscribers: Set<(value: T) => void>;
264
341
 
265
- getValue(): string | undefined;
266
- set(value: string): void;
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: string) => binding.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
- constructor(options: BindValueOptions);
9
- getValue(): string | undefined;
10
- set(newValue: string): void;
11
- subscribe(callback: (value: string | undefined) => void): () => void;
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 value = localStorage.getItem(this.options.key);
30
- 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
+ }
31
62
  }
32
63
  saveToStorage(value) {
33
- localStorage.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
+ }
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: string | undefined;\n private subscribers: Set<(value: string | undefined) => void>;\n\n constructor(private options: BindValueOptions) {\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 = localStorage.getItem(this.options.key);\n return value !== null ? value : undefined;\n }\n\n private saveToStorage(value: string): void {\n localStorage.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":";;;;;AAIO,MAAM,UAAU;AAAA,EAIrB,YAAoB,SAA2B;AAHvC;AACA;AAEY,SAAA,UAAA;AAClB,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,aAAa,QAAQ,KAAK,QAAQ,GAAG;AACnD,WAAO,UAAU,OAAO,QAAQ;AAAA,EAClC;AAAA,EAEQ,cAAc,OAAqB;AACzC,iBAAa,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,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
- constructor(options: BindValueOptions);
9
- getValue(): string | undefined;
10
- set(newValue: string): void;
11
- subscribe(callback: (value: string | undefined) => void): () => void;
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 value = localStorage.getItem(this.options.key);
28
- 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
+ }
29
60
  }
30
61
  saveToStorage(value) {
31
- localStorage.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
+ }
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: string | undefined;\n private subscribers: Set<(value: string | undefined) => void>;\n\n constructor(private options: BindValueOptions) {\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 = localStorage.getItem(this.options.key);\n return value !== null ? value : undefined;\n }\n\n private saveToStorage(value: string): void {\n localStorage.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":";;;AAIO,MAAM,UAAU;AAAA,EAIrB,YAAoB,SAA2B;AAHvC;AACA;AAEY,SAAA,UAAA;AAClB,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,aAAa,QAAQ,KAAK,QAAQ,GAAG;AACnD,WAAO,UAAU,OAAO,QAAQ;AAAA,EAClC;AAAA,EAEQ,cAAc,OAAqB;AACzC,iBAAa,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.0.1",
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",