react-mnemonic 1.0.0-beta.0 → 1.2.0-beta1
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 +43 -533
- package/dist/core.cjs +1322 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.d.cts +15 -0
- package/dist/core.d.ts +15 -0
- package/dist/core.js +1313 -0
- package/dist/core.js.map +1 -0
- package/dist/index.cjs +1568 -777
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -1719
- package/dist/index.d.ts +4 -1719
- package/dist/index.js +1565 -779
- package/dist/index.js.map +1 -1
- package/dist/key-BvFvcKiR.d.cts +1723 -0
- package/dist/key-BvFvcKiR.d.ts +1723 -0
- package/dist/schema.cjs +2276 -0
- package/dist/schema.cjs.map +1 -0
- package/dist/schema.d.cts +317 -0
- package/dist/schema.d.ts +317 -0
- package/dist/schema.js +2256 -0
- package/dist/schema.js.map +1 -0
- package/package.json +24 -1
package/README.md
CHANGED
|
@@ -1,70 +1,28 @@
|
|
|
1
1
|
# react-mnemonic
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
AI-friendly, persistent, type-safe state for React.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/react-mnemonic)
|
|
6
|
-
[](
|
|
8
|
-
[](https://www.typescriptlang.org/)
|
|
6
|
+
[](https://thirtytwobits.github.io/react-mnemonic/)
|
|
7
|
+
[](https://github.com/thirtytwobits/react-mnemonic/blob/main/LICENSE.md)
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
page refreshes, synchronize across tabs, and stay type-safe end-to-end -- all
|
|
12
|
-
through a single hook that works like `useState`.
|
|
13
|
-
|
|
14
|
-
`1.0.0` is currently being shipped as a beta on the npm `beta` dist-tag while
|
|
15
|
-
the release candidate hardening work finishes.
|
|
16
|
-
|
|
17
|
-
## Features
|
|
18
|
-
|
|
19
|
-
- **`useState`-like API** -- `useMnemonicKey` returns `{ value, set, reset, remove }`
|
|
20
|
-
- **JSON Schema validation** -- optional schema-based validation using a built-in JSON Schema subset
|
|
21
|
-
- **Namespace isolation** -- `MnemonicProvider` prefixes every key to prevent collisions
|
|
22
|
-
- **Cross-tab sync** -- opt-in `listenCrossTab` uses the browser `storage` event
|
|
23
|
-
- **Pluggable storage** -- bring your own backend via the `StorageLike` interface (IndexedDB, sessionStorage, etc.)
|
|
24
|
-
- **Schema versioning and migration** -- upgrade stored data with versioned schemas and migration rules
|
|
25
|
-
- **Structural migration helpers** -- optional tree utilities for idempotent insert/rename/dedupe migration steps
|
|
26
|
-
- **Read-time reconciliation** -- selectively enforce new defaults on persisted values without clearing the whole key
|
|
27
|
-
- **Recovery helpers** -- build user-facing soft reset and hard reset flows with namespace-scoped clear helpers
|
|
28
|
-
- **Write-time normalization** -- migrations where `fromVersion === toVersion` run on every write
|
|
29
|
-
- **Lifecycle callbacks** -- `onMount` and `onChange` hooks
|
|
30
|
-
- **DevTools** -- inspect and mutate state from the browser console
|
|
31
|
-
- **SSR-safe** -- returns defaults when `window` is unavailable
|
|
32
|
-
- **Tree-shakeable, zero dependencies** -- ships ESM + CJS with full TypeScript declarations
|
|
9
|
+
`react-mnemonic` gives your components persistent memory through a hook that feels like `useState`. Values survive reloads, can stay in sync across tabs, and remain SSR-safe by default. It is designed to be AI-friendly, prioritizing visible structure and unambiguous specifications. When you need more than raw storage, the package can validate, version, and migrate persisted data.
|
|
33
10
|
|
|
34
11
|
## Installation
|
|
35
12
|
|
|
36
13
|
```bash
|
|
37
|
-
npm install react-mnemonic
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
yarn add react-mnemonic@beta
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
```bash
|
|
45
|
-
pnpm add react-mnemonic@beta
|
|
14
|
+
npm install react-mnemonic
|
|
46
15
|
```
|
|
47
16
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
React 18 or later is required. CI verifies packaged-consumer installs against
|
|
51
|
-
React 18 and React 19.
|
|
52
|
-
|
|
53
|
-
```json
|
|
54
|
-
{
|
|
55
|
-
"peerDependencies": {
|
|
56
|
-
"react": ">=18",
|
|
57
|
-
"react-dom": ">=18"
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
```
|
|
17
|
+
React 18 or later is required.
|
|
61
18
|
|
|
62
19
|
## Quick start
|
|
63
20
|
|
|
64
|
-
Wrap your app in a `MnemonicProvider`, then call `useMnemonicKey` anywhere
|
|
21
|
+
Wrap your app in a `MnemonicProvider`, then call `useMnemonicKey` anywhere
|
|
22
|
+
inside it.
|
|
65
23
|
|
|
66
24
|
```tsx
|
|
67
|
-
import { MnemonicProvider, useMnemonicKey } from "react-mnemonic";
|
|
25
|
+
import { MnemonicProvider, useMnemonicKey } from "react-mnemonic/core";
|
|
68
26
|
|
|
69
27
|
function Counter() {
|
|
70
28
|
const { value: count, set } = useMnemonicKey("count", {
|
|
@@ -88,496 +46,48 @@ export default function App() {
|
|
|
88
46
|
}
|
|
89
47
|
```
|
|
90
48
|
|
|
91
|
-
|
|
92
|
-
survives full page
|
|
93
|
-
|
|
94
|
-
Persist only the durable slice of your app state. `useMnemonicKey` stores
|
|
95
|
-
whatever you pass to `set`, so keep transient UI state like loading flags,
|
|
96
|
-
hover state, and draft search text in plain React state unless you explicitly
|
|
97
|
-
want them to rehydrate after reload. See the
|
|
98
|
-
[Persisted vs Ephemeral State guide](https://thirtytwobits.github.io/react-mnemonic/docs/guides/persisted-vs-ephemeral-state)
|
|
99
|
-
for patterns and an interactive example.
|
|
100
|
-
|
|
101
|
-
For self-service recovery UX, pair your per-key hooks with
|
|
102
|
-
`useMnemonicRecovery` so users can clear stale filters, reset broken settings,
|
|
103
|
-
or fully wipe a namespace without opening DevTools. See the
|
|
104
|
-
[Reset and Recovery guide](https://thirtytwobits.github.io/react-mnemonic/docs/guides/reset-and-recovery)
|
|
105
|
-
for soft-reset and hard-reset recipes.
|
|
106
|
-
|
|
107
|
-
If a field must stay cleared across reloads, model it as nullable and persist
|
|
108
|
-
`null` explicitly. `remove()` deletes the key and falls back to `defaultValue`,
|
|
109
|
-
while `reset()` writes the default again. See the
|
|
110
|
-
[Clearable Persisted Values guide](https://thirtytwobits.github.io/react-mnemonic/docs/guides/clearable-persisted-values)
|
|
111
|
-
for the canonical nullable pattern.
|
|
112
|
-
|
|
113
|
-
## API
|
|
49
|
+
This persists the counter in `localStorage` as `my-app.count`, so the value
|
|
50
|
+
survives a full page reload.
|
|
114
51
|
|
|
115
|
-
|
|
52
|
+
## Why use it
|
|
116
53
|
|
|
117
|
-
|
|
54
|
+
- `useState`-like API: `useMnemonicKey` returns `{ value, set, reset, remove }`
|
|
55
|
+
- Namespaced persistence through `MnemonicProvider`
|
|
56
|
+
- Optional cross-tab synchronization
|
|
57
|
+
- SSR-safe defaults for server-rendered React apps
|
|
58
|
+
- Optional schema validation, versioning, migrations, and reconciliation
|
|
59
|
+
- Zero runtime dependencies with published TypeScript types
|
|
118
60
|
|
|
119
|
-
|
|
120
|
-
<MnemonicProvider
|
|
121
|
-
namespace="my-app" // key prefix (required)
|
|
122
|
-
storage={localStorage} // StorageLike backend (default: localStorage)
|
|
123
|
-
schemaMode="default" // "default" | "strict" | "autoschema" (default: "default")
|
|
124
|
-
schemaRegistry={registry} // optional SchemaRegistry for versioned schemas
|
|
125
|
-
enableDevTools={false} // expose console helpers (default: false)
|
|
126
|
-
>
|
|
127
|
-
{children}
|
|
128
|
-
</MnemonicProvider>
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
Multiple providers with different namespaces can coexist in the same app.
|
|
132
|
-
|
|
133
|
-
### `useMnemonicKey<T>(key, options)`
|
|
134
|
-
|
|
135
|
-
Hook for reading and writing a single persistent value.
|
|
136
|
-
|
|
137
|
-
```ts
|
|
138
|
-
const { value, set, reset, remove } = useMnemonicKey<T>(key, options);
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
| Return | Type | Description |
|
|
142
|
-
| -------- | ------------------------------------ | --------------------------------------------- |
|
|
143
|
-
| `value` | `T` | Current decoded value (or default) |
|
|
144
|
-
| `set` | `(next: T \| (cur: T) => T) => void` | Update the value (direct or updater function) |
|
|
145
|
-
| `reset` | `() => void` | Reset to `defaultValue` and persist it |
|
|
146
|
-
| `remove` | `() => void` | Delete the key from storage entirely |
|
|
147
|
-
|
|
148
|
-
For clearable fields, remember the semantic split:
|
|
149
|
-
|
|
150
|
-
- `set(null)` persists a cleared value and stays cleared after reload
|
|
151
|
-
- `remove()` deletes the key, so the next read falls back to `defaultValue`
|
|
152
|
-
- `reset()` persists `defaultValue`
|
|
153
|
-
|
|
154
|
-
#### Options
|
|
155
|
-
|
|
156
|
-
| Option | Type | Default | Description |
|
|
157
|
-
| ---------------- | ------------------------------------------------- | ----------- | ------------------------------------------------------------- |
|
|
158
|
-
| `defaultValue` | `T \| ((error?: CodecError \| SchemaError) => T)` | _required_ | Fallback value or error-aware factory |
|
|
159
|
-
| `codec` | `Codec<T>` | `JSONCodec` | Encode/decode strategy (bypasses schema validation) |
|
|
160
|
-
| `reconcile` | `(value: T, context: ReconcileContext) => T` | -- | Adjust a persisted value after read and persist if it changes |
|
|
161
|
-
| `onMount` | `(value: T) => void` | -- | Called once with the initial value |
|
|
162
|
-
| `onChange` | `(value: T, prev: T) => void` | -- | Called on every value change |
|
|
163
|
-
| `listenCrossTab` | `boolean` | `false` | Sync via the browser `storage` event |
|
|
164
|
-
| `schema` | `{ version?: number }` | -- | Pin writes to a specific schema version |
|
|
165
|
-
|
|
166
|
-
### `useMnemonicRecovery(options)`
|
|
167
|
-
|
|
168
|
-
Hook for namespace-scoped recovery actions such as "clear saved filters" or
|
|
169
|
-
"reset all persisted app data".
|
|
170
|
-
|
|
171
|
-
```ts
|
|
172
|
-
const { namespace, canEnumerateKeys, listKeys, clearAll, clearKeys, clearMatching } = useMnemonicRecovery({
|
|
173
|
-
onRecover: (event) => console.log(event.action, event.clearedKeys),
|
|
174
|
-
});
|
|
175
|
-
```
|
|
61
|
+
## Pick the right entrypoint
|
|
176
62
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
| `canEnumerateKeys` | `boolean` | Whether the storage backend can list namespace keys |
|
|
181
|
-
| `listKeys` | `() => string[]` | List visible unprefixed keys in the current namespace |
|
|
182
|
-
| `clearAll` | `() => string[]` | Clear every key in the namespace |
|
|
183
|
-
| `clearKeys` | `(keys: readonly string[]) => string[]` | Clear an explicit set of unprefixed keys |
|
|
184
|
-
| `clearMatching` | `(predicate: (key: string) => boolean) => string[]` | Clear keys whose names match a predicate |
|
|
63
|
+
- `react-mnemonic/core` for the lean persisted-state path
|
|
64
|
+
- `react-mnemonic/schema` when you want schemas, validation, and migrations
|
|
65
|
+
- `react-mnemonic` if you need the backward-compatible root entrypoint
|
|
185
66
|
|
|
186
|
-
|
|
187
|
-
as `localStorage` or `sessionStorage`. If your custom storage does not support
|
|
188
|
-
`length` and `key(index)`, use `clearKeys([...])` with the explicit durable-key
|
|
189
|
-
list your app owns.
|
|
190
|
-
|
|
191
|
-
### Codecs
|
|
192
|
-
|
|
193
|
-
The default codec is `JSONCodec`, which handles all JSON-serializable values.
|
|
194
|
-
You can create custom codecs using `createCodec` for types that need special
|
|
195
|
-
serialization (e.g., `Date`, `Set`, `Map`).
|
|
196
|
-
|
|
197
|
-
Using a custom codec bypasses JSON Schema validation -- the codec is a low-level
|
|
198
|
-
escape hatch for when you need full control over serialization.
|
|
199
|
-
|
|
200
|
-
```ts
|
|
201
|
-
import { createCodec } from "react-mnemonic";
|
|
202
|
-
|
|
203
|
-
const DateCodec = createCodec<Date>(
|
|
204
|
-
(date) => date.toISOString(),
|
|
205
|
-
(str) => new Date(str),
|
|
206
|
-
);
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
### `StorageLike`
|
|
210
|
-
|
|
211
|
-
The interface your custom storage backend must satisfy.
|
|
212
|
-
|
|
213
|
-
```ts
|
|
214
|
-
interface StorageLike {
|
|
215
|
-
getItem(key: string): string | null;
|
|
216
|
-
setItem(key: string, value: string): void;
|
|
217
|
-
removeItem(key: string): void;
|
|
218
|
-
key?(index: number): string | null;
|
|
219
|
-
readonly length?: number;
|
|
220
|
-
onExternalChange?: (callback: (changedKeys?: string[]) => void) => () => void;
|
|
221
|
-
}
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
`onExternalChange` enables cross-tab sync for non-localStorage backends (e.g.
|
|
225
|
-
IndexedDB over `BroadcastChannel`). The library handles all error cases
|
|
226
|
-
internally -- see the `StorageLike` JSDoc for the full error-handling contract.
|
|
227
|
-
Namespace-wide recovery helpers can only enumerate keys when the backend also
|
|
228
|
-
implements `length` and `key(index)`.
|
|
229
|
-
|
|
230
|
-
### `validateJsonSchema(schema, value)`
|
|
231
|
-
|
|
232
|
-
Validate an arbitrary value against a JSON Schema (the same subset used by the
|
|
233
|
-
hook). Returns an array of validation errors, empty when the value is valid.
|
|
234
|
-
|
|
235
|
-
```ts
|
|
236
|
-
import { validateJsonSchema } from "react-mnemonic";
|
|
237
|
-
|
|
238
|
-
const errors = validateJsonSchema(
|
|
239
|
-
{ type: "object", properties: { name: { type: "string" } }, required: ["name"] },
|
|
240
|
-
{ name: 42 },
|
|
241
|
-
);
|
|
242
|
-
// [{ path: ".name", message: 'Expected type "string"' }]
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
### `compileSchema(schema)`
|
|
246
|
-
|
|
247
|
-
Pre-compile a JSON Schema into a reusable validator function. The compiled
|
|
248
|
-
validator is cached by schema reference (via `WeakMap`), so calling
|
|
249
|
-
`compileSchema` twice with the same object returns the identical function.
|
|
250
|
-
|
|
251
|
-
```ts
|
|
252
|
-
import { compileSchema } from "react-mnemonic";
|
|
253
|
-
import type { CompiledValidator } from "react-mnemonic";
|
|
254
|
-
|
|
255
|
-
const validate: CompiledValidator = compileSchema({
|
|
256
|
-
type: "object",
|
|
257
|
-
properties: {
|
|
258
|
-
name: { type: "string", minLength: 1 },
|
|
259
|
-
age: { type: "number", minimum: 0 },
|
|
260
|
-
},
|
|
261
|
-
required: ["name"],
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
validate({ name: "Alice", age: 30 }); // []
|
|
265
|
-
validate({ age: -1 }); // [{ path: "", … }, { path: ".age", … }]
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
This is useful when you validate the same schema frequently outside of the hook
|
|
269
|
-
(e.g. in form validation or server responses).
|
|
270
|
-
|
|
271
|
-
### Error classes
|
|
272
|
-
|
|
273
|
-
| Class | Thrown when |
|
|
274
|
-
| ------------- | ------------------------------------ |
|
|
275
|
-
| `CodecError` | Encoding or decoding fails |
|
|
276
|
-
| `SchemaError` | Schema validation or migration fails |
|
|
277
|
-
|
|
278
|
-
Both are passed to `defaultValue` factories so you can inspect or log the
|
|
279
|
-
failure reason.
|
|
280
|
-
|
|
281
|
-
## Usage examples
|
|
282
|
-
|
|
283
|
-
### Cross-tab theme sync
|
|
284
|
-
|
|
285
|
-
```tsx
|
|
286
|
-
const { value: theme, set } = useMnemonicKey<"light" | "dark">("theme", {
|
|
287
|
-
defaultValue: "light",
|
|
288
|
-
listenCrossTab: true,
|
|
289
|
-
onChange: (t) => {
|
|
290
|
-
document.documentElement.setAttribute("data-theme", t);
|
|
291
|
-
},
|
|
292
|
-
});
|
|
293
|
-
```
|
|
67
|
+
## AI resources
|
|
294
68
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
}
|
|
304
|
-
if (error instanceof SchemaError) {
|
|
305
|
-
console.warn("Schema validation failed:", error.message);
|
|
306
|
-
}
|
|
307
|
-
return { count: 0 };
|
|
308
|
-
};
|
|
309
|
-
|
|
310
|
-
const { value } = useMnemonicKey("counter", { defaultValue: getDefault });
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
## Schema modes and versioning
|
|
314
|
-
|
|
315
|
-
Mnemonic supports optional schema versioning through `schemaMode` and an
|
|
316
|
-
optional `schemaRegistry`.
|
|
317
|
-
|
|
318
|
-
- `default`: Schemas are optional. Reads use a schema when one exists for the
|
|
319
|
-
stored version, otherwise the hook codec. Writes use the highest registered
|
|
320
|
-
schema for the key; if no schemas are registered, writes use an unversioned
|
|
321
|
-
(v0) envelope.
|
|
322
|
-
- `strict`: Every stored version must have a registered schema. Reads without a
|
|
323
|
-
matching schema fall back to `defaultValue` with a `SchemaError`.
|
|
324
|
-
Writes require a registered schema when any schemas exist, but fall back to
|
|
325
|
-
a v0 envelope when the registry has none.
|
|
326
|
-
- `autoschema`: Like `default`, but if no schema exists for a key, the first
|
|
327
|
-
successful read infers and registers a v1 schema. Subsequent reads/writes use
|
|
328
|
-
that schema.
|
|
329
|
-
|
|
330
|
-
Version `0` is valid for schemas and migrations. Schemas at version `0` are
|
|
331
|
-
treated like any other version.
|
|
332
|
-
|
|
333
|
-
### JSON Schema validation
|
|
334
|
-
|
|
335
|
-
Schemas use a subset of JSON Schema for validation. The supported keywords are:
|
|
336
|
-
|
|
337
|
-
- `type` (including array form for nullable types, e.g., `["string", "null"]`)
|
|
338
|
-
- `enum`, `const`
|
|
339
|
-
- `minimum`, `maximum`, `exclusiveMinimum`, `exclusiveMaximum`
|
|
340
|
-
- `minLength`, `maxLength`
|
|
341
|
-
- `properties`, `required`, `additionalProperties`
|
|
342
|
-
- `items`, `minItems`, `maxItems`
|
|
343
|
-
|
|
344
|
-
```ts
|
|
345
|
-
// Schema definition -- fully serializable JSON, no functions
|
|
346
|
-
const schema: KeySchema = {
|
|
347
|
-
key: "profile",
|
|
348
|
-
version: 1,
|
|
349
|
-
schema: {
|
|
350
|
-
type: "object",
|
|
351
|
-
properties: {
|
|
352
|
-
name: { type: "string", minLength: 1 },
|
|
353
|
-
email: { type: "string" },
|
|
354
|
-
age: { type: "number", minimum: 0 },
|
|
355
|
-
},
|
|
356
|
-
required: ["name", "email"],
|
|
357
|
-
},
|
|
358
|
-
};
|
|
359
|
-
```
|
|
360
|
-
|
|
361
|
-
### Write-time migrations (normalizers)
|
|
362
|
-
|
|
363
|
-
A migration where `fromVersion === toVersion` runs on every write, acting as a
|
|
364
|
-
normalizer. This is useful for trimming whitespace, lowercasing strings, etc.
|
|
365
|
-
|
|
366
|
-
```ts
|
|
367
|
-
const normalizer: MigrationRule = {
|
|
368
|
-
key: "name",
|
|
369
|
-
fromVersion: 1,
|
|
370
|
-
toVersion: 1,
|
|
371
|
-
migrate: (value) => String(value).trim().toLowerCase(),
|
|
372
|
-
};
|
|
373
|
-
```
|
|
374
|
-
|
|
375
|
-
### Reconciliation
|
|
376
|
-
|
|
377
|
-
Use `reconcile` when you want to keep persisted data but selectively enforce
|
|
378
|
-
new application defaults after the value has been decoded and any read-time
|
|
379
|
-
migrations have already run.
|
|
380
|
-
|
|
381
|
-
```ts
|
|
382
|
-
const { value } = useMnemonicKey("preferences", {
|
|
383
|
-
defaultValue: { theme: "dark", density: "comfortable", accents: true },
|
|
384
|
-
reconcile: (persisted, { persistedVersion }) => ({
|
|
385
|
-
...persisted,
|
|
386
|
-
accents: persistedVersion === 0 ? true : persisted.accents,
|
|
387
|
-
}),
|
|
388
|
-
});
|
|
389
|
-
```
|
|
390
|
-
|
|
391
|
-
Use a schema migration when the stored shape must move from one explicit version
|
|
392
|
-
to another. Use `reconcile` for conditional, field-level policy changes such as
|
|
393
|
-
rolling out a new default while preserving the rest of a user's stored data.
|
|
394
|
-
|
|
395
|
-
### Structural migration helpers
|
|
396
|
-
|
|
397
|
-
For layout-like data that already uses `id` and `children`, Mnemonic ships
|
|
398
|
-
optional pure helpers for common idempotent migration steps:
|
|
399
|
-
|
|
400
|
-
```ts
|
|
401
|
-
import { insertChildIfMissing, renameNode, dedupeChildrenBy } from "react-mnemonic";
|
|
402
|
-
|
|
403
|
-
const migrated = dedupeChildrenBy(
|
|
404
|
-
renameNode(insertChildIfMissing(layout, "sidebar", { id: "search", title: "Search" }), "prefs", "preferences"),
|
|
405
|
-
(node) => node.id,
|
|
406
|
-
);
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
Use these inside your `MigrationRule.migrate` functions when you want repeatable
|
|
410
|
-
tree edits without hand-writing the same traversal logic each time. See the
|
|
411
|
-
[Schema Migration guide](https://thirtytwobits.github.io/react-mnemonic/docs/guides/schema-migration)
|
|
412
|
-
for a cookbook example and custom adapter usage.
|
|
413
|
-
|
|
414
|
-
### Example schema registry
|
|
415
|
-
|
|
416
|
-
A schema registry stores versioned schemas for each key, and resolves migration
|
|
417
|
-
paths to upgrade stored data. For the common immutable case, use
|
|
418
|
-
`createSchemaRegistry(...)` instead of hand-rolling the indexing boilerplate.
|
|
419
|
-
|
|
420
|
-
```tsx
|
|
421
|
-
import {
|
|
422
|
-
createSchemaRegistry,
|
|
423
|
-
MnemonicProvider,
|
|
424
|
-
useMnemonicKey,
|
|
425
|
-
type KeySchema,
|
|
426
|
-
type MigrationRule,
|
|
427
|
-
} from "react-mnemonic";
|
|
428
|
-
|
|
429
|
-
const schemas: KeySchema[] = [
|
|
430
|
-
{
|
|
431
|
-
key: "profile",
|
|
432
|
-
version: 1,
|
|
433
|
-
schema: {
|
|
434
|
-
type: "object",
|
|
435
|
-
properties: { name: { type: "string" }, email: { type: "string" } },
|
|
436
|
-
required: ["name", "email"],
|
|
437
|
-
},
|
|
438
|
-
},
|
|
439
|
-
{
|
|
440
|
-
key: "profile",
|
|
441
|
-
version: 2,
|
|
442
|
-
schema: {
|
|
443
|
-
type: "object",
|
|
444
|
-
properties: {
|
|
445
|
-
name: { type: "string" },
|
|
446
|
-
email: { type: "string" },
|
|
447
|
-
migratedAt: { type: "string" },
|
|
448
|
-
},
|
|
449
|
-
required: ["name", "email", "migratedAt"],
|
|
450
|
-
},
|
|
451
|
-
},
|
|
452
|
-
];
|
|
453
|
-
|
|
454
|
-
const migrations: MigrationRule[] = [
|
|
455
|
-
{
|
|
456
|
-
key: "profile",
|
|
457
|
-
fromVersion: 1,
|
|
458
|
-
toVersion: 2,
|
|
459
|
-
migrate: (value) => {
|
|
460
|
-
const v1 = value as { name: string; email: string };
|
|
461
|
-
return { ...v1, migratedAt: new Date().toISOString() };
|
|
462
|
-
},
|
|
463
|
-
},
|
|
464
|
-
{
|
|
465
|
-
key: "profile",
|
|
466
|
-
fromVersion: 2,
|
|
467
|
-
toVersion: 2,
|
|
468
|
-
migrate: (value) => {
|
|
469
|
-
const profile = value as { name: string; email: string; migratedAt: string };
|
|
470
|
-
return { ...profile, email: profile.email.trim().toLowerCase() };
|
|
471
|
-
},
|
|
472
|
-
},
|
|
473
|
-
];
|
|
474
|
-
|
|
475
|
-
const registry = createSchemaRegistry({
|
|
476
|
-
schemas,
|
|
477
|
-
migrations,
|
|
478
|
-
});
|
|
479
|
-
|
|
480
|
-
function ProfileEditor() {
|
|
481
|
-
const { value, set } = useMnemonicKey<{ name: string; email: string; migratedAt: string }>("profile", {
|
|
482
|
-
defaultValue: { name: "", email: "", migratedAt: "" },
|
|
483
|
-
});
|
|
484
|
-
return <input value={value.name} onChange={(e) => set({ ...value, name: e.target.value })} />;
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
<MnemonicProvider namespace="app" schemaMode="default" schemaRegistry={registry}>
|
|
488
|
-
<ProfileEditor />
|
|
489
|
-
</MnemonicProvider>;
|
|
490
|
-
```
|
|
491
|
-
|
|
492
|
-
`createSchemaRegistry` validates duplicate schemas and ambiguous migration
|
|
493
|
-
graphs up front. If you need runtime schema registration for
|
|
494
|
-
`schemaMode="autoschema"`, keep a custom mutable `SchemaRegistry`
|
|
495
|
-
implementation.
|
|
496
|
-
|
|
497
|
-
### Registry immutability
|
|
498
|
-
|
|
499
|
-
In `default` and `strict` modes, the schema registry is treated as immutable for
|
|
500
|
-
the lifetime of the provider. The hook caches registry lookups to keep read and
|
|
501
|
-
write hot paths fast. To ship new schemas or migrations, publish a new app
|
|
502
|
-
version and remount the provider.
|
|
503
|
-
|
|
504
|
-
`autoschema` remains mutable because inferred schemas are registered at runtime.
|
|
505
|
-
|
|
506
|
-
### Custom storage backend
|
|
507
|
-
|
|
508
|
-
```tsx
|
|
509
|
-
import { MnemonicProvider } from "react-mnemonic";
|
|
510
|
-
import type { StorageLike } from "react-mnemonic";
|
|
511
|
-
|
|
512
|
-
const idbStorage: StorageLike = {
|
|
513
|
-
getItem: (key) => /* read from IndexedDB */,
|
|
514
|
-
setItem: (key, value) => /* write to IndexedDB */,
|
|
515
|
-
removeItem: (key) => /* delete from IndexedDB */,
|
|
516
|
-
onExternalChange: (cb) => {
|
|
517
|
-
const bc = new BroadcastChannel("my-app-sync");
|
|
518
|
-
bc.onmessage = (e) => cb(e.data.keys);
|
|
519
|
-
return () => bc.close();
|
|
520
|
-
},
|
|
521
|
-
};
|
|
522
|
-
|
|
523
|
-
<MnemonicProvider namespace="my-app" storage={idbStorage}>
|
|
524
|
-
<App />
|
|
525
|
-
</MnemonicProvider>
|
|
526
|
-
```
|
|
527
|
-
|
|
528
|
-
### DevTools
|
|
529
|
-
|
|
530
|
-
Enable the console inspector in development:
|
|
531
|
-
|
|
532
|
-
```tsx
|
|
533
|
-
<MnemonicProvider namespace="app" enableDevTools={process.env.NODE_ENV === "development"}>
|
|
534
|
-
```
|
|
535
|
-
|
|
536
|
-
Then in the browser console:
|
|
537
|
-
|
|
538
|
-
```js
|
|
539
|
-
const app = window.__REACT_MNEMONIC_DEVTOOLS__?.resolve("app");
|
|
540
|
-
|
|
541
|
-
app?.dump(); // table of all keys
|
|
542
|
-
app?.get("theme"); // read a decoded value
|
|
543
|
-
app?.set("theme", "dark"); // write
|
|
544
|
-
app?.remove("theme"); // delete
|
|
545
|
-
app?.keys(); // list all keys
|
|
546
|
-
app?.clear(); // remove all keys
|
|
547
|
-
```
|
|
548
|
-
|
|
549
|
-
## TypeScript
|
|
550
|
-
|
|
551
|
-
The library is written in strict TypeScript and ships its own declarations.
|
|
552
|
-
All public types are re-exported from the package root:
|
|
553
|
-
|
|
554
|
-
```ts
|
|
555
|
-
import type {
|
|
556
|
-
Codec,
|
|
557
|
-
StorageLike,
|
|
558
|
-
MnemonicProviderOptions,
|
|
559
|
-
MnemonicProviderProps,
|
|
560
|
-
UseMnemonicKeyOptions,
|
|
561
|
-
KeySchema,
|
|
562
|
-
MigrationRule,
|
|
563
|
-
MigrationPath,
|
|
564
|
-
SchemaRegistry,
|
|
565
|
-
SchemaMode,
|
|
566
|
-
JsonSchema,
|
|
567
|
-
JsonSchemaType,
|
|
568
|
-
JsonSchemaValidationError,
|
|
569
|
-
CompiledValidator,
|
|
570
|
-
} from "react-mnemonic";
|
|
571
|
-
```
|
|
69
|
+
| Resource | Purpose |
|
|
70
|
+
| ------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
|
|
71
|
+
| [AI Docs](https://thirtytwobits.github.io/react-mnemonic/docs/ai) | Canonical invariants, decision matrix, recipes, anti-patterns, and setup guidance |
|
|
72
|
+
| [`llms.txt`](https://thirtytwobits.github.io/react-mnemonic/llms.txt) | Compact retrieval index for tight context windows |
|
|
73
|
+
| [`llms-full.txt`](https://thirtytwobits.github.io/react-mnemonic/llms-full.txt) | Long-form export for indexing and larger prompt contexts |
|
|
74
|
+
| [`ai-contract.json`](https://thirtytwobits.github.io/react-mnemonic/ai-contract.json) | Machine-readable persistence contract for tooling and agent integrations |
|
|
75
|
+
| [DeepWiki priorities](https://github.com/thirtytwobits/react-mnemonic/blob/main/.devin/wiki.json) | Steering file that points DeepWiki toward the highest-signal sources |
|
|
76
|
+
| [AI Assistant Setup](https://thirtytwobits.github.io/react-mnemonic/docs/ai/assistant-setup) | Generated instruction packs plus the documented MCP-friendly retrieval path |
|
|
572
77
|
|
|
573
|
-
##
|
|
78
|
+
## Learn more
|
|
574
79
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
80
|
+
- [Documentation home](https://thirtytwobits.github.io/react-mnemonic/)
|
|
81
|
+
- [Quick Start](https://thirtytwobits.github.io/react-mnemonic/docs/getting-started/quick-start)
|
|
82
|
+
- [Server Rendering](https://thirtytwobits.github.io/react-mnemonic/docs/guides/server-rendering)
|
|
83
|
+
- [Canonical Key Definitions](https://thirtytwobits.github.io/react-mnemonic/docs/guides/canonical-key-definitions)
|
|
84
|
+
- [Single Source of Truth Schemas](https://thirtytwobits.github.io/react-mnemonic/docs/guides/single-source-of-truth-schemas)
|
|
85
|
+
- [Schema Migration](https://thirtytwobits.github.io/react-mnemonic/docs/guides/schema-migration)
|
|
86
|
+
- [Auth-Aware Persistence](https://thirtytwobits.github.io/react-mnemonic/docs/guides/auth-aware-persistence)
|
|
87
|
+
- [Context7 Rankings](https://thirtytwobits.github.io/react-mnemonic/docs/guides/context7-rankings)
|
|
88
|
+
- [API Reference](https://thirtytwobits.github.io/react-mnemonic/docs/api)
|
|
89
|
+
- [AI Overview](https://thirtytwobits.github.io/react-mnemonic/docs/ai)
|
|
580
90
|
|
|
581
91
|
## License
|
|
582
92
|
|
|
583
|
-
[MIT](
|
|
93
|
+
[MIT](https://github.com/thirtytwobits/react-mnemonic/blob/main/LICENSE.md)
|