react-mnemonic 1.0.0-beta.0 → 1.1.0-beta0
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 +236 -19
- package/dist/index.cjs +1355 -643
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +387 -37
- package/dist/index.d.ts +387 -37
- package/dist/index.js +1352 -645
- package/dist/index.js.map +1 -1
- package/package.json +9 -1
package/README.md
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
Persistent, type-safe state management for React.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/react-mnemonic)
|
|
6
|
-
[](https://thirtytwobits.github.io/react-mnemonic/)
|
|
7
|
+
[](https://sonarcloud.io/summary/new_code?id=thirtytwobits_react-mnemonic)
|
|
8
|
+
[](https://www.npmjs.com/package/react-mnemonic)
|
|
7
9
|
[](./LICENSE.md)
|
|
8
10
|
[](https://www.typescriptlang.org/)
|
|
9
11
|
|
|
@@ -11,8 +13,16 @@ Persistent, type-safe state management for React.
|
|
|
11
13
|
page refreshes, synchronize across tabs, and stay type-safe end-to-end -- all
|
|
12
14
|
through a single hook that works like `useState`.
|
|
13
15
|
|
|
14
|
-
`1.
|
|
15
|
-
the
|
|
16
|
+
`1.1.0-beta0` is the current prerelease on the npm `latest` dist-tag. This minor
|
|
17
|
+
`beta0` cut lands ahead of the `beta1` stabilization milestone, which is
|
|
18
|
+
expected to receive only fixes and polish before the first production release.
|
|
19
|
+
|
|
20
|
+
If you are evaluating alternatives, see the
|
|
21
|
+
[Comparison Guide](https://thirtytwobits.github.io/react-mnemonic/docs/guides/comparisons-and-benchmarks).
|
|
22
|
+
It publishes reproducible bundle measurements, an AI-friendliness benchmark
|
|
23
|
+
built around one-shot app-building prompts, and controlled SSR/API tradeoff
|
|
24
|
+
notes for `react-mnemonic`, Zustand persist, Jotai `atomWithStorage`,
|
|
25
|
+
`use-local-storage-state`, and `usehooks-ts`.
|
|
16
26
|
|
|
17
27
|
## Features
|
|
18
28
|
|
|
@@ -20,29 +30,31 @@ the release candidate hardening work finishes.
|
|
|
20
30
|
- **JSON Schema validation** -- optional schema-based validation using a built-in JSON Schema subset
|
|
21
31
|
- **Namespace isolation** -- `MnemonicProvider` prefixes every key to prevent collisions
|
|
22
32
|
- **Cross-tab sync** -- opt-in `listenCrossTab` uses the browser `storage` event
|
|
23
|
-
- **Pluggable storage** --
|
|
33
|
+
- **Pluggable storage** -- use `localStorage`, `sessionStorage`, or a synchronous `StorageLike` facade over custom persistence
|
|
24
34
|
- **Schema versioning and migration** -- upgrade stored data with versioned schemas and migration rules
|
|
35
|
+
- **Typed schema cohesion helpers** -- define one schema object and reuse it across runtime validation, key descriptors, and migrations
|
|
25
36
|
- **Structural migration helpers** -- optional tree utilities for idempotent insert/rename/dedupe migration steps
|
|
26
37
|
- **Read-time reconciliation** -- selectively enforce new defaults on persisted values without clearing the whole key
|
|
27
38
|
- **Recovery helpers** -- build user-facing soft reset and hard reset flows with namespace-scoped clear helpers
|
|
28
39
|
- **Write-time normalization** -- migrations where `fromVersion === toVersion` run on every write
|
|
40
|
+
- **First-class key descriptors** -- define canonical, reusable key contracts once with `defineMnemonicKey(...)`
|
|
29
41
|
- **Lifecycle callbacks** -- `onMount` and `onChange` hooks
|
|
30
42
|
- **DevTools** -- inspect and mutate state from the browser console
|
|
31
|
-
- **SSR-safe** --
|
|
43
|
+
- **SSR-safe with explicit controls** -- defaults to `defaultValue` on the server, with optional `ssr.serverValue` and `client-only` hydration
|
|
32
44
|
- **Tree-shakeable, zero dependencies** -- ships ESM + CJS with full TypeScript declarations
|
|
33
45
|
|
|
34
46
|
## Installation
|
|
35
47
|
|
|
36
48
|
```bash
|
|
37
|
-
npm install react-mnemonic
|
|
49
|
+
npm install react-mnemonic
|
|
38
50
|
```
|
|
39
51
|
|
|
40
52
|
```bash
|
|
41
|
-
yarn add react-mnemonic
|
|
53
|
+
yarn add react-mnemonic
|
|
42
54
|
```
|
|
43
55
|
|
|
44
56
|
```bash
|
|
45
|
-
pnpm add react-mnemonic
|
|
57
|
+
pnpm add react-mnemonic
|
|
46
58
|
```
|
|
47
59
|
|
|
48
60
|
### Peer dependencies
|
|
@@ -91,6 +103,35 @@ export default function App() {
|
|
|
91
103
|
The counter value persists in `localStorage` under the key `my-app.count` and
|
|
92
104
|
survives full page reloads.
|
|
93
105
|
|
|
106
|
+
In server-rendered apps, `useMnemonicKey(...)` renders `defaultValue` on the
|
|
107
|
+
server by default and then hydrates to persisted storage on the client. When
|
|
108
|
+
you need a deterministic server placeholder or want to delay storage reads
|
|
109
|
+
until after mount, use `ssr.serverValue` and `ssr.hydration: "client-only"`.
|
|
110
|
+
See the
|
|
111
|
+
[Server Rendering guide](https://thirtytwobits.github.io/react-mnemonic/docs/guides/server-rendering)
|
|
112
|
+
for Next.js and Remix examples.
|
|
113
|
+
|
|
114
|
+
If the same key is used in multiple components, consider defining it once with
|
|
115
|
+
`defineMnemonicKey(...)` and reusing that descriptor everywhere. This keeps the
|
|
116
|
+
contract importable and explicit for both humans and AI-assisted tooling.
|
|
117
|
+
|
|
118
|
+
For a deterministic implementation reference aimed at AI agents and advanced
|
|
119
|
+
users, see the
|
|
120
|
+
[AI docs overview](https://thirtytwobits.github.io/react-mnemonic/docs/ai).
|
|
121
|
+
The canonical agent-facing prose lives under
|
|
122
|
+
[`website/docs/ai/`](https://github.com/thirtytwobits/react-mnemonic/tree/main/website/docs/ai),
|
|
123
|
+
with compact retrieval surfaces published as
|
|
124
|
+
[`llms.txt`](https://thirtytwobits.github.io/react-mnemonic/llms.txt),
|
|
125
|
+
[`llms-full.txt`](https://thirtytwobits.github.io/react-mnemonic/llms-full.txt),
|
|
126
|
+
and
|
|
127
|
+
[`ai-contract.json`](https://thirtytwobits.github.io/react-mnemonic/ai-contract.json).
|
|
128
|
+
Agents should import published types from `react-mnemonic` and must not invent
|
|
129
|
+
local `.d.ts` shims for the package.
|
|
130
|
+
|
|
131
|
+
If you need evidence for where `react-mnemonic` is heavier, more explicit, or
|
|
132
|
+
better suited to SSR-sensitive persistence than lighter hooks, see the
|
|
133
|
+
[Comparison Guide](https://thirtytwobits.github.io/react-mnemonic/docs/guides/comparisons-and-benchmarks).
|
|
134
|
+
|
|
94
135
|
Persist only the durable slice of your app state. `useMnemonicKey` stores
|
|
95
136
|
whatever you pass to `set`, so keep transient UI state like loading flags,
|
|
96
137
|
hover state, and draft search text in plain React state unless you explicitly
|
|
@@ -110,6 +151,125 @@ while `reset()` writes the default again. See the
|
|
|
110
151
|
[Clearable Persisted Values guide](https://thirtytwobits.github.io/react-mnemonic/docs/guides/clearable-persisted-values)
|
|
111
152
|
for the canonical nullable pattern.
|
|
112
153
|
|
|
154
|
+
## Canonical key definitions
|
|
155
|
+
|
|
156
|
+
When the same persisted key appears in more than one component, define it once
|
|
157
|
+
and reuse it:
|
|
158
|
+
|
|
159
|
+
```tsx
|
|
160
|
+
import { defineMnemonicKey, useMnemonicKey } from "react-mnemonic";
|
|
161
|
+
|
|
162
|
+
export const themeKey = defineMnemonicKey("theme", {
|
|
163
|
+
defaultValue: "light" as "light" | "dark",
|
|
164
|
+
listenCrossTab: true,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
function ThemeToggle() {
|
|
168
|
+
const { value: theme, set } = useMnemonicKey(themeKey);
|
|
169
|
+
return <button onClick={() => set(theme === "light" ? "dark" : "light")}>{theme}</button>;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function ThemePreview() {
|
|
173
|
+
const { value: theme } = useMnemonicKey(themeKey);
|
|
174
|
+
return <p>Current theme: {theme}</p>;
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Descriptors help with:
|
|
179
|
+
|
|
180
|
+
- keeping one canonical key contract per persisted value
|
|
181
|
+
- reusing the same `defaultValue`, `schema`, `codec`, and `reconcile` logic
|
|
182
|
+
- making AI-assisted code generation and refactors less ambiguous
|
|
183
|
+
|
|
184
|
+
The original lightweight form still works:
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
const { value, set } = useMnemonicKey("theme", {
|
|
188
|
+
defaultValue: "light" as "light" | "dark",
|
|
189
|
+
});
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Single source of truth schemas
|
|
193
|
+
|
|
194
|
+
If you want the same schema object to drive both runtime validation and
|
|
195
|
+
TypeScript inference, use the typed schema helpers:
|
|
196
|
+
|
|
197
|
+
```tsx
|
|
198
|
+
import {
|
|
199
|
+
MnemonicProvider,
|
|
200
|
+
createSchemaRegistry,
|
|
201
|
+
defineKeySchema,
|
|
202
|
+
defineMnemonicKey,
|
|
203
|
+
defineMigration,
|
|
204
|
+
mnemonicSchema,
|
|
205
|
+
useMnemonicKey,
|
|
206
|
+
} from "react-mnemonic";
|
|
207
|
+
|
|
208
|
+
const profileV1 = defineKeySchema(
|
|
209
|
+
"profile",
|
|
210
|
+
1,
|
|
211
|
+
mnemonicSchema.object({
|
|
212
|
+
name: mnemonicSchema.string(),
|
|
213
|
+
}),
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
const profileV2 = defineKeySchema(
|
|
217
|
+
"profile",
|
|
218
|
+
2,
|
|
219
|
+
mnemonicSchema.object({
|
|
220
|
+
name: mnemonicSchema.string(),
|
|
221
|
+
email: mnemonicSchema.string(),
|
|
222
|
+
}),
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
const profileKey = defineMnemonicKey(profileV2, {
|
|
226
|
+
defaultValue: { name: "", email: "" },
|
|
227
|
+
reconcile: (value) => ({
|
|
228
|
+
...value,
|
|
229
|
+
email: value.email.trim().toLowerCase(),
|
|
230
|
+
}),
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const registry = createSchemaRegistry({
|
|
234
|
+
schemas: [profileV1, profileV2],
|
|
235
|
+
migrations: [
|
|
236
|
+
defineMigration(profileV1, profileV2, (value) => ({
|
|
237
|
+
...value,
|
|
238
|
+
email: "",
|
|
239
|
+
})),
|
|
240
|
+
],
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
function ProfileForm() {
|
|
244
|
+
const { value: profile, set } = useMnemonicKey(profileKey);
|
|
245
|
+
return <button onClick={() => set({ ...profile, email: "hello@example.com" })}>Save</button>;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export default function App() {
|
|
249
|
+
return (
|
|
250
|
+
<MnemonicProvider namespace="my-app" schemaMode="default" schemaRegistry={registry}>
|
|
251
|
+
<ProfileForm />
|
|
252
|
+
</MnemonicProvider>
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
This path gives you:
|
|
258
|
+
|
|
259
|
+
- one schema object reused in the registry, key descriptor, and migrations
|
|
260
|
+
- inferred types for `defaultValue`, `value`, `set`, and `reconcile`
|
|
261
|
+
- typed migration callbacks via `defineMigration(...)`
|
|
262
|
+
|
|
263
|
+
Tradeoffs versus the lightweight JSON-only path:
|
|
264
|
+
|
|
265
|
+
- more setup upfront
|
|
266
|
+
- best fit when a key is schema-managed and long-lived
|
|
267
|
+
- not necessary for simple keys where `defaultValue` inference is already enough
|
|
268
|
+
|
|
269
|
+
For a side-by-side comparison against competing persistence libraries, including
|
|
270
|
+
bundle-size measurements and qualitative SSR/API notes, see the
|
|
271
|
+
[Comparison Guide](https://thirtytwobits.github.io/react-mnemonic/docs/guides/comparisons-and-benchmarks).
|
|
272
|
+
|
|
113
273
|
## API
|
|
114
274
|
|
|
115
275
|
### `<MnemonicProvider>`
|
|
@@ -130,11 +290,42 @@ Context provider that scopes storage keys under a namespace.
|
|
|
130
290
|
|
|
131
291
|
Multiple providers with different namespaces can coexist in the same app.
|
|
132
292
|
|
|
133
|
-
### `
|
|
293
|
+
### `defineMnemonicKey(key, options)`
|
|
294
|
+
|
|
295
|
+
Define a reusable descriptor for a single persisted key.
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
const themeKey = defineMnemonicKey("theme", {
|
|
299
|
+
defaultValue: "light" as "light" | "dark",
|
|
300
|
+
listenCrossTab: true,
|
|
301
|
+
});
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
The returned descriptor can be imported and passed directly to `useMnemonicKey`.
|
|
305
|
+
|
|
306
|
+
### `mnemonicSchema`, `defineKeySchema`, and `defineMigration`
|
|
307
|
+
|
|
308
|
+
Use these helpers when you want a schema-managed key to have one source of
|
|
309
|
+
truth for runtime validation and TypeScript inference.
|
|
310
|
+
|
|
311
|
+
```ts
|
|
312
|
+
const themeSchema = defineKeySchema("theme", 1, mnemonicSchema.enum(["light", "dark"] as const));
|
|
313
|
+
|
|
314
|
+
const themeKey = defineMnemonicKey(themeSchema, {
|
|
315
|
+
defaultValue: "light",
|
|
316
|
+
});
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
`defineMigration(fromSchema, toSchema, migrate)` infers the migration callback
|
|
320
|
+
from the source and target schemas, while `defineWriteMigration(schema, migrate)`
|
|
321
|
+
creates a typed same-version normalizer.
|
|
322
|
+
|
|
323
|
+
### `useMnemonicKey<T>(descriptor)` / `useMnemonicKey<T>(key, options)`
|
|
134
324
|
|
|
135
325
|
Hook for reading and writing a single persistent value.
|
|
136
326
|
|
|
137
327
|
```ts
|
|
328
|
+
const { value, set, reset, remove } = useMnemonicKey(themeKey);
|
|
138
329
|
const { value, set, reset, remove } = useMnemonicKey<T>(key, options);
|
|
139
330
|
```
|
|
140
331
|
|
|
@@ -221,6 +412,13 @@ interface StorageLike {
|
|
|
221
412
|
}
|
|
222
413
|
```
|
|
223
414
|
|
|
415
|
+
`StorageLike` is intentionally synchronous in v1 because Mnemonic's core
|
|
416
|
+
store is built on React's synchronous snapshot contract. Async backends are
|
|
417
|
+
still possible, but they need a synchronous facade: keep an in-memory cache for
|
|
418
|
+
`getItem`/`setItem`/`removeItem`, then flush to IndexedDB or another async
|
|
419
|
+
system outside the hook contract. Promise-returning `StorageLike` methods are
|
|
420
|
+
unsupported and are treated as a storage misuse fallback at runtime.
|
|
421
|
+
|
|
224
422
|
`onExternalChange` enables cross-tab sync for non-localStorage backends (e.g.
|
|
225
423
|
IndexedDB over `BroadcastChannel`). The library handles all error cases
|
|
226
424
|
internally -- see the `StorageLike` JSDoc for the full error-handling contract.
|
|
@@ -509,22 +707,40 @@ version and remount the provider.
|
|
|
509
707
|
import { MnemonicProvider } from "react-mnemonic";
|
|
510
708
|
import type { StorageLike } from "react-mnemonic";
|
|
511
709
|
|
|
710
|
+
const cache = new Map<string, string>();
|
|
711
|
+
const queueIndexedDbWrite = (key: string, value: string) => {
|
|
712
|
+
// application-specific async persistence
|
|
713
|
+
};
|
|
714
|
+
const queueIndexedDbDelete = (key: string) => {
|
|
715
|
+
// application-specific async persistence
|
|
716
|
+
};
|
|
717
|
+
|
|
512
718
|
const idbStorage: StorageLike = {
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
719
|
+
getItem: (key) => cache.get(key) ?? null,
|
|
720
|
+
setItem: (key, value) => {
|
|
721
|
+
cache.set(key, value);
|
|
722
|
+
queueIndexedDbWrite(key, value);
|
|
723
|
+
},
|
|
724
|
+
removeItem: (key) => {
|
|
725
|
+
cache.delete(key);
|
|
726
|
+
queueIndexedDbDelete(key);
|
|
727
|
+
},
|
|
728
|
+
onExternalChange: (cb) => {
|
|
729
|
+
const bc = new BroadcastChannel("my-app-sync");
|
|
730
|
+
bc.onmessage = (e) => cb(e.data.keys);
|
|
731
|
+
return () => bc.close();
|
|
732
|
+
},
|
|
521
733
|
};
|
|
522
734
|
|
|
523
735
|
<MnemonicProvider namespace="my-app" storage={idbStorage}>
|
|
524
|
-
|
|
525
|
-
</MnemonicProvider
|
|
736
|
+
<App />
|
|
737
|
+
</MnemonicProvider>;
|
|
526
738
|
```
|
|
527
739
|
|
|
740
|
+
If your real persistence layer is async, initialize that adapter before
|
|
741
|
+
rendering the provider and return a synchronous `StorageLike` facade, like the
|
|
742
|
+
IndexedDB demo in the docs site.
|
|
743
|
+
|
|
528
744
|
### DevTools
|
|
529
745
|
|
|
530
746
|
Enable the console inspector in development:
|
|
@@ -555,6 +771,7 @@ All public types are re-exported from the package root:
|
|
|
555
771
|
import type {
|
|
556
772
|
Codec,
|
|
557
773
|
StorageLike,
|
|
774
|
+
MnemonicKeyDescriptor,
|
|
558
775
|
MnemonicProviderOptions,
|
|
559
776
|
MnemonicProviderProps,
|
|
560
777
|
UseMnemonicKeyOptions,
|