@efficimo/storage-react 0.0.0 → 0.2.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 +178 -0
- package/package.json +1 -3
package/README.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# @efficimo/storage
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@efficimo/storage)
|
|
4
|
+
[](https://www.npmjs.com/package/@efficimo/storage-react)
|
|
5
|
+
[](./LICENSE)
|
|
6
|
+
[](https://www.npmjs.com/package/@efficimo/storage)
|
|
7
|
+
|
|
8
|
+
> Subscribe to browser storage. Typed keys, reactive updates, cross-tab sync — no boilerplate.
|
|
9
|
+
|
|
10
|
+
Built on [`@efficimo/observable`](https://www.npmjs.com/package/@efficimo/observable).
|
|
11
|
+
|
|
12
|
+
## Packages
|
|
13
|
+
|
|
14
|
+
| Package | Description |
|
|
15
|
+
|---|---|
|
|
16
|
+
| [`@efficimo/storage`](./core) | Core — framework-agnostic |
|
|
17
|
+
| [`@efficimo/storage-react`](./react) | React bindings — `useStorageObservable` hook |
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# core only
|
|
23
|
+
npm install @efficimo/storage
|
|
24
|
+
|
|
25
|
+
# with React hook
|
|
26
|
+
npm install @efficimo/storage @efficimo/storage-react
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick start
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { BaseStorage } from '@efficimo/storage';
|
|
33
|
+
|
|
34
|
+
type Keys = 'auth.token' | 'user.theme';
|
|
35
|
+
const store = new BaseStorage<Keys>(localStorage);
|
|
36
|
+
|
|
37
|
+
// reactive subscription
|
|
38
|
+
store.getObservable('auth.token').subscribe(token => {
|
|
39
|
+
console.log('token changed:', token);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// set / get / remove
|
|
43
|
+
store.set('auth.token', 'abc123');
|
|
44
|
+
store.get('auth.token'); // 'abc123'
|
|
45
|
+
store.remove('auth.token');
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Typed keys via module augmentation
|
|
49
|
+
|
|
50
|
+
Extend `DefaultLocalStorageKeys` or `DefaultSessionStorageKeys` to get typed keys on the `LocalStorage` and `SessionStorage` singletons:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
// storage-keys.d.ts
|
|
54
|
+
import '@efficimo/storage';
|
|
55
|
+
|
|
56
|
+
declare module '@efficimo/storage' {
|
|
57
|
+
interface DefaultLocalStorageKeys {
|
|
58
|
+
'auth.token': true;
|
|
59
|
+
'user.theme': true;
|
|
60
|
+
}
|
|
61
|
+
interface DefaultSessionStorageKeys {
|
|
62
|
+
'wizard.step': true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
import { LocalStorage, SessionStorage } from '@efficimo/storage';
|
|
69
|
+
|
|
70
|
+
LocalStorage.set('auth.token', 'abc123'); // typed ✓
|
|
71
|
+
LocalStorage.set('unknown.key', 'value'); // TS error ✓
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## JSON serialization
|
|
75
|
+
|
|
76
|
+
`getSerializedObservable` accepts any schema with a `safeParse` method (Zod, Valibot, etc.):
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import { z } from 'zod';
|
|
80
|
+
import { LocalStorage } from '@efficimo/storage';
|
|
81
|
+
|
|
82
|
+
declare module '@efficimo/storage' {
|
|
83
|
+
interface DefaultLocalStorageKeys {
|
|
84
|
+
'user.prefs': true;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const schema = z.object({ theme: z.enum(['light', 'dark']), lang: z.string() });
|
|
89
|
+
const prefs = LocalStorage.getSerializedObservable('user.prefs', schema, { theme: 'light', lang: 'en' });
|
|
90
|
+
|
|
91
|
+
prefs.subscribe(v => console.log(v)); // { theme: 'light', lang: 'en' }
|
|
92
|
+
|
|
93
|
+
await prefs.next({ theme: 'dark', lang: 'fr' });
|
|
94
|
+
// localStorage now holds '{"theme":"dark","lang":"fr"}'
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Cross-tab sync
|
|
98
|
+
|
|
99
|
+
`BaseStorage` listens to the browser `storage` event automatically. Any change made in another tab propagates to all active observables in the current tab — no extra setup required.
|
|
100
|
+
|
|
101
|
+
## API
|
|
102
|
+
|
|
103
|
+
### `BaseStorage<StorageKey>`
|
|
104
|
+
|
|
105
|
+
| Method | Description |
|
|
106
|
+
|---|---|
|
|
107
|
+
| `getObservable(key, defaultValue?)` | Returns (or creates) a `StorageObservable` for the given key. |
|
|
108
|
+
| `getSerializedObservable(key, schema, defaultValue?)` | Returns a `JsonSerializeObservableValue` backed by the storage key. |
|
|
109
|
+
| `get(key, defaultValue?)` | Returns the current string value. |
|
|
110
|
+
| `set(key, value)` | Sets the value. |
|
|
111
|
+
| `has(key)` | Returns `true` if the key exists in storage. |
|
|
112
|
+
| `remove(key)` | Removes the key (sets to `null`). |
|
|
113
|
+
| `clear()` | Clears all keys from storage. |
|
|
114
|
+
|
|
115
|
+
### `StorageObservable<StorageKey>`
|
|
116
|
+
|
|
117
|
+
Implements `ObservableValueInterface<string | null>` from `@efficimo/observable`.
|
|
118
|
+
|
|
119
|
+
| Member | Description |
|
|
120
|
+
|---|---|
|
|
121
|
+
| `getValue()` | Returns the current storage value. |
|
|
122
|
+
| `subscribe(fn)` | Registers subscriber and immediately calls it with the current value. |
|
|
123
|
+
| `next(value \| setter)` | Updates the value. Supports async setter `(prev) => newValue`. No-op if unchanged. |
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## React bindings — `@efficimo/storage-react`
|
|
128
|
+
|
|
129
|
+
### `useStorageObservable`
|
|
130
|
+
|
|
131
|
+
Subscribe to a raw `string | null` storage value.
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
import { LocalStorage } from '@efficimo/storage';
|
|
135
|
+
import { useStorageObservable } from '@efficimo/storage-react';
|
|
136
|
+
|
|
137
|
+
function AuthStatus() {
|
|
138
|
+
const [token, setToken] = useStorageObservable(LocalStorage.getObservable('auth.token'));
|
|
139
|
+
|
|
140
|
+
return token
|
|
141
|
+
? <button onClick={() => setToken(null)}>Logout</button>
|
|
142
|
+
: <span>Not logged in</span>;
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### `useStorageSerializedObservable`
|
|
147
|
+
|
|
148
|
+
Subscribe to a JSON-serialized storage value. Returns a typed `[T | null, setter]` tuple — no manual parsing needed.
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
import { z } from 'zod';
|
|
152
|
+
import { LocalStorage } from '@efficimo/storage';
|
|
153
|
+
import { useStorageSerializedObservable } from '@efficimo/storage-react';
|
|
154
|
+
|
|
155
|
+
const prefsObs = LocalStorage.getSerializedObservable(
|
|
156
|
+
'user.prefs',
|
|
157
|
+
z.object({ theme: z.enum(['light', 'dark']), lang: z.string() }),
|
|
158
|
+
{ theme: 'light', lang: 'en' },
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
function ThemeToggle() {
|
|
162
|
+
const [prefs, setPrefs] = useStorageSerializedObservable(prefsObs);
|
|
163
|
+
|
|
164
|
+
return (
|
|
165
|
+
<button onClick={() => setPrefs({ ...prefs, theme: prefs?.theme === 'light' ? 'dark' : 'light' })}>
|
|
166
|
+
{prefs?.theme ?? 'light'}
|
|
167
|
+
</button>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Both hooks re-render the component whenever the storage value changes — including updates from other components or cross-tab events.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## License
|
|
177
|
+
|
|
178
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@efficimo/storage-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "React bindings for @efficimo/storage: hooks for localStorage and sessionStorage observables.",
|
|
5
5
|
"author": "efficimo",
|
|
6
6
|
"license": "MIT",
|
|
@@ -52,12 +52,10 @@
|
|
|
52
52
|
"prepublishOnly": "npm run build && npm run typecheck"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
|
-
"@efficimo/observable": ">=0.1.0",
|
|
56
55
|
"@efficimo/storage": ">=0.1.0",
|
|
57
56
|
"react": ">=18.0.0"
|
|
58
57
|
},
|
|
59
58
|
"devDependencies": {
|
|
60
|
-
"@efficimo/observable": "*",
|
|
61
59
|
"@efficimo/storage": "file:../core",
|
|
62
60
|
"@types/node": "^25.0.0",
|
|
63
61
|
"@types/react": "^18.0.0",
|