react-mnemonic 0.1.1-alpha.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/LICENSE.md +21 -0
- package/README.md +497 -0
- package/dist/index.cjs +996 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1151 -0
- package/dist/index.d.ts +1151 -0
- package/dist/index.js +987 -0
- package/dist/index.js.map +1 -0
- package/package.json +80 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,1151 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @fileoverview Codec implementations for encoding and decoding values to/from storage.
|
|
6
|
+
*
|
|
7
|
+
* This module provides the built-in JSON codec and a factory for creating custom
|
|
8
|
+
* codecs. Codecs handle the bidirectional transformation between typed JavaScript
|
|
9
|
+
* values and their string representations for storage.
|
|
10
|
+
*
|
|
11
|
+
* Codecs are a low-level mechanism for keys that opt out of the JSON Schema
|
|
12
|
+
* validation system. When a schema is registered for a key, the schema's
|
|
13
|
+
* JSON Schema is used for validation and the payload is stored as a JSON value
|
|
14
|
+
* directly (no codec encoding needed).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Custom error class for codec encoding and decoding failures.
|
|
19
|
+
*
|
|
20
|
+
* Thrown when a codec cannot successfully encode a value to a string or
|
|
21
|
+
* decode a string back to its typed representation. This allows callers
|
|
22
|
+
* to distinguish codec errors from other types of errors.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* try {
|
|
27
|
+
* const value = JSONCodec.decode('not-valid-json');
|
|
28
|
+
* } catch (error) {
|
|
29
|
+
* if (error instanceof CodecError) {
|
|
30
|
+
* console.error('Failed to decode:', error.message);
|
|
31
|
+
* }
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
declare class CodecError extends Error {
|
|
36
|
+
/**
|
|
37
|
+
* The underlying error that caused the codec failure, if any.
|
|
38
|
+
*
|
|
39
|
+
* Useful for debugging when wrapping errors from JSON.parse or
|
|
40
|
+
* other parsing operations.
|
|
41
|
+
*/
|
|
42
|
+
readonly cause?: unknown;
|
|
43
|
+
/**
|
|
44
|
+
* Creates a new CodecError.
|
|
45
|
+
*
|
|
46
|
+
* @param message - Human-readable error description
|
|
47
|
+
* @param cause - Optional underlying error that caused this failure
|
|
48
|
+
*/
|
|
49
|
+
constructor(message: string, cause?: unknown);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* JSON codec for encoding and decoding JSON-serializable values.
|
|
53
|
+
*
|
|
54
|
+
* This is the default codec used by `useMnemonicKey` when no codec is specified.
|
|
55
|
+
* It uses `JSON.stringify` for encoding and `JSON.parse` for decoding, making it
|
|
56
|
+
* suitable for objects, arrays, and primitive values.
|
|
57
|
+
*
|
|
58
|
+
* @remarks
|
|
59
|
+
* - Supports any JSON-serializable type: objects, arrays, strings, numbers, booleans, null
|
|
60
|
+
* - Does not preserve JavaScript-specific types like Date, Map, Set, or undefined
|
|
61
|
+
* - Throws standard JSON parsing errors for malformed JSON strings
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```typescript
|
|
65
|
+
* // Used automatically as the default
|
|
66
|
+
* const { value, set } = useMnemonicKey('userProfile', {
|
|
67
|
+
* defaultValue: { name: 'Guest', preferences: { theme: 'dark' } }
|
|
68
|
+
* // codec: JSONCodec is implicit
|
|
69
|
+
* });
|
|
70
|
+
*
|
|
71
|
+
* // Can be specified explicitly
|
|
72
|
+
* const { value, set } = useMnemonicKey('settings', {
|
|
73
|
+
* defaultValue: { notifications: true },
|
|
74
|
+
* codec: JSONCodec
|
|
75
|
+
* });
|
|
76
|
+
* ```
|
|
77
|
+
*
|
|
78
|
+
* @see {@link createCodec} - For custom encoding schemes
|
|
79
|
+
*/
|
|
80
|
+
declare const JSONCodec: Codec<any>;
|
|
81
|
+
/**
|
|
82
|
+
* Factory function for creating custom codecs.
|
|
83
|
+
*
|
|
84
|
+
* Creates a `Codec<T>` from separate encode and decode functions. This is
|
|
85
|
+
* useful for implementing custom serialization strategies for types that
|
|
86
|
+
* aren't supported by JSONCodec. Using a custom codec on a key opts out
|
|
87
|
+
* of JSON Schema validation for that key.
|
|
88
|
+
*
|
|
89
|
+
* @template T - The TypeScript type of values to encode/decode
|
|
90
|
+
*
|
|
91
|
+
* @param encode - Function that converts a typed value to a string
|
|
92
|
+
* @param decode - Function that converts a string back to a typed value
|
|
93
|
+
* @returns A `Codec<T>` object compatible with useMnemonicKey
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```typescript
|
|
97
|
+
* // Codec for Date objects
|
|
98
|
+
* const DateCodec = createCodec<Date>(
|
|
99
|
+
* (date) => date.toISOString(),
|
|
100
|
+
* (str) => new Date(str)
|
|
101
|
+
* );
|
|
102
|
+
*
|
|
103
|
+
* const { value, set } = useMnemonicKey('lastLogin', {
|
|
104
|
+
* defaultValue: new Date(),
|
|
105
|
+
* codec: DateCodec
|
|
106
|
+
* });
|
|
107
|
+
* ```
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```typescript
|
|
111
|
+
* // Codec for Set<string>
|
|
112
|
+
* const StringSetCodec = createCodec<Set<string>>(
|
|
113
|
+
* (set) => JSON.stringify(Array.from(set)),
|
|
114
|
+
* (str) => new Set(JSON.parse(str))
|
|
115
|
+
* );
|
|
116
|
+
*
|
|
117
|
+
* const { value, set } = useMnemonicKey('tags', {
|
|
118
|
+
* defaultValue: new Set<string>(),
|
|
119
|
+
* codec: StringSetCodec
|
|
120
|
+
* });
|
|
121
|
+
* ```
|
|
122
|
+
*
|
|
123
|
+
* @see {@link Codec} - The codec interface
|
|
124
|
+
* @see {@link CodecError} - Error to throw when encoding/decoding fails
|
|
125
|
+
* @see {@link JSONCodec} - Built-in codec for JSON values
|
|
126
|
+
*/
|
|
127
|
+
declare function createCodec<T>(encode: (value: T) => string, decode: (encoded: string) => T): Codec<T>;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* @fileoverview Schema versioning primitives for Mnemonic.
|
|
131
|
+
*
|
|
132
|
+
* This module defines the envelope format used to wrap every persisted value
|
|
133
|
+
* and the error class thrown when schema-related operations fail.
|
|
134
|
+
*/
|
|
135
|
+
/**
|
|
136
|
+
* Error thrown for schema registry, versioning, and migration failures.
|
|
137
|
+
*
|
|
138
|
+
* Each instance carries a machine-readable {@link code} that categorises
|
|
139
|
+
* the failure. When a `defaultValue` factory is provided to
|
|
140
|
+
* `useMnemonicKey`, the `SchemaError` is passed as the `error` argument
|
|
141
|
+
* so the factory can inspect the failure reason.
|
|
142
|
+
*
|
|
143
|
+
* Error codes:
|
|
144
|
+
*
|
|
145
|
+
* | Code | Meaning |
|
|
146
|
+
* | ------------------------------- | --------------------------------------------------------------- |
|
|
147
|
+
* | `INVALID_ENVELOPE` | The raw stored value is not a valid `MnemonicEnvelope`. |
|
|
148
|
+
* | `SCHEMA_NOT_FOUND` | No schema registered for the stored key + version. |
|
|
149
|
+
* | `WRITE_SCHEMA_REQUIRED` | Strict mode requires a schema to write, but none was found. |
|
|
150
|
+
* | `MIGRATION_PATH_NOT_FOUND` | No contiguous migration path between the stored and latest version. |
|
|
151
|
+
* | `MIGRATION_FAILED` | A migration step threw during execution. |
|
|
152
|
+
* | `SCHEMA_REGISTRATION_CONFLICT` | `registerSchema` was called with a conflicting definition. |
|
|
153
|
+
* | `TYPE_MISMATCH` | The decoded value failed JSON Schema validation. |
|
|
154
|
+
* | `MODE_CONFIGURATION_INVALID` | The schema mode requires a capability the registry doesn't provide. |
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* ```typescript
|
|
158
|
+
* defaultValue: (error) => {
|
|
159
|
+
* if (error instanceof SchemaError) {
|
|
160
|
+
* console.warn(`Schema issue [${error.code}]:`, error.message);
|
|
161
|
+
* }
|
|
162
|
+
* return { name: "Guest" };
|
|
163
|
+
* }
|
|
164
|
+
* ```
|
|
165
|
+
*
|
|
166
|
+
* @see {@link SchemaMode} - How the provider uses schemas
|
|
167
|
+
* @see {@link SchemaRegistry} - Where schemas and migrations are registered
|
|
168
|
+
*/
|
|
169
|
+
declare class SchemaError extends Error {
|
|
170
|
+
/**
|
|
171
|
+
* Machine-readable code identifying the category of schema failure.
|
|
172
|
+
*/
|
|
173
|
+
readonly code: "INVALID_ENVELOPE" | "SCHEMA_NOT_FOUND" | "WRITE_SCHEMA_REQUIRED" | "MIGRATION_PATH_NOT_FOUND" | "MIGRATION_FAILED" | "SCHEMA_REGISTRATION_CONFLICT" | "TYPE_MISMATCH" | "MODE_CONFIGURATION_INVALID";
|
|
174
|
+
/**
|
|
175
|
+
* The underlying error that caused this failure, if any.
|
|
176
|
+
*/
|
|
177
|
+
readonly cause?: unknown;
|
|
178
|
+
/**
|
|
179
|
+
* Creates a new SchemaError.
|
|
180
|
+
*
|
|
181
|
+
* @param code - Machine-readable failure category
|
|
182
|
+
* @param message - Human-readable error description
|
|
183
|
+
* @param cause - Optional underlying error
|
|
184
|
+
*/
|
|
185
|
+
constructor(code: SchemaError["code"], message: string, cause?: unknown);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* @fileoverview JSON Schema subset validator for Mnemonic.
|
|
190
|
+
*
|
|
191
|
+
* This module implements a minimal JSON Schema validator sufficient for
|
|
192
|
+
* validating localStorage state. Only a subset of JSON Schema keywords
|
|
193
|
+
* are supported; see {@link JsonSchema} for the full list.
|
|
194
|
+
*
|
|
195
|
+
* JSON Schema documents are plain JSON objects (inherently serializable),
|
|
196
|
+
* making them suitable for storage alongside the data they describe.
|
|
197
|
+
*/
|
|
198
|
+
/**
|
|
199
|
+
* Supported JSON Schema type keywords.
|
|
200
|
+
*
|
|
201
|
+
* `"integer"` is a JSON Schema keyword meaning "a number that is a whole number."
|
|
202
|
+
*/
|
|
203
|
+
type JsonSchemaType = "string" | "number" | "integer" | "boolean" | "null" | "object" | "array";
|
|
204
|
+
/**
|
|
205
|
+
* A subset of JSON Schema sufficient for localStorage state management.
|
|
206
|
+
*
|
|
207
|
+
* Supported keywords:
|
|
208
|
+
* type, enum, const,
|
|
209
|
+
* minimum, maximum, exclusiveMinimum, exclusiveMaximum,
|
|
210
|
+
* minLength, maxLength,
|
|
211
|
+
* properties, required, additionalProperties,
|
|
212
|
+
* items, minItems, maxItems
|
|
213
|
+
*
|
|
214
|
+
* Deliberately omitted: $ref, $id, $schema, $defs, allOf, anyOf,
|
|
215
|
+
* oneOf, not, pattern, format, patternProperties, if/then/else,
|
|
216
|
+
* dependencies, uniqueItems, multipleOf, propertyNames.
|
|
217
|
+
*
|
|
218
|
+
* An empty schema `{}` accepts any value.
|
|
219
|
+
*/
|
|
220
|
+
interface JsonSchema {
|
|
221
|
+
/** The expected JSON type(s). An array form like `["string", "null"]` accepts either type. */
|
|
222
|
+
type?: JsonSchemaType | JsonSchemaType[];
|
|
223
|
+
/** The value must be deeply equal to one of these entries. */
|
|
224
|
+
enum?: readonly unknown[];
|
|
225
|
+
/** The value must be deeply equal to this exact value. */
|
|
226
|
+
const?: unknown;
|
|
227
|
+
/** Inclusive lower bound for numbers. */
|
|
228
|
+
minimum?: number;
|
|
229
|
+
/** Inclusive upper bound for numbers. */
|
|
230
|
+
maximum?: number;
|
|
231
|
+
/** Exclusive lower bound for numbers. */
|
|
232
|
+
exclusiveMinimum?: number;
|
|
233
|
+
/** Exclusive upper bound for numbers. */
|
|
234
|
+
exclusiveMaximum?: number;
|
|
235
|
+
/** Minimum string length (inclusive). */
|
|
236
|
+
minLength?: number;
|
|
237
|
+
/** Maximum string length (inclusive). */
|
|
238
|
+
maxLength?: number;
|
|
239
|
+
/** Property name to sub-schema mapping for objects. */
|
|
240
|
+
properties?: Record<string, JsonSchema>;
|
|
241
|
+
/** Properties that must be present on the object. */
|
|
242
|
+
required?: readonly string[];
|
|
243
|
+
/**
|
|
244
|
+
* Controls extra properties not listed in `properties`.
|
|
245
|
+
* `false` disallows them. A schema validates their values.
|
|
246
|
+
* `true` (or omitted) allows anything.
|
|
247
|
+
*/
|
|
248
|
+
additionalProperties?: boolean | JsonSchema;
|
|
249
|
+
/** Schema applied to every element of an array. */
|
|
250
|
+
items?: JsonSchema;
|
|
251
|
+
/** Minimum array length (inclusive). */
|
|
252
|
+
minItems?: number;
|
|
253
|
+
/** Maximum array length (inclusive). */
|
|
254
|
+
maxItems?: number;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* A single validation error produced by {@link validateJsonSchema}.
|
|
258
|
+
*/
|
|
259
|
+
type JsonSchemaValidationError = {
|
|
260
|
+
/** JSON Pointer path to the failing value (e.g., "/foo/bar/0"). Empty string for root. */
|
|
261
|
+
path: string;
|
|
262
|
+
/** Human-readable error description. */
|
|
263
|
+
message: string;
|
|
264
|
+
/** The JSON Schema keyword that failed. */
|
|
265
|
+
keyword: string;
|
|
266
|
+
};
|
|
267
|
+
/**
|
|
268
|
+
* A pre-compiled validation function generated by {@link compileSchema}.
|
|
269
|
+
*
|
|
270
|
+
* Accepts a value and an optional JSON Pointer path for error reporting.
|
|
271
|
+
* Returns an array of validation errors (empty = valid).
|
|
272
|
+
*/
|
|
273
|
+
type CompiledValidator = (value: unknown, path?: string) => JsonSchemaValidationError[];
|
|
274
|
+
/**
|
|
275
|
+
* Pre-compiles a {@link JsonSchema} into a reusable validation function.
|
|
276
|
+
*
|
|
277
|
+
* Inspects the schema once and builds a specialized closure that
|
|
278
|
+
* eliminates runtime branching for unused keywords, pre-converts
|
|
279
|
+
* `required` arrays to `Set`s, recursively pre-compiles nested property
|
|
280
|
+
* and item schemas, and pre-builds primitive `Set`s for O(1) enum
|
|
281
|
+
* lookups when possible.
|
|
282
|
+
*
|
|
283
|
+
* Results are cached by schema object identity in a `WeakMap`, so
|
|
284
|
+
* calling `compileSchema` with the same schema reference is free
|
|
285
|
+
* after the first call.
|
|
286
|
+
*
|
|
287
|
+
* @param schema - The JSON Schema to compile
|
|
288
|
+
* @returns A compiled validation function
|
|
289
|
+
*/
|
|
290
|
+
declare function compileSchema(schema: JsonSchema): CompiledValidator;
|
|
291
|
+
/**
|
|
292
|
+
* Validates a value against a {@link JsonSchema}.
|
|
293
|
+
*
|
|
294
|
+
* Returns an empty array when the value is valid.
|
|
295
|
+
* Returns one or more {@link JsonSchemaValidationError} entries on failure.
|
|
296
|
+
* Short-circuits on type mismatch (does not report downstream keyword errors).
|
|
297
|
+
*
|
|
298
|
+
* @param value - The value to validate
|
|
299
|
+
* @param schema - The JSON Schema to validate against
|
|
300
|
+
* @param path - Internal: JSON Pointer path for error reporting (default: `""`)
|
|
301
|
+
* @returns Array of validation errors (empty = valid)
|
|
302
|
+
*/
|
|
303
|
+
declare function validateJsonSchema(value: unknown, schema: JsonSchema, path?: string): JsonSchemaValidationError[];
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* @fileoverview Type definitions for the Mnemonic library.
|
|
307
|
+
*
|
|
308
|
+
* This module defines the core types and interfaces used throughout the Mnemonic
|
|
309
|
+
* library for type-safe, persistent state management in React applications.
|
|
310
|
+
*/
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Codec for encoding and decoding values to and from storage.
|
|
314
|
+
*
|
|
315
|
+
* Codecs provide bidirectional transformations between typed values and their
|
|
316
|
+
* string representations suitable for storage in localStorage or similar backends.
|
|
317
|
+
*
|
|
318
|
+
* Using a codec on a key opts out of JSON Schema validation. Schema-managed
|
|
319
|
+
* keys store JSON values directly and are validated against their JSON Schema.
|
|
320
|
+
*
|
|
321
|
+
* @template T - The TypeScript type of the value to encode/decode
|
|
322
|
+
*
|
|
323
|
+
* @example
|
|
324
|
+
* ```typescript
|
|
325
|
+
* const DateCodec: Codec<Date> = {
|
|
326
|
+
* encode: (date) => date.toISOString(),
|
|
327
|
+
* decode: (str) => new Date(str)
|
|
328
|
+
* };
|
|
329
|
+
* ```
|
|
330
|
+
*
|
|
331
|
+
* @see {@link JSONCodec} - Default codec for JSON-serializable values
|
|
332
|
+
* @see {@link createCodec} - Helper function to create custom codecs
|
|
333
|
+
*/
|
|
334
|
+
interface Codec<T> {
|
|
335
|
+
/**
|
|
336
|
+
* Transforms a typed value into a string suitable for storage.
|
|
337
|
+
*
|
|
338
|
+
* @param value - The typed value to encode
|
|
339
|
+
* @returns A string representation of the value
|
|
340
|
+
* @throws {CodecError} If the value cannot be encoded
|
|
341
|
+
*/
|
|
342
|
+
encode: (value: T) => string;
|
|
343
|
+
/**
|
|
344
|
+
* Transforms a stored string back into a typed value.
|
|
345
|
+
*
|
|
346
|
+
* @param encoded - The string representation from storage
|
|
347
|
+
* @returns The decoded typed value
|
|
348
|
+
* @throws {CodecError} If the string cannot be decoded
|
|
349
|
+
*/
|
|
350
|
+
decode: (encoded: string) => T;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Configuration options for MnemonicProvider.
|
|
354
|
+
*
|
|
355
|
+
* These options configure the behavior of the storage provider, including
|
|
356
|
+
* namespace isolation, storage backend selection, cross-tab synchronization,
|
|
357
|
+
* and developer tools integration.
|
|
358
|
+
*
|
|
359
|
+
* @example
|
|
360
|
+
* ```tsx
|
|
361
|
+
* <MnemonicProvider
|
|
362
|
+
* namespace="myApp"
|
|
363
|
+
* storage={localStorage}
|
|
364
|
+
* enableDevTools={process.env.NODE_ENV === 'development'}
|
|
365
|
+
* >
|
|
366
|
+
* <App />
|
|
367
|
+
* </MnemonicProvider>
|
|
368
|
+
* ```
|
|
369
|
+
*/
|
|
370
|
+
interface MnemonicProviderOptions {
|
|
371
|
+
/**
|
|
372
|
+
* Namespace prefix for all storage keys.
|
|
373
|
+
*
|
|
374
|
+
* All keys stored by this provider will be prefixed with `${namespace}.`
|
|
375
|
+
* to avoid collisions between different parts of your application or
|
|
376
|
+
* different applications sharing the same storage backend.
|
|
377
|
+
*
|
|
378
|
+
* @example
|
|
379
|
+
* ```typescript
|
|
380
|
+
* // With namespace="myApp", a key "user" becomes "myApp.user" in storage
|
|
381
|
+
* namespace: "myApp"
|
|
382
|
+
* ```
|
|
383
|
+
*/
|
|
384
|
+
namespace: string;
|
|
385
|
+
/**
|
|
386
|
+
* Storage backend to use for persistence.
|
|
387
|
+
*
|
|
388
|
+
* Defaults to `window.localStorage` in browser environments. You can provide
|
|
389
|
+
* a custom implementation (e.g., sessionStorage, AsyncStorage, or a mock for testing).
|
|
390
|
+
*
|
|
391
|
+
* @default window.localStorage
|
|
392
|
+
*
|
|
393
|
+
* @example
|
|
394
|
+
* ```typescript
|
|
395
|
+
* // Use sessionStorage instead of localStorage
|
|
396
|
+
* storage: window.sessionStorage
|
|
397
|
+
*
|
|
398
|
+
* // Use a custom storage implementation
|
|
399
|
+
* storage: {
|
|
400
|
+
* getItem: (key) => myCustomStore.get(key),
|
|
401
|
+
* setItem: (key, value) => myCustomStore.set(key, value),
|
|
402
|
+
* removeItem: (key) => myCustomStore.delete(key)
|
|
403
|
+
* }
|
|
404
|
+
* ```
|
|
405
|
+
*/
|
|
406
|
+
storage?: StorageLike;
|
|
407
|
+
/**
|
|
408
|
+
* Enable DevTools debugging interface.
|
|
409
|
+
*
|
|
410
|
+
* When enabled, exposes the store on `window.__REACT_MNEMONIC_DEVTOOLS__[namespace]`
|
|
411
|
+
* with methods to inspect, modify, and dump storage state from the console.
|
|
412
|
+
*
|
|
413
|
+
* @default false
|
|
414
|
+
*
|
|
415
|
+
* @example
|
|
416
|
+
* ```typescript
|
|
417
|
+
* // Enable in development only
|
|
418
|
+
* enableDevTools: process.env.NODE_ENV === 'development'
|
|
419
|
+
*
|
|
420
|
+
* // Then in browser console:
|
|
421
|
+
* window.__REACT_MNEMONIC_DEVTOOLS__.myApp.dump()
|
|
422
|
+
* window.__REACT_MNEMONIC_DEVTOOLS__.myApp.get('user')
|
|
423
|
+
* window.__REACT_MNEMONIC_DEVTOOLS__.myApp.set('user', { name: 'Test' })
|
|
424
|
+
* ```
|
|
425
|
+
*/
|
|
426
|
+
enableDevTools?: boolean;
|
|
427
|
+
/**
|
|
428
|
+
* Versioning and schema enforcement mode.
|
|
429
|
+
*
|
|
430
|
+
* Controls whether stored values require a registered schema, and how
|
|
431
|
+
* missing schemas are handled. See {@link SchemaMode} for the behaviour
|
|
432
|
+
* of each mode.
|
|
433
|
+
*
|
|
434
|
+
* @default "default"
|
|
435
|
+
*
|
|
436
|
+
* @see {@link SchemaMode} - Detailed description of each mode
|
|
437
|
+
* @see {@link SchemaRegistry} - Registry supplied via `schemaRegistry`
|
|
438
|
+
*/
|
|
439
|
+
schemaMode?: SchemaMode;
|
|
440
|
+
/**
|
|
441
|
+
* Schema registry used for version lookup and migration resolution.
|
|
442
|
+
*
|
|
443
|
+
* When provided, the library uses the registry to find the correct
|
|
444
|
+
* JSON Schema for each stored version, and to resolve migration paths
|
|
445
|
+
* when upgrading old data to the latest schema.
|
|
446
|
+
*
|
|
447
|
+
* Required when `schemaMode` is `"strict"` or `"autoschema"`.
|
|
448
|
+
* Optional (but recommended) in `"default"` mode.
|
|
449
|
+
*
|
|
450
|
+
* @remarks
|
|
451
|
+
* In `"default"` and `"strict"` modes, the registry is treated as
|
|
452
|
+
* immutable after the provider initializes. Updates should be shipped
|
|
453
|
+
* as part of a new app version and applied by remounting the provider.
|
|
454
|
+
* `"autoschema"` remains mutable so inferred schemas can be registered
|
|
455
|
+
* at runtime.
|
|
456
|
+
*
|
|
457
|
+
* @see {@link SchemaRegistry} - Interface the registry must implement
|
|
458
|
+
* @see {@link KeySchema} - Schema definition stored in the registry
|
|
459
|
+
*/
|
|
460
|
+
schemaRegistry?: SchemaRegistry;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Controls how the provider enforces versioned schemas on stored values.
|
|
464
|
+
*
|
|
465
|
+
* - `"default"` — Schemas are optional. When a schema exists for the stored
|
|
466
|
+
* version it is used for validation; otherwise the hook's `codec` option is
|
|
467
|
+
* used directly with no validation. This is the recommended starting mode.
|
|
468
|
+
*
|
|
469
|
+
* - `"strict"` — Every read and write **must** have a registered schema for
|
|
470
|
+
* the stored version. If no matching schema is found the value falls back
|
|
471
|
+
* to `defaultValue` with a `SchemaError` (`SCHEMA_NOT_FOUND` on reads,
|
|
472
|
+
* `WRITE_SCHEMA_REQUIRED` on writes).
|
|
473
|
+
* When no schemas are registered and no explicit schema is provided, writes
|
|
474
|
+
* fall back to a codec-encoded (v0) envelope.
|
|
475
|
+
*
|
|
476
|
+
* - `"autoschema"` — Like `"default"`, but when a key has **no** schema
|
|
477
|
+
* registered at all, the library infers a JSON Schema at version 1 from the
|
|
478
|
+
* first successfully decoded value and registers it via
|
|
479
|
+
* `SchemaRegistry.registerSchema`. Subsequent reads/writes for that key
|
|
480
|
+
* then behave as if the schema had been registered manually.
|
|
481
|
+
*
|
|
482
|
+
* @remarks
|
|
483
|
+
* In `"default"` and `"strict"` modes, registry lookups are cached under the
|
|
484
|
+
* assumption that the schema registry is immutable for the lifetime of the
|
|
485
|
+
* provider. If you need to update schemas, publish a new app version and
|
|
486
|
+
* remount the provider. `"autoschema"` does not assume immutability.
|
|
487
|
+
*
|
|
488
|
+
* @default "default"
|
|
489
|
+
*
|
|
490
|
+
* @see {@link SchemaRegistry} - Registry that stores schemas and migrations
|
|
491
|
+
* @see {@link KeySchema} - Individual schema definition
|
|
492
|
+
*/
|
|
493
|
+
type SchemaMode = "strict" | "default" | "autoschema";
|
|
494
|
+
/**
|
|
495
|
+
* Schema definition for a single key at a specific version.
|
|
496
|
+
*
|
|
497
|
+
* Each registered schema binds a storage key + version number to a
|
|
498
|
+
* JSON Schema that validates the payload. Schemas are fully serializable
|
|
499
|
+
* (no functions).
|
|
500
|
+
*
|
|
501
|
+
* When the provider reads a value whose envelope version matches a
|
|
502
|
+
* registered schema, the payload is validated against the schema's
|
|
503
|
+
* JSON Schema definition.
|
|
504
|
+
*
|
|
505
|
+
* @example
|
|
506
|
+
* ```typescript
|
|
507
|
+
* const userSchemaV1: KeySchema = {
|
|
508
|
+
* key: "user",
|
|
509
|
+
* version: 1,
|
|
510
|
+
* schema: {
|
|
511
|
+
* type: "object",
|
|
512
|
+
* properties: {
|
|
513
|
+
* name: { type: "string" },
|
|
514
|
+
* },
|
|
515
|
+
* required: ["name"],
|
|
516
|
+
* },
|
|
517
|
+
* };
|
|
518
|
+
* ```
|
|
519
|
+
*
|
|
520
|
+
* @see {@link SchemaRegistry} - Where schemas are registered and looked up
|
|
521
|
+
* @see {@link MigrationRule} - How values migrate between schema versions
|
|
522
|
+
* @see {@link JsonSchema} - The JSON Schema subset used for validation
|
|
523
|
+
*/
|
|
524
|
+
type KeySchema = {
|
|
525
|
+
/**
|
|
526
|
+
* The unprefixed storage key this schema applies to.
|
|
527
|
+
*/
|
|
528
|
+
key: string;
|
|
529
|
+
/**
|
|
530
|
+
* The version number for this schema.
|
|
531
|
+
*
|
|
532
|
+
* Must be a non-negative integer. Any version (including `0`) is valid.
|
|
533
|
+
*/
|
|
534
|
+
version: number;
|
|
535
|
+
/**
|
|
536
|
+
* JSON Schema that validates the payload at this version.
|
|
537
|
+
*
|
|
538
|
+
* Only the subset of JSON Schema keywords defined in {@link JsonSchema}
|
|
539
|
+
* are supported. An empty schema `{}` accepts any value.
|
|
540
|
+
*/
|
|
541
|
+
schema: JsonSchema;
|
|
542
|
+
};
|
|
543
|
+
/**
|
|
544
|
+
* A single migration step that transforms data from one schema version to
|
|
545
|
+
* another, or normalizes data at the same version.
|
|
546
|
+
*
|
|
547
|
+
* Migration rules are composed into a {@link MigrationPath} by the
|
|
548
|
+
* {@link SchemaRegistry} to upgrade stored data across multiple versions in
|
|
549
|
+
* sequence (e.g. v1 -> v2 -> v3).
|
|
550
|
+
*
|
|
551
|
+
* When `fromVersion === toVersion`, the rule is a **write-time normalizer**
|
|
552
|
+
* that runs on every write to that version. This is useful for data
|
|
553
|
+
* normalization (trimming strings, clamping values, injecting defaults).
|
|
554
|
+
*
|
|
555
|
+
* @example
|
|
556
|
+
* ```typescript
|
|
557
|
+
* // Version upgrade migration
|
|
558
|
+
* const userV1ToV2: MigrationRule = {
|
|
559
|
+
* key: "user",
|
|
560
|
+
* fromVersion: 1,
|
|
561
|
+
* toVersion: 2,
|
|
562
|
+
* migrate: (v1) => {
|
|
563
|
+
* const old = v1 as { name: string };
|
|
564
|
+
* return { firstName: old.name, lastName: "" };
|
|
565
|
+
* },
|
|
566
|
+
* };
|
|
567
|
+
*
|
|
568
|
+
* // Write-time normalizer (same version)
|
|
569
|
+
* const trimUserV2: MigrationRule = {
|
|
570
|
+
* key: "user",
|
|
571
|
+
* fromVersion: 2,
|
|
572
|
+
* toVersion: 2,
|
|
573
|
+
* migrate: (v) => {
|
|
574
|
+
* const user = v as { firstName: string; lastName: string };
|
|
575
|
+
* return { firstName: user.firstName.trim(), lastName: user.lastName.trim() };
|
|
576
|
+
* },
|
|
577
|
+
* };
|
|
578
|
+
* ```
|
|
579
|
+
*
|
|
580
|
+
* @see {@link MigrationPath} - Ordered list of rules applied in sequence
|
|
581
|
+
* @see {@link SchemaRegistry.getMigrationPath} - How the path is resolved
|
|
582
|
+
* @see {@link SchemaRegistry.getWriteMigration} - How write-time normalizers are resolved
|
|
583
|
+
*/
|
|
584
|
+
type MigrationRule = {
|
|
585
|
+
/**
|
|
586
|
+
* The unprefixed storage key this rule applies to.
|
|
587
|
+
*/
|
|
588
|
+
key: string;
|
|
589
|
+
/**
|
|
590
|
+
* The version the stored data is migrating **from**.
|
|
591
|
+
*
|
|
592
|
+
* Version `0` is allowed, enabling migrations from unversioned data.
|
|
593
|
+
*/
|
|
594
|
+
fromVersion: number;
|
|
595
|
+
/**
|
|
596
|
+
* The version the stored data is migrating **to**.
|
|
597
|
+
*
|
|
598
|
+
* When equal to `fromVersion`, this rule is a write-time normalizer
|
|
599
|
+
* that runs on every write to that version.
|
|
600
|
+
*/
|
|
601
|
+
toVersion: number;
|
|
602
|
+
/**
|
|
603
|
+
* Transformation function that converts data from `fromVersion`
|
|
604
|
+
* to `toVersion`.
|
|
605
|
+
*
|
|
606
|
+
* Receives the decoded value at `fromVersion` and must return
|
|
607
|
+
* the value in the shape expected by `toVersion`.
|
|
608
|
+
*
|
|
609
|
+
* @param value - The decoded value at `fromVersion`
|
|
610
|
+
* @returns The transformed value for `toVersion`
|
|
611
|
+
*/
|
|
612
|
+
migrate: (value: unknown) => unknown;
|
|
613
|
+
};
|
|
614
|
+
/**
|
|
615
|
+
* An ordered sequence of {@link MigrationRule} steps that upgrades stored
|
|
616
|
+
* data from an older schema version to a newer one.
|
|
617
|
+
*
|
|
618
|
+
* The rules are applied in array order. Each step's output becomes the
|
|
619
|
+
* next step's input. After the final step the result is validated against
|
|
620
|
+
* the target schema and persisted back to storage so the migration only
|
|
621
|
+
* runs once per key.
|
|
622
|
+
*
|
|
623
|
+
* @see {@link MigrationRule} - Individual migration step
|
|
624
|
+
* @see {@link SchemaRegistry.getMigrationPath} - Resolves a path between versions
|
|
625
|
+
*/
|
|
626
|
+
type MigrationPath = MigrationRule[];
|
|
627
|
+
/**
|
|
628
|
+
* Lookup and registration API for key schemas and migration paths.
|
|
629
|
+
*
|
|
630
|
+
* Implementations of this interface are passed to `MnemonicProvider` via the
|
|
631
|
+
* `schemaRegistry` option. The provider calls these methods at read and write
|
|
632
|
+
* time to resolve the correct JSON Schema and migration chain for each
|
|
633
|
+
* stored value.
|
|
634
|
+
*
|
|
635
|
+
* In `"default"` and `"strict"` modes, callers should treat registry contents
|
|
636
|
+
* as immutable after provider initialization. The hook caches lookups to keep
|
|
637
|
+
* read/write hot paths fast. `"autoschema"` remains mutable to support
|
|
638
|
+
* inferred schema registration.
|
|
639
|
+
*
|
|
640
|
+
* @example
|
|
641
|
+
* ```typescript
|
|
642
|
+
* const registry: SchemaRegistry = {
|
|
643
|
+
* getSchema: (key, version) => schemas.get(`${key}@${version}`),
|
|
644
|
+
* getLatestSchema: (key) => latestByKey.get(key),
|
|
645
|
+
* getMigrationPath: (key, from, to) => buildPath(key, from, to),
|
|
646
|
+
* getWriteMigration: (key, version) => normalizers.get(`${key}@${version}`),
|
|
647
|
+
* registerSchema: (schema) => { schemas.set(`${schema.key}@${schema.version}`, schema); },
|
|
648
|
+
* };
|
|
649
|
+
*
|
|
650
|
+
* <MnemonicProvider namespace="app" schemaRegistry={registry} schemaMode="strict">
|
|
651
|
+
* <App />
|
|
652
|
+
* </MnemonicProvider>
|
|
653
|
+
* ```
|
|
654
|
+
*
|
|
655
|
+
* @see {@link KeySchema} - Schema definition
|
|
656
|
+
* @see {@link MigrationPath} - Migration chain returned by `getMigrationPath`
|
|
657
|
+
* @see {@link SchemaMode} - How the provider uses the registry
|
|
658
|
+
*/
|
|
659
|
+
interface SchemaRegistry {
|
|
660
|
+
/**
|
|
661
|
+
* Look up the schema registered for a specific key and version.
|
|
662
|
+
*
|
|
663
|
+
* @param key - The unprefixed storage key
|
|
664
|
+
* @param version - The version number to look up
|
|
665
|
+
* @returns The matching schema, or `undefined` if none is registered
|
|
666
|
+
*/
|
|
667
|
+
getSchema(key: string, version: number): KeySchema | undefined;
|
|
668
|
+
/**
|
|
669
|
+
* Look up the highest-version schema registered for a key.
|
|
670
|
+
*
|
|
671
|
+
* Used by the write path to determine which version to stamp on new
|
|
672
|
+
* values, and by the read path to detect when a migration is needed.
|
|
673
|
+
*
|
|
674
|
+
* @param key - The unprefixed storage key
|
|
675
|
+
* @returns The latest schema, or `undefined` if none is registered
|
|
676
|
+
*/
|
|
677
|
+
getLatestSchema(key: string): KeySchema | undefined;
|
|
678
|
+
/**
|
|
679
|
+
* Resolve an ordered migration path between two versions of a key.
|
|
680
|
+
*
|
|
681
|
+
* Returns `null` when no contiguous path exists. The returned rules
|
|
682
|
+
* are applied in order to transform data from `fromVersion` to
|
|
683
|
+
* `toVersion`.
|
|
684
|
+
*
|
|
685
|
+
* @param key - The unprefixed storage key
|
|
686
|
+
* @param fromVersion - The stored data's current version
|
|
687
|
+
* @param toVersion - The target version to migrate to
|
|
688
|
+
* @returns An ordered array of migration rules, or `null`
|
|
689
|
+
*/
|
|
690
|
+
getMigrationPath(key: string, fromVersion: number, toVersion: number): MigrationPath | null;
|
|
691
|
+
/**
|
|
692
|
+
* Look up a write-time normalizer for a specific key and version.
|
|
693
|
+
*
|
|
694
|
+
* A write-time normalizer is a {@link MigrationRule} where
|
|
695
|
+
* `fromVersion === toVersion`. It runs on every write to that version,
|
|
696
|
+
* transforming the value before storage. The normalized value is
|
|
697
|
+
* re-validated against the schema after transformation.
|
|
698
|
+
*
|
|
699
|
+
* Optional. When not implemented or returns `undefined`, no write-time
|
|
700
|
+
* normalization is applied.
|
|
701
|
+
*
|
|
702
|
+
* @param key - The unprefixed storage key
|
|
703
|
+
* @param version - The target schema version
|
|
704
|
+
* @returns The normalizer rule, or `undefined` if none is registered
|
|
705
|
+
*/
|
|
706
|
+
getWriteMigration?(key: string, version: number): MigrationRule | undefined;
|
|
707
|
+
/**
|
|
708
|
+
* Register a new schema.
|
|
709
|
+
*
|
|
710
|
+
* Optional. Required when `schemaMode` is `"autoschema"` so the
|
|
711
|
+
* library can persist inferred schemas. Implementations should throw
|
|
712
|
+
* if a schema already exists for the same key + version with a
|
|
713
|
+
* conflicting definition.
|
|
714
|
+
*
|
|
715
|
+
* @param schema - The schema to register
|
|
716
|
+
*/
|
|
717
|
+
registerSchema?(schema: KeySchema): void;
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* Storage interface compatible with localStorage and custom storage implementations.
|
|
721
|
+
*
|
|
722
|
+
* Defines the minimum contract required for a storage backend. Compatible with
|
|
723
|
+
* browser Storage API (localStorage, sessionStorage) and custom implementations
|
|
724
|
+
* for testing or alternative storage solutions.
|
|
725
|
+
*
|
|
726
|
+
* @remarks
|
|
727
|
+
* **Error handling contract**
|
|
728
|
+
*
|
|
729
|
+
* The library wraps every storage call in a try/catch. Errors are handled as
|
|
730
|
+
* follows:
|
|
731
|
+
*
|
|
732
|
+
* - **`DOMException` with `name === "QuotaExceededError"`** — Logged once via
|
|
733
|
+
* `console.error` with the prefix `[Mnemonic] Storage quota exceeded`.
|
|
734
|
+
* Squelched until a write succeeds, then the flag resets.
|
|
735
|
+
*
|
|
736
|
+
* - **Other `DOMException` errors (including `SecurityError`)** — Logged once
|
|
737
|
+
* via `console.error` with the prefix `[Mnemonic] Storage access error`.
|
|
738
|
+
* Squelched until any storage operation succeeds, then the flag resets.
|
|
739
|
+
*
|
|
740
|
+
* - **All other error types** — Silently suppressed.
|
|
741
|
+
*
|
|
742
|
+
* Custom `StorageLike` implementations are encouraged to throw `DOMException`
|
|
743
|
+
* for storage access failures so the library can surface diagnostics. Throwing
|
|
744
|
+
* non-`DOMException` errors is safe but results in silent suppression.
|
|
745
|
+
*
|
|
746
|
+
* In all error cases the library falls back to its in-memory cache, so
|
|
747
|
+
* components continue to function when the storage backend is unavailable.
|
|
748
|
+
*
|
|
749
|
+
* @example
|
|
750
|
+
* ```typescript
|
|
751
|
+
* // In-memory storage for testing
|
|
752
|
+
* const mockStorage: StorageLike = {
|
|
753
|
+
* items: new Map<string, string>(),
|
|
754
|
+
* getItem(key) { return this.items.get(key) ?? null; },
|
|
755
|
+
* setItem(key, value) { this.items.set(key, value); },
|
|
756
|
+
* removeItem(key) { this.items.delete(key); },
|
|
757
|
+
* get length() { return this.items.size; },
|
|
758
|
+
* key(index) {
|
|
759
|
+
* return Array.from(this.items.keys())[index] ?? null;
|
|
760
|
+
* }
|
|
761
|
+
* };
|
|
762
|
+
* ```
|
|
763
|
+
*/
|
|
764
|
+
type StorageLike = {
|
|
765
|
+
/**
|
|
766
|
+
* Retrieves the value associated with a key.
|
|
767
|
+
*
|
|
768
|
+
* @param key - The storage key to retrieve
|
|
769
|
+
* @returns The stored value as a string, or null if not found
|
|
770
|
+
*/
|
|
771
|
+
getItem(key: string): string | null;
|
|
772
|
+
/**
|
|
773
|
+
* Stores a key-value pair.
|
|
774
|
+
*
|
|
775
|
+
* @param key - The storage key
|
|
776
|
+
* @param value - The string value to store
|
|
777
|
+
*/
|
|
778
|
+
setItem(key: string, value: string): void;
|
|
779
|
+
/**
|
|
780
|
+
* Removes a key-value pair from storage.
|
|
781
|
+
*
|
|
782
|
+
* @param key - The storage key to remove
|
|
783
|
+
*/
|
|
784
|
+
removeItem(key: string): void;
|
|
785
|
+
/**
|
|
786
|
+
* Returns the key at the specified index in storage.
|
|
787
|
+
*
|
|
788
|
+
* Optional method for enumeration support.
|
|
789
|
+
*
|
|
790
|
+
* @param index - The numeric index
|
|
791
|
+
* @returns The key at the given index, or null if out of bounds
|
|
792
|
+
*/
|
|
793
|
+
key?(index: number): string | null;
|
|
794
|
+
/**
|
|
795
|
+
* The number of items currently stored.
|
|
796
|
+
*
|
|
797
|
+
* Optional property for enumeration support.
|
|
798
|
+
*/
|
|
799
|
+
readonly length?: number;
|
|
800
|
+
/**
|
|
801
|
+
* Subscribe to notifications when data changes externally.
|
|
802
|
+
*
|
|
803
|
+
* localStorage has built-in cross-tab notification via the browser's
|
|
804
|
+
* native `storage` event (used by the `listenCrossTab` hook option).
|
|
805
|
+
* Non-localStorage backends (IndexedDB, custom stores, etc.) lack this
|
|
806
|
+
* built-in mechanism. Implementing `onExternalChange` allows those
|
|
807
|
+
* adapters to provide equivalent cross-tab synchronization through
|
|
808
|
+
* their own transport (e.g., BroadcastChannel).
|
|
809
|
+
*
|
|
810
|
+
* The callback accepts an optional `changedKeys` parameter:
|
|
811
|
+
* - `callback()` or `callback(undefined)` triggers a blanket reload
|
|
812
|
+
* of all actively subscribed keys.
|
|
813
|
+
* - `callback(["ns.key1", "ns.key2"])` reloads only the specified
|
|
814
|
+
* fully-qualified keys, which is more efficient when the adapter
|
|
815
|
+
* knows exactly which keys changed.
|
|
816
|
+
* - `callback([])` is a no-op.
|
|
817
|
+
*
|
|
818
|
+
* On a blanket reload the provider re-reads all actively subscribed
|
|
819
|
+
* keys from the storage backend and emits change notifications for
|
|
820
|
+
* any whose values differ from the cache.
|
|
821
|
+
*
|
|
822
|
+
* @param callback - Invoked when external data changes
|
|
823
|
+
* @returns An unsubscribe function that removes the callback
|
|
824
|
+
*/
|
|
825
|
+
onExternalChange?: (callback: (changedKeys?: string[]) => void) => () => void;
|
|
826
|
+
};
|
|
827
|
+
/**
|
|
828
|
+
* Configuration options for the useMnemonicKey hook.
|
|
829
|
+
*
|
|
830
|
+
* These options control how a value is persisted, decoded, and
|
|
831
|
+
* synchronized across the application.
|
|
832
|
+
*
|
|
833
|
+
* @template T - The TypeScript type of the stored value
|
|
834
|
+
*
|
|
835
|
+
* @example
|
|
836
|
+
* ```typescript
|
|
837
|
+
* const { value, set } = useMnemonicKey<User>('currentUser', {
|
|
838
|
+
* defaultValue: { name: 'Guest', id: null },
|
|
839
|
+
* onMount: (user) => console.log('Loaded user:', user),
|
|
840
|
+
* onChange: (current, previous) => {
|
|
841
|
+
* console.log('User changed from', previous, 'to', current);
|
|
842
|
+
* },
|
|
843
|
+
* listenCrossTab: true
|
|
844
|
+
* });
|
|
845
|
+
* ```
|
|
846
|
+
*/
|
|
847
|
+
type UseMnemonicKeyOptions<T> = {
|
|
848
|
+
/**
|
|
849
|
+
* Default value to use when no stored value exists, or when decoding/validation fails.
|
|
850
|
+
*
|
|
851
|
+
* Can be a literal value or a factory function that returns the default.
|
|
852
|
+
* Factory functions receive an optional error argument describing why the
|
|
853
|
+
* fallback is being used:
|
|
854
|
+
*
|
|
855
|
+
* - `undefined` — Nominal path: no value exists in storage for this key.
|
|
856
|
+
* - `CodecError` — The stored value could not be decoded by the codec.
|
|
857
|
+
* - `SchemaError` — A schema, migration, or validation issue occurred
|
|
858
|
+
* (e.g. missing schema, failed migration, JSON Schema validation failure).
|
|
859
|
+
*
|
|
860
|
+
* Static (non-function) default values ignore the error entirely.
|
|
861
|
+
*
|
|
862
|
+
* @remarks
|
|
863
|
+
* If a factory function is defined inline, it creates a new reference on
|
|
864
|
+
* every render, which forces internal memoization to recompute. For best
|
|
865
|
+
* performance, define the factory at module level or wrap it in `useCallback`:
|
|
866
|
+
*
|
|
867
|
+
* ```typescript
|
|
868
|
+
* // Module-level (stable reference, preferred)
|
|
869
|
+
* const getDefault = (error?: CodecError | SchemaError) => {
|
|
870
|
+
* if (error) console.warn('Fallback:', error.message);
|
|
871
|
+
* return { count: 0 };
|
|
872
|
+
* };
|
|
873
|
+
*
|
|
874
|
+
* // Or with useCallback inside a component
|
|
875
|
+
* const getDefault = useCallback(
|
|
876
|
+
* (error?: CodecError | SchemaError) => ({ count: 0 }),
|
|
877
|
+
* [],
|
|
878
|
+
* );
|
|
879
|
+
* ```
|
|
880
|
+
*
|
|
881
|
+
* @example
|
|
882
|
+
* ```typescript
|
|
883
|
+
* // Static default
|
|
884
|
+
* defaultValue: { count: 0 }
|
|
885
|
+
*
|
|
886
|
+
* // Factory with no error handling
|
|
887
|
+
* defaultValue: () => ({ timestamp: Date.now() })
|
|
888
|
+
*
|
|
889
|
+
* // Error-aware factory
|
|
890
|
+
* defaultValue: (error) => {
|
|
891
|
+
* if (error instanceof CodecError) {
|
|
892
|
+
* console.error('Corrupt data:', error.message);
|
|
893
|
+
* }
|
|
894
|
+
* if (error instanceof SchemaError) {
|
|
895
|
+
* console.warn('Schema issue:', error.code, error.message);
|
|
896
|
+
* }
|
|
897
|
+
* return { count: 0 };
|
|
898
|
+
* }
|
|
899
|
+
* ```
|
|
900
|
+
*/
|
|
901
|
+
defaultValue: T | ((error?: CodecError | SchemaError) => T);
|
|
902
|
+
/**
|
|
903
|
+
* Codec for encoding and decoding values to/from storage.
|
|
904
|
+
*
|
|
905
|
+
* Determines how the typed value is serialized to a string and
|
|
906
|
+
* deserialized back. Defaults to JSONCodec if not specified.
|
|
907
|
+
*
|
|
908
|
+
* Using a codec is a low-level option that bypasses JSON Schema
|
|
909
|
+
* validation. Schema-managed keys store JSON values directly and
|
|
910
|
+
* are validated against their registered JSON Schema.
|
|
911
|
+
*
|
|
912
|
+
* @default JSONCodec
|
|
913
|
+
*
|
|
914
|
+
* @example
|
|
915
|
+
* ```typescript
|
|
916
|
+
* // Custom codec for dates
|
|
917
|
+
* codec: createCodec(
|
|
918
|
+
* (date) => date.toISOString(),
|
|
919
|
+
* (str) => new Date(str)
|
|
920
|
+
* )
|
|
921
|
+
* ```
|
|
922
|
+
*/
|
|
923
|
+
codec?: Codec<T>;
|
|
924
|
+
/**
|
|
925
|
+
* Callback invoked once when the hook is first mounted.
|
|
926
|
+
*
|
|
927
|
+
* Receives the initial value (either from storage or the default).
|
|
928
|
+
* Useful for triggering side effects based on the loaded state.
|
|
929
|
+
*
|
|
930
|
+
* @param value - The initial value loaded on mount
|
|
931
|
+
*
|
|
932
|
+
* @example
|
|
933
|
+
* ```typescript
|
|
934
|
+
* onMount: (theme) => {
|
|
935
|
+
* document.body.className = theme;
|
|
936
|
+
* console.log('Theme loaded:', theme);
|
|
937
|
+
* }
|
|
938
|
+
* ```
|
|
939
|
+
*/
|
|
940
|
+
onMount?: (value: T) => void;
|
|
941
|
+
/**
|
|
942
|
+
* Callback invoked whenever the value changes.
|
|
943
|
+
*
|
|
944
|
+
* Receives both the new value and the previous value. This is called
|
|
945
|
+
* for all changes, including those triggered by other components or tabs.
|
|
946
|
+
*
|
|
947
|
+
* @param value - The new current value
|
|
948
|
+
* @param prev - The previous value
|
|
949
|
+
*
|
|
950
|
+
* @example
|
|
951
|
+
* ```typescript
|
|
952
|
+
* onChange: (newTheme, oldTheme) => {
|
|
953
|
+
* document.body.classList.remove(oldTheme);
|
|
954
|
+
* document.body.classList.add(newTheme);
|
|
955
|
+
* console.log(`Theme changed: ${oldTheme} -> ${newTheme}`);
|
|
956
|
+
* }
|
|
957
|
+
* ```
|
|
958
|
+
*/
|
|
959
|
+
onChange?: (value: T, prev: T) => void;
|
|
960
|
+
/**
|
|
961
|
+
* Enable listening for changes from other browser tabs.
|
|
962
|
+
*
|
|
963
|
+
* When true, uses the browser's `storage` event to detect changes
|
|
964
|
+
* made to localStorage in other tabs and synchronizes them to this component.
|
|
965
|
+
*
|
|
966
|
+
* Only effective when using localStorage as the storage backend.
|
|
967
|
+
*
|
|
968
|
+
* @default false
|
|
969
|
+
*
|
|
970
|
+
* @example
|
|
971
|
+
* ```typescript
|
|
972
|
+
* // Enable cross-tab sync for shared state
|
|
973
|
+
* listenCrossTab: true
|
|
974
|
+
* ```
|
|
975
|
+
*
|
|
976
|
+
* @remarks
|
|
977
|
+
* The `storage` event only fires for changes made in *other* tabs,
|
|
978
|
+
* not the current tab. Changes within the same tab are synchronized
|
|
979
|
+
* automatically via React's state management.
|
|
980
|
+
*/
|
|
981
|
+
listenCrossTab?: boolean;
|
|
982
|
+
/**
|
|
983
|
+
* Optional schema controls for this key.
|
|
984
|
+
*
|
|
985
|
+
* Allows overriding the version written by the `set` function. When
|
|
986
|
+
* omitted, the library writes using the highest registered schema for
|
|
987
|
+
* this key, or version `0` when no schemas are registered.
|
|
988
|
+
*
|
|
989
|
+
* @example
|
|
990
|
+
* ```typescript
|
|
991
|
+
* // Pin writes to schema version 2 even if version 3 exists
|
|
992
|
+
* const { value, set } = useMnemonicKey("user", {
|
|
993
|
+
* defaultValue: { name: "" },
|
|
994
|
+
* schema: { version: 2 },
|
|
995
|
+
* });
|
|
996
|
+
* ```
|
|
997
|
+
*/
|
|
998
|
+
schema?: {
|
|
999
|
+
/**
|
|
1000
|
+
* Explicit schema version to use when writing values.
|
|
1001
|
+
*
|
|
1002
|
+
* When set, the `set` and `reset` functions encode using the
|
|
1003
|
+
* schema registered at this version instead of the latest. Useful
|
|
1004
|
+
* during gradual rollouts where not all consumers have been
|
|
1005
|
+
* updated yet.
|
|
1006
|
+
*
|
|
1007
|
+
* Must reference a version that exists in the `SchemaRegistry`.
|
|
1008
|
+
* If not found the write falls back to the latest schema (default
|
|
1009
|
+
* mode) or fails with a `SchemaError` (strict mode).
|
|
1010
|
+
*/
|
|
1011
|
+
version?: number;
|
|
1012
|
+
};
|
|
1013
|
+
};
|
|
1014
|
+
|
|
1015
|
+
/**
|
|
1016
|
+
* Props for the MnemonicProvider component.
|
|
1017
|
+
*
|
|
1018
|
+
* Extends MnemonicProviderOptions with required children prop.
|
|
1019
|
+
*
|
|
1020
|
+
* @see {@link MnemonicProviderOptions} - Configuration options
|
|
1021
|
+
* @see {@link MnemonicProvider} - Provider component
|
|
1022
|
+
*/
|
|
1023
|
+
interface MnemonicProviderProps extends MnemonicProviderOptions {
|
|
1024
|
+
/**
|
|
1025
|
+
* React children to render within the provider.
|
|
1026
|
+
*/
|
|
1027
|
+
children: ReactNode;
|
|
1028
|
+
}
|
|
1029
|
+
/**
|
|
1030
|
+
* React Context provider for namespace-isolated persistent state.
|
|
1031
|
+
*
|
|
1032
|
+
* Creates a scoped storage environment where all keys are automatically prefixed
|
|
1033
|
+
* with the namespace to prevent collisions. Implements an in-memory cache with
|
|
1034
|
+
* read-through behavior to the underlying storage backend (localStorage by default).
|
|
1035
|
+
*
|
|
1036
|
+
* This provider must wrap any components that use `useMnemonicKey`. Multiple
|
|
1037
|
+
* providers with different namespaces can coexist in the same application.
|
|
1038
|
+
*
|
|
1039
|
+
* @param props - Provider configuration and children
|
|
1040
|
+
* @param props.children - React children to render within the provider
|
|
1041
|
+
* @param props.namespace - Unique namespace for isolating storage keys
|
|
1042
|
+
* @param props.storage - Optional custom storage backend (defaults to localStorage)
|
|
1043
|
+
* @param props.enableDevTools - Enable DevTools debugging interface (defaults to false)
|
|
1044
|
+
* @param props.schemaMode - Schema enforcement mode (default: "default")
|
|
1045
|
+
* @param props.schemaRegistry - Optional schema registry for storing schemas and migrations
|
|
1046
|
+
*
|
|
1047
|
+
* @example
|
|
1048
|
+
* ```tsx
|
|
1049
|
+
* // Basic usage with default settings
|
|
1050
|
+
* function App() {
|
|
1051
|
+
* return (
|
|
1052
|
+
* <MnemonicProvider namespace="myApp">
|
|
1053
|
+
* <MyComponents />
|
|
1054
|
+
* </MnemonicProvider>
|
|
1055
|
+
* );
|
|
1056
|
+
* }
|
|
1057
|
+
* ```
|
|
1058
|
+
*
|
|
1059
|
+
* @example
|
|
1060
|
+
* ```tsx
|
|
1061
|
+
* // With custom storage backend
|
|
1062
|
+
* function App() {
|
|
1063
|
+
* return (
|
|
1064
|
+
* <MnemonicProvider
|
|
1065
|
+
* namespace="myApp"
|
|
1066
|
+
* storage={window.sessionStorage}
|
|
1067
|
+
* >
|
|
1068
|
+
* <MyComponents />
|
|
1069
|
+
* </MnemonicProvider>
|
|
1070
|
+
* );
|
|
1071
|
+
* }
|
|
1072
|
+
* ```
|
|
1073
|
+
*
|
|
1074
|
+
* @example
|
|
1075
|
+
* ```tsx
|
|
1076
|
+
* // With DevTools enabled (development only)
|
|
1077
|
+
* function App() {
|
|
1078
|
+
* return (
|
|
1079
|
+
* <MnemonicProvider
|
|
1080
|
+
* namespace="myApp"
|
|
1081
|
+
* enableDevTools={process.env.NODE_ENV === 'development'}
|
|
1082
|
+
* >
|
|
1083
|
+
* <MyComponents />
|
|
1084
|
+
* </MnemonicProvider>
|
|
1085
|
+
* );
|
|
1086
|
+
* }
|
|
1087
|
+
*
|
|
1088
|
+
* // Then in browser console:
|
|
1089
|
+
* window.__REACT_MNEMONIC_DEVTOOLS__.myApp.dump()
|
|
1090
|
+
* window.__REACT_MNEMONIC_DEVTOOLS__.myApp.get('user')
|
|
1091
|
+
* window.__REACT_MNEMONIC_DEVTOOLS__.myApp.set('theme', 'dark')
|
|
1092
|
+
* ```
|
|
1093
|
+
*
|
|
1094
|
+
* @example
|
|
1095
|
+
* ```tsx
|
|
1096
|
+
* // Multiple providers with different namespaces
|
|
1097
|
+
* function App() {
|
|
1098
|
+
* return (
|
|
1099
|
+
* <MnemonicProvider namespace="user-prefs">
|
|
1100
|
+
* <UserSettings />
|
|
1101
|
+
* <MnemonicProvider namespace="app-state">
|
|
1102
|
+
* <Dashboard />
|
|
1103
|
+
* </MnemonicProvider>
|
|
1104
|
+
* </MnemonicProvider>
|
|
1105
|
+
* );
|
|
1106
|
+
* }
|
|
1107
|
+
* ```
|
|
1108
|
+
*
|
|
1109
|
+
* @remarks
|
|
1110
|
+
* - Creates a stable store instance that only recreates when namespace, storage, or enableDevTools change
|
|
1111
|
+
* - All storage operations are cached in memory for fast reads
|
|
1112
|
+
* - Storage failures are handled gracefully (logged but not thrown)
|
|
1113
|
+
* - In SSR environments, the provider works but no storage persistence occurs
|
|
1114
|
+
* - The store implements React's useSyncExternalStore contract for efficient updates
|
|
1115
|
+
*
|
|
1116
|
+
* @see {@link useMnemonicKey} - Hook for using persistent state
|
|
1117
|
+
* @see {@link MnemonicProviderOptions} - Configuration options
|
|
1118
|
+
*/
|
|
1119
|
+
declare function MnemonicProvider({ children, namespace, storage, enableDevTools, schemaMode, schemaRegistry, }: MnemonicProviderProps): react_jsx_runtime.JSX.Element;
|
|
1120
|
+
|
|
1121
|
+
/**
|
|
1122
|
+
* React hook for persistent, type-safe state management.
|
|
1123
|
+
*
|
|
1124
|
+
* Creates a stateful value that persists to storage and synchronizes across
|
|
1125
|
+
* components. Works like `useState` but with persistent storage, automatic
|
|
1126
|
+
* encoding/decoding, JSON Schema validation, and optional cross-tab synchronization.
|
|
1127
|
+
*
|
|
1128
|
+
* Must be used within a `MnemonicProvider`. Uses React's `useSyncExternalStore`
|
|
1129
|
+
* internally for efficient, tearing-free state synchronization.
|
|
1130
|
+
*
|
|
1131
|
+
* @template T - The TypeScript type of the stored value
|
|
1132
|
+
*
|
|
1133
|
+
* @param key - The storage key (unprefixed, namespace is applied automatically)
|
|
1134
|
+
* @param options - Configuration options controlling persistence, encoding, and behavior
|
|
1135
|
+
*
|
|
1136
|
+
* @returns Object with the current value and methods to update it
|
|
1137
|
+
*
|
|
1138
|
+
* @throws {Error} If used outside of a MnemonicProvider
|
|
1139
|
+
*/
|
|
1140
|
+
declare function useMnemonicKey<T>(key: string, options: UseMnemonicKeyOptions<T>): {
|
|
1141
|
+
/** Current decoded value, or the default when the key is absent or invalid. */
|
|
1142
|
+
value: T;
|
|
1143
|
+
/** Persist a new value (direct or updater function). */
|
|
1144
|
+
set: (next: T | ((cur: T) => T)) => void;
|
|
1145
|
+
/** Reset to `defaultValue` and persist it. */
|
|
1146
|
+
reset: () => void;
|
|
1147
|
+
/** Delete the key from storage entirely. */
|
|
1148
|
+
remove: () => void;
|
|
1149
|
+
};
|
|
1150
|
+
|
|
1151
|
+
export { type Codec, CodecError, type CompiledValidator, JSONCodec, type JsonSchema, type JsonSchemaType, type JsonSchemaValidationError, type KeySchema, type MigrationPath, type MigrationRule, MnemonicProvider, type MnemonicProviderOptions, type MnemonicProviderProps, SchemaError, type SchemaMode, type SchemaRegistry, type StorageLike, type UseMnemonicKeyOptions, compileSchema, createCodec, useMnemonicKey, validateJsonSchema };
|