@efficimo/storage 0.0.0 → 0.1.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.
Files changed (2) hide show
  1. package/README.md +178 -0
  2. package/package.json +3 -4
package/README.md ADDED
@@ -0,0 +1,178 @@
1
+ # @efficimo/storage
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@efficimo/storage)](https://www.npmjs.com/package/@efficimo/storage)
4
+ [![npm version](https://img.shields.io/npm/v/@efficimo/storage-react?label=%40efficimo%2Fstorage-react)](https://www.npmjs.com/package/@efficimo/storage-react)
5
+ [![license](https://img.shields.io/npm/l/@efficimo/storage)](./LICENSE)
6
+ [![types](https://img.shields.io/npm/types/@efficimo/storage)](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). Zero additional dependencies.
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/observable @efficimo/storage
24
+
25
+ # with React hook
26
+ npm install @efficimo/observable @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",
3
- "version": "0.0.0",
3
+ "version": "0.1.0",
4
4
  "description": "Subscribe to browser storage. Typed keys, reactive updates, cross-tab sync — no boilerplate.",
5
5
  "author": "efficimo",
6
6
  "license": "MIT",
@@ -52,11 +52,10 @@
52
52
  "test": "tsc -p tsconfig.test.json && node --test .test-out/test/*.test.js",
53
53
  "prepublishOnly": "npm run build && npm run typecheck"
54
54
  },
55
- "peerDependencies": {
56
- "@efficimo/observable": ">=0.1.0"
55
+ "dependencies": {
56
+ "@efficimo/observable": "^0.2.0"
57
57
  },
58
58
  "devDependencies": {
59
- "@efficimo/observable": "*",
60
59
  "@types/node": "^25.0.0",
61
60
  "typescript": "^5.0.0"
62
61
  }