kvozy 0.5.0 → 0.7.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 +448 -4
- package/__compiled__/cjs/src/bindTypes.d.ts +12 -1
- package/__compiled__/cjs/src/bindTypes.js +60 -0
- package/__compiled__/cjs/src/bindTypes.js.map +1 -1
- package/__compiled__/cjs/src/bindValue.d.ts +18 -0
- package/__compiled__/cjs/src/bindValue.js +69 -0
- package/__compiled__/cjs/src/bindValue.js.map +1 -1
- package/__compiled__/cjs/src/index.d.ts +3 -2
- package/__compiled__/cjs/src/index.js +8 -0
- package/__compiled__/cjs/src/index.js.map +1 -1
- package/__compiled__/cjs/src/useStorage.d.ts +4 -1
- package/__compiled__/cjs/src/useStorage.js +5 -0
- package/__compiled__/cjs/src/useStorage.js.map +1 -1
- package/__compiled__/esm/src/bindTypes.d.mts +12 -1
- package/__compiled__/esm/src/bindTypes.mjs +62 -2
- package/__compiled__/esm/src/bindTypes.mjs.map +1 -1
- package/__compiled__/esm/src/bindValue.d.mts +18 -0
- package/__compiled__/esm/src/bindValue.mjs +70 -1
- package/__compiled__/esm/src/bindValue.mjs.map +1 -1
- package/__compiled__/esm/src/index.d.mts +3 -2
- package/__compiled__/esm/src/index.mjs +12 -4
- package/__compiled__/esm/src/useStorage.d.mts +4 -1
- package/__compiled__/esm/src/useStorage.mjs +6 -1
- package/__compiled__/esm/src/useStorage.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Kvozy -
|
|
1
|
+
# Kvozy - localStorage Binding Library
|
|
2
2
|
|
|
3
|
-
Simple, minimal
|
|
3
|
+
Simple, minimal library for encapsulating localStorage logic.
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
@@ -14,6 +14,7 @@ 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
|
+
- Namespace-based bindings (bindValueNS) for grouping related keys
|
|
17
18
|
- Type-safe generic API with custom serialization/deserialization
|
|
18
19
|
- Flexible storage support (localStorage, sessionStorage, in-memory)
|
|
19
20
|
- Graceful fallback to in-memory storage when storage is unavailable
|
|
@@ -106,13 +107,94 @@ React hook that connects a BindValue instance to React state.
|
|
|
106
107
|
|
|
107
108
|
**Behavior:**
|
|
108
109
|
|
|
109
|
-
- `subscribe()` does NOT call
|
|
110
|
-
- Callbacks are only invoked when
|
|
110
|
+
- `subscribe()` does NOT call callback immediately when subscribing
|
|
111
|
+
- Callbacks are only invoked when value changes via `set()`
|
|
111
112
|
- If `serialize()` fails, in-memory value is kept but storage is NOT updated
|
|
112
113
|
- If `deserialize()` fails, `defaultValue` is returned
|
|
113
114
|
|
|
114
115
|
**Example:**
|
|
115
116
|
|
|
117
|
+
```typescript
|
|
118
|
+
const Component = () => {
|
|
119
|
+
const { value, setValue } = useStorage(myBinding);
|
|
120
|
+
|
|
121
|
+
return <div>
|
|
122
|
+
<p>Current value: {value}</p>
|
|
123
|
+
<button onClick={() => setValue('new value')}>
|
|
124
|
+
Update Value
|
|
125
|
+
</button>
|
|
126
|
+
</div>;
|
|
127
|
+
};
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Namespace API
|
|
131
|
+
|
|
132
|
+
> ⚠️ **Note:** `bindValueNS` is an internal API. Use `useStorageNS` for React components. Direct usage of `bindValueNS` is not recommended.
|
|
133
|
+
|
|
134
|
+
### BindValueNSOptions<T>
|
|
135
|
+
|
|
136
|
+
Options for creating a namespace binder. A namespace allows grouping related keys with a shared prefix and configuration.
|
|
137
|
+
|
|
138
|
+
**Parameters:**
|
|
139
|
+
|
|
140
|
+
- `prefix` (string, required) - Prefix for all keys in this namespace (cannot be empty or whitespace)
|
|
141
|
+
- `defaultValue` (T, required) - Default value shared across all keys in namespace
|
|
142
|
+
- `serialize` (function, required) - Convert value to string: `(value: T) => string`
|
|
143
|
+
- `deserialize` (function, required) - Convert string to value: `(serialized: string) => T`
|
|
144
|
+
- `storage` (Storage, optional) - localStorage, sessionStorage, or undefined for in-memory
|
|
145
|
+
- `version` (string, optional) - Schema version for migration support
|
|
146
|
+
- `migrate` (function, optional) - Migration function: `(oldSerialized: string, oldVersion: string | undefined) => T`
|
|
147
|
+
|
|
148
|
+
### bindValueNS<T>(options)
|
|
149
|
+
|
|
150
|
+
Factory function for creating a namespace binder.
|
|
151
|
+
|
|
152
|
+
**Returns:** `BindValueNS<T>` instance
|
|
153
|
+
|
|
154
|
+
**Behavior:**
|
|
155
|
+
|
|
156
|
+
- Throws error if prefix is empty or whitespace-only
|
|
157
|
+
- All bindings created from the namespace share the same configuration
|
|
158
|
+
- Keys are combined as `${prefix}\x1F${key}` using Unit Separator
|
|
159
|
+
|
|
160
|
+
### BindValueNS<T>.bind(key)
|
|
161
|
+
|
|
162
|
+
Creates a individual binding for a specific key within the namespace.
|
|
163
|
+
|
|
164
|
+
**Parameters:**
|
|
165
|
+
|
|
166
|
+
- `key` (string, required) - Key for this specific binding
|
|
167
|
+
|
|
168
|
+
**Returns:** `BindValue<T>` instance
|
|
169
|
+
|
|
170
|
+
**Behavior:**
|
|
171
|
+
|
|
172
|
+
- Throws error if key is empty or whitespace-only
|
|
173
|
+
- Combines namespace prefix with key: `${prefix}\x1F${key}`
|
|
174
|
+
- Each binding has independent subscribers and state
|
|
175
|
+
|
|
176
|
+
### useStorageNS<T>(namespace, options)
|
|
177
|
+
|
|
178
|
+
React hook that connects a namespace to React state with a specific key.
|
|
179
|
+
|
|
180
|
+
**Parameters:**
|
|
181
|
+
|
|
182
|
+
- `namespace` (BindValueNS<T>, required) - Namespace instance
|
|
183
|
+
- `options: { key: string }` - Key for this specific binding
|
|
184
|
+
|
|
185
|
+
**Returns:** `{ value, setValue }`
|
|
186
|
+
|
|
187
|
+
- `value` - `T` - current value from storage
|
|
188
|
+
- `setValue` - `(value: T) => void` - function to update value
|
|
189
|
+
|
|
190
|
+
**Behavior:**
|
|
191
|
+
|
|
192
|
+
- Creates a binding internally using `namespace.bind(options.key)`
|
|
193
|
+
- Delegates to existing `useStorage` hook
|
|
194
|
+
- Components using different keys from the same namespace don't share state
|
|
195
|
+
|
|
196
|
+
**Example:**
|
|
197
|
+
|
|
116
198
|
```typescript
|
|
117
199
|
const Component = () => {
|
|
118
200
|
const { value, setValue } = useStorage(myBinding);
|
|
@@ -293,6 +375,280 @@ const statusBinding = bindEnumValue<Color>({
|
|
|
293
375
|
});
|
|
294
376
|
```
|
|
295
377
|
|
|
378
|
+
## Namespace Type-Specific Shortcuts
|
|
379
|
+
|
|
380
|
+
Kvozy provides namespace shortcuts for common types, eliminating boilerplate while maintaining the benefits of namespace-based key organization.
|
|
381
|
+
|
|
382
|
+
| Shortcut | Type | Default Value | Storage Format |
|
|
383
|
+
| -------------------- | ---------- | ----------------- | --------------------- |
|
|
384
|
+
| `bindStringValueNS` | `string` | `""` | String as-is |
|
|
385
|
+
| `bindNumberValueNS` | `number` | `0` | Decimal string |
|
|
386
|
+
| `bindBooleanValueNS` | `boolean` | `false` | `"true"` or `"false"` |
|
|
387
|
+
| `bindJSONValueNS<T>` | `T` | User must provide | JSON string |
|
|
388
|
+
| `bindEnumValueNS<E>` | `E` (enum) | User must provide | String/number as-is |
|
|
389
|
+
|
|
390
|
+
### bindStringValueNS
|
|
391
|
+
|
|
392
|
+
For string values with identity serialization.
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
import { bindStringValueNS, useStorageNS } from "kvozy";
|
|
396
|
+
|
|
397
|
+
// Default empty string
|
|
398
|
+
const appNS = bindStringValueNS({
|
|
399
|
+
prefix: "app",
|
|
400
|
+
storage: localStorage,
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
// With custom default value
|
|
404
|
+
const userNS = bindStringValueNS({
|
|
405
|
+
prefix: "user",
|
|
406
|
+
defaultValue: "guest",
|
|
407
|
+
storage: localStorage,
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
// Use in components
|
|
411
|
+
const Component = () => {
|
|
412
|
+
const { value: name, setValue: setName } = useStorageNS(userNS, { key: "name" });
|
|
413
|
+
return <input value={name} onChange={(e) => setName(e.target.value)} />;
|
|
414
|
+
};
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### bindNumberValueNS
|
|
418
|
+
|
|
419
|
+
For numeric values.
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
import { bindNumberValueNS, useStorageNS } from "kvozy";
|
|
423
|
+
|
|
424
|
+
const counterNS = bindNumberValueNS({
|
|
425
|
+
prefix: "counters",
|
|
426
|
+
storage: localStorage,
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
const { value: count, setValue: setCount } = useStorageNS(counterNS, {
|
|
430
|
+
key: "views",
|
|
431
|
+
});
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### bindBooleanValueNS
|
|
435
|
+
|
|
436
|
+
For boolean values. Stores as `"true"` or `"false"` for readability in devtools.
|
|
437
|
+
|
|
438
|
+
```typescript
|
|
439
|
+
import { bindBooleanValueNS, useStorageNS } from "kvozy";
|
|
440
|
+
|
|
441
|
+
const settingsNS = bindBooleanValueNS({
|
|
442
|
+
prefix: "settings",
|
|
443
|
+
storage: localStorage,
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
const { value: enabled, setValue: setEnabled } = useStorageNS(settingsNS, {
|
|
447
|
+
key: "notifications",
|
|
448
|
+
});
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### bindJSONValueNS<T>
|
|
452
|
+
|
|
453
|
+
For complex objects and arrays. Requires a default value.
|
|
454
|
+
|
|
455
|
+
```typescript
|
|
456
|
+
import { bindJSONValueNS, useStorageNS } from "kvozy";
|
|
457
|
+
|
|
458
|
+
interface User {
|
|
459
|
+
name: string;
|
|
460
|
+
email: string;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const userNS = bindJSONValueNS<User>({
|
|
464
|
+
prefix: "users",
|
|
465
|
+
defaultValue: { name: "", email: "" },
|
|
466
|
+
storage: localStorage,
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
const { value: user, setValue: setUser } = useStorageNS(userNS, {
|
|
470
|
+
key: "current",
|
|
471
|
+
});
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### bindEnumValueNS<E>
|
|
475
|
+
|
|
476
|
+
For TypeScript enums. Works with both string and number enums.
|
|
477
|
+
|
|
478
|
+
```typescript
|
|
479
|
+
import { bindEnumValueNS, useStorageNS } from "kvozy";
|
|
480
|
+
|
|
481
|
+
enum Theme {
|
|
482
|
+
Light = "light",
|
|
483
|
+
Dark = "dark",
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const settingsNS = bindEnumValueNS<Theme>({
|
|
487
|
+
prefix: "settings",
|
|
488
|
+
defaultValue: Theme.Light,
|
|
489
|
+
storage: localStorage,
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
const { value: theme, setValue: setTheme } = useStorageNS(settingsNS, {
|
|
493
|
+
key: "theme",
|
|
494
|
+
});
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
### When to Use Namespaces vs. Individual Bindings
|
|
498
|
+
|
|
499
|
+
**Use namespaces when:**
|
|
500
|
+
|
|
501
|
+
- You have multiple related keys (e.g., all user settings)
|
|
502
|
+
- You want to share configuration across multiple keys
|
|
503
|
+
- You want to organize keys by feature area (user, app, settings)
|
|
504
|
+
- You want to avoid repeating serialize/deserialize logic
|
|
505
|
+
|
|
506
|
+
**Use individual bindings when:**
|
|
507
|
+
|
|
508
|
+
- You have a single key
|
|
509
|
+
- You need different serialization logic per key
|
|
510
|
+
- You want maximum flexibility per binding
|
|
511
|
+
|
|
512
|
+
## Namespace Examples
|
|
513
|
+
|
|
514
|
+
### Basic Namespace Usage
|
|
515
|
+
|
|
516
|
+
```typescript
|
|
517
|
+
import { bindValueNS, useStorageNS } from 'kvozy';
|
|
518
|
+
|
|
519
|
+
// Create namespace with shared configuration
|
|
520
|
+
const appNS = bindValueNS<string>({
|
|
521
|
+
prefix: 'app',
|
|
522
|
+
defaultValue: '',
|
|
523
|
+
serialize: (v) => v,
|
|
524
|
+
deserialize: (s) => s,
|
|
525
|
+
storage: localStorage,
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
// Use in component with specific key
|
|
529
|
+
const Component = () => {
|
|
530
|
+
const { value, setValue } = useStorageNS(appNS, { key: 'user' });
|
|
531
|
+
|
|
532
|
+
return <input value={value} onChange={(e) => setValue(e.target.value)} />;
|
|
533
|
+
};
|
|
534
|
+
// Storage key will be: 'app\x1Fuser'
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
### Multiple Components Sharing Namespace
|
|
538
|
+
|
|
539
|
+
```typescript
|
|
540
|
+
import { bindValueNS, useStorageNS } from 'kvozy';
|
|
541
|
+
|
|
542
|
+
const appNS = bindValueNS<string>({
|
|
543
|
+
prefix: 'app',
|
|
544
|
+
defaultValue: '',
|
|
545
|
+
serialize: (v) => v,
|
|
546
|
+
deserialize: (s) => s,
|
|
547
|
+
storage: localStorage,
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
const UserSettings = () => {
|
|
551
|
+
const { value: language, setValue: setLanguage } = useStorageNS(appNS, { key: 'language' });
|
|
552
|
+
const { value: theme, setValue: setTheme } = useStorageNS(appNS, { key: 'theme' });
|
|
553
|
+
|
|
554
|
+
return (
|
|
555
|
+
<div>
|
|
556
|
+
<select value={language} onChange={(e) => setLanguage(e.target.value)}>
|
|
557
|
+
<option value="en">English</option>
|
|
558
|
+
<option value="es">Spanish</option>
|
|
559
|
+
</select>
|
|
560
|
+
<button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
|
|
561
|
+
Toggle Theme
|
|
562
|
+
</button>
|
|
563
|
+
</div>
|
|
564
|
+
);
|
|
565
|
+
};
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
### Namespace with Versioning
|
|
569
|
+
|
|
570
|
+
```typescript
|
|
571
|
+
import { bindValueNS, useStorageNS } from "kvozy";
|
|
572
|
+
|
|
573
|
+
const appNS = bindValueNS<UserData>({
|
|
574
|
+
prefix: "app",
|
|
575
|
+
defaultValue: { name: "", email: "" },
|
|
576
|
+
serialize: (v) => JSON.stringify(v),
|
|
577
|
+
deserialize: (s) => JSON.parse(s),
|
|
578
|
+
storage: localStorage,
|
|
579
|
+
version: "2.0.0",
|
|
580
|
+
migrate: (old, oldVersion) => {
|
|
581
|
+
const oldData = JSON.parse(old);
|
|
582
|
+
return { name: oldData.name, email: "" };
|
|
583
|
+
},
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
const { value: user, setValue: setUser } = useStorageNS(appNS, { key: "user" });
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
### Namespace Organization Pattern
|
|
590
|
+
|
|
591
|
+
Organize your app by creating separate namespaces for different domains:
|
|
592
|
+
|
|
593
|
+
```typescript
|
|
594
|
+
// User-related keys
|
|
595
|
+
const userNS = bindValueNS<string>({
|
|
596
|
+
prefix: "user",
|
|
597
|
+
defaultValue: "",
|
|
598
|
+
storage: localStorage,
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
// Application settings keys
|
|
602
|
+
const settingsNS = bindValueNS<string>({
|
|
603
|
+
prefix: "settings",
|
|
604
|
+
defaultValue: "",
|
|
605
|
+
storage: localStorage,
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
// Temporary state keys
|
|
609
|
+
const tempNS = bindValueNS<string>({
|
|
610
|
+
prefix: "temp",
|
|
611
|
+
defaultValue: "",
|
|
612
|
+
storage: undefined, // in-memory only
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
// Keys will be stored as:
|
|
616
|
+
// user\x1Fname', 'user\x1Femail', 'user\x1Ftheme'
|
|
617
|
+
// Settings keys: 'settings\x1Fnotifications', 'settings\x1Flanguage', 'settings\x1Ftheme'
|
|
618
|
+
// Temp keys: 'temp\x1Fdraft', 'temp\x1Funsaved'
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
## Namespace Isolation
|
|
622
|
+
|
|
623
|
+
Each namespace maintains complete isolation from other namespaces:
|
|
624
|
+
|
|
625
|
+
```typescript
|
|
626
|
+
const appNS = bindValueNS<string>({
|
|
627
|
+
prefix: "app",
|
|
628
|
+
defaultValue: "",
|
|
629
|
+
storage: localStorage,
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
const userNS = bindValueNS<string>({
|
|
633
|
+
prefix: "user",
|
|
634
|
+
defaultValue: "",
|
|
635
|
+
storage: localStorage,
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
// These are completely isolated:
|
|
639
|
+
// appNS.bind('name') stores to 'app\x1Fname'
|
|
640
|
+
// userNS.bind('name') stores to 'user\x1Fname'
|
|
641
|
+
// No risk of key collisions between namespaces
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
**Benefits:**
|
|
645
|
+
|
|
646
|
+
- **Organized storage**: Group related keys with meaningful prefixes
|
|
647
|
+
- **Collision prevention**: Different prefixes create separate storage domains
|
|
648
|
+
- **Shared configuration**: All bindings in a namespace inherit same settings
|
|
649
|
+
- **Easy maintenance**: Update settings once for all keys in namespace
|
|
650
|
+
- **Clean separation**: Logical boundaries between different app features
|
|
651
|
+
|
|
296
652
|
### When to Use Shortcuts vs. bindValue
|
|
297
653
|
|
|
298
654
|
**Use shortcuts when:**
|
|
@@ -368,6 +724,37 @@ const themeBinding = bindEnumValue<Theme>({
|
|
|
368
724
|
});
|
|
369
725
|
```
|
|
370
726
|
|
|
727
|
+
## Namespace Isolation
|
|
728
|
+
|
|
729
|
+
Each namespace maintains complete isolation from other namespaces:
|
|
730
|
+
|
|
731
|
+
```typescript
|
|
732
|
+
const appNS = bindValueNS<string>({
|
|
733
|
+
prefix: "app",
|
|
734
|
+
defaultValue: "",
|
|
735
|
+
storage: localStorage,
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
const userNS = bindValueNS<string>({
|
|
739
|
+
prefix: "user",
|
|
740
|
+
defaultValue: "",
|
|
741
|
+
storage: localStorage,
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
// These are completely isolated:
|
|
745
|
+
// appNS.bind('name') stores to 'app\x1Fname'
|
|
746
|
+
// userNS.bind('name') stores to 'user\x1Fname'
|
|
747
|
+
// No risk of key collisions between namespaces
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
**Benefits:**
|
|
751
|
+
|
|
752
|
+
- **Organized storage**: Group related keys with meaningful prefixes
|
|
753
|
+
- **Collision prevention**: Different prefixes create separate storage domains
|
|
754
|
+
- **Shared configuration**: All bindings in a namespace inherit same settings
|
|
755
|
+
- **Easy maintenance**: Update settings once for all keys in namespace
|
|
756
|
+
- **Clean separation**: Logical boundaries between different app features
|
|
757
|
+
|
|
371
758
|
## Usage Examples
|
|
372
759
|
|
|
373
760
|
### Basic Usage
|
|
@@ -685,6 +1072,46 @@ const Counter = () => {
|
|
|
685
1072
|
};
|
|
686
1073
|
```
|
|
687
1074
|
|
|
1075
|
+
### Namespace-Based Organization
|
|
1076
|
+
|
|
1077
|
+
Organize your app by creating separate namespaces for different domains:
|
|
1078
|
+
|
|
1079
|
+
```typescript
|
|
1080
|
+
// User-related keys
|
|
1081
|
+
const userNS = bindValueNS<string>({
|
|
1082
|
+
prefix: 'user',
|
|
1083
|
+
defaultValue: '',
|
|
1084
|
+
storage: localStorage,
|
|
1085
|
+
});
|
|
1086
|
+
|
|
1087
|
+
// Application settings keys
|
|
1088
|
+
const settingsNS = bindValueNS<string>({
|
|
1089
|
+
prefix: 'settings',
|
|
1090
|
+
defaultValue: '',
|
|
1091
|
+
storage: localStorage,
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1094
|
+
const UserPreferences = () => {
|
|
1095
|
+
const { value: language, setValue: setLanguage } = useStorageNS(userNS, { key: 'language' });
|
|
1096
|
+
const { value: notificationsEnabled, setValue: setNotifications } = useStorageNS(settingsNS, { key: 'notifications' });
|
|
1097
|
+
|
|
1098
|
+
return (
|
|
1099
|
+
<div>
|
|
1100
|
+
<select value={language} onChange={(e) => setLanguage(e.target.value)}>
|
|
1101
|
+
<option value="en">English</option>
|
|
1102
|
+
<option value="es">Spanish</option>
|
|
1103
|
+
</select>
|
|
1104
|
+
<button onClick={() => setNotifications(!notificationsEnabled)}>
|
|
1105
|
+
{notificationsEnabled ? 'Disable' : 'Enable'} Notifications
|
|
1106
|
+
</button>
|
|
1107
|
+
</div>
|
|
1108
|
+
);
|
|
1109
|
+
};
|
|
1110
|
+
|
|
1111
|
+
// User keys: 'user\x1Flanguage', 'user\x1Femail', 'user\x1Ftheme'
|
|
1112
|
+
// Settings keys: 'settings\x1Fnotifications', 'settings\x1Flanguage', 'settings\x1Ftheme'
|
|
1113
|
+
```
|
|
1114
|
+
|
|
688
1115
|
## Architecture
|
|
689
1116
|
|
|
690
1117
|
### Core: bindValue
|
|
@@ -708,6 +1135,22 @@ class BindValue<T> {
|
|
|
708
1135
|
}
|
|
709
1136
|
```
|
|
710
1137
|
|
|
1138
|
+
### Namespace: BindValueNS
|
|
1139
|
+
|
|
1140
|
+
Namespace binder for grouping related keys with shared configuration:
|
|
1141
|
+
|
|
1142
|
+
- Manages namespace configuration (prefix, defaultValue, serialize, deserialize)
|
|
1143
|
+
- Provides `bind(key)` method to create individual `BindValue` instances
|
|
1144
|
+
- Combines prefix and key using Unit Separator: `${prefix}\x1F${key}`
|
|
1145
|
+
- Validates prefix and key are not empty/whitespace
|
|
1146
|
+
- Creates isolated storage domains for different prefixes
|
|
1147
|
+
|
|
1148
|
+
```typescript
|
|
1149
|
+
class BindValueNS<T> {
|
|
1150
|
+
bind(key: string): BindValue<T>;
|
|
1151
|
+
}
|
|
1152
|
+
```
|
|
1153
|
+
|
|
711
1154
|
### React: useStorage
|
|
712
1155
|
|
|
713
1156
|
Thin wrapper that connects `bindValue` to React:
|
|
@@ -737,6 +1180,7 @@ function useStorage<T>(binding: BindValue<T>): UseStorageReturn<T> {
|
|
|
737
1180
|
- **No SSR support**: Currently designed for client-side only (requires `window.localStorage`).
|
|
738
1181
|
- **No cross-tab sync**: Changes in one tab don't update other tabs automatically.
|
|
739
1182
|
- **Serialize failures**: If `serialize()` fails, the value is kept in memory but not persisted to storage (graceful degradation).
|
|
1183
|
+
- **Namespace state isolation**: Components using the same namespace but different keys don't share React state. Each `useStorageNS(namespace, { key: 'x' })` call creates an independent binding.
|
|
740
1184
|
|
|
741
1185
|
## Future Plans
|
|
742
1186
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BindValue, type BindValueOptions } from "./bindValue.js";
|
|
1
|
+
import { BindValue, BindValueNS, type BindValueOptions, type BindValueNSOptions } from "./bindValue.js";
|
|
2
2
|
export declare function bindStringValue(options: Omit<BindValueOptions<string>, "serialize" | "deserialize" | "defaultValue"> & {
|
|
3
3
|
defaultValue?: string;
|
|
4
4
|
}): BindValue<string>;
|
|
@@ -10,3 +10,14 @@ export declare function bindBooleanValue(options: Omit<BindValueOptions<boolean>
|
|
|
10
10
|
}): BindValue<boolean>;
|
|
11
11
|
export declare function bindJSONValue<T>(options: Omit<BindValueOptions<T>, "serialize" | "deserialize">): BindValue<T>;
|
|
12
12
|
export declare function bindEnumValue<E extends string | number>(options: Omit<BindValueOptions<E>, "serialize" | "deserialize">): BindValue<E>;
|
|
13
|
+
export declare function bindStringValueNS(options: Omit<BindValueNSOptions<string>, "serialize" | "deserialize" | "defaultValue"> & {
|
|
14
|
+
defaultValue?: string;
|
|
15
|
+
}): BindValueNS<string>;
|
|
16
|
+
export declare function bindNumberValueNS(options: Omit<BindValueNSOptions<number>, "serialize" | "deserialize" | "defaultValue"> & {
|
|
17
|
+
defaultValue?: number;
|
|
18
|
+
}): BindValueNS<number>;
|
|
19
|
+
export declare function bindBooleanValueNS(options: Omit<BindValueNSOptions<boolean>, "serialize" | "deserialize" | "defaultValue"> & {
|
|
20
|
+
defaultValue?: boolean;
|
|
21
|
+
}): BindValueNS<boolean>;
|
|
22
|
+
export declare function bindJSONValueNS<T>(options: Omit<BindValueNSOptions<T>, "serialize" | "deserialize">): BindValueNS<T>;
|
|
23
|
+
export declare function bindEnumValueNS<E extends string | number>(options: Omit<BindValueNSOptions<E>, "serialize" | "deserialize">): BindValueNS<E>;
|
|
@@ -56,9 +56,69 @@ function bindEnumValue(options) {
|
|
|
56
56
|
migrate: options.migrate
|
|
57
57
|
});
|
|
58
58
|
}
|
|
59
|
+
function bindStringValueNS(options) {
|
|
60
|
+
return bindValue.bindValueNS({
|
|
61
|
+
prefix: options.prefix,
|
|
62
|
+
defaultValue: options.defaultValue ?? "",
|
|
63
|
+
serialize: (v) => v,
|
|
64
|
+
deserialize: (s) => s,
|
|
65
|
+
storage: options.storage,
|
|
66
|
+
version: options.version,
|
|
67
|
+
migrate: options.migrate
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
function bindNumberValueNS(options) {
|
|
71
|
+
return bindValue.bindValueNS({
|
|
72
|
+
prefix: options.prefix,
|
|
73
|
+
defaultValue: options.defaultValue ?? 0,
|
|
74
|
+
serialize: (v) => String(v),
|
|
75
|
+
deserialize: (s) => Number(s),
|
|
76
|
+
storage: options.storage,
|
|
77
|
+
version: options.version,
|
|
78
|
+
migrate: options.migrate
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
function bindBooleanValueNS(options) {
|
|
82
|
+
return bindValue.bindValueNS({
|
|
83
|
+
prefix: options.prefix,
|
|
84
|
+
defaultValue: options.defaultValue ?? false,
|
|
85
|
+
serialize: (v) => String(v),
|
|
86
|
+
deserialize: (s) => s === "true",
|
|
87
|
+
storage: options.storage,
|
|
88
|
+
version: options.version,
|
|
89
|
+
migrate: options.migrate
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
function bindJSONValueNS(options) {
|
|
93
|
+
return bindValue.bindValueNS({
|
|
94
|
+
prefix: options.prefix,
|
|
95
|
+
defaultValue: options.defaultValue,
|
|
96
|
+
serialize: (v) => JSON.stringify(v),
|
|
97
|
+
deserialize: (s) => JSON.parse(s),
|
|
98
|
+
storage: options.storage,
|
|
99
|
+
version: options.version,
|
|
100
|
+
migrate: options.migrate
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
function bindEnumValueNS(options) {
|
|
104
|
+
return bindValue.bindValueNS({
|
|
105
|
+
prefix: options.prefix,
|
|
106
|
+
defaultValue: options.defaultValue,
|
|
107
|
+
serialize: (v) => String(v),
|
|
108
|
+
deserialize: (s) => typeof options.defaultValue === "number" ? Number(s) : s,
|
|
109
|
+
storage: options.storage,
|
|
110
|
+
version: options.version,
|
|
111
|
+
migrate: options.migrate
|
|
112
|
+
});
|
|
113
|
+
}
|
|
59
114
|
exports.bindBooleanValue = bindBooleanValue;
|
|
115
|
+
exports.bindBooleanValueNS = bindBooleanValueNS;
|
|
60
116
|
exports.bindEnumValue = bindEnumValue;
|
|
117
|
+
exports.bindEnumValueNS = bindEnumValueNS;
|
|
61
118
|
exports.bindJSONValue = bindJSONValue;
|
|
119
|
+
exports.bindJSONValueNS = bindJSONValueNS;
|
|
62
120
|
exports.bindNumberValue = bindNumberValue;
|
|
121
|
+
exports.bindNumberValueNS = bindNumberValueNS;
|
|
63
122
|
exports.bindStringValue = bindStringValue;
|
|
123
|
+
exports.bindStringValueNS = bindStringValueNS;
|
|
64
124
|
//# sourceMappingURL=bindTypes.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bindTypes.js","sources":["../../../../src/bindTypes.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"bindTypes.js","sources":["../../../../src/bindTypes.ts"],"sourcesContent":["import {\n bindValue,\n bindValueNS,\n BindValue,\n BindValueNS,\n type BindValueOptions,\n type BindValueNSOptions,\n} from \"./bindValue.js\";\n\nexport function bindStringValue(\n options: Omit<\n BindValueOptions<string>,\n \"serialize\" | \"deserialize\" | \"defaultValue\"\n > & {\n defaultValue?: string;\n },\n): BindValue<string> {\n return bindValue<string>({\n key: options.key,\n defaultValue: options.defaultValue ?? \"\",\n serialize: (v) => v,\n deserialize: (s) => s,\n storage: options.storage,\n version: options.version,\n migrate: options.migrate,\n });\n}\n\nexport function bindNumberValue(\n options: Omit<\n BindValueOptions<number>,\n \"serialize\" | \"deserialize\" | \"defaultValue\"\n > & {\n defaultValue?: number;\n },\n): BindValue<number> {\n return bindValue<number>({\n key: options.key,\n defaultValue: options.defaultValue ?? 0,\n serialize: (v) => String(v),\n deserialize: (s) => Number(s),\n storage: options.storage,\n version: options.version,\n migrate: options.migrate,\n });\n}\n\nexport function bindBooleanValue(\n options: Omit<\n BindValueOptions<boolean>,\n \"serialize\" | \"deserialize\" | \"defaultValue\"\n > & {\n defaultValue?: boolean;\n },\n): BindValue<boolean> {\n return bindValue<boolean>({\n key: options.key,\n defaultValue: options.defaultValue ?? false,\n serialize: (v) => String(v),\n deserialize: (s) => s === \"true\",\n storage: options.storage,\n version: options.version,\n migrate: options.migrate,\n });\n}\n\nexport function bindJSONValue<T>(\n options: Omit<BindValueOptions<T>, \"serialize\" | \"deserialize\">,\n): BindValue<T> {\n return bindValue<T>({\n key: options.key,\n defaultValue: options.defaultValue,\n serialize: (v) => JSON.stringify(v),\n deserialize: (s) => JSON.parse(s),\n storage: options.storage,\n version: options.version,\n migrate: options.migrate,\n });\n}\n\nexport function bindEnumValue<E extends string | number>(\n options: Omit<BindValueOptions<E>, \"serialize\" | \"deserialize\">,\n): BindValue<E> {\n return bindValue<E>({\n key: options.key,\n defaultValue: options.defaultValue,\n serialize: (v) => String(v),\n deserialize: (s) =>\n (typeof options.defaultValue === \"number\" ? Number(s) : s) as E,\n storage: options.storage,\n version: options.version,\n migrate: options.migrate,\n });\n}\n\nexport function bindStringValueNS(\n options: Omit<\n BindValueNSOptions<string>,\n \"serialize\" | \"deserialize\" | \"defaultValue\"\n > & {\n defaultValue?: string;\n },\n): BindValueNS<string> {\n return bindValueNS<string>({\n prefix: options.prefix,\n defaultValue: options.defaultValue ?? \"\",\n serialize: (v) => v,\n deserialize: (s) => s,\n storage: options.storage,\n version: options.version,\n migrate: options.migrate,\n });\n}\n\nexport function bindNumberValueNS(\n options: Omit<\n BindValueNSOptions<number>,\n \"serialize\" | \"deserialize\" | \"defaultValue\"\n > & {\n defaultValue?: number;\n },\n): BindValueNS<number> {\n return bindValueNS<number>({\n prefix: options.prefix,\n defaultValue: options.defaultValue ?? 0,\n serialize: (v) => String(v),\n deserialize: (s) => Number(s),\n storage: options.storage,\n version: options.version,\n migrate: options.migrate,\n });\n}\n\nexport function bindBooleanValueNS(\n options: Omit<\n BindValueNSOptions<boolean>,\n \"serialize\" | \"deserialize\" | \"defaultValue\"\n > & {\n defaultValue?: boolean;\n },\n): BindValueNS<boolean> {\n return bindValueNS<boolean>({\n prefix: options.prefix,\n defaultValue: options.defaultValue ?? false,\n serialize: (v) => String(v),\n deserialize: (s) => s === \"true\",\n storage: options.storage,\n version: options.version,\n migrate: options.migrate,\n });\n}\n\nexport function bindJSONValueNS<T>(\n options: Omit<BindValueNSOptions<T>, \"serialize\" | \"deserialize\">,\n): BindValueNS<T> {\n return bindValueNS<T>({\n prefix: options.prefix,\n defaultValue: options.defaultValue,\n serialize: (v) => JSON.stringify(v),\n deserialize: (s) => JSON.parse(s),\n storage: options.storage,\n version: options.version,\n migrate: options.migrate,\n });\n}\n\nexport function bindEnumValueNS<E extends string | number>(\n options: Omit<BindValueNSOptions<E>, \"serialize\" | \"deserialize\">,\n): BindValueNS<E> {\n return bindValueNS<E>({\n prefix: options.prefix,\n defaultValue: options.defaultValue,\n serialize: (v) => String(v),\n deserialize: (s) =>\n (typeof options.defaultValue === \"number\" ? Number(s) : s) as E,\n storage: options.storage,\n version: options.version,\n migrate: options.migrate,\n });\n}\n"],"names":["bindValue","bindValueNS"],"mappings":"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"}
|
|
@@ -7,6 +7,15 @@ export interface BindValueOptions<T> {
|
|
|
7
7
|
version?: string;
|
|
8
8
|
migrate?: (oldSerialized: string, oldVersion: string | undefined) => T;
|
|
9
9
|
}
|
|
10
|
+
export interface BindValueNSOptions<T> {
|
|
11
|
+
prefix: string;
|
|
12
|
+
defaultValue: T;
|
|
13
|
+
serialize: (value: T) => string;
|
|
14
|
+
deserialize: (serialized: string) => T;
|
|
15
|
+
storage?: Storage;
|
|
16
|
+
version?: string;
|
|
17
|
+
migrate?: (oldSerialized: string, oldVersion: string | undefined) => T;
|
|
18
|
+
}
|
|
10
19
|
export declare class BindValue<T> {
|
|
11
20
|
private options;
|
|
12
21
|
private value;
|
|
@@ -16,8 +25,17 @@ export declare class BindValue<T> {
|
|
|
16
25
|
getValue(): T;
|
|
17
26
|
set(newValue: T): void;
|
|
18
27
|
subscribe(callback: (value: T) => void): () => void;
|
|
28
|
+
getRaw(): string | null;
|
|
29
|
+
setRaw(rawValue: string): void;
|
|
19
30
|
private loadFromStorage;
|
|
20
31
|
private saveToStorage;
|
|
21
32
|
private notifySubscribers;
|
|
22
33
|
}
|
|
23
34
|
export declare function bindValue<T>(options: BindValueOptions<T>): BindValue<T>;
|
|
35
|
+
export declare function _clearInMemoryStorage(): void;
|
|
36
|
+
export declare class BindValueNS<T> {
|
|
37
|
+
private options;
|
|
38
|
+
constructor(options: BindValueNSOptions<T>);
|
|
39
|
+
bind(key: string): BindValue<T>;
|
|
40
|
+
}
|
|
41
|
+
export declare function bindValueNS<T>(options: BindValueNSOptions<T>): BindValueNS<T>;
|
|
@@ -49,6 +49,48 @@ class BindValue {
|
|
|
49
49
|
this.subscribers.delete(callback);
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
|
+
getRaw() {
|
|
53
|
+
return this.storage.getItem(this.options.key);
|
|
54
|
+
}
|
|
55
|
+
setRaw(rawValue) {
|
|
56
|
+
let deserializedValue;
|
|
57
|
+
const version = this.options.version;
|
|
58
|
+
let serializedPart;
|
|
59
|
+
if (version) {
|
|
60
|
+
if (!rawValue.startsWith("\0")) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const parts = rawValue.split("\0");
|
|
64
|
+
if (parts.length < 3) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const versionPart = parts[1];
|
|
68
|
+
if (versionPart !== version) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
serializedPart = parts.slice(2).join("\0");
|
|
72
|
+
} else {
|
|
73
|
+
serializedPart = rawValue;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
deserializedValue = this.options.deserialize(serializedPart);
|
|
77
|
+
} catch {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const serializedValue = this.options.serialize(deserializedValue);
|
|
81
|
+
let reconstructedRaw;
|
|
82
|
+
if (version) {
|
|
83
|
+
reconstructedRaw = `\0${version}\0${serializedValue}`;
|
|
84
|
+
} else {
|
|
85
|
+
reconstructedRaw = serializedValue;
|
|
86
|
+
}
|
|
87
|
+
if (reconstructedRaw !== rawValue) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
this.storage.setItem(this.options.key, rawValue);
|
|
91
|
+
this.value = deserializedValue;
|
|
92
|
+
this.notifySubscribers();
|
|
93
|
+
}
|
|
52
94
|
loadFromStorage() {
|
|
53
95
|
const rawValue = this.storage.getItem(this.options.key);
|
|
54
96
|
if (rawValue === null) {
|
|
@@ -116,6 +158,33 @@ class BindValue {
|
|
|
116
158
|
function bindValue(options) {
|
|
117
159
|
return new BindValue(options);
|
|
118
160
|
}
|
|
161
|
+
class BindValueNS {
|
|
162
|
+
constructor(options) {
|
|
163
|
+
this.options = options;
|
|
164
|
+
if (!this.options.prefix || this.options.prefix.trim() === "") {
|
|
165
|
+
throw new Error("Prefix cannot be empty");
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
bind(key) {
|
|
169
|
+
if (!key || key.trim() === "") {
|
|
170
|
+
throw new Error("Key cannot be empty");
|
|
171
|
+
}
|
|
172
|
+
return new BindValue({
|
|
173
|
+
key: `${this.options.prefix}${key}`,
|
|
174
|
+
defaultValue: this.options.defaultValue,
|
|
175
|
+
serialize: this.options.serialize,
|
|
176
|
+
deserialize: this.options.deserialize,
|
|
177
|
+
storage: this.options.storage,
|
|
178
|
+
version: this.options.version,
|
|
179
|
+
migrate: this.options.migrate
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function bindValueNS(options) {
|
|
184
|
+
return new BindValueNS(options);
|
|
185
|
+
}
|
|
119
186
|
exports.BindValue = BindValue;
|
|
187
|
+
exports.BindValueNS = BindValueNS;
|
|
120
188
|
exports.bindValue = bindValue;
|
|
189
|
+
exports.bindValueNS = bindValueNS;
|
|
121
190
|
//# sourceMappingURL=bindValue.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bindValue.js","sources":["../../../../src/bindValue.ts"],"sourcesContent":["export interface BindValueOptions<T> {\n key: string;\n defaultValue: T;\n serialize: (value: T) => string;\n deserialize: (serialized: string) => T;\n storage?: Storage;\n version?: string;\n migrate?: (oldSerialized: string, oldVersion: string | undefined) => T;\n}\n\nconst memoryStorage = new Map<string, string>();\n\nconst inMemoryStorage: Storage = {\n get length() {\n return memoryStorage.size;\n },\n clear() {\n memoryStorage.clear();\n },\n getItem(key: string) {\n return memoryStorage.get(key) ?? null;\n },\n key(index: number) {\n const keys = Array.from(memoryStorage.keys());\n return keys[index] ?? null;\n },\n removeItem(key: string) {\n memoryStorage.delete(key);\n },\n setItem(key: string, value: string) {\n memoryStorage.set(key, value);\n },\n};\n\nexport class BindValue<T> {\n private value: T;\n private subscribers: Set<(value: T) => void>;\n private storage: Storage;\n\n constructor(private options: BindValueOptions<T>) {\n this.storage = options.storage ?? inMemoryStorage;\n this.subscribers = new Set();\n this.value = this.loadFromStorage();\n }\n\n getValue(): T {\n return this.value;\n }\n\n set(newValue: T): void {\n this.value = newValue;\n this.saveToStorage(newValue);\n this.notifySubscribers();\n }\n\n subscribe(callback: (value: T) => void): () => void {\n this.subscribers.add(callback);\n return () => {\n this.subscribers.delete(callback);\n };\n }\n\n private loadFromStorage(): T {\n const rawValue = this.storage.getItem(this.options.key);\n\n if (rawValue === null) {\n return this.options.defaultValue;\n }\n\n let oldVersion: string | undefined;\n let serializedValue: string;\n\n if (rawValue.startsWith(\"\\x00\")) {\n const parts = rawValue.split(\"\\x00\");\n if (parts.length >= 3) {\n oldVersion = parts[1];\n serializedValue = parts.slice(2).join(\"\\x00\");\n } else {\n serializedValue = rawValue;\n }\n } else {\n serializedValue = rawValue;\n }\n\n const currentVersion = this.options.version;\n\n if (oldVersion !== currentVersion) {\n if (this.options.migrate) {\n try {\n const migratedValue = this.options.migrate(\n serializedValue,\n oldVersion,\n );\n this.saveToStorage(migratedValue);\n return migratedValue;\n } catch {\n this.storage.removeItem(this.options.key);\n return this.options.defaultValue;\n }\n } else {\n this.storage.removeItem(this.options.key);\n return this.options.defaultValue;\n }\n }\n\n try {\n return this.options.deserialize(serializedValue);\n } catch {\n return this.options.defaultValue;\n }\n }\n\n private saveToStorage(value: T): void {\n try {\n const serialized = this.options.serialize(value);\n const version = this.options.version;\n\n if (version) {\n this.storage.setItem(\n this.options.key,\n `\\x00${version}\\x00${serialized}`,\n );\n } else {\n this.storage.setItem(this.options.key, serialized);\n }\n } catch {}\n }\n\n private notifySubscribers(): void {\n for (const subscriber of this.subscribers) {\n subscriber(this.value);\n }\n }\n}\n\nexport function bindValue<T>(options: BindValueOptions<T>): BindValue<T> {\n return new BindValue(options);\n}\n"],"names":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"bindValue.js","sources":["../../../../src/bindValue.ts"],"sourcesContent":["export interface BindValueOptions<T> {\n key: string;\n defaultValue: T;\n serialize: (value: T) => string;\n deserialize: (serialized: string) => T;\n storage?: Storage;\n version?: string;\n migrate?: (oldSerialized: string, oldVersion: string | undefined) => T;\n}\n\nexport interface BindValueNSOptions<T> {\n prefix: string;\n defaultValue: T;\n serialize: (value: T) => string;\n deserialize: (serialized: string) => T;\n storage?: Storage;\n version?: string;\n migrate?: (oldSerialized: string, oldVersion: string | undefined) => T;\n}\n\nconst memoryStorage = new Map<string, string>();\n\nconst inMemoryStorage: Storage = {\n get length() {\n return memoryStorage.size;\n },\n clear() {\n memoryStorage.clear();\n },\n getItem(key: string) {\n return memoryStorage.get(key) ?? null;\n },\n key(index: number) {\n const keys = Array.from(memoryStorage.keys());\n return keys[index] ?? null;\n },\n removeItem(key: string) {\n memoryStorage.delete(key);\n },\n setItem(key: string, value: string) {\n memoryStorage.set(key, value);\n },\n};\n\nexport class BindValue<T> {\n private value: T;\n private subscribers: Set<(value: T) => void>;\n private storage: Storage;\n\n constructor(private options: BindValueOptions<T>) {\n this.storage = options.storage ?? inMemoryStorage;\n this.subscribers = new Set();\n this.value = this.loadFromStorage();\n }\n\n getValue(): T {\n return this.value;\n }\n\n set(newValue: T): void {\n this.value = newValue;\n this.saveToStorage(newValue);\n this.notifySubscribers();\n }\n\n subscribe(callback: (value: T) => void): () => void {\n this.subscribers.add(callback);\n return () => {\n this.subscribers.delete(callback);\n };\n }\n\n getRaw(): string | null {\n return this.storage.getItem(this.options.key);\n }\n\n setRaw(rawValue: string): void {\n let deserializedValue: T;\n const version = this.options.version;\n let serializedPart: string;\n\n if (version) {\n if (!rawValue.startsWith(\"\\x00\")) {\n return;\n }\n\n const parts = rawValue.split(\"\\x00\");\n if (parts.length < 3) {\n return;\n }\n\n const versionPart = parts[1];\n if (versionPart !== version) {\n return;\n }\n\n serializedPart = parts.slice(2).join(\"\\x00\");\n } else {\n serializedPart = rawValue;\n }\n\n try {\n deserializedValue = this.options.deserialize(serializedPart);\n } catch {\n return;\n }\n const serializedValue = this.options.serialize(deserializedValue);\n let reconstructedRaw: string;\n\n if (version) {\n reconstructedRaw = `\\x00${version}\\x00${serializedValue}`;\n } else {\n reconstructedRaw = serializedValue;\n }\n\n if (reconstructedRaw !== rawValue) {\n return;\n }\n\n this.storage.setItem(this.options.key, rawValue);\n this.value = deserializedValue;\n this.notifySubscribers();\n }\n\n private loadFromStorage(): T {\n const rawValue = this.storage.getItem(this.options.key);\n\n if (rawValue === null) {\n return this.options.defaultValue;\n }\n\n let oldVersion: string | undefined;\n let serializedValue: string;\n\n if (rawValue.startsWith(\"\\x00\")) {\n const parts = rawValue.split(\"\\x00\");\n if (parts.length >= 3) {\n oldVersion = parts[1];\n serializedValue = parts.slice(2).join(\"\\x00\");\n } else {\n serializedValue = rawValue;\n }\n } else {\n serializedValue = rawValue;\n }\n\n const currentVersion = this.options.version;\n\n if (oldVersion !== currentVersion) {\n if (this.options.migrate) {\n try {\n const migratedValue = this.options.migrate(\n serializedValue,\n oldVersion,\n );\n this.saveToStorage(migratedValue);\n return migratedValue;\n } catch {\n this.storage.removeItem(this.options.key);\n return this.options.defaultValue;\n }\n } else {\n this.storage.removeItem(this.options.key);\n return this.options.defaultValue;\n }\n }\n\n try {\n return this.options.deserialize(serializedValue);\n } catch {\n return this.options.defaultValue;\n }\n }\n\n private saveToStorage(value: T): void {\n try {\n const serialized = this.options.serialize(value);\n const version = this.options.version;\n\n if (version) {\n this.storage.setItem(\n this.options.key,\n `\\x00${version}\\x00${serialized}`,\n );\n } else {\n this.storage.setItem(this.options.key, serialized);\n }\n } catch {}\n }\n\n private notifySubscribers(): void {\n for (const subscriber of this.subscribers) {\n subscriber(this.value);\n }\n }\n}\n\nexport function bindValue<T>(options: BindValueOptions<T>): BindValue<T> {\n return new BindValue(options);\n}\n\nexport function _clearInMemoryStorage(): void {\n memoryStorage.clear();\n}\n\nexport class BindValueNS<T> {\n constructor(private options: BindValueNSOptions<T>) {\n if (!this.options.prefix || this.options.prefix.trim() === \"\") {\n throw new Error(\"Prefix cannot be empty\");\n }\n }\n\n bind(key: string): BindValue<T> {\n if (!key || key.trim() === \"\") {\n throw new Error(\"Key cannot be empty\");\n }\n\n return new BindValue<T>({\n key: `${this.options.prefix}\\x1F${key}`,\n defaultValue: this.options.defaultValue,\n serialize: this.options.serialize,\n deserialize: this.options.deserialize,\n storage: this.options.storage,\n version: this.options.version,\n migrate: this.options.migrate,\n });\n }\n}\n\nexport function bindValueNS<T>(options: BindValueNSOptions<T>): BindValueNS<T> {\n return new BindValueNS(options);\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { bindValue, BindValue, type BindValueOptions } from "./bindValue.js";
|
|
2
|
-
export {
|
|
3
|
-
export {
|
|
2
|
+
export { bindValueNS, BindValueNS, type BindValueNSOptions, } from "./bindValue.js";
|
|
3
|
+
export { useStorage, useStorageNS, type UseStorageReturn, } from "./useStorage.js";
|
|
4
|
+
export { bindStringValue, bindNumberValue, bindBooleanValue, bindJSONValue, bindEnumValue, bindStringValueNS, bindNumberValueNS, bindBooleanValueNS, bindJSONValueNS, bindEnumValueNS, } from "./bindTypes.js";
|
|
@@ -4,11 +4,19 @@ const bindValue = require("./bindValue.js");
|
|
|
4
4
|
const useStorage = require("./useStorage.js");
|
|
5
5
|
const bindTypes = require("./bindTypes.js");
|
|
6
6
|
exports.BindValue = bindValue.BindValue;
|
|
7
|
+
exports.BindValueNS = bindValue.BindValueNS;
|
|
7
8
|
exports.bindValue = bindValue.bindValue;
|
|
9
|
+
exports.bindValueNS = bindValue.bindValueNS;
|
|
8
10
|
exports.useStorage = useStorage.useStorage;
|
|
11
|
+
exports.useStorageNS = useStorage.useStorageNS;
|
|
9
12
|
exports.bindBooleanValue = bindTypes.bindBooleanValue;
|
|
13
|
+
exports.bindBooleanValueNS = bindTypes.bindBooleanValueNS;
|
|
10
14
|
exports.bindEnumValue = bindTypes.bindEnumValue;
|
|
15
|
+
exports.bindEnumValueNS = bindTypes.bindEnumValueNS;
|
|
11
16
|
exports.bindJSONValue = bindTypes.bindJSONValue;
|
|
17
|
+
exports.bindJSONValueNS = bindTypes.bindJSONValueNS;
|
|
12
18
|
exports.bindNumberValue = bindTypes.bindNumberValue;
|
|
19
|
+
exports.bindNumberValueNS = bindTypes.bindNumberValueNS;
|
|
13
20
|
exports.bindStringValue = bindTypes.bindStringValue;
|
|
21
|
+
exports.bindStringValueNS = bindTypes.bindStringValueNS;
|
|
14
22
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import { type BindValue } from "./bindValue.js";
|
|
1
|
+
import { type BindValue, type BindValueNS } from "./bindValue.js";
|
|
2
2
|
export interface UseStorageReturn<T> {
|
|
3
3
|
value: T;
|
|
4
4
|
setValue: (value: T) => void;
|
|
5
5
|
}
|
|
6
6
|
export declare function useStorage<T>(binding: BindValue<T>): UseStorageReturn<T>;
|
|
7
|
+
export declare function useStorageNS<T>(namespace: BindValueNS<T>, options: {
|
|
8
|
+
key: string;
|
|
9
|
+
}): UseStorageReturn<T>;
|
|
@@ -12,5 +12,10 @@ function useStorage(binding) {
|
|
|
12
12
|
};
|
|
13
13
|
return { value, setValue: set };
|
|
14
14
|
}
|
|
15
|
+
function useStorageNS(namespace, options) {
|
|
16
|
+
const binding = namespace.bind(options.key);
|
|
17
|
+
return useStorage(binding);
|
|
18
|
+
}
|
|
15
19
|
exports.useStorage = useStorage;
|
|
20
|
+
exports.useStorageNS = useStorageNS;
|
|
16
21
|
//# sourceMappingURL=useStorage.js.map
|
|
@@ -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<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
|
+
{"version":3,"file":"useStorage.js","sources":["../../../../src/useStorage.ts"],"sourcesContent":["import { useState, useEffect } from \"react\";\nimport { type BindValue, type BindValueNS } 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\nexport function useStorageNS<T>(\n namespace: BindValueNS<T>,\n options: { key: string },\n): UseStorageReturn<T> {\n const binding = namespace.bind(options.key);\n return useStorage(binding);\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;AAEO,SAAS,aACd,WACA,SACqB;AACrB,QAAM,UAAU,UAAU,KAAK,QAAQ,GAAG;AAC1C,SAAO,WAAW,OAAO;AAC3B;;;"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BindValue, type BindValueOptions } from "./bindValue.js";
|
|
1
|
+
import { BindValue, BindValueNS, type BindValueOptions, type BindValueNSOptions } from "./bindValue.js";
|
|
2
2
|
export declare function bindStringValue(options: Omit<BindValueOptions<string>, "serialize" | "deserialize" | "defaultValue"> & {
|
|
3
3
|
defaultValue?: string;
|
|
4
4
|
}): BindValue<string>;
|
|
@@ -10,3 +10,14 @@ export declare function bindBooleanValue(options: Omit<BindValueOptions<boolean>
|
|
|
10
10
|
}): BindValue<boolean>;
|
|
11
11
|
export declare function bindJSONValue<T>(options: Omit<BindValueOptions<T>, "serialize" | "deserialize">): BindValue<T>;
|
|
12
12
|
export declare function bindEnumValue<E extends string | number>(options: Omit<BindValueOptions<E>, "serialize" | "deserialize">): BindValue<E>;
|
|
13
|
+
export declare function bindStringValueNS(options: Omit<BindValueNSOptions<string>, "serialize" | "deserialize" | "defaultValue"> & {
|
|
14
|
+
defaultValue?: string;
|
|
15
|
+
}): BindValueNS<string>;
|
|
16
|
+
export declare function bindNumberValueNS(options: Omit<BindValueNSOptions<number>, "serialize" | "deserialize" | "defaultValue"> & {
|
|
17
|
+
defaultValue?: number;
|
|
18
|
+
}): BindValueNS<number>;
|
|
19
|
+
export declare function bindBooleanValueNS(options: Omit<BindValueNSOptions<boolean>, "serialize" | "deserialize" | "defaultValue"> & {
|
|
20
|
+
defaultValue?: boolean;
|
|
21
|
+
}): BindValueNS<boolean>;
|
|
22
|
+
export declare function bindJSONValueNS<T>(options: Omit<BindValueNSOptions<T>, "serialize" | "deserialize">): BindValueNS<T>;
|
|
23
|
+
export declare function bindEnumValueNS<E extends string | number>(options: Omit<BindValueNSOptions<E>, "serialize" | "deserialize">): BindValueNS<E>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { bindValue } from "./bindValue.mjs";
|
|
1
|
+
import { bindValue, bindValueNS } from "./bindValue.mjs";
|
|
2
2
|
function bindStringValue(options) {
|
|
3
3
|
return bindValue({
|
|
4
4
|
key: options.key,
|
|
@@ -54,11 +54,71 @@ function bindEnumValue(options) {
|
|
|
54
54
|
migrate: options.migrate
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
|
+
function bindStringValueNS(options) {
|
|
58
|
+
return bindValueNS({
|
|
59
|
+
prefix: options.prefix,
|
|
60
|
+
defaultValue: options.defaultValue ?? "",
|
|
61
|
+
serialize: (v) => v,
|
|
62
|
+
deserialize: (s) => s,
|
|
63
|
+
storage: options.storage,
|
|
64
|
+
version: options.version,
|
|
65
|
+
migrate: options.migrate
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function bindNumberValueNS(options) {
|
|
69
|
+
return bindValueNS({
|
|
70
|
+
prefix: options.prefix,
|
|
71
|
+
defaultValue: options.defaultValue ?? 0,
|
|
72
|
+
serialize: (v) => String(v),
|
|
73
|
+
deserialize: (s) => Number(s),
|
|
74
|
+
storage: options.storage,
|
|
75
|
+
version: options.version,
|
|
76
|
+
migrate: options.migrate
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function bindBooleanValueNS(options) {
|
|
80
|
+
return bindValueNS({
|
|
81
|
+
prefix: options.prefix,
|
|
82
|
+
defaultValue: options.defaultValue ?? false,
|
|
83
|
+
serialize: (v) => String(v),
|
|
84
|
+
deserialize: (s) => s === "true",
|
|
85
|
+
storage: options.storage,
|
|
86
|
+
version: options.version,
|
|
87
|
+
migrate: options.migrate
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function bindJSONValueNS(options) {
|
|
91
|
+
return bindValueNS({
|
|
92
|
+
prefix: options.prefix,
|
|
93
|
+
defaultValue: options.defaultValue,
|
|
94
|
+
serialize: (v) => JSON.stringify(v),
|
|
95
|
+
deserialize: (s) => JSON.parse(s),
|
|
96
|
+
storage: options.storage,
|
|
97
|
+
version: options.version,
|
|
98
|
+
migrate: options.migrate
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function bindEnumValueNS(options) {
|
|
102
|
+
return bindValueNS({
|
|
103
|
+
prefix: options.prefix,
|
|
104
|
+
defaultValue: options.defaultValue,
|
|
105
|
+
serialize: (v) => String(v),
|
|
106
|
+
deserialize: (s) => typeof options.defaultValue === "number" ? Number(s) : s,
|
|
107
|
+
storage: options.storage,
|
|
108
|
+
version: options.version,
|
|
109
|
+
migrate: options.migrate
|
|
110
|
+
});
|
|
111
|
+
}
|
|
57
112
|
export {
|
|
58
113
|
bindBooleanValue,
|
|
114
|
+
bindBooleanValueNS,
|
|
59
115
|
bindEnumValue,
|
|
116
|
+
bindEnumValueNS,
|
|
60
117
|
bindJSONValue,
|
|
118
|
+
bindJSONValueNS,
|
|
61
119
|
bindNumberValue,
|
|
62
|
-
|
|
120
|
+
bindNumberValueNS,
|
|
121
|
+
bindStringValue,
|
|
122
|
+
bindStringValueNS
|
|
63
123
|
};
|
|
64
124
|
//# sourceMappingURL=bindTypes.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bindTypes.mjs","sources":["../../../../src/bindTypes.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"bindTypes.mjs","sources":["../../../../src/bindTypes.ts"],"sourcesContent":["import {\n bindValue,\n bindValueNS,\n BindValue,\n BindValueNS,\n type BindValueOptions,\n type BindValueNSOptions,\n} from \"./bindValue.js\";\n\nexport function bindStringValue(\n options: Omit<\n BindValueOptions<string>,\n \"serialize\" | \"deserialize\" | \"defaultValue\"\n > & {\n defaultValue?: string;\n },\n): BindValue<string> {\n return bindValue<string>({\n key: options.key,\n defaultValue: options.defaultValue ?? \"\",\n serialize: (v) => v,\n deserialize: (s) => s,\n storage: options.storage,\n version: options.version,\n migrate: options.migrate,\n });\n}\n\nexport function bindNumberValue(\n options: Omit<\n BindValueOptions<number>,\n \"serialize\" | \"deserialize\" | \"defaultValue\"\n > & {\n defaultValue?: number;\n },\n): BindValue<number> {\n return bindValue<number>({\n key: options.key,\n defaultValue: options.defaultValue ?? 0,\n serialize: (v) => String(v),\n deserialize: (s) => Number(s),\n storage: options.storage,\n version: options.version,\n migrate: options.migrate,\n });\n}\n\nexport function bindBooleanValue(\n options: Omit<\n BindValueOptions<boolean>,\n \"serialize\" | \"deserialize\" | \"defaultValue\"\n > & {\n defaultValue?: boolean;\n },\n): BindValue<boolean> {\n return bindValue<boolean>({\n key: options.key,\n defaultValue: options.defaultValue ?? false,\n serialize: (v) => String(v),\n deserialize: (s) => s === \"true\",\n storage: options.storage,\n version: options.version,\n migrate: options.migrate,\n });\n}\n\nexport function bindJSONValue<T>(\n options: Omit<BindValueOptions<T>, \"serialize\" | \"deserialize\">,\n): BindValue<T> {\n return bindValue<T>({\n key: options.key,\n defaultValue: options.defaultValue,\n serialize: (v) => JSON.stringify(v),\n deserialize: (s) => JSON.parse(s),\n storage: options.storage,\n version: options.version,\n migrate: options.migrate,\n });\n}\n\nexport function bindEnumValue<E extends string | number>(\n options: Omit<BindValueOptions<E>, \"serialize\" | \"deserialize\">,\n): BindValue<E> {\n return bindValue<E>({\n key: options.key,\n defaultValue: options.defaultValue,\n serialize: (v) => String(v),\n deserialize: (s) =>\n (typeof options.defaultValue === \"number\" ? Number(s) : s) as E,\n storage: options.storage,\n version: options.version,\n migrate: options.migrate,\n });\n}\n\nexport function bindStringValueNS(\n options: Omit<\n BindValueNSOptions<string>,\n \"serialize\" | \"deserialize\" | \"defaultValue\"\n > & {\n defaultValue?: string;\n },\n): BindValueNS<string> {\n return bindValueNS<string>({\n prefix: options.prefix,\n defaultValue: options.defaultValue ?? \"\",\n serialize: (v) => v,\n deserialize: (s) => s,\n storage: options.storage,\n version: options.version,\n migrate: options.migrate,\n });\n}\n\nexport function bindNumberValueNS(\n options: Omit<\n BindValueNSOptions<number>,\n \"serialize\" | \"deserialize\" | \"defaultValue\"\n > & {\n defaultValue?: number;\n },\n): BindValueNS<number> {\n return bindValueNS<number>({\n prefix: options.prefix,\n defaultValue: options.defaultValue ?? 0,\n serialize: (v) => String(v),\n deserialize: (s) => Number(s),\n storage: options.storage,\n version: options.version,\n migrate: options.migrate,\n });\n}\n\nexport function bindBooleanValueNS(\n options: Omit<\n BindValueNSOptions<boolean>,\n \"serialize\" | \"deserialize\" | \"defaultValue\"\n > & {\n defaultValue?: boolean;\n },\n): BindValueNS<boolean> {\n return bindValueNS<boolean>({\n prefix: options.prefix,\n defaultValue: options.defaultValue ?? false,\n serialize: (v) => String(v),\n deserialize: (s) => s === \"true\",\n storage: options.storage,\n version: options.version,\n migrate: options.migrate,\n });\n}\n\nexport function bindJSONValueNS<T>(\n options: Omit<BindValueNSOptions<T>, \"serialize\" | \"deserialize\">,\n): BindValueNS<T> {\n return bindValueNS<T>({\n prefix: options.prefix,\n defaultValue: options.defaultValue,\n serialize: (v) => JSON.stringify(v),\n deserialize: (s) => JSON.parse(s),\n storage: options.storage,\n version: options.version,\n migrate: options.migrate,\n });\n}\n\nexport function bindEnumValueNS<E extends string | number>(\n options: Omit<BindValueNSOptions<E>, \"serialize\" | \"deserialize\">,\n): BindValueNS<E> {\n return bindValueNS<E>({\n prefix: options.prefix,\n defaultValue: options.defaultValue,\n serialize: (v) => String(v),\n deserialize: (s) =>\n (typeof options.defaultValue === \"number\" ? Number(s) : s) as E,\n storage: options.storage,\n version: options.version,\n migrate: options.migrate,\n });\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -7,6 +7,15 @@ export interface BindValueOptions<T> {
|
|
|
7
7
|
version?: string;
|
|
8
8
|
migrate?: (oldSerialized: string, oldVersion: string | undefined) => T;
|
|
9
9
|
}
|
|
10
|
+
export interface BindValueNSOptions<T> {
|
|
11
|
+
prefix: string;
|
|
12
|
+
defaultValue: T;
|
|
13
|
+
serialize: (value: T) => string;
|
|
14
|
+
deserialize: (serialized: string) => T;
|
|
15
|
+
storage?: Storage;
|
|
16
|
+
version?: string;
|
|
17
|
+
migrate?: (oldSerialized: string, oldVersion: string | undefined) => T;
|
|
18
|
+
}
|
|
10
19
|
export declare class BindValue<T> {
|
|
11
20
|
private options;
|
|
12
21
|
private value;
|
|
@@ -16,8 +25,17 @@ export declare class BindValue<T> {
|
|
|
16
25
|
getValue(): T;
|
|
17
26
|
set(newValue: T): void;
|
|
18
27
|
subscribe(callback: (value: T) => void): () => void;
|
|
28
|
+
getRaw(): string | null;
|
|
29
|
+
setRaw(rawValue: string): void;
|
|
19
30
|
private loadFromStorage;
|
|
20
31
|
private saveToStorage;
|
|
21
32
|
private notifySubscribers;
|
|
22
33
|
}
|
|
23
34
|
export declare function bindValue<T>(options: BindValueOptions<T>): BindValue<T>;
|
|
35
|
+
export declare function _clearInMemoryStorage(): void;
|
|
36
|
+
export declare class BindValueNS<T> {
|
|
37
|
+
private options;
|
|
38
|
+
constructor(options: BindValueNSOptions<T>);
|
|
39
|
+
bind(key: string): BindValue<T>;
|
|
40
|
+
}
|
|
41
|
+
export declare function bindValueNS<T>(options: BindValueNSOptions<T>): BindValueNS<T>;
|
|
@@ -47,6 +47,48 @@ class BindValue {
|
|
|
47
47
|
this.subscribers.delete(callback);
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
|
+
getRaw() {
|
|
51
|
+
return this.storage.getItem(this.options.key);
|
|
52
|
+
}
|
|
53
|
+
setRaw(rawValue) {
|
|
54
|
+
let deserializedValue;
|
|
55
|
+
const version = this.options.version;
|
|
56
|
+
let serializedPart;
|
|
57
|
+
if (version) {
|
|
58
|
+
if (!rawValue.startsWith("\0")) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const parts = rawValue.split("\0");
|
|
62
|
+
if (parts.length < 3) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const versionPart = parts[1];
|
|
66
|
+
if (versionPart !== version) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
serializedPart = parts.slice(2).join("\0");
|
|
70
|
+
} else {
|
|
71
|
+
serializedPart = rawValue;
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
deserializedValue = this.options.deserialize(serializedPart);
|
|
75
|
+
} catch {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const serializedValue = this.options.serialize(deserializedValue);
|
|
79
|
+
let reconstructedRaw;
|
|
80
|
+
if (version) {
|
|
81
|
+
reconstructedRaw = `\0${version}\0${serializedValue}`;
|
|
82
|
+
} else {
|
|
83
|
+
reconstructedRaw = serializedValue;
|
|
84
|
+
}
|
|
85
|
+
if (reconstructedRaw !== rawValue) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
this.storage.setItem(this.options.key, rawValue);
|
|
89
|
+
this.value = deserializedValue;
|
|
90
|
+
this.notifySubscribers();
|
|
91
|
+
}
|
|
50
92
|
loadFromStorage() {
|
|
51
93
|
const rawValue = this.storage.getItem(this.options.key);
|
|
52
94
|
if (rawValue === null) {
|
|
@@ -114,8 +156,35 @@ class BindValue {
|
|
|
114
156
|
function bindValue(options) {
|
|
115
157
|
return new BindValue(options);
|
|
116
158
|
}
|
|
159
|
+
class BindValueNS {
|
|
160
|
+
constructor(options) {
|
|
161
|
+
this.options = options;
|
|
162
|
+
if (!this.options.prefix || this.options.prefix.trim() === "") {
|
|
163
|
+
throw new Error("Prefix cannot be empty");
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
bind(key) {
|
|
167
|
+
if (!key || key.trim() === "") {
|
|
168
|
+
throw new Error("Key cannot be empty");
|
|
169
|
+
}
|
|
170
|
+
return new BindValue({
|
|
171
|
+
key: `${this.options.prefix}${key}`,
|
|
172
|
+
defaultValue: this.options.defaultValue,
|
|
173
|
+
serialize: this.options.serialize,
|
|
174
|
+
deserialize: this.options.deserialize,
|
|
175
|
+
storage: this.options.storage,
|
|
176
|
+
version: this.options.version,
|
|
177
|
+
migrate: this.options.migrate
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function bindValueNS(options) {
|
|
182
|
+
return new BindValueNS(options);
|
|
183
|
+
}
|
|
117
184
|
export {
|
|
118
185
|
BindValue,
|
|
119
|
-
|
|
186
|
+
BindValueNS,
|
|
187
|
+
bindValue,
|
|
188
|
+
bindValueNS
|
|
120
189
|
};
|
|
121
190
|
//# sourceMappingURL=bindValue.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bindValue.mjs","sources":["../../../../src/bindValue.ts"],"sourcesContent":["export interface BindValueOptions<T> {\n key: string;\n defaultValue: T;\n serialize: (value: T) => string;\n deserialize: (serialized: string) => T;\n storage?: Storage;\n version?: string;\n migrate?: (oldSerialized: string, oldVersion: string | undefined) => T;\n}\n\nconst memoryStorage = new Map<string, string>();\n\nconst inMemoryStorage: Storage = {\n get length() {\n return memoryStorage.size;\n },\n clear() {\n memoryStorage.clear();\n },\n getItem(key: string) {\n return memoryStorage.get(key) ?? null;\n },\n key(index: number) {\n const keys = Array.from(memoryStorage.keys());\n return keys[index] ?? null;\n },\n removeItem(key: string) {\n memoryStorage.delete(key);\n },\n setItem(key: string, value: string) {\n memoryStorage.set(key, value);\n },\n};\n\nexport class BindValue<T> {\n private value: T;\n private subscribers: Set<(value: T) => void>;\n private storage: Storage;\n\n constructor(private options: BindValueOptions<T>) {\n this.storage = options.storage ?? inMemoryStorage;\n this.subscribers = new Set();\n this.value = this.loadFromStorage();\n }\n\n getValue(): T {\n return this.value;\n }\n\n set(newValue: T): void {\n this.value = newValue;\n this.saveToStorage(newValue);\n this.notifySubscribers();\n }\n\n subscribe(callback: (value: T) => void): () => void {\n this.subscribers.add(callback);\n return () => {\n this.subscribers.delete(callback);\n };\n }\n\n private loadFromStorage(): T {\n const rawValue = this.storage.getItem(this.options.key);\n\n if (rawValue === null) {\n return this.options.defaultValue;\n }\n\n let oldVersion: string | undefined;\n let serializedValue: string;\n\n if (rawValue.startsWith(\"\\x00\")) {\n const parts = rawValue.split(\"\\x00\");\n if (parts.length >= 3) {\n oldVersion = parts[1];\n serializedValue = parts.slice(2).join(\"\\x00\");\n } else {\n serializedValue = rawValue;\n }\n } else {\n serializedValue = rawValue;\n }\n\n const currentVersion = this.options.version;\n\n if (oldVersion !== currentVersion) {\n if (this.options.migrate) {\n try {\n const migratedValue = this.options.migrate(\n serializedValue,\n oldVersion,\n );\n this.saveToStorage(migratedValue);\n return migratedValue;\n } catch {\n this.storage.removeItem(this.options.key);\n return this.options.defaultValue;\n }\n } else {\n this.storage.removeItem(this.options.key);\n return this.options.defaultValue;\n }\n }\n\n try {\n return this.options.deserialize(serializedValue);\n } catch {\n return this.options.defaultValue;\n }\n }\n\n private saveToStorage(value: T): void {\n try {\n const serialized = this.options.serialize(value);\n const version = this.options.version;\n\n if (version) {\n this.storage.setItem(\n this.options.key,\n `\\x00${version}\\x00${serialized}`,\n );\n } else {\n this.storage.setItem(this.options.key, serialized);\n }\n } catch {}\n }\n\n private notifySubscribers(): void {\n for (const subscriber of this.subscribers) {\n subscriber(this.value);\n }\n }\n}\n\nexport function bindValue<T>(options: BindValueOptions<T>): BindValue<T> {\n return new BindValue(options);\n}\n"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"bindValue.mjs","sources":["../../../../src/bindValue.ts"],"sourcesContent":["export interface BindValueOptions<T> {\n key: string;\n defaultValue: T;\n serialize: (value: T) => string;\n deserialize: (serialized: string) => T;\n storage?: Storage;\n version?: string;\n migrate?: (oldSerialized: string, oldVersion: string | undefined) => T;\n}\n\nexport interface BindValueNSOptions<T> {\n prefix: string;\n defaultValue: T;\n serialize: (value: T) => string;\n deserialize: (serialized: string) => T;\n storage?: Storage;\n version?: string;\n migrate?: (oldSerialized: string, oldVersion: string | undefined) => T;\n}\n\nconst memoryStorage = new Map<string, string>();\n\nconst inMemoryStorage: Storage = {\n get length() {\n return memoryStorage.size;\n },\n clear() {\n memoryStorage.clear();\n },\n getItem(key: string) {\n return memoryStorage.get(key) ?? null;\n },\n key(index: number) {\n const keys = Array.from(memoryStorage.keys());\n return keys[index] ?? null;\n },\n removeItem(key: string) {\n memoryStorage.delete(key);\n },\n setItem(key: string, value: string) {\n memoryStorage.set(key, value);\n },\n};\n\nexport class BindValue<T> {\n private value: T;\n private subscribers: Set<(value: T) => void>;\n private storage: Storage;\n\n constructor(private options: BindValueOptions<T>) {\n this.storage = options.storage ?? inMemoryStorage;\n this.subscribers = new Set();\n this.value = this.loadFromStorage();\n }\n\n getValue(): T {\n return this.value;\n }\n\n set(newValue: T): void {\n this.value = newValue;\n this.saveToStorage(newValue);\n this.notifySubscribers();\n }\n\n subscribe(callback: (value: T) => void): () => void {\n this.subscribers.add(callback);\n return () => {\n this.subscribers.delete(callback);\n };\n }\n\n getRaw(): string | null {\n return this.storage.getItem(this.options.key);\n }\n\n setRaw(rawValue: string): void {\n let deserializedValue: T;\n const version = this.options.version;\n let serializedPart: string;\n\n if (version) {\n if (!rawValue.startsWith(\"\\x00\")) {\n return;\n }\n\n const parts = rawValue.split(\"\\x00\");\n if (parts.length < 3) {\n return;\n }\n\n const versionPart = parts[1];\n if (versionPart !== version) {\n return;\n }\n\n serializedPart = parts.slice(2).join(\"\\x00\");\n } else {\n serializedPart = rawValue;\n }\n\n try {\n deserializedValue = this.options.deserialize(serializedPart);\n } catch {\n return;\n }\n const serializedValue = this.options.serialize(deserializedValue);\n let reconstructedRaw: string;\n\n if (version) {\n reconstructedRaw = `\\x00${version}\\x00${serializedValue}`;\n } else {\n reconstructedRaw = serializedValue;\n }\n\n if (reconstructedRaw !== rawValue) {\n return;\n }\n\n this.storage.setItem(this.options.key, rawValue);\n this.value = deserializedValue;\n this.notifySubscribers();\n }\n\n private loadFromStorage(): T {\n const rawValue = this.storage.getItem(this.options.key);\n\n if (rawValue === null) {\n return this.options.defaultValue;\n }\n\n let oldVersion: string | undefined;\n let serializedValue: string;\n\n if (rawValue.startsWith(\"\\x00\")) {\n const parts = rawValue.split(\"\\x00\");\n if (parts.length >= 3) {\n oldVersion = parts[1];\n serializedValue = parts.slice(2).join(\"\\x00\");\n } else {\n serializedValue = rawValue;\n }\n } else {\n serializedValue = rawValue;\n }\n\n const currentVersion = this.options.version;\n\n if (oldVersion !== currentVersion) {\n if (this.options.migrate) {\n try {\n const migratedValue = this.options.migrate(\n serializedValue,\n oldVersion,\n );\n this.saveToStorage(migratedValue);\n return migratedValue;\n } catch {\n this.storage.removeItem(this.options.key);\n return this.options.defaultValue;\n }\n } else {\n this.storage.removeItem(this.options.key);\n return this.options.defaultValue;\n }\n }\n\n try {\n return this.options.deserialize(serializedValue);\n } catch {\n return this.options.defaultValue;\n }\n }\n\n private saveToStorage(value: T): void {\n try {\n const serialized = this.options.serialize(value);\n const version = this.options.version;\n\n if (version) {\n this.storage.setItem(\n this.options.key,\n `\\x00${version}\\x00${serialized}`,\n );\n } else {\n this.storage.setItem(this.options.key, serialized);\n }\n } catch {}\n }\n\n private notifySubscribers(): void {\n for (const subscriber of this.subscribers) {\n subscriber(this.value);\n }\n }\n}\n\nexport function bindValue<T>(options: BindValueOptions<T>): BindValue<T> {\n return new BindValue(options);\n}\n\nexport function _clearInMemoryStorage(): void {\n memoryStorage.clear();\n}\n\nexport class BindValueNS<T> {\n constructor(private options: BindValueNSOptions<T>) {\n if (!this.options.prefix || this.options.prefix.trim() === \"\") {\n throw new Error(\"Prefix cannot be empty\");\n }\n }\n\n bind(key: string): BindValue<T> {\n if (!key || key.trim() === \"\") {\n throw new Error(\"Key cannot be empty\");\n }\n\n return new BindValue<T>({\n key: `${this.options.prefix}\\x1F${key}`,\n defaultValue: this.options.defaultValue,\n serialize: this.options.serialize,\n deserialize: this.options.deserialize,\n storage: this.options.storage,\n version: this.options.version,\n migrate: this.options.migrate,\n });\n }\n}\n\nexport function bindValueNS<T>(options: BindValueNSOptions<T>): BindValueNS<T> {\n return new BindValueNS(options);\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { bindValue, BindValue, type BindValueOptions } from "./bindValue.js";
|
|
2
|
-
export {
|
|
3
|
-
export {
|
|
2
|
+
export { bindValueNS, BindValueNS, type BindValueNSOptions, } from "./bindValue.js";
|
|
3
|
+
export { useStorage, useStorageNS, type UseStorageReturn, } from "./useStorage.js";
|
|
4
|
+
export { bindStringValue, bindNumberValue, bindBooleanValue, bindJSONValue, bindEnumValue, bindStringValueNS, bindNumberValueNS, bindBooleanValueNS, bindJSONValueNS, bindEnumValueNS, } from "./bindTypes.js";
|
|
@@ -1,14 +1,22 @@
|
|
|
1
|
-
import { BindValue, bindValue } from "./bindValue.mjs";
|
|
2
|
-
import { useStorage } from "./useStorage.mjs";
|
|
3
|
-
import { bindBooleanValue, bindEnumValue, bindJSONValue, bindNumberValue, bindStringValue } from "./bindTypes.mjs";
|
|
1
|
+
import { BindValue, BindValueNS, bindValue, bindValueNS } from "./bindValue.mjs";
|
|
2
|
+
import { useStorage, useStorageNS } from "./useStorage.mjs";
|
|
3
|
+
import { bindBooleanValue, bindBooleanValueNS, bindEnumValue, bindEnumValueNS, bindJSONValue, bindJSONValueNS, bindNumberValue, bindNumberValueNS, bindStringValue, bindStringValueNS } from "./bindTypes.mjs";
|
|
4
4
|
export {
|
|
5
5
|
BindValue,
|
|
6
|
+
BindValueNS,
|
|
6
7
|
bindBooleanValue,
|
|
8
|
+
bindBooleanValueNS,
|
|
7
9
|
bindEnumValue,
|
|
10
|
+
bindEnumValueNS,
|
|
8
11
|
bindJSONValue,
|
|
12
|
+
bindJSONValueNS,
|
|
9
13
|
bindNumberValue,
|
|
14
|
+
bindNumberValueNS,
|
|
10
15
|
bindStringValue,
|
|
16
|
+
bindStringValueNS,
|
|
11
17
|
bindValue,
|
|
12
|
-
|
|
18
|
+
bindValueNS,
|
|
19
|
+
useStorage,
|
|
20
|
+
useStorageNS
|
|
13
21
|
};
|
|
14
22
|
//# sourceMappingURL=index.mjs.map
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import { type BindValue } from "./bindValue.js";
|
|
1
|
+
import { type BindValue, type BindValueNS } from "./bindValue.js";
|
|
2
2
|
export interface UseStorageReturn<T> {
|
|
3
3
|
value: T;
|
|
4
4
|
setValue: (value: T) => void;
|
|
5
5
|
}
|
|
6
6
|
export declare function useStorage<T>(binding: BindValue<T>): UseStorageReturn<T>;
|
|
7
|
+
export declare function useStorageNS<T>(namespace: BindValueNS<T>, options: {
|
|
8
|
+
key: string;
|
|
9
|
+
}): UseStorageReturn<T>;
|
|
@@ -10,7 +10,12 @@ function useStorage(binding) {
|
|
|
10
10
|
};
|
|
11
11
|
return { value, setValue: set };
|
|
12
12
|
}
|
|
13
|
+
function useStorageNS(namespace, options) {
|
|
14
|
+
const binding = namespace.bind(options.key);
|
|
15
|
+
return useStorage(binding);
|
|
16
|
+
}
|
|
13
17
|
export {
|
|
14
|
-
useStorage
|
|
18
|
+
useStorage,
|
|
19
|
+
useStorageNS
|
|
15
20
|
};
|
|
16
21
|
//# sourceMappingURL=useStorage.mjs.map
|
|
@@ -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<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;"}
|
|
1
|
+
{"version":3,"file":"useStorage.mjs","sources":["../../../../src/useStorage.ts"],"sourcesContent":["import { useState, useEffect } from \"react\";\nimport { type BindValue, type BindValueNS } 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\nexport function useStorageNS<T>(\n namespace: BindValueNS<T>,\n options: { key: string },\n): UseStorageReturn<T> {\n const binding = namespace.bind(options.key);\n return useStorage(binding);\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;AAEO,SAAS,aACd,WACA,SACqB;AACrB,QAAM,UAAU,UAAU,KAAK,QAAQ,GAAG;AAC1C,SAAO,WAAW,OAAO;AAC3B;"}
|