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.
@@ -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 };