react-mnemonic 1.2.0-beta1 → 1.3.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/dist/core.cjs CHANGED
@@ -5,6 +5,415 @@ var jsxRuntime = require('react/jsx-runtime');
5
5
 
6
6
  // src/Mnemonic/provider.tsx
7
7
 
8
+ // src/Mnemonic/codecs.ts
9
+ var CodecError = class extends Error {
10
+ /**
11
+ * Creates a new CodecError.
12
+ *
13
+ * @param message - Human-readable error description
14
+ * @param cause - Optional underlying error that caused this failure
15
+ */
16
+ constructor(message, cause) {
17
+ super(message);
18
+ this.name = "CodecError";
19
+ this.cause = cause;
20
+ Object.setPrototypeOf(this, new.target.prototype);
21
+ }
22
+ };
23
+ var JSONCodec = {
24
+ encode: (value) => JSON.stringify(value),
25
+ decode: (encoded) => JSON.parse(encoded)
26
+ };
27
+ function createCodec(encode, decode) {
28
+ return { encode, decode };
29
+ }
30
+
31
+ // src/Mnemonic/json-schema.ts
32
+ function matchesType(value, type) {
33
+ switch (type) {
34
+ case "string":
35
+ return typeof value === "string";
36
+ case "number":
37
+ return typeof value === "number" && Number.isFinite(value);
38
+ case "integer":
39
+ return typeof value === "number" && Number.isInteger(value);
40
+ case "boolean":
41
+ return typeof value === "boolean";
42
+ case "null":
43
+ return value === null;
44
+ case "object":
45
+ return typeof value === "object" && value !== null && !Array.isArray(value);
46
+ case "array":
47
+ return Array.isArray(value);
48
+ default:
49
+ return false;
50
+ }
51
+ }
52
+ function jsonDeepEqualArray(a, b) {
53
+ if (a.length !== b.length) return false;
54
+ for (let i = 0; i < a.length; i++) {
55
+ if (!jsonDeepEqual(a[i], b[i])) return false;
56
+ }
57
+ return true;
58
+ }
59
+ function jsonDeepEqualObject(a, b) {
60
+ const aKeys = Object.keys(a);
61
+ const bKeys = Object.keys(b);
62
+ if (aKeys.length !== bKeys.length) return false;
63
+ for (const key of aKeys) {
64
+ if (!objectHasOwn(b, key)) return false;
65
+ if (!jsonDeepEqual(a[key], b[key])) return false;
66
+ }
67
+ return true;
68
+ }
69
+ function jsonDeepEqual(a, b) {
70
+ if (a === b) return true;
71
+ if (a === null || b === null) return false;
72
+ if (typeof a !== typeof b) return false;
73
+ if (Array.isArray(a)) {
74
+ if (!Array.isArray(b)) return false;
75
+ return jsonDeepEqualArray(a, b);
76
+ }
77
+ if (typeof a === "object") {
78
+ if (Array.isArray(b)) return false;
79
+ return jsonDeepEqualObject(a, b);
80
+ }
81
+ return false;
82
+ }
83
+ var compiledCache = /* @__PURE__ */ new WeakMap();
84
+ function compileSchema(schema) {
85
+ const cached = compiledCache.get(schema);
86
+ if (cached) return cached;
87
+ const compiled = buildValidator(schema);
88
+ compiledCache.set(schema, compiled);
89
+ return compiled;
90
+ }
91
+ function isJsonPrimitive(value) {
92
+ return value === null || typeof value !== "object";
93
+ }
94
+ function isJsonObjectRecord(value) {
95
+ return typeof value === "object" && value !== null && !Array.isArray(value);
96
+ }
97
+ function objectHasOwn(value, property) {
98
+ const hasOwn = Object.hasOwn;
99
+ if (typeof hasOwn === "function") {
100
+ return hasOwn(value, property);
101
+ }
102
+ return Object.getOwnPropertyDescriptor(value, property) !== void 0;
103
+ }
104
+ function buildValidator(schema) {
105
+ const typeStep = buildTypeValidationStep(schema);
106
+ const validationSteps = [
107
+ buildEnumValidationStep(schema),
108
+ buildConstValidationStep(schema),
109
+ buildNumberValidationStep(schema),
110
+ buildStringValidationStep(schema),
111
+ buildObjectValidationStep(schema),
112
+ buildArrayValidationStep(schema)
113
+ ].filter((step) => step !== null);
114
+ if (typeStep === null && validationSteps.length === 0) {
115
+ return (_value, _path) => [];
116
+ }
117
+ return (value, path = "") => {
118
+ const errors = [];
119
+ if (typeStep && !typeStep(value, path, errors)) {
120
+ return errors;
121
+ }
122
+ for (const step of validationSteps) {
123
+ step(value, path, errors);
124
+ }
125
+ return errors;
126
+ };
127
+ }
128
+ function buildTypeValidationStep(schema) {
129
+ if (schema.type === void 0) {
130
+ return null;
131
+ }
132
+ const resolvedTypes = Array.isArray(schema.type) ? schema.type : [schema.type];
133
+ const typeLabel = JSON.stringify(schema.type);
134
+ return (value, path, errors) => {
135
+ if (resolvedTypes.some((type) => matchesType(value, type))) {
136
+ return true;
137
+ }
138
+ errors.push({
139
+ path,
140
+ message: `Expected type ${typeLabel}, got ${jsonTypeLabel(value)}`,
141
+ keyword: "type"
142
+ });
143
+ return false;
144
+ };
145
+ }
146
+ function buildEnumValidationStep(schema) {
147
+ if (schema.enum === void 0) {
148
+ return null;
149
+ }
150
+ const enumPrimitiveSet = new Set(schema.enum.filter((member) => isJsonPrimitive(member)));
151
+ const enumComplexMembers = schema.enum.filter((member) => !isJsonPrimitive(member));
152
+ return (value, path, errors) => {
153
+ const primitiveMatch = isJsonPrimitive(value) && enumPrimitiveSet.has(value);
154
+ const complexMatch = !primitiveMatch && enumComplexMembers.some((entry) => jsonDeepEqual(value, entry));
155
+ if (primitiveMatch || complexMatch) {
156
+ return;
157
+ }
158
+ errors.push({
159
+ path,
160
+ message: `Value does not match any enum member`,
161
+ keyword: "enum"
162
+ });
163
+ };
164
+ }
165
+ function buildConstValidationStep(schema) {
166
+ if (!("const" in schema)) {
167
+ return null;
168
+ }
169
+ return (value, path, errors) => {
170
+ if (jsonDeepEqual(value, schema.const)) {
171
+ return;
172
+ }
173
+ errors.push({
174
+ path,
175
+ message: `Value does not match const`,
176
+ keyword: "const"
177
+ });
178
+ };
179
+ }
180
+ function buildNumberValidationStep(schema) {
181
+ const hasMinimum = schema.minimum !== void 0;
182
+ const hasMaximum = schema.maximum !== void 0;
183
+ const hasExMin = schema.exclusiveMinimum !== void 0;
184
+ const hasExMax = schema.exclusiveMaximum !== void 0;
185
+ if (!hasMinimum && !hasMaximum && !hasExMin && !hasExMax) {
186
+ return null;
187
+ }
188
+ const minimum = schema.minimum;
189
+ const maximum = schema.maximum;
190
+ const exMin = schema.exclusiveMinimum;
191
+ const exMax = schema.exclusiveMaximum;
192
+ return (value, path, errors) => {
193
+ if (typeof value !== "number") {
194
+ return;
195
+ }
196
+ if (hasMinimum && value < minimum) {
197
+ errors.push({
198
+ path,
199
+ message: `Value ${value} is less than minimum ${minimum}`,
200
+ keyword: "minimum"
201
+ });
202
+ }
203
+ if (hasMaximum && value > maximum) {
204
+ errors.push({
205
+ path,
206
+ message: `Value ${value} is greater than maximum ${maximum}`,
207
+ keyword: "maximum"
208
+ });
209
+ }
210
+ if (hasExMin && value <= exMin) {
211
+ errors.push({
212
+ path,
213
+ message: `Value ${value} is not greater than exclusiveMinimum ${exMin}`,
214
+ keyword: "exclusiveMinimum"
215
+ });
216
+ }
217
+ if (hasExMax && value >= exMax) {
218
+ errors.push({
219
+ path,
220
+ message: `Value ${value} is not less than exclusiveMaximum ${exMax}`,
221
+ keyword: "exclusiveMaximum"
222
+ });
223
+ }
224
+ };
225
+ }
226
+ function buildStringValidationStep(schema) {
227
+ const hasMinLength = schema.minLength !== void 0;
228
+ const hasMaxLength = schema.maxLength !== void 0;
229
+ if (!hasMinLength && !hasMaxLength) {
230
+ return null;
231
+ }
232
+ const minLength = schema.minLength;
233
+ const maxLength = schema.maxLength;
234
+ return (value, path, errors) => {
235
+ if (typeof value !== "string") {
236
+ return;
237
+ }
238
+ if (hasMinLength && value.length < minLength) {
239
+ errors.push({
240
+ path,
241
+ message: `String length ${value.length} is less than minLength ${minLength}`,
242
+ keyword: "minLength"
243
+ });
244
+ }
245
+ if (hasMaxLength && value.length > maxLength) {
246
+ errors.push({
247
+ path,
248
+ message: `String length ${value.length} is greater than maxLength ${maxLength}`,
249
+ keyword: "maxLength"
250
+ });
251
+ }
252
+ };
253
+ }
254
+ function buildObjectValidationStep(schema) {
255
+ const requiredKeys = schema.required ?? [];
256
+ const propertyValidators = schema.properties ? Object.entries(schema.properties).map(([name, propertySchema]) => [
257
+ name,
258
+ compileSchema(propertySchema)
259
+ ]) : null;
260
+ const checkAdditional = schema.additionalProperties !== void 0 && schema.additionalProperties !== true;
261
+ const additionalIsFalse = schema.additionalProperties === false;
262
+ const additionalValidator = checkAdditional && !additionalIsFalse ? compileSchema(schema.additionalProperties) : null;
263
+ const definedPropKeys = checkAdditional ? new Set(Object.keys(schema.properties ?? {})) : null;
264
+ const objectValidationSteps = [];
265
+ if (requiredKeys.length > 0) {
266
+ objectValidationSteps.push(createRequiredPropertyStep(requiredKeys));
267
+ }
268
+ if (propertyValidators !== null) {
269
+ objectValidationSteps.push(createDeclaredPropertyStep(propertyValidators));
270
+ }
271
+ if (checkAdditional) {
272
+ objectValidationSteps.push(
273
+ createAdditionalPropertyStep({
274
+ additionalIsFalse,
275
+ additionalValidator,
276
+ definedPropKeys: definedPropKeys ?? /* @__PURE__ */ new Set()
277
+ })
278
+ );
279
+ }
280
+ if (objectValidationSteps.length === 0) {
281
+ return null;
282
+ }
283
+ return (value, path, errors) => {
284
+ if (!isJsonObjectRecord(value)) {
285
+ return;
286
+ }
287
+ for (const step of objectValidationSteps) {
288
+ step(value, path, errors);
289
+ }
290
+ };
291
+ }
292
+ function createRequiredPropertyStep(requiredKeys) {
293
+ return (value, path, errors) => {
294
+ for (const requiredKey of requiredKeys) {
295
+ if (objectHasOwn(value, requiredKey)) {
296
+ continue;
297
+ }
298
+ errors.push({
299
+ path,
300
+ message: `Missing required property "${requiredKey}"`,
301
+ keyword: "required"
302
+ });
303
+ }
304
+ };
305
+ }
306
+ function createDeclaredPropertyStep(propertyValidators) {
307
+ return (value, path, errors) => {
308
+ for (const [propertyName, validator] of propertyValidators) {
309
+ if (!objectHasOwn(value, propertyName)) {
310
+ continue;
311
+ }
312
+ errors.push(...validator(value[propertyName], `${path}/${propertyName}`));
313
+ }
314
+ };
315
+ }
316
+ function createAdditionalPropertyStep({
317
+ additionalIsFalse,
318
+ additionalValidator,
319
+ definedPropKeys
320
+ }) {
321
+ return (value, path, errors) => {
322
+ for (const objectKey of Object.keys(value)) {
323
+ if (definedPropKeys.has(objectKey)) {
324
+ continue;
325
+ }
326
+ if (additionalIsFalse) {
327
+ errors.push({
328
+ path,
329
+ message: `Additional property "${objectKey}" is not allowed`,
330
+ keyword: "additionalProperties"
331
+ });
332
+ continue;
333
+ }
334
+ errors.push(...additionalValidator(value[objectKey], `${path}/${objectKey}`));
335
+ }
336
+ };
337
+ }
338
+ function buildArrayValidationStep(schema) {
339
+ const hasMinItems = schema.minItems !== void 0;
340
+ const hasMaxItems = schema.maxItems !== void 0;
341
+ const itemsValidator = schema.items ? compileSchema(schema.items) : null;
342
+ if (!hasMinItems && !hasMaxItems && itemsValidator === null) {
343
+ return null;
344
+ }
345
+ const minItems = schema.minItems;
346
+ const maxItems = schema.maxItems;
347
+ return (value, path, errors) => {
348
+ if (!Array.isArray(value)) {
349
+ return;
350
+ }
351
+ if (hasMinItems && value.length < minItems) {
352
+ errors.push({
353
+ path,
354
+ message: `Array length ${value.length} is less than minItems ${minItems}`,
355
+ keyword: "minItems"
356
+ });
357
+ }
358
+ if (hasMaxItems && value.length > maxItems) {
359
+ errors.push({
360
+ path,
361
+ message: `Array length ${value.length} is greater than maxItems ${maxItems}`,
362
+ keyword: "maxItems"
363
+ });
364
+ }
365
+ if (itemsValidator === null) {
366
+ return;
367
+ }
368
+ for (const [index, item] of value.entries()) {
369
+ errors.push(...itemsValidator(item, `${path}/${index}`));
370
+ }
371
+ };
372
+ }
373
+ function validateJsonSchema(value, schema, path = "") {
374
+ const compiled = compileSchema(schema);
375
+ return compiled(value, path);
376
+ }
377
+ function jsonTypeLabel(value) {
378
+ if (value === null) return "null";
379
+ if (Array.isArray(value)) return "array";
380
+ return typeof value;
381
+ }
382
+ function inferJsonSchema(sample) {
383
+ if (sample === null) return { type: "null" };
384
+ if (Array.isArray(sample)) return { type: "array" };
385
+ switch (typeof sample) {
386
+ case "string":
387
+ return { type: "string" };
388
+ case "number":
389
+ return { type: "number" };
390
+ case "boolean":
391
+ return { type: "boolean" };
392
+ case "object":
393
+ return { type: "object" };
394
+ default:
395
+ return {};
396
+ }
397
+ }
398
+
399
+ // src/Mnemonic/schema.ts
400
+ var SchemaError = class extends Error {
401
+ /**
402
+ * Creates a new SchemaError.
403
+ *
404
+ * @param code - Machine-readable failure category
405
+ * @param message - Human-readable error description
406
+ * @param cause - Optional underlying error
407
+ */
408
+ constructor(code, message, cause) {
409
+ super(message);
410
+ this.name = "SchemaError";
411
+ this.code = code;
412
+ this.cause = cause;
413
+ Object.setPrototypeOf(this, new.target.prototype);
414
+ }
415
+ };
416
+
8
417
  // src/Mnemonic/runtime.ts
9
418
  function getGlobalProcess() {
10
419
  return globalThis.process;
@@ -30,1120 +439,1316 @@ function getNativeBrowserStorages() {
30
439
  addStorage(() => globalWindow.sessionStorage);
31
440
  return storages;
32
441
  }
33
- var MnemonicContext = react.createContext(null);
34
- function useMnemonic() {
35
- const context = react.useContext(MnemonicContext);
36
- if (!context) {
37
- throw new Error("useMnemonic must be used within a MnemonicProvider");
38
- }
39
- return context;
442
+
443
+ // src/Mnemonic/use-shared.ts
444
+ var SSR_SNAPSHOT_TOKEN = /* @__PURE__ */ Symbol("mnemonic:ssr-snapshot");
445
+ var diagnosticContractRegistry = /* @__PURE__ */ new WeakMap();
446
+ var diagnosticWarningRegistry = /* @__PURE__ */ new WeakMap();
447
+ var diagnosticObjectIds = /* @__PURE__ */ new WeakMap();
448
+ var nextDiagnosticObjectId = 1;
449
+ function serializeEnvelope(version, payload) {
450
+ return JSON.stringify({
451
+ version,
452
+ payload
453
+ });
40
454
  }
41
- function defaultBrowserStorage() {
42
- const globalWindow = globalThis.window;
43
- if (globalWindow === void 0) return void 0;
44
- try {
45
- return globalWindow.localStorage;
46
- } catch {
47
- return void 0;
455
+ function withReadMetadata(value, rewriteRaw, extra) {
456
+ const result = { value };
457
+ if (extra !== void 0) {
458
+ Object.assign(result, extra);
48
459
  }
460
+ if (rewriteRaw !== void 0) result.rewriteRaw = rewriteRaw;
461
+ return result;
49
462
  }
50
- function detectEnumerableStorage(storage) {
51
- if (!storage) return false;
52
- try {
53
- return typeof storage.length === "number" && typeof storage.key === "function";
54
- } catch {
55
- return false;
56
- }
463
+ function isDevelopmentRuntime() {
464
+ return getRuntimeNodeEnv() === "development";
57
465
  }
58
- function isProductionRuntime() {
59
- const env = getRuntimeNodeEnv();
60
- if (env === void 0) {
61
- return true;
466
+ function getDiagnosticWarnings(api) {
467
+ let warnings = diagnosticWarningRegistry.get(api);
468
+ if (!warnings) {
469
+ warnings = /* @__PURE__ */ new Set();
470
+ diagnosticWarningRegistry.set(api, warnings);
62
471
  }
63
- return env === "production";
64
- }
65
- function weakRefConstructor() {
66
- const ctor = globalThis.WeakRef;
67
- return typeof ctor === "function" ? ctor : null;
68
- }
69
- function hasFinalizationRegistry() {
70
- return typeof globalThis.FinalizationRegistry === "function";
472
+ return warnings;
71
473
  }
72
- function isPromiseLike(value) {
73
- if (value == null) return false;
74
- if (typeof value !== "object" && typeof value !== "function") return false;
75
- return typeof value.then === "function";
474
+ function warnOnce(api, id, message) {
475
+ const warnings = getDiagnosticWarnings(api);
476
+ if (warnings.has(id)) return;
477
+ warnings.add(id);
478
+ console.warn(message);
76
479
  }
77
- function getCrossTabSyncMode(requestedStorage, activeStorage) {
78
- const isExplicitNativeBrowserStorage = activeStorage !== void 0 && requestedStorage !== void 0 && getNativeBrowserStorages().includes(activeStorage);
79
- if (requestedStorage === void 0 && activeStorage !== void 0 || isExplicitNativeBrowserStorage) {
80
- return "browser-storage-event";
480
+ function stableDiagnosticValue(value) {
481
+ if (typeof value === "function") {
482
+ const source = Function.prototype.toString.call(value).split(/\s+/).join(" ").trim();
483
+ const name = value.name || "anonymous";
484
+ return `[factory:${name}/${value.length}:${source}]`;
81
485
  }
82
- if (typeof activeStorage?.onExternalChange === "function") {
83
- return "custom-external-change";
486
+ if (typeof value === "bigint") return `${value.toString()}n`;
487
+ if (typeof value === "symbol") return value.toString();
488
+ if (value === void 0) return "undefined";
489
+ try {
490
+ return JSON.stringify(value);
491
+ } catch {
492
+ const tag = Object.prototype.toString.call(value);
493
+ if (value !== null && (typeof value === "object" || typeof value === "function")) {
494
+ return `${tag}#${getDiagnosticObjectId(value)}`;
495
+ }
496
+ return tag;
84
497
  }
85
- return "none";
86
498
  }
87
- function getDevToolsWindow() {
88
- return globalThis.window;
499
+ function isObjectLike(value) {
500
+ return value !== null && (typeof value === "object" || typeof value === "function");
89
501
  }
90
- function sanitizeDevToolsRoot(root) {
91
- const reserved = /* @__PURE__ */ new Set(["providers", "resolve", "list", "capabilities", "__meta"]);
92
- for (const key of Object.keys(root)) {
93
- if (reserved.has(key)) continue;
94
- const descriptor = Object.getOwnPropertyDescriptor(root, key);
95
- if (descriptor && !descriptor.configurable) continue;
96
- try {
97
- delete root[key];
98
- } catch {
99
- }
100
- }
101
- }
102
- function ensureDevToolsRoot(enableDevTools) {
103
- if (!enableDevTools) return null;
104
- const globalWindow = getDevToolsWindow();
105
- if (!globalWindow) return null;
106
- const weakRefSupported = weakRefConstructor() !== null;
107
- const finalizationRegistrySupported = hasFinalizationRegistry();
108
- const existing = globalWindow.__REACT_MNEMONIC_DEVTOOLS__;
109
- const root = existing && typeof existing === "object" ? existing : {};
110
- sanitizeDevToolsRoot(root);
111
- if (!root.providers || typeof root.providers !== "object") {
112
- root.providers = {};
113
- }
114
- if (!root.capabilities || typeof root.capabilities !== "object") {
115
- root.capabilities = {};
116
- }
117
- const capabilities = root.capabilities;
118
- capabilities.weakRef = weakRefSupported;
119
- capabilities.finalizationRegistry = finalizationRegistrySupported;
120
- if (!root.__meta || typeof root.__meta !== "object") {
121
- root.__meta = {
122
- version: 0,
123
- lastUpdated: Date.now(),
124
- lastChange: ""
125
- };
126
- }
127
- const meta = root.__meta;
128
- if (typeof meta.version !== "number" || !Number.isFinite(meta.version)) {
129
- meta.version = 0;
130
- }
131
- if (typeof meta.lastUpdated !== "number" || !Number.isFinite(meta.lastUpdated)) {
132
- meta.lastUpdated = Date.now();
133
- }
134
- if (typeof meta.lastChange !== "string") {
135
- meta.lastChange = "";
136
- }
137
- const providers = root.providers;
138
- if (typeof root.resolve !== "function") {
139
- root.resolve = (namespace) => {
140
- const entry = providers[namespace];
141
- if (!entry || typeof entry.weakRef?.deref !== "function") return null;
142
- const live = entry.weakRef.deref();
143
- if (live) {
144
- entry.lastSeenAt = Date.now();
145
- entry.staleSince = null;
146
- return live;
147
- }
148
- entry.staleSince ?? (entry.staleSince = Date.now());
149
- return null;
150
- };
151
- }
152
- if (typeof root.list !== "function") {
153
- root.list = () => Object.entries(providers).map(([namespace, entry]) => {
154
- const live = typeof entry.weakRef?.deref === "function" ? entry.weakRef.deref() : void 0;
155
- const available = Boolean(live);
156
- if (available) {
157
- entry.lastSeenAt = Date.now();
158
- entry.staleSince = null;
159
- } else {
160
- entry.staleSince ?? (entry.staleSince = Date.now());
161
- }
162
- return {
163
- namespace,
164
- available,
165
- registeredAt: entry.registeredAt,
166
- lastSeenAt: entry.lastSeenAt,
167
- staleSince: entry.staleSince
168
- };
169
- }).sort((left, right) => left.namespace.localeCompare(right.namespace));
170
- }
171
- globalWindow.__REACT_MNEMONIC_DEVTOOLS__ = root;
172
- return root;
173
- }
174
- function bumpDevToolsVersion(root, namespace, reason) {
175
- if (!root) return;
176
- root.__meta.version += 1;
177
- root.__meta.lastUpdated = Date.now();
178
- root.__meta.lastChange = `${namespace}.${reason}`;
179
- }
180
- function decodeDevToolsValue(raw) {
181
- try {
182
- return JSON.parse(raw);
183
- } catch {
184
- return raw;
185
- }
186
- }
187
- function readStorageRaw(storage, storageKey, callbacks) {
188
- if (!storage) return null;
189
- try {
190
- const raw = storage.getItem(storageKey);
191
- if (isPromiseLike(raw)) {
192
- callbacks.onAsyncViolation("getItem", raw);
193
- return null;
194
- }
195
- callbacks.onAccessSuccess();
196
- return raw;
197
- } catch (error) {
198
- callbacks.onAccessError(error);
199
- return null;
502
+ function objectHasOwn2(value, property) {
503
+ const hasOwn = Object.hasOwn;
504
+ if (typeof hasOwn === "function") {
505
+ return hasOwn(value, property);
200
506
  }
507
+ return Object.getOwnPropertyDescriptor(value, property) !== void 0;
201
508
  }
202
- function enumerateNamespaceKeys(storage, prefix, callbacks) {
203
- if (!storage) {
204
- return [];
205
- }
206
- const keys = [];
207
- try {
208
- const storageLength = storage.length;
209
- const getStorageKey = storage.key;
210
- if (typeof storageLength !== "number" || typeof getStorageKey !== "function") {
211
- return [];
212
- }
213
- for (let index = 0; index < storageLength; index++) {
214
- const fullKey = getStorageKey.call(storage, index);
215
- if (!fullKey?.startsWith(prefix)) continue;
216
- keys.push(fullKey.slice(prefix.length));
217
- }
218
- callbacks.onAccessSuccess();
219
- } catch (error) {
220
- callbacks.onAccessError(error);
221
- }
222
- return keys;
509
+ function getDiagnosticObjectId(value) {
510
+ const existing = diagnosticObjectIds.get(value);
511
+ if (existing !== void 0) return existing;
512
+ const id = nextDiagnosticObjectId++;
513
+ diagnosticObjectIds.set(value, id);
514
+ return id;
223
515
  }
224
- function syncCacheEntryFromStorage({
516
+ function buildContractFingerprint({
517
+ api,
225
518
  key,
226
- storageKey,
227
- storage,
228
- cache,
229
- emit,
230
- callbacks
231
- }) {
232
- const fresh = readStorageRaw(storage, storageKey, callbacks);
233
- const cached = cache.get(key) ?? null;
234
- if (fresh === cached) {
235
- return false;
236
- }
237
- cache.set(key, fresh);
238
- emit(key);
239
- return true;
240
- }
241
- function reloadNamedKeysFromStorage({
242
- changedKeys,
243
- prefix,
244
- storage,
245
- listeners,
246
- cache,
247
- emit,
248
- callbacks
519
+ defaultValue,
520
+ codecOpt,
521
+ schemaVersion,
522
+ reconcile,
523
+ listenCrossTab,
524
+ ssrOptions
249
525
  }) {
250
- let changed = false;
251
- for (const fullStorageKey of changedKeys) {
252
- if (!fullStorageKey.startsWith(prefix)) continue;
253
- const key = fullStorageKey.slice(prefix.length);
254
- const listenerSet = listeners.get(key);
255
- if (listenerSet && listenerSet.size > 0) {
256
- changed = syncCacheEntryFromStorage({
257
- key,
258
- storageKey: fullStorageKey,
259
- storage,
260
- cache,
261
- emit,
262
- callbacks
263
- }) || changed;
264
- continue;
265
- }
266
- if (cache.has(key)) {
267
- cache.delete(key);
268
- }
269
- }
270
- return changed;
526
+ const codecSignature = codecOpt == null || !isObjectLike(codecOpt) ? "default-json-codec" : `codec:${stableDiagnosticValue(codecOpt.encode)}:${stableDiagnosticValue(codecOpt.decode)}`;
527
+ const reconcileSignature = reconcile == null || !isObjectLike(reconcile) ? "no-reconcile" : `reconcile:${stableDiagnosticValue(reconcile)}`;
528
+ return JSON.stringify({
529
+ key,
530
+ defaultValue: stableDiagnosticValue(defaultValue),
531
+ codec: codecSignature,
532
+ schemaVersion: schemaVersion ?? null,
533
+ listenCrossTab: Boolean(listenCrossTab),
534
+ reconcile: reconcileSignature,
535
+ ssrHydration: ssrOptions?.hydration ?? null,
536
+ hasServerValue: ssrOptions?.serverValue !== void 0,
537
+ providerHydration: api.ssrHydration ?? null
538
+ });
271
539
  }
272
- function reloadSubscribedKeysFromStorage({
273
- prefix,
274
- storage,
275
- listeners,
276
- cache,
277
- emit,
278
- callbacks
279
- }) {
280
- let changed = false;
281
- for (const [key, listenerSet] of listeners) {
282
- if (listenerSet.size === 0) continue;
283
- changed = syncCacheEntryFromStorage({
284
- key,
285
- storageKey: `${prefix}${key}`,
286
- storage,
287
- cache,
288
- emit,
289
- callbacks
290
- }) || changed;
540
+ function resolveMnemonicKeyArgs(keyOrDescriptor, options) {
541
+ if (typeof keyOrDescriptor !== "string") {
542
+ return keyOrDescriptor;
291
543
  }
292
- for (const key of cache.keys()) {
293
- const listenerSet = listeners.get(key);
294
- if (listenerSet && listenerSet.size > 0) continue;
295
- cache.delete(key);
544
+ if (!options) {
545
+ throw new Error("useMnemonicKey requires options when called with a string key");
296
546
  }
297
- return changed;
298
- }
299
- function createDevToolsProviderApi({
300
- store,
301
- dump,
302
- keys,
303
- readThrough,
304
- writeRaw,
305
- removeRaw
306
- }) {
307
547
  return {
308
- getStore: () => store,
309
- dump: () => {
310
- const data = dump();
311
- console.table(
312
- Object.entries(data).map(([key, value]) => ({
313
- key,
314
- value,
315
- decoded: decodeDevToolsValue(value)
316
- }))
317
- );
318
- return data;
319
- },
320
- get: (key) => {
321
- const raw = readThrough(key);
322
- if (raw == null) return void 0;
323
- return decodeDevToolsValue(raw);
324
- },
325
- set: (key, value) => {
326
- writeRaw(key, JSON.stringify(value));
548
+ key: keyOrDescriptor,
549
+ options
550
+ };
551
+ }
552
+ function useMnemonicKeySharedFromApi(api, keyOrDescriptor, options, schemaVersion) {
553
+ const descriptor = resolveMnemonicKeyArgs(keyOrDescriptor, options);
554
+ const key = descriptor.key;
555
+ const resolvedOptions = descriptor.options;
556
+ const {
557
+ defaultValue,
558
+ onMount,
559
+ onChange,
560
+ listenCrossTab,
561
+ codec: codecOpt,
562
+ schema,
563
+ reconcile,
564
+ ssr: ssrOptions
565
+ } = resolvedOptions;
566
+ const codec = codecOpt ?? JSONCodec;
567
+ const hydrationMode = ssrOptions?.hydration ?? api.ssrHydration;
568
+ const [hasMounted, setHasMounted] = react.useState(hydrationMode !== "client-only");
569
+ const developmentRuntime = isDevelopmentRuntime();
570
+ const contractFingerprint = react.useMemo(
571
+ () => developmentRuntime ? buildContractFingerprint({
572
+ api,
573
+ key,
574
+ defaultValue,
575
+ codecOpt,
576
+ ...{} ,
577
+ reconcile,
578
+ listenCrossTab,
579
+ ssrOptions
580
+ }) : null,
581
+ [
582
+ developmentRuntime,
583
+ api,
584
+ key,
585
+ defaultValue,
586
+ codecOpt,
587
+ schemaVersion,
588
+ reconcile,
589
+ listenCrossTab,
590
+ ssrOptions?.hydration,
591
+ ssrOptions?.serverValue
592
+ ]
593
+ );
594
+ const getFallback = react.useCallback(
595
+ (error) => typeof defaultValue === "function" ? defaultValue(error) : defaultValue,
596
+ [defaultValue]
597
+ );
598
+ const getServerValue = react.useCallback(() => {
599
+ const serverValue = ssrOptions?.serverValue;
600
+ if (serverValue === void 0) {
601
+ return getFallback();
602
+ }
603
+ return typeof serverValue === "function" ? serverValue() : serverValue;
604
+ }, [getFallback, ssrOptions?.serverValue]);
605
+ const parseEnvelope2 = react.useCallback(
606
+ (rawText) => {
607
+ try {
608
+ const parsed = JSON.parse(rawText);
609
+ if (typeof parsed !== "object" || parsed == null || !Number.isInteger(parsed.version) || parsed.version < 0 || !objectHasOwn2(parsed, "payload")) {
610
+ return {
611
+ ok: false,
612
+ error: new SchemaError("INVALID_ENVELOPE", `Invalid envelope for key "${key}"`)
613
+ };
614
+ }
615
+ return { ok: true, envelope: parsed };
616
+ } catch (err) {
617
+ return {
618
+ ok: false,
619
+ error: new SchemaError("INVALID_ENVELOPE", `Invalid envelope for key "${key}"`, err)
620
+ };
621
+ }
327
622
  },
328
- remove: (key) => removeRaw(key),
329
- clear: () => {
330
- for (const key of keys()) {
331
- removeRaw(key);
623
+ [key]
624
+ );
625
+ const decodeStringPayload2 = react.useCallback(
626
+ (payload, activeCodec) => {
627
+ try {
628
+ return activeCodec.decode(payload);
629
+ } catch (err) {
630
+ throw err instanceof CodecError ? err : new CodecError(`Codec decode failed for key "${key}"`, err);
332
631
  }
333
632
  },
334
- keys
633
+ [key]
634
+ );
635
+ const buildFallbackResult2 = react.useCallback(
636
+ (error, extra) => {
637
+ return withReadMetadata(getFallback(error), void 0, extra);
638
+ },
639
+ [getFallback]
640
+ );
641
+ return {
642
+ api,
643
+ key,
644
+ codec,
645
+ codecOpt,
646
+ schema,
647
+ reconcile,
648
+ onMount,
649
+ onChange,
650
+ listenCrossTab,
651
+ getFallback,
652
+ getServerValue,
653
+ parseEnvelope: parseEnvelope2,
654
+ decodeStringPayload: decodeStringPayload2,
655
+ buildFallbackResult: buildFallbackResult2,
656
+ developmentRuntime,
657
+ contractFingerprint,
658
+ hasMounted,
659
+ setHasMounted,
660
+ hydrationMode,
661
+ ssrOptions
335
662
  };
336
663
  }
337
- function createReloadFromStorage({
338
- storage,
339
- hasAsyncContractViolation,
340
- prefix,
341
- listeners,
342
- cache,
343
- emit,
344
- callbacks,
345
- devToolsRoot,
346
- namespace
664
+ function useApplyReconcile({
665
+ key,
666
+ reconcile,
667
+ buildFallbackResult: buildFallbackResult2
347
668
  }) {
348
- return (changedKeys) => {
349
- if (!storage || hasAsyncContractViolation()) return;
350
- if (changedKeys?.length === 0) return;
351
- const isFullReload = changedKeys === void 0;
352
- const changed = isFullReload ? reloadSubscribedKeysFromStorage({
353
- prefix,
354
- storage,
355
- listeners,
356
- cache,
357
- emit,
358
- callbacks
359
- }) : reloadNamedKeysFromStorage({
360
- changedKeys,
361
- prefix,
362
- storage,
363
- listeners,
364
- cache,
365
- emit,
366
- callbacks
669
+ return react.useCallback(
670
+ ({
671
+ value,
672
+ rewriteRaw,
673
+ extra,
674
+ persistedVersion,
675
+ latestVersion,
676
+ serializeForPersist,
677
+ deriveExtra
678
+ }) => {
679
+ if (!reconcile) {
680
+ return withReadMetadata(value, rewriteRaw, extra);
681
+ }
682
+ const context = {
683
+ key,
684
+ persistedVersion
685
+ };
686
+ if (latestVersion !== void 0) {
687
+ context.latestVersion = latestVersion;
688
+ }
689
+ const baselineSerialized = (() => {
690
+ try {
691
+ return serializeForPersist(value);
692
+ } catch {
693
+ return rewriteRaw;
694
+ }
695
+ })();
696
+ try {
697
+ const reconciled = reconcile(value, context);
698
+ const nextExtra = deriveExtra ? deriveExtra(reconciled, extra) : extra;
699
+ const nextSerialized = serializeForPersist(reconciled);
700
+ const nextRewriteRaw = baselineSerialized === void 0 || nextSerialized !== baselineSerialized ? nextSerialized : rewriteRaw;
701
+ return withReadMetadata(reconciled, nextRewriteRaw, nextExtra);
702
+ } catch (err) {
703
+ const typedErr = err instanceof SchemaError ? err : new SchemaError("RECONCILE_FAILED", `Reconciliation failed for key "${key}"`, err);
704
+ return buildFallbackResult2(typedErr, extra);
705
+ }
706
+ },
707
+ [buildFallbackResult2, key, reconcile]
708
+ );
709
+ }
710
+ function useMnemonicKeyState(shared, config) {
711
+ const {
712
+ api,
713
+ key,
714
+ codecOpt,
715
+ schema,
716
+ onMount,
717
+ onChange,
718
+ listenCrossTab,
719
+ getFallback,
720
+ getServerValue,
721
+ developmentRuntime,
722
+ contractFingerprint,
723
+ hasMounted,
724
+ setHasMounted,
725
+ hydrationMode,
726
+ ssrOptions
727
+ } = shared;
728
+ const { decodeForRead, encodeForWrite, active = true, additionalDevWarnings, onDecodedEffect } = config;
729
+ const getServerRawSnapshot = react.useCallback(
730
+ () => ssrOptions?.serverValue === void 0 ? null : SSR_SNAPSHOT_TOKEN,
731
+ [ssrOptions?.serverValue]
732
+ );
733
+ const deferStorageRead = hydrationMode === "client-only" && !hasMounted;
734
+ const subscribe = react.useCallback(
735
+ (listener) => {
736
+ if (!active) {
737
+ return () => void 0;
738
+ }
739
+ if (deferStorageRead) {
740
+ return () => void 0;
741
+ }
742
+ return api.subscribeRaw(key, listener);
743
+ },
744
+ [active, api, deferStorageRead, key]
745
+ );
746
+ const raw = react.useSyncExternalStore(
747
+ subscribe,
748
+ () => active && !deferStorageRead ? api.getRawSnapshot(key) : getServerRawSnapshot(),
749
+ getServerRawSnapshot
750
+ );
751
+ const decoded = react.useMemo(() => {
752
+ if (raw === SSR_SNAPSHOT_TOKEN) {
753
+ return withReadMetadata(getServerValue());
754
+ }
755
+ return decodeForRead(raw);
756
+ }, [decodeForRead, getServerValue, raw]);
757
+ const value = decoded.value;
758
+ react.useEffect(() => {
759
+ if (!active) return;
760
+ if (!developmentRuntime) return;
761
+ const globalWindow = globalThis.window;
762
+ if (listenCrossTab && (api.crossTabSyncMode ?? "none") === "none" && globalWindow !== void 0) {
763
+ warnOnce(
764
+ api,
765
+ `listenCrossTab:${key}`,
766
+ `[Mnemonic] useMnemonicKey("${key}") enabled listenCrossTab, but the active storage backend may not be able to notify external changes. If you're using a custom Storage-like wrapper around localStorage, ensure it forwards browser "storage" events or implements storage.onExternalChange(...); otherwise, use localStorage or implement storage.onExternalChange(...) on your custom backend.`
767
+ );
768
+ }
769
+ additionalDevWarnings?.({
770
+ api,
771
+ key,
772
+ listenCrossTab,
773
+ codecOpt,
774
+ schema,
775
+ warnOnce: (id, message) => warnOnce(api, id, message)
367
776
  });
368
- if (changed) {
369
- bumpDevToolsVersion(devToolsRoot, namespace, isFullReload ? "reload:full" : "reload:granular");
777
+ let keyContracts = diagnosticContractRegistry.get(api);
778
+ if (!keyContracts) {
779
+ keyContracts = /* @__PURE__ */ new Map();
780
+ diagnosticContractRegistry.set(api, keyContracts);
370
781
  }
371
- };
372
- }
373
- function registerDevToolsProvider({
374
- devToolsRoot,
375
- namespace,
376
- store,
377
- dump,
378
- keys,
379
- readThrough,
380
- writeRaw,
381
- removeRaw
382
- }) {
383
- let infoMessage = `[Mnemonic DevTools] Namespace "${namespace}" available via window.__REACT_MNEMONIC_DEVTOOLS__.resolve("${namespace}")`;
384
- if (!devToolsRoot.capabilities.weakRef) {
385
- console.info(
386
- `[Mnemonic DevTools] WeakRef is not available; registry provider "${namespace}" was not registered.`
387
- );
388
- return;
389
- }
390
- const existingLive = devToolsRoot.resolve(namespace);
391
- if (existingLive) {
392
- const duplicateMessage = `[Mnemonic DevTools] Duplicate provider namespace "${namespace}" detected. Each window must have at most one live MnemonicProvider per namespace.`;
393
- if (!isProductionRuntime()) {
394
- throw new Error(duplicateMessage);
782
+ if (contractFingerprint === null) {
783
+ return;
395
784
  }
396
- console.warn(`${duplicateMessage} Keeping the first provider and ignoring the duplicate.`);
397
- console.info(
398
- `[Mnemonic DevTools] Namespace "${namespace}" already registered. Keeping existing provider reference.`
785
+ const previousContract = keyContracts.get(key);
786
+ if (previousContract === void 0) {
787
+ keyContracts.set(key, contractFingerprint);
788
+ return;
789
+ }
790
+ if (previousContract === contractFingerprint) {
791
+ return;
792
+ }
793
+ warnOnce(
794
+ api,
795
+ `contract-conflict:${key}`,
796
+ `[Mnemonic] Conflicting useMnemonicKey contracts detected for key "${key}" in namespace "${api.prefix.slice(0, -1)}". Reuse a shared descriptor with defineMnemonicKey(...) or align defaultValue/codec/schema/reconcile options so every consumer describes the same persisted contract.`
399
797
  );
400
- return;
401
- }
402
- const providerApi = createDevToolsProviderApi({
403
- store,
404
- dump,
405
- keys,
406
- readThrough,
407
- writeRaw,
408
- removeRaw
409
- });
410
- const WeakRefCtor = weakRefConstructor();
411
- if (!WeakRefCtor) {
412
- console.info(`[Mnemonic DevTools] WeakRef became unavailable while registering "${namespace}".`);
413
- return;
414
- }
415
- store.__devToolsProviderApiHold = providerApi;
416
- const now = Date.now();
417
- devToolsRoot.providers[namespace] = {
418
- namespace,
419
- weakRef: new WeakRefCtor(providerApi),
420
- registeredAt: now,
421
- lastSeenAt: now,
422
- staleSince: null
423
- };
424
- bumpDevToolsVersion(devToolsRoot, namespace, "registry:namespace-registered");
425
- console.info(infoMessage);
426
- }
427
- function MnemonicProvider({
428
- children,
429
- namespace,
430
- storage,
431
- enableDevTools = false,
432
- schemaMode = "default",
433
- schemaRegistry,
434
- ssr
435
- }) {
436
- if (schemaMode === "strict" && !schemaRegistry) {
437
- throw new Error("MnemonicProvider strict mode requires schemaRegistry");
438
- }
439
- if (schemaMode === "autoschema" && typeof schemaRegistry?.registerSchema !== "function") {
440
- throw new Error("MnemonicProvider autoschema mode requires schemaRegistry.registerSchema");
441
- }
442
- const store = react.useMemo(() => {
443
- const prefix = `${namespace}.`;
444
- const st = storage ?? defaultBrowserStorage();
445
- const ssrHydration = ssr?.hydration ?? "immediate";
446
- const devToolsRoot = ensureDevToolsRoot(enableDevTools);
447
- const canEnumerateKeys = detectEnumerableStorage(st);
448
- const crossTabSyncMode = getCrossTabSyncMode(storage, st);
449
- const cache = /* @__PURE__ */ new Map();
450
- const listeners = /* @__PURE__ */ new Map();
451
- let quotaErrorLogged = false;
452
- let accessErrorLogged = false;
453
- let asyncContractViolationDetected = false;
454
- const storageAccessCallbacks = {
455
- onAccessError: (err) => logAccessError(err),
456
- onAccessSuccess: () => {
457
- accessErrorLogged = false;
458
- },
459
- onAsyncViolation: (method, thenable) => handleAsyncStorageContractViolation(method, thenable)
460
- };
461
- const fullKey = (key) => prefix + key;
462
- const emit = (key) => {
463
- const set = listeners.get(key);
464
- if (!set) return;
465
- for (const fn of set) fn();
466
- };
467
- const logAccessError = (err) => {
468
- if (!accessErrorLogged && err instanceof DOMException && err.name !== "QuotaExceededError") {
469
- console.error(
470
- `[Mnemonic] Storage access error (${err.name}): ${err.message}. Data is cached in memory but may not persist.`
471
- );
472
- accessErrorLogged = true;
798
+ }, [
799
+ active,
800
+ additionalDevWarnings,
801
+ api,
802
+ key,
803
+ developmentRuntime,
804
+ contractFingerprint,
805
+ listenCrossTab,
806
+ codecOpt,
807
+ schema,
808
+ api.crossTabSyncMode
809
+ ]);
810
+ react.useEffect(() => {
811
+ if (hasMounted) return;
812
+ setHasMounted(true);
813
+ }, [hasMounted, setHasMounted]);
814
+ react.useEffect(() => {
815
+ if (!active) return;
816
+ if (decoded.rewriteRaw && decoded.rewriteRaw !== raw) {
817
+ api.setRaw(key, decoded.rewriteRaw);
818
+ }
819
+ }, [active, api, decoded.rewriteRaw, key, raw]);
820
+ react.useEffect(() => {
821
+ if (!active) return;
822
+ onDecodedEffect?.(decoded);
823
+ }, [active, decoded, onDecodedEffect]);
824
+ const prevRef = react.useRef(value);
825
+ const mounted = react.useRef(false);
826
+ react.useEffect(() => {
827
+ if (!active) return;
828
+ if (mounted.current) return;
829
+ mounted.current = true;
830
+ onMount?.(value);
831
+ prevRef.current = value;
832
+ }, [active]);
833
+ react.useEffect(() => {
834
+ if (!active) return;
835
+ const prev = prevRef.current;
836
+ if (Object.is(prev, value)) return;
837
+ prevRef.current = value;
838
+ onChange?.(value, prev);
839
+ }, [active, value, onChange]);
840
+ react.useEffect(() => {
841
+ if (!active) return;
842
+ if (!listenCrossTab) return;
843
+ const globalWindow = globalThis.window;
844
+ if (globalWindow === void 0) return;
845
+ const storageKey = api.prefix + key;
846
+ const handler = (e) => {
847
+ if (e.key === null) {
848
+ api.removeRaw(key);
849
+ return;
473
850
  }
474
- };
475
- const handleAsyncStorageContractViolation = (method, thenable) => {
476
- asyncContractViolationDetected = true;
477
- void Promise.resolve(thenable).catch(() => void 0);
478
- if (accessErrorLogged) return;
479
- console.error(
480
- `[Mnemonic] StorageLike.${method} returned a Promise. StorageLike must remain synchronous for react-mnemonic v1. Wrap async persistence behind a synchronous cache facade instead.`
481
- );
482
- accessErrorLogged = true;
483
- };
484
- const readThrough = (key) => {
485
- if (cache.has(key)) return cache.get(key) ?? null;
486
- if (!st || asyncContractViolationDetected) {
487
- cache.set(key, null);
488
- return null;
851
+ if (e.key !== storageKey) return;
852
+ if (e.newValue == null) {
853
+ api.removeRaw(key);
854
+ return;
489
855
  }
490
- const raw = readStorageRaw(st, fullKey(key), storageAccessCallbacks);
491
- cache.set(key, raw);
492
- return raw;
856
+ api.setRaw(key, e.newValue);
493
857
  };
494
- const writeRaw = (key, raw) => {
495
- cache.set(key, raw);
496
- if (st && !asyncContractViolationDetected) {
497
- try {
498
- const result = st.setItem(fullKey(key), raw);
499
- if (isPromiseLike(result)) {
500
- handleAsyncStorageContractViolation("setItem", result);
501
- } else {
502
- quotaErrorLogged = false;
503
- accessErrorLogged = false;
504
- }
505
- } catch (err) {
506
- if (!quotaErrorLogged && err instanceof DOMException && err.name === "QuotaExceededError") {
507
- console.error(
508
- `[Mnemonic] Storage quota exceeded writing key "${key}". Data is cached in memory but will not persist.`
509
- );
510
- quotaErrorLogged = true;
511
- }
512
- logAccessError(err);
858
+ globalWindow.addEventListener("storage", handler);
859
+ return () => globalWindow.removeEventListener("storage", handler);
860
+ }, [active, listenCrossTab, api, key]);
861
+ const set = react.useMemo(() => {
862
+ if (!active) {
863
+ return () => void 0;
864
+ }
865
+ return (next) => {
866
+ const nextVal = typeof next === "function" ? next(decodeForRead(api.getRawSnapshot(key)).value) : next;
867
+ try {
868
+ const encoded = encodeForWrite(nextVal);
869
+ api.setRaw(key, encoded);
870
+ } catch (err) {
871
+ if (err instanceof SchemaError) {
872
+ console.error(`[Mnemonic] Schema error for key "${key}" (${err.code}):`, err.message);
873
+ return;
513
874
  }
514
- }
515
- emit(key);
516
- bumpDevToolsVersion(devToolsRoot, namespace, `set:${key}`);
517
- };
518
- const removeRaw = (key) => {
519
- cache.set(key, null);
520
- if (st && !asyncContractViolationDetected) {
521
- try {
522
- const result = st.removeItem(fullKey(key));
523
- if (isPromiseLike(result)) {
524
- handleAsyncStorageContractViolation("removeItem", result);
525
- } else {
526
- accessErrorLogged = false;
527
- }
528
- } catch (err) {
529
- logAccessError(err);
875
+ if (err instanceof CodecError) {
876
+ console.error(`[Mnemonic] Codec encode error for key "${key}":`, err.message);
877
+ return;
530
878
  }
879
+ console.error(`[Mnemonic] Failed to persist key "${key}":`, err);
531
880
  }
532
- emit(key);
533
- bumpDevToolsVersion(devToolsRoot, namespace, `remove:${key}`);
534
- };
535
- const subscribeRaw = (key, listener) => {
536
- let set = listeners.get(key);
537
- if (!set) {
538
- set = /* @__PURE__ */ new Set();
539
- listeners.set(key, set);
540
- }
541
- set.add(listener);
542
- readThrough(key);
543
- return () => {
544
- const s = listeners.get(key);
545
- if (!s) return;
546
- s.delete(listener);
547
- if (s.size === 0) listeners.delete(key);
548
- };
549
- };
550
- const getRawSnapshot = (key) => readThrough(key);
551
- const keys = () => {
552
- if (asyncContractViolationDetected) {
553
- return Array.from(cache.entries()).filter(([, value]) => value != null).map(([key]) => key);
554
- }
555
- if (!canEnumerateKeys) return [];
556
- return enumerateNamespaceKeys(st, prefix, storageAccessCallbacks);
557
881
  };
558
- const dump = () => {
559
- const out = {};
560
- for (const k of keys()) {
561
- const raw = readThrough(k);
562
- if (raw != null) out[k] = raw;
882
+ }, [active, api, key, decodeForRead, encodeForWrite]);
883
+ const reset = react.useMemo(() => {
884
+ if (!active) {
885
+ return () => void 0;
886
+ }
887
+ return () => {
888
+ const v = getFallback();
889
+ try {
890
+ const encoded = encodeForWrite(v);
891
+ api.setRaw(key, encoded);
892
+ } catch (err) {
893
+ if (err instanceof SchemaError) {
894
+ console.error(`[Mnemonic] Schema error for key "${key}" (${err.code}):`, err.message);
895
+ return;
896
+ }
897
+ if (err instanceof CodecError) {
898
+ console.error(`[Mnemonic] Codec encode error for key "${key}":`, err.message);
899
+ }
900
+ return;
563
901
  }
564
- return out;
565
- };
566
- const reloadFromStorage = createReloadFromStorage({
567
- storage: st,
568
- hasAsyncContractViolation: () => asyncContractViolationDetected,
569
- prefix,
570
- listeners,
571
- cache,
572
- emit,
573
- callbacks: storageAccessCallbacks,
574
- devToolsRoot,
575
- namespace
576
- });
577
- const store2 = {
578
- prefix,
579
- canEnumerateKeys,
580
- subscribeRaw,
581
- getRawSnapshot,
582
- setRaw: writeRaw,
583
- removeRaw,
584
- keys,
585
- dump,
586
- reloadFromStorage,
587
- schemaMode,
588
- ssrHydration,
589
- crossTabSyncMode,
590
- ...schemaRegistry ? { schemaRegistry } : {}
591
902
  };
592
- if (devToolsRoot) {
593
- registerDevToolsProvider({
594
- devToolsRoot,
595
- namespace,
596
- store: store2,
597
- dump,
598
- keys,
599
- readThrough,
600
- writeRaw,
601
- removeRaw
602
- });
903
+ }, [active, api, key, getFallback, encodeForWrite]);
904
+ const remove = react.useMemo(() => {
905
+ if (!active) {
906
+ return () => void 0;
603
907
  }
604
- return store2;
605
- }, [namespace, storage, enableDevTools, schemaMode, schemaRegistry, ssr?.hydration]);
606
- react.useEffect(() => {
607
- if (!storage?.onExternalChange) return;
608
- return storage.onExternalChange((changedKeys) => store.reloadFromStorage(changedKeys));
609
- }, [storage, store]);
610
- return /* @__PURE__ */ jsxRuntime.jsx(MnemonicContext.Provider, { value: store, children });
611
- }
612
- function throwCoreProviderSchemaImportError(propName) {
613
- throw new Error(
614
- `[Mnemonic] MnemonicProvider from react-mnemonic/core does not support ${propName}. Import MnemonicProvider from "react-mnemonic/schema" or "react-mnemonic" for schema validation, autoschema, and migration support.`
908
+ return () => api.removeRaw(key);
909
+ }, [active, api, key]);
910
+ return react.useMemo(
911
+ () => ({
912
+ value,
913
+ set,
914
+ reset,
915
+ remove
916
+ }),
917
+ [value, set, reset, remove]
615
918
  );
616
919
  }
617
- function assertNoSchemaProps(props) {
618
- if (props.schemaMode !== void 0) {
619
- throwCoreProviderSchemaImportError("schemaMode");
920
+
921
+ // src/Mnemonic/optional-bridge-adapter.ts
922
+ function resolveOptionalDefaultValue(defaultValue) {
923
+ return typeof defaultValue === "function" ? defaultValue() : defaultValue;
924
+ }
925
+ function objectHasOwn3(value, property) {
926
+ const hasOwn = Object.hasOwn;
927
+ if (typeof hasOwn === "function") {
928
+ return hasOwn(value, property);
929
+ }
930
+ return Object.getOwnPropertyDescriptor(value, property) !== void 0;
931
+ }
932
+ function parseEnvelope(key, rawText) {
933
+ try {
934
+ const parsed = JSON.parse(rawText);
935
+ if (typeof parsed !== "object" || parsed == null || !Number.isInteger(parsed.version) || parsed.version < 0 || !objectHasOwn3(parsed, "payload")) {
936
+ throw new SchemaError("INVALID_ENVELOPE", `Invalid envelope for key "${key}"`);
937
+ }
938
+ return parsed;
939
+ } catch (error) {
940
+ if (error instanceof SchemaError) {
941
+ throw error;
942
+ }
943
+ throw new SchemaError("INVALID_ENVELOPE", `Invalid envelope for key "${key}"`, error);
944
+ }
945
+ }
946
+ function decodeStringPayload(key, payload, options) {
947
+ const codec = options.codec ?? JSONCodec;
948
+ try {
949
+ return codec.decode(payload);
950
+ } catch (error) {
951
+ throw error instanceof CodecError ? error : new CodecError(`Codec decode failed for key "${key}"`, error);
952
+ }
953
+ }
954
+ function validateAgainstSchema(key, value, jsonSchema) {
955
+ const errors = validateJsonSchema(value, jsonSchema);
956
+ if (errors.length === 0) {
957
+ return;
958
+ }
959
+ const message = errors.map((entry) => `${entry.path || "/"}: ${entry.message}`).join("; ");
960
+ throw new SchemaError("TYPE_MISMATCH", `Schema validation failed for key "${key}": ${message}`);
961
+ }
962
+ function getSchemaCapabilities(schemaRegistry) {
963
+ return schemaRegistry !== void 0;
964
+ }
965
+ function buildFallbackResult(options) {
966
+ return {
967
+ value: resolveOptionalDefaultValue(options.defaultValue)
968
+ };
969
+ }
970
+ function getLatestSchema(schemaRegistry, key) {
971
+ return schemaRegistry?.getLatestSchema(key);
972
+ }
973
+ function getSchemaForVersion(schemaRegistry, key, version) {
974
+ return schemaRegistry?.getSchema(key, version);
975
+ }
976
+ function getMigrationPath(schemaRegistry, key, fromVersion, toVersion) {
977
+ return schemaRegistry?.getMigrationPath(key, fromVersion, toVersion) ?? null;
978
+ }
979
+ function encodeValueForWrite(key, nextValue, options, schemaRegistry) {
980
+ const explicitVersion = options.schema?.version;
981
+ const latestSchema = getLatestSchema(schemaRegistry, key);
982
+ const targetSchema = explicitVersion === void 0 ? latestSchema : getSchemaForVersion(schemaRegistry, key, explicitVersion) ?? latestSchema;
983
+ if (!targetSchema) {
984
+ const codec = options.codec ?? JSONCodec;
985
+ return serializeEnvelope(0, codec.encode(nextValue));
986
+ }
987
+ let valueToStore = nextValue;
988
+ const writeMigration = schemaRegistry?.getWriteMigration?.(key, targetSchema.version);
989
+ if (writeMigration) {
990
+ try {
991
+ valueToStore = writeMigration.migrate(valueToStore);
992
+ } catch (error) {
993
+ throw error instanceof SchemaError ? error : new SchemaError("MIGRATION_FAILED", `Write-time migration failed for key "${key}"`, error);
994
+ }
995
+ }
996
+ validateAgainstSchema(key, valueToStore, targetSchema.schema);
997
+ return serializeEnvelope(targetSchema.version, valueToStore);
998
+ }
999
+ function decodeCodecManagedEnvelope(key, envelope, options) {
1000
+ if (typeof envelope.payload !== "string") {
1001
+ return {
1002
+ value: envelope.payload
1003
+ };
1004
+ }
1005
+ return {
1006
+ value: decodeStringPayload(key, envelope.payload, options)
1007
+ };
1008
+ }
1009
+ function decodeAutoschemaEnvelope(key, envelope, options, schemaRegistry) {
1010
+ if (!schemaRegistry?.registerSchema) {
1011
+ throw new SchemaError(
1012
+ "MODE_CONFIGURATION_INVALID",
1013
+ `Autoschema mode requires schema registry registration for key "${key}"`
1014
+ );
1015
+ }
1016
+ const decoded = typeof envelope.payload === "string" ? decodeStringPayload(key, envelope.payload, options) : envelope.payload;
1017
+ const pendingSchema = {
1018
+ key,
1019
+ version: 1,
1020
+ schema: inferJsonSchema(decoded)
1021
+ };
1022
+ return {
1023
+ value: decoded,
1024
+ rewriteRaw: serializeEnvelope(pendingSchema.version, decoded),
1025
+ pendingSchema
1026
+ };
1027
+ }
1028
+ function decodeSchemaManagedEnvelope(key, envelope, schemaForVersion, latestSchema, schemaRegistry) {
1029
+ let current = envelope.payload;
1030
+ validateAgainstSchema(key, current, schemaForVersion.schema);
1031
+ if (!latestSchema || envelope.version >= latestSchema.version) {
1032
+ return {
1033
+ value: current
1034
+ };
1035
+ }
1036
+ const path = getMigrationPath(schemaRegistry, key, envelope.version, latestSchema.version);
1037
+ if (!path) {
1038
+ throw new SchemaError(
1039
+ "MIGRATION_PATH_NOT_FOUND",
1040
+ `No migration path for key "${key}" from v${envelope.version} to v${latestSchema.version}`
1041
+ );
1042
+ }
1043
+ for (const step of path) {
1044
+ current = step.migrate(current);
1045
+ }
1046
+ validateAgainstSchema(key, current, latestSchema.schema);
1047
+ return {
1048
+ value: current,
1049
+ rewriteRaw: serializeEnvelope(latestSchema.version, current)
1050
+ };
1051
+ }
1052
+ function decodePersistedValue(key, raw, options, api, schemaRegistry) {
1053
+ if (raw == null) {
1054
+ return buildFallbackResult(options);
620
1055
  }
621
- if (props.schemaRegistry !== void 0) {
622
- throwCoreProviderSchemaImportError("schemaRegistry");
1056
+ const envelope = parseEnvelope(key, raw);
1057
+ const latestSchema = getLatestSchema(schemaRegistry, key);
1058
+ const schemaForVersion = getSchemaForVersion(schemaRegistry, key, envelope.version);
1059
+ if (api.schemaMode === "strict" && !schemaForVersion) {
1060
+ throw new SchemaError("SCHEMA_NOT_FOUND", `No schema for key "${key}" v${envelope.version}`);
1061
+ }
1062
+ if (api.schemaMode === "autoschema" && !schemaForVersion) {
1063
+ return decodeAutoschemaEnvelope(key, envelope, options, schemaRegistry);
1064
+ }
1065
+ if (!schemaForVersion) {
1066
+ return decodeCodecManagedEnvelope(key, envelope, options);
623
1067
  }
1068
+ return decodeSchemaManagedEnvelope(key, envelope, schemaForVersion, latestSchema, schemaRegistry);
624
1069
  }
625
- function MnemonicProvider2(props) {
626
- assertNoSchemaProps(props);
627
- return /* @__PURE__ */ jsxRuntime.jsx(MnemonicProvider, { ...props });
1070
+ function createMnemonicOptionalBridge({
1071
+ api,
1072
+ schemaRegistry
1073
+ }) {
1074
+ return {
1075
+ namespace: api.prefix.endsWith(".") ? api.prefix.slice(0, -1) : api.prefix,
1076
+ capabilities: {
1077
+ persistence: true,
1078
+ schema: getSchemaCapabilities(schemaRegistry)
1079
+ },
1080
+ subscribeRaw: (key, listener) => api.subscribeRaw(key, listener),
1081
+ getRawSnapshot: (key) => api.getRawSnapshot(key),
1082
+ decodeSnapshot: (key, raw, options) => {
1083
+ try {
1084
+ return decodePersistedValue(key, raw, options, api, schemaRegistry);
1085
+ } catch {
1086
+ return buildFallbackResult(options);
1087
+ }
1088
+ },
1089
+ setValue: (key, nextValue, options) => {
1090
+ try {
1091
+ api.setRaw(key, encodeValueForWrite(key, nextValue, options, schemaRegistry));
1092
+ } catch (error) {
1093
+ if (error instanceof SchemaError) {
1094
+ console.error(`[Mnemonic] Schema error for key "${key}" (${error.code}):`, error.message);
1095
+ return;
1096
+ }
1097
+ if (error instanceof CodecError) {
1098
+ console.error(`[Mnemonic] Codec error for key "${key}":`, error.message);
1099
+ return;
1100
+ }
1101
+ throw error;
1102
+ }
1103
+ },
1104
+ removeValue: (key) => {
1105
+ api.removeRaw(key);
1106
+ },
1107
+ commitSnapshot: (key, raw, snapshot) => {
1108
+ if (snapshot.pendingSchema && schemaRegistry?.registerSchema) {
1109
+ if (!schemaRegistry.getSchema(snapshot.pendingSchema.key, snapshot.pendingSchema.version)) {
1110
+ try {
1111
+ schemaRegistry.registerSchema(snapshot.pendingSchema);
1112
+ } catch {
1113
+ }
1114
+ }
1115
+ }
1116
+ if (snapshot.rewriteRaw !== void 0 && snapshot.rewriteRaw !== raw) {
1117
+ api.setRaw(key, snapshot.rewriteRaw);
1118
+ }
1119
+ }
1120
+ };
628
1121
  }
1122
+ var MnemonicOptionalBridgeContext = react.createContext(null);
629
1123
 
630
- // src/Mnemonic/codecs.ts
631
- var CodecError = class extends Error {
632
- /**
633
- * Creates a new CodecError.
634
- *
635
- * @param message - Human-readable error description
636
- * @param cause - Optional underlying error that caused this failure
637
- */
638
- constructor(message, cause) {
639
- super(message);
640
- this.name = "CodecError";
641
- this.cause = cause;
642
- Object.setPrototypeOf(this, new.target.prototype);
1124
+ // src/Mnemonic/optional-bridge-provider.tsx
1125
+ function MnemonicOptionalBridgeProvider({
1126
+ bridge,
1127
+ children
1128
+ }) {
1129
+ return react.createElement(MnemonicOptionalBridgeContext.Provider, { value: bridge }, children);
1130
+ }
1131
+ var MnemonicContext = react.createContext(null);
1132
+ function useMnemonic() {
1133
+ const context = useMnemonicOptional();
1134
+ if (!context) {
1135
+ throw new Error("useMnemonic must be used within a MnemonicProvider");
643
1136
  }
644
- };
645
- var JSONCodec = {
646
- encode: (value) => JSON.stringify(value),
647
- decode: (encoded) => JSON.parse(encoded)
648
- };
649
- function createCodec(encode, decode) {
650
- return { encode, decode };
1137
+ return context;
651
1138
  }
652
-
653
- // src/Mnemonic/schema.ts
654
- var SchemaError = class extends Error {
655
- /**
656
- * Creates a new SchemaError.
657
- *
658
- * @param code - Machine-readable failure category
659
- * @param message - Human-readable error description
660
- * @param cause - Optional underlying error
661
- */
662
- constructor(code, message, cause) {
663
- super(message);
664
- this.name = "SchemaError";
665
- this.code = code;
666
- this.cause = cause;
667
- Object.setPrototypeOf(this, new.target.prototype);
1139
+ function useMnemonicOptional() {
1140
+ return react.useContext(MnemonicContext);
1141
+ }
1142
+ function defaultBrowserStorage() {
1143
+ const globalWindow = globalThis.window;
1144
+ if (globalWindow === void 0) return void 0;
1145
+ try {
1146
+ return globalWindow.localStorage;
1147
+ } catch {
1148
+ return void 0;
668
1149
  }
669
- };
670
-
671
- // src/Mnemonic/use-shared.ts
672
- var SSR_SNAPSHOT_TOKEN = /* @__PURE__ */ Symbol("mnemonic:ssr-snapshot");
673
- var diagnosticContractRegistry = /* @__PURE__ */ new WeakMap();
674
- var diagnosticWarningRegistry = /* @__PURE__ */ new WeakMap();
675
- var diagnosticObjectIds = /* @__PURE__ */ new WeakMap();
676
- var nextDiagnosticObjectId = 1;
677
- function serializeEnvelope(version, payload) {
678
- return JSON.stringify({
679
- version,
680
- payload
681
- });
682
1150
  }
683
- function withReadMetadata(value, rewriteRaw, extra) {
684
- const result = { value };
685
- if (extra !== void 0) {
686
- Object.assign(result, extra);
1151
+ function detectEnumerableStorage(storage) {
1152
+ if (!storage) return false;
1153
+ try {
1154
+ return typeof storage.length === "number" && typeof storage.key === "function";
1155
+ } catch {
1156
+ return false;
687
1157
  }
688
- if (rewriteRaw !== void 0) result.rewriteRaw = rewriteRaw;
689
- return result;
690
1158
  }
691
- function isDevelopmentRuntime() {
692
- return getRuntimeNodeEnv() === "development";
1159
+ function isProductionRuntime() {
1160
+ const env = getRuntimeNodeEnv();
1161
+ if (env === void 0) {
1162
+ return true;
1163
+ }
1164
+ return env === "production";
693
1165
  }
694
- function getDiagnosticWarnings(api) {
695
- let warnings = diagnosticWarningRegistry.get(api);
696
- if (!warnings) {
697
- warnings = /* @__PURE__ */ new Set();
698
- diagnosticWarningRegistry.set(api, warnings);
1166
+ function weakRefConstructor() {
1167
+ const ctor = globalThis.WeakRef;
1168
+ return typeof ctor === "function" ? ctor : null;
1169
+ }
1170
+ function hasFinalizationRegistry() {
1171
+ return typeof globalThis.FinalizationRegistry === "function";
1172
+ }
1173
+ function isPromiseLike(value) {
1174
+ if (value == null) return false;
1175
+ if (typeof value !== "object" && typeof value !== "function") return false;
1176
+ return typeof value.then === "function";
1177
+ }
1178
+ function getCrossTabSyncMode(requestedStorage, activeStorage) {
1179
+ const isExplicitNativeBrowserStorage = activeStorage !== void 0 && requestedStorage !== void 0 && getNativeBrowserStorages().includes(activeStorage);
1180
+ if (requestedStorage === void 0 && activeStorage !== void 0 || isExplicitNativeBrowserStorage) {
1181
+ return "browser-storage-event";
699
1182
  }
700
- return warnings;
1183
+ if (typeof activeStorage?.onExternalChange === "function") {
1184
+ return "custom-external-change";
1185
+ }
1186
+ return "none";
701
1187
  }
702
- function warnOnce(api, id, message) {
703
- const warnings = getDiagnosticWarnings(api);
704
- if (warnings.has(id)) return;
705
- warnings.add(id);
706
- console.warn(message);
1188
+ function getDevToolsWindow() {
1189
+ return globalThis.window;
707
1190
  }
708
- function stableDiagnosticValue(value) {
709
- if (typeof value === "function") {
710
- const source = Function.prototype.toString.call(value).split(/\s+/).join(" ").trim();
711
- const name = value.name || "anonymous";
712
- return `[factory:${name}/${value.length}:${source}]`;
1191
+ function sanitizeDevToolsRoot(root) {
1192
+ const reserved = /* @__PURE__ */ new Set(["providers", "resolve", "list", "capabilities", "__meta"]);
1193
+ for (const key of Object.keys(root)) {
1194
+ if (reserved.has(key)) continue;
1195
+ const descriptor = Object.getOwnPropertyDescriptor(root, key);
1196
+ if (descriptor && !descriptor.configurable) continue;
1197
+ try {
1198
+ delete root[key];
1199
+ } catch {
1200
+ }
713
1201
  }
714
- if (typeof value === "bigint") return `${value.toString()}n`;
715
- if (typeof value === "symbol") return value.toString();
716
- if (value === void 0) return "undefined";
1202
+ }
1203
+ function ensureDevToolsRoot(enableDevTools) {
1204
+ if (!enableDevTools) return null;
1205
+ const globalWindow = getDevToolsWindow();
1206
+ if (!globalWindow) return null;
1207
+ const weakRefSupported = weakRefConstructor() !== null;
1208
+ const finalizationRegistrySupported = hasFinalizationRegistry();
1209
+ const existing = globalWindow.__REACT_MNEMONIC_DEVTOOLS__;
1210
+ const root = existing && typeof existing === "object" ? existing : {};
1211
+ sanitizeDevToolsRoot(root);
1212
+ if (!root.providers || typeof root.providers !== "object") {
1213
+ root.providers = {};
1214
+ }
1215
+ if (!root.capabilities || typeof root.capabilities !== "object") {
1216
+ root.capabilities = {};
1217
+ }
1218
+ const capabilities = root.capabilities;
1219
+ capabilities.weakRef = weakRefSupported;
1220
+ capabilities.finalizationRegistry = finalizationRegistrySupported;
1221
+ if (!root.__meta || typeof root.__meta !== "object") {
1222
+ root.__meta = {
1223
+ version: 0,
1224
+ lastUpdated: Date.now(),
1225
+ lastChange: ""
1226
+ };
1227
+ }
1228
+ const meta = root.__meta;
1229
+ if (typeof meta.version !== "number" || !Number.isFinite(meta.version)) {
1230
+ meta.version = 0;
1231
+ }
1232
+ if (typeof meta.lastUpdated !== "number" || !Number.isFinite(meta.lastUpdated)) {
1233
+ meta.lastUpdated = Date.now();
1234
+ }
1235
+ if (typeof meta.lastChange !== "string") {
1236
+ meta.lastChange = "";
1237
+ }
1238
+ const providers = root.providers;
1239
+ if (typeof root.resolve !== "function") {
1240
+ root.resolve = (namespace) => {
1241
+ const entry = providers[namespace];
1242
+ if (!entry || typeof entry.weakRef?.deref !== "function") return null;
1243
+ const live = entry.weakRef.deref();
1244
+ if (live) {
1245
+ entry.lastSeenAt = Date.now();
1246
+ entry.staleSince = null;
1247
+ return live;
1248
+ }
1249
+ entry.staleSince ?? (entry.staleSince = Date.now());
1250
+ return null;
1251
+ };
1252
+ }
1253
+ if (typeof root.list !== "function") {
1254
+ root.list = () => Object.entries(providers).map(([namespace, entry]) => {
1255
+ const live = typeof entry.weakRef?.deref === "function" ? entry.weakRef.deref() : void 0;
1256
+ const available = Boolean(live);
1257
+ if (available) {
1258
+ entry.lastSeenAt = Date.now();
1259
+ entry.staleSince = null;
1260
+ } else {
1261
+ entry.staleSince ?? (entry.staleSince = Date.now());
1262
+ }
1263
+ return {
1264
+ namespace,
1265
+ available,
1266
+ registeredAt: entry.registeredAt,
1267
+ lastSeenAt: entry.lastSeenAt,
1268
+ staleSince: entry.staleSince
1269
+ };
1270
+ }).sort((left, right) => left.namespace.localeCompare(right.namespace));
1271
+ }
1272
+ globalWindow.__REACT_MNEMONIC_DEVTOOLS__ = root;
1273
+ return root;
1274
+ }
1275
+ function bumpDevToolsVersion(root, namespace, reason) {
1276
+ if (!root) return;
1277
+ root.__meta.version += 1;
1278
+ root.__meta.lastUpdated = Date.now();
1279
+ root.__meta.lastChange = `${namespace}.${reason}`;
1280
+ }
1281
+ function decodeDevToolsValue(raw) {
717
1282
  try {
718
- return JSON.stringify(value);
1283
+ return JSON.parse(raw);
719
1284
  } catch {
720
- const tag = Object.prototype.toString.call(value);
721
- if (value !== null && (typeof value === "object" || typeof value === "function")) {
722
- return `${tag}#${getDiagnosticObjectId(value)}`;
1285
+ return raw;
1286
+ }
1287
+ }
1288
+ function readStorageRaw(storage, storageKey, callbacks) {
1289
+ if (!storage) return null;
1290
+ try {
1291
+ const raw = storage.getItem(storageKey);
1292
+ if (isPromiseLike(raw)) {
1293
+ callbacks.onAsyncViolation("getItem", raw);
1294
+ return null;
723
1295
  }
724
- return tag;
1296
+ callbacks.onAccessSuccess();
1297
+ return raw;
1298
+ } catch (error) {
1299
+ callbacks.onAccessError(error);
1300
+ return null;
725
1301
  }
726
1302
  }
727
- function isObjectLike(value) {
728
- return value !== null && (typeof value === "object" || typeof value === "function");
729
- }
730
- function objectHasOwn(value, property) {
731
- const hasOwn = Object.hasOwn;
732
- if (typeof hasOwn === "function") {
733
- return hasOwn(value, property);
1303
+ function enumerateNamespaceKeys(storage, prefix, callbacks) {
1304
+ if (!storage) {
1305
+ return [];
734
1306
  }
735
- return Object.getOwnPropertyDescriptor(value, property) !== void 0;
736
- }
737
- function getDiagnosticObjectId(value) {
738
- const existing = diagnosticObjectIds.get(value);
739
- if (existing !== void 0) return existing;
740
- const id = nextDiagnosticObjectId++;
741
- diagnosticObjectIds.set(value, id);
742
- return id;
1307
+ const keys = [];
1308
+ try {
1309
+ const storageLength = storage.length;
1310
+ const getStorageKey = storage.key;
1311
+ if (typeof storageLength !== "number" || typeof getStorageKey !== "function") {
1312
+ return [];
1313
+ }
1314
+ for (let index = 0; index < storageLength; index++) {
1315
+ const fullKey = getStorageKey.call(storage, index);
1316
+ if (!fullKey?.startsWith(prefix)) continue;
1317
+ keys.push(fullKey.slice(prefix.length));
1318
+ }
1319
+ callbacks.onAccessSuccess();
1320
+ } catch (error) {
1321
+ callbacks.onAccessError(error);
1322
+ }
1323
+ return keys;
743
1324
  }
744
- function buildContractFingerprint({
745
- api,
1325
+ function syncCacheEntryFromStorage({
746
1326
  key,
747
- defaultValue,
748
- codecOpt,
749
- schemaVersion,
750
- reconcile,
751
- listenCrossTab,
752
- ssrOptions
1327
+ storageKey,
1328
+ storage,
1329
+ cache,
1330
+ emit,
1331
+ callbacks
753
1332
  }) {
754
- const codecSignature = codecOpt == null || !isObjectLike(codecOpt) ? "default-json-codec" : `codec:${stableDiagnosticValue(codecOpt.encode)}:${stableDiagnosticValue(codecOpt.decode)}`;
755
- const reconcileSignature = reconcile == null || !isObjectLike(reconcile) ? "no-reconcile" : `reconcile:${stableDiagnosticValue(reconcile)}`;
756
- return JSON.stringify({
757
- key,
758
- defaultValue: stableDiagnosticValue(defaultValue),
759
- codec: codecSignature,
760
- schemaVersion: schemaVersion ?? null,
761
- listenCrossTab: Boolean(listenCrossTab),
762
- reconcile: reconcileSignature,
763
- ssrHydration: ssrOptions?.hydration ?? null,
764
- hasServerValue: ssrOptions?.serverValue !== void 0,
765
- providerHydration: api.ssrHydration ?? null
766
- });
767
- }
768
- function resolveMnemonicKeyArgs(keyOrDescriptor, options) {
769
- if (typeof keyOrDescriptor !== "string") {
770
- return keyOrDescriptor;
1333
+ const fresh = readStorageRaw(storage, storageKey, callbacks);
1334
+ const cached = cache.get(key) ?? null;
1335
+ if (fresh === cached) {
1336
+ return false;
771
1337
  }
772
- if (!options) {
773
- throw new Error("useMnemonicKey requires options when called with a string key");
1338
+ cache.set(key, fresh);
1339
+ emit(key);
1340
+ return true;
1341
+ }
1342
+ function reloadNamedKeysFromStorage({
1343
+ changedKeys,
1344
+ prefix,
1345
+ storage,
1346
+ listeners,
1347
+ cache,
1348
+ emit,
1349
+ callbacks
1350
+ }) {
1351
+ let changed = false;
1352
+ for (const fullStorageKey of changedKeys) {
1353
+ if (!fullStorageKey.startsWith(prefix)) continue;
1354
+ const key = fullStorageKey.slice(prefix.length);
1355
+ const listenerSet = listeners.get(key);
1356
+ if (listenerSet && listenerSet.size > 0) {
1357
+ changed = syncCacheEntryFromStorage({
1358
+ key,
1359
+ storageKey: fullStorageKey,
1360
+ storage,
1361
+ cache,
1362
+ emit,
1363
+ callbacks
1364
+ }) || changed;
1365
+ continue;
1366
+ }
1367
+ if (cache.has(key)) {
1368
+ cache.delete(key);
1369
+ }
774
1370
  }
775
- return {
776
- key: keyOrDescriptor,
777
- options
778
- };
1371
+ return changed;
779
1372
  }
780
- function useMnemonicKeyShared(keyOrDescriptor, options, schemaVersion) {
781
- const descriptor = resolveMnemonicKeyArgs(keyOrDescriptor, options);
782
- const key = descriptor.key;
783
- const resolvedOptions = descriptor.options;
784
- const api = useMnemonic();
785
- const {
786
- defaultValue,
787
- onMount,
788
- onChange,
789
- listenCrossTab,
790
- codec: codecOpt,
791
- schema,
792
- reconcile,
793
- ssr: ssrOptions
794
- } = resolvedOptions;
795
- const codec = codecOpt ?? JSONCodec;
796
- const hydrationMode = ssrOptions?.hydration ?? api.ssrHydration;
797
- const [hasMounted, setHasMounted] = react.useState(hydrationMode !== "client-only");
798
- const developmentRuntime = isDevelopmentRuntime();
799
- const contractFingerprint = react.useMemo(
800
- () => developmentRuntime ? buildContractFingerprint({
801
- api,
802
- key,
803
- defaultValue,
804
- codecOpt,
805
- ...{} ,
806
- reconcile,
807
- listenCrossTab,
808
- ssrOptions
809
- }) : null,
810
- [
811
- developmentRuntime,
812
- api,
1373
+ function reloadSubscribedKeysFromStorage({
1374
+ prefix,
1375
+ storage,
1376
+ listeners,
1377
+ cache,
1378
+ emit,
1379
+ callbacks
1380
+ }) {
1381
+ let changed = false;
1382
+ for (const [key, listenerSet] of listeners) {
1383
+ if (listenerSet.size === 0) continue;
1384
+ changed = syncCacheEntryFromStorage({
813
1385
  key,
814
- defaultValue,
815
- codecOpt,
816
- schemaVersion,
817
- reconcile,
818
- listenCrossTab,
819
- ssrOptions?.hydration,
820
- ssrOptions?.serverValue
821
- ]
822
- );
823
- const getFallback = react.useCallback(
824
- (error) => typeof defaultValue === "function" ? defaultValue(error) : defaultValue,
825
- [defaultValue]
826
- );
827
- const getServerValue = react.useCallback(() => {
828
- const serverValue = ssrOptions?.serverValue;
829
- if (serverValue === void 0) {
830
- return getFallback();
831
- }
832
- return typeof serverValue === "function" ? serverValue() : serverValue;
833
- }, [getFallback, ssrOptions?.serverValue]);
834
- const parseEnvelope = react.useCallback(
835
- (rawText) => {
836
- try {
837
- const parsed = JSON.parse(rawText);
838
- if (typeof parsed !== "object" || parsed == null || !Number.isInteger(parsed.version) || parsed.version < 0 || !objectHasOwn(parsed, "payload")) {
839
- return {
840
- ok: false,
841
- error: new SchemaError("INVALID_ENVELOPE", `Invalid envelope for key "${key}"`)
842
- };
843
- }
844
- return { ok: true, envelope: parsed };
845
- } catch (err) {
846
- return {
847
- ok: false,
848
- error: new SchemaError("INVALID_ENVELOPE", `Invalid envelope for key "${key}"`, err)
849
- };
850
- }
851
- },
852
- [key]
853
- );
854
- const decodeStringPayload = react.useCallback(
855
- (payload, activeCodec) => {
856
- try {
857
- return activeCodec.decode(payload);
858
- } catch (err) {
859
- throw err instanceof CodecError ? err : new CodecError(`Codec decode failed for key "${key}"`, err);
860
- }
861
- },
862
- [key]
863
- );
864
- const buildFallbackResult = react.useCallback(
865
- (error, extra) => {
866
- return withReadMetadata(getFallback(error), void 0, extra);
867
- },
868
- [getFallback]
869
- );
870
- return {
871
- api,
872
- key,
873
- codec,
874
- codecOpt,
875
- schema,
876
- reconcile,
877
- onMount,
878
- onChange,
879
- listenCrossTab,
880
- getFallback,
881
- getServerValue,
882
- parseEnvelope,
883
- decodeStringPayload,
884
- buildFallbackResult,
885
- developmentRuntime,
886
- contractFingerprint,
887
- hasMounted,
888
- setHasMounted,
889
- hydrationMode,
890
- ssrOptions
891
- };
1386
+ storageKey: `${prefix}${key}`,
1387
+ storage,
1388
+ cache,
1389
+ emit,
1390
+ callbacks
1391
+ }) || changed;
1392
+ }
1393
+ for (const key of cache.keys()) {
1394
+ const listenerSet = listeners.get(key);
1395
+ if (listenerSet && listenerSet.size > 0) continue;
1396
+ cache.delete(key);
1397
+ }
1398
+ return changed;
892
1399
  }
893
- function useApplyReconcile({
894
- key,
895
- reconcile,
896
- buildFallbackResult
1400
+ function createDevToolsProviderApi({
1401
+ store,
1402
+ dump,
1403
+ keys,
1404
+ readThrough,
1405
+ writeRaw,
1406
+ removeRaw
897
1407
  }) {
898
- return react.useCallback(
899
- ({
900
- value,
901
- rewriteRaw,
902
- extra,
903
- persistedVersion,
904
- latestVersion,
905
- serializeForPersist,
906
- deriveExtra
907
- }) => {
908
- if (!reconcile) {
909
- return withReadMetadata(value, rewriteRaw, extra);
910
- }
911
- const context = {
912
- key,
913
- persistedVersion
914
- };
915
- if (latestVersion !== void 0) {
916
- context.latestVersion = latestVersion;
917
- }
918
- const baselineSerialized = (() => {
919
- try {
920
- return serializeForPersist(value);
921
- } catch {
922
- return rewriteRaw;
923
- }
924
- })();
925
- try {
926
- const reconciled = reconcile(value, context);
927
- const nextExtra = deriveExtra ? deriveExtra(reconciled, extra) : extra;
928
- const nextSerialized = serializeForPersist(reconciled);
929
- const nextRewriteRaw = baselineSerialized === void 0 || nextSerialized !== baselineSerialized ? nextSerialized : rewriteRaw;
930
- return withReadMetadata(reconciled, nextRewriteRaw, nextExtra);
931
- } catch (err) {
932
- const typedErr = err instanceof SchemaError ? err : new SchemaError("RECONCILE_FAILED", `Reconciliation failed for key "${key}"`, err);
933
- return buildFallbackResult(typedErr, extra);
934
- }
1408
+ return {
1409
+ getStore: () => store,
1410
+ dump: () => {
1411
+ const data = dump();
1412
+ console.table(
1413
+ Object.entries(data).map(([key, value]) => ({
1414
+ key,
1415
+ value,
1416
+ decoded: decodeDevToolsValue(value)
1417
+ }))
1418
+ );
1419
+ return data;
935
1420
  },
936
- [buildFallbackResult, key, reconcile]
937
- );
938
- }
939
- function useMnemonicKeyState(shared, config) {
940
- const {
941
- api,
942
- key,
943
- codecOpt,
944
- schema,
945
- onMount,
946
- onChange,
947
- listenCrossTab,
948
- getFallback,
949
- getServerValue,
950
- developmentRuntime,
951
- contractFingerprint,
952
- hasMounted,
953
- setHasMounted,
954
- hydrationMode,
955
- ssrOptions
956
- } = shared;
957
- const { decodeForRead, encodeForWrite, additionalDevWarnings, onDecodedEffect } = config;
958
- const getServerRawSnapshot = react.useCallback(
959
- () => ssrOptions?.serverValue === void 0 ? null : SSR_SNAPSHOT_TOKEN,
960
- [ssrOptions?.serverValue]
961
- );
962
- const deferStorageRead = hydrationMode === "client-only" && !hasMounted;
963
- const subscribe = react.useCallback(
964
- (listener) => {
965
- if (deferStorageRead) {
966
- return () => void 0;
1421
+ get: (key) => {
1422
+ const raw = readThrough(key);
1423
+ if (raw == null) return void 0;
1424
+ return decodeDevToolsValue(raw);
1425
+ },
1426
+ set: (key, value) => {
1427
+ writeRaw(key, JSON.stringify(value));
1428
+ },
1429
+ remove: (key) => removeRaw(key),
1430
+ clear: () => {
1431
+ for (const key of keys()) {
1432
+ removeRaw(key);
967
1433
  }
968
- return api.subscribeRaw(key, listener);
969
1434
  },
970
- [api, deferStorageRead, key]
971
- );
972
- const raw = react.useSyncExternalStore(
973
- subscribe,
974
- () => deferStorageRead ? getServerRawSnapshot() : api.getRawSnapshot(key),
975
- getServerRawSnapshot
976
- );
977
- const decoded = react.useMemo(() => {
978
- if (raw === SSR_SNAPSHOT_TOKEN) {
979
- return withReadMetadata(getServerValue());
980
- }
981
- return decodeForRead(raw);
982
- }, [decodeForRead, getServerValue, raw]);
983
- const value = decoded.value;
984
- react.useEffect(() => {
985
- if (!developmentRuntime) return;
986
- const globalWindow = globalThis.window;
987
- if (listenCrossTab && (api.crossTabSyncMode ?? "none") === "none" && globalWindow !== void 0) {
988
- warnOnce(
989
- api,
990
- `listenCrossTab:${key}`,
991
- `[Mnemonic] useMnemonicKey("${key}") enabled listenCrossTab, but the active storage backend may not be able to notify external changes. If you're using a custom Storage-like wrapper around localStorage, ensure it forwards browser "storage" events or implements storage.onExternalChange(...); otherwise, use localStorage or implement storage.onExternalChange(...) on your custom backend.`
992
- );
993
- }
994
- additionalDevWarnings?.({
995
- api,
996
- key,
997
- listenCrossTab,
998
- codecOpt,
999
- schema,
1000
- warnOnce: (id, message) => warnOnce(api, id, message)
1435
+ keys
1436
+ };
1437
+ }
1438
+ function createReloadFromStorage({
1439
+ storage,
1440
+ hasAsyncContractViolation,
1441
+ prefix,
1442
+ listeners,
1443
+ cache,
1444
+ emit,
1445
+ callbacks,
1446
+ devToolsRoot,
1447
+ namespace
1448
+ }) {
1449
+ return (changedKeys) => {
1450
+ if (!storage || hasAsyncContractViolation()) return;
1451
+ if (changedKeys?.length === 0) return;
1452
+ const isFullReload = changedKeys === void 0;
1453
+ const changed = isFullReload ? reloadSubscribedKeysFromStorage({
1454
+ prefix,
1455
+ storage,
1456
+ listeners,
1457
+ cache,
1458
+ emit,
1459
+ callbacks
1460
+ }) : reloadNamedKeysFromStorage({
1461
+ changedKeys,
1462
+ prefix,
1463
+ storage,
1464
+ listeners,
1465
+ cache,
1466
+ emit,
1467
+ callbacks
1001
1468
  });
1002
- let keyContracts = diagnosticContractRegistry.get(api);
1003
- if (!keyContracts) {
1004
- keyContracts = /* @__PURE__ */ new Map();
1005
- diagnosticContractRegistry.set(api, keyContracts);
1006
- }
1007
- if (contractFingerprint === null) {
1008
- return;
1009
- }
1010
- const previousContract = keyContracts.get(key);
1011
- if (previousContract === void 0) {
1012
- keyContracts.set(key, contractFingerprint);
1013
- return;
1014
- }
1015
- if (previousContract === contractFingerprint) {
1016
- return;
1469
+ if (changed) {
1470
+ bumpDevToolsVersion(devToolsRoot, namespace, isFullReload ? "reload:full" : "reload:granular");
1017
1471
  }
1018
- warnOnce(
1019
- api,
1020
- `contract-conflict:${key}`,
1021
- `[Mnemonic] Conflicting useMnemonicKey contracts detected for key "${key}" in namespace "${api.prefix.slice(0, -1)}". Reuse a shared descriptor with defineMnemonicKey(...) or align defaultValue/codec/schema/reconcile options so every consumer describes the same persisted contract.`
1472
+ };
1473
+ }
1474
+ function registerDevToolsProvider({
1475
+ devToolsRoot,
1476
+ namespace,
1477
+ store,
1478
+ dump,
1479
+ keys,
1480
+ readThrough,
1481
+ writeRaw,
1482
+ removeRaw
1483
+ }) {
1484
+ let infoMessage = `[Mnemonic DevTools] Namespace "${namespace}" available via window.__REACT_MNEMONIC_DEVTOOLS__.resolve("${namespace}")`;
1485
+ if (!devToolsRoot.capabilities.weakRef) {
1486
+ console.info(
1487
+ `[Mnemonic DevTools] WeakRef is not available; registry provider "${namespace}" was not registered.`
1022
1488
  );
1023
- }, [
1024
- additionalDevWarnings,
1025
- api,
1026
- key,
1027
- developmentRuntime,
1028
- contractFingerprint,
1029
- listenCrossTab,
1030
- codecOpt,
1031
- schema,
1032
- api.crossTabSyncMode
1033
- ]);
1034
- react.useEffect(() => {
1035
- if (hasMounted) return;
1036
- setHasMounted(true);
1037
- }, [hasMounted, setHasMounted]);
1038
- react.useEffect(() => {
1039
- if (decoded.rewriteRaw && decoded.rewriteRaw !== raw) {
1040
- api.setRaw(key, decoded.rewriteRaw);
1489
+ return;
1490
+ }
1491
+ const existingLive = devToolsRoot.resolve(namespace);
1492
+ if (existingLive) {
1493
+ const duplicateMessage = `[Mnemonic DevTools] Duplicate provider namespace "${namespace}" detected. Each window must have at most one live MnemonicProvider per namespace.`;
1494
+ if (!isProductionRuntime()) {
1495
+ throw new Error(duplicateMessage);
1041
1496
  }
1042
- }, [api, decoded.rewriteRaw, key, raw]);
1043
- react.useEffect(() => {
1044
- onDecodedEffect?.(decoded);
1045
- }, [decoded, onDecodedEffect]);
1046
- const prevRef = react.useRef(value);
1047
- const mounted = react.useRef(false);
1048
- react.useEffect(() => {
1049
- if (mounted.current) return;
1050
- mounted.current = true;
1051
- onMount?.(value);
1052
- prevRef.current = value;
1053
- }, []);
1054
- react.useEffect(() => {
1055
- const prev = prevRef.current;
1056
- if (Object.is(prev, value)) return;
1057
- prevRef.current = value;
1058
- onChange?.(value, prev);
1059
- }, [value, onChange]);
1060
- react.useEffect(() => {
1061
- if (!listenCrossTab) return;
1062
- const globalWindow = globalThis.window;
1063
- if (globalWindow === void 0) return;
1064
- const storageKey = api.prefix + key;
1065
- const handler = (e) => {
1066
- if (e.key === null) {
1067
- api.removeRaw(key);
1068
- return;
1497
+ console.warn(`${duplicateMessage} Keeping the first provider and ignoring the duplicate.`);
1498
+ console.info(
1499
+ `[Mnemonic DevTools] Namespace "${namespace}" already registered. Keeping existing provider reference.`
1500
+ );
1501
+ return;
1502
+ }
1503
+ const providerApi = createDevToolsProviderApi({
1504
+ store,
1505
+ dump,
1506
+ keys,
1507
+ readThrough,
1508
+ writeRaw,
1509
+ removeRaw
1510
+ });
1511
+ const WeakRefCtor = weakRefConstructor();
1512
+ if (!WeakRefCtor) {
1513
+ console.info(`[Mnemonic DevTools] WeakRef became unavailable while registering "${namespace}".`);
1514
+ return;
1515
+ }
1516
+ store.__devToolsProviderApiHold = providerApi;
1517
+ const now = Date.now();
1518
+ devToolsRoot.providers[namespace] = {
1519
+ namespace,
1520
+ weakRef: new WeakRefCtor(providerApi),
1521
+ registeredAt: now,
1522
+ lastSeenAt: now,
1523
+ staleSince: null
1524
+ };
1525
+ bumpDevToolsVersion(devToolsRoot, namespace, "registry:namespace-registered");
1526
+ console.info(infoMessage);
1527
+ }
1528
+ function MnemonicProvider({
1529
+ children,
1530
+ namespace,
1531
+ storage,
1532
+ enableDevTools = false,
1533
+ schemaMode = "default",
1534
+ schemaRegistry,
1535
+ ssr
1536
+ }) {
1537
+ if (schemaMode === "strict" && !schemaRegistry) {
1538
+ throw new Error("MnemonicProvider strict mode requires schemaRegistry");
1539
+ }
1540
+ if (schemaMode === "autoschema" && typeof schemaRegistry?.registerSchema !== "function") {
1541
+ throw new Error("MnemonicProvider autoschema mode requires schemaRegistry.registerSchema");
1542
+ }
1543
+ const store = react.useMemo(() => {
1544
+ const prefix = `${namespace}.`;
1545
+ const st = storage ?? defaultBrowserStorage();
1546
+ const ssrHydration = ssr?.hydration ?? "immediate";
1547
+ const devToolsRoot = ensureDevToolsRoot(enableDevTools);
1548
+ const canEnumerateKeys = detectEnumerableStorage(st);
1549
+ const crossTabSyncMode = getCrossTabSyncMode(storage, st);
1550
+ const cache = /* @__PURE__ */ new Map();
1551
+ const listeners = /* @__PURE__ */ new Map();
1552
+ let quotaErrorLogged = false;
1553
+ let accessErrorLogged = false;
1554
+ let asyncContractViolationDetected = false;
1555
+ const storageAccessCallbacks = {
1556
+ onAccessError: (err) => logAccessError(err),
1557
+ onAccessSuccess: () => {
1558
+ accessErrorLogged = false;
1559
+ },
1560
+ onAsyncViolation: (method, thenable) => handleAsyncStorageContractViolation(method, thenable)
1561
+ };
1562
+ const fullKey = (key) => prefix + key;
1563
+ const emit = (key) => {
1564
+ const set = listeners.get(key);
1565
+ if (!set) return;
1566
+ for (const fn of set) fn();
1567
+ };
1568
+ const logAccessError = (err) => {
1569
+ if (!accessErrorLogged && err instanceof DOMException && err.name !== "QuotaExceededError") {
1570
+ console.error(
1571
+ `[Mnemonic] Storage access error (${err.name}): ${err.message}. Data is cached in memory but may not persist.`
1572
+ );
1573
+ accessErrorLogged = true;
1069
1574
  }
1070
- if (e.key !== storageKey) return;
1071
- if (e.newValue == null) {
1072
- api.removeRaw(key);
1073
- return;
1575
+ };
1576
+ const handleAsyncStorageContractViolation = (method, thenable) => {
1577
+ asyncContractViolationDetected = true;
1578
+ void Promise.resolve(thenable).catch(() => void 0);
1579
+ if (accessErrorLogged) return;
1580
+ console.error(
1581
+ `[Mnemonic] StorageLike.${method} returned a Promise. StorageLike must remain synchronous for react-mnemonic v1. Wrap async persistence behind a synchronous cache facade instead.`
1582
+ );
1583
+ accessErrorLogged = true;
1584
+ };
1585
+ const readThrough = (key) => {
1586
+ if (cache.has(key)) return cache.get(key) ?? null;
1587
+ if (!st || asyncContractViolationDetected) {
1588
+ cache.set(key, null);
1589
+ return null;
1074
1590
  }
1075
- api.setRaw(key, e.newValue);
1591
+ const raw = readStorageRaw(st, fullKey(key), storageAccessCallbacks);
1592
+ cache.set(key, raw);
1593
+ return raw;
1076
1594
  };
1077
- globalWindow.addEventListener("storage", handler);
1078
- return () => globalWindow.removeEventListener("storage", handler);
1079
- }, [listenCrossTab, api, key]);
1080
- const set = react.useMemo(() => {
1081
- return (next) => {
1082
- const nextVal = typeof next === "function" ? next(decodeForRead(api.getRawSnapshot(key)).value) : next;
1083
- try {
1084
- const encoded = encodeForWrite(nextVal);
1085
- api.setRaw(key, encoded);
1086
- } catch (err) {
1087
- if (err instanceof SchemaError) {
1088
- console.error(`[Mnemonic] Schema error for key "${key}" (${err.code}):`, err.message);
1089
- return;
1090
- }
1091
- if (err instanceof CodecError) {
1092
- console.error(`[Mnemonic] Codec encode error for key "${key}":`, err.message);
1093
- return;
1595
+ const writeRaw = (key, raw) => {
1596
+ cache.set(key, raw);
1597
+ if (st && !asyncContractViolationDetected) {
1598
+ try {
1599
+ const result = st.setItem(fullKey(key), raw);
1600
+ if (isPromiseLike(result)) {
1601
+ handleAsyncStorageContractViolation("setItem", result);
1602
+ } else {
1603
+ quotaErrorLogged = false;
1604
+ accessErrorLogged = false;
1605
+ }
1606
+ } catch (err) {
1607
+ if (!quotaErrorLogged && err instanceof DOMException && err.name === "QuotaExceededError") {
1608
+ console.error(
1609
+ `[Mnemonic] Storage quota exceeded writing key "${key}". Data is cached in memory but will not persist.`
1610
+ );
1611
+ quotaErrorLogged = true;
1612
+ }
1613
+ logAccessError(err);
1094
1614
  }
1095
- console.error(`[Mnemonic] Failed to persist key "${key}":`, err);
1096
1615
  }
1616
+ emit(key);
1617
+ bumpDevToolsVersion(devToolsRoot, namespace, `set:${key}`);
1097
1618
  };
1098
- }, [api, key, decodeForRead, encodeForWrite]);
1099
- const reset = react.useMemo(() => {
1100
- return () => {
1101
- const v = getFallback();
1102
- try {
1103
- const encoded = encodeForWrite(v);
1104
- api.setRaw(key, encoded);
1105
- } catch (err) {
1106
- if (err instanceof SchemaError) {
1107
- console.error(`[Mnemonic] Schema error for key "${key}" (${err.code}):`, err.message);
1108
- return;
1109
- }
1110
- if (err instanceof CodecError) {
1111
- console.error(`[Mnemonic] Codec encode error for key "${key}":`, err.message);
1619
+ const removeRaw = (key) => {
1620
+ cache.set(key, null);
1621
+ if (st && !asyncContractViolationDetected) {
1622
+ try {
1623
+ const result = st.removeItem(fullKey(key));
1624
+ if (isPromiseLike(result)) {
1625
+ handleAsyncStorageContractViolation("removeItem", result);
1626
+ } else {
1627
+ accessErrorLogged = false;
1628
+ }
1629
+ } catch (err) {
1630
+ logAccessError(err);
1112
1631
  }
1113
- return;
1114
1632
  }
1633
+ emit(key);
1634
+ bumpDevToolsVersion(devToolsRoot, namespace, `remove:${key}`);
1115
1635
  };
1116
- }, [api, key, getFallback, encodeForWrite]);
1117
- const remove = react.useMemo(() => {
1118
- return () => api.removeRaw(key);
1119
- }, [api, key]);
1120
- return react.useMemo(
1121
- () => ({
1122
- value,
1123
- set,
1124
- reset,
1125
- remove
1636
+ const subscribeRaw = (key, listener) => {
1637
+ let set = listeners.get(key);
1638
+ if (!set) {
1639
+ set = /* @__PURE__ */ new Set();
1640
+ listeners.set(key, set);
1641
+ }
1642
+ set.add(listener);
1643
+ readThrough(key);
1644
+ return () => {
1645
+ const s = listeners.get(key);
1646
+ if (!s) return;
1647
+ s.delete(listener);
1648
+ if (s.size === 0) listeners.delete(key);
1649
+ };
1650
+ };
1651
+ const getRawSnapshot = (key) => readThrough(key);
1652
+ const keys = () => {
1653
+ if (asyncContractViolationDetected) {
1654
+ return Array.from(cache.entries()).filter(([, value]) => value != null).map(([key]) => key);
1655
+ }
1656
+ if (!canEnumerateKeys) return [];
1657
+ return enumerateNamespaceKeys(st, prefix, storageAccessCallbacks);
1658
+ };
1659
+ const dump = () => {
1660
+ const out = {};
1661
+ for (const k of keys()) {
1662
+ const raw = readThrough(k);
1663
+ if (raw != null) out[k] = raw;
1664
+ }
1665
+ return out;
1666
+ };
1667
+ const reloadFromStorage = createReloadFromStorage({
1668
+ storage: st,
1669
+ hasAsyncContractViolation: () => asyncContractViolationDetected,
1670
+ prefix,
1671
+ listeners,
1672
+ cache,
1673
+ emit,
1674
+ callbacks: storageAccessCallbacks,
1675
+ devToolsRoot,
1676
+ namespace
1677
+ });
1678
+ const store2 = {
1679
+ prefix,
1680
+ canEnumerateKeys,
1681
+ subscribeRaw,
1682
+ getRawSnapshot,
1683
+ setRaw: writeRaw,
1684
+ removeRaw,
1685
+ keys,
1686
+ dump,
1687
+ reloadFromStorage,
1688
+ schemaMode,
1689
+ ssrHydration,
1690
+ crossTabSyncMode,
1691
+ ...schemaRegistry ? { schemaRegistry } : {}
1692
+ };
1693
+ if (devToolsRoot) {
1694
+ registerDevToolsProvider({
1695
+ devToolsRoot,
1696
+ namespace,
1697
+ store: store2,
1698
+ dump,
1699
+ keys,
1700
+ readThrough,
1701
+ writeRaw,
1702
+ removeRaw
1703
+ });
1704
+ }
1705
+ return store2;
1706
+ }, [namespace, storage, enableDevTools, schemaMode, schemaRegistry, ssr?.hydration]);
1707
+ const optionalBridge = react.useMemo(
1708
+ () => createMnemonicOptionalBridge({
1709
+ api: store,
1710
+ ...schemaRegistry ? { schemaRegistry } : {}
1126
1711
  }),
1127
- [value, set, reset, remove]
1712
+ [schemaRegistry, store]
1713
+ );
1714
+ react.useEffect(() => {
1715
+ if (!storage?.onExternalChange) return;
1716
+ return storage.onExternalChange((changedKeys) => store.reloadFromStorage(changedKeys));
1717
+ }, [storage, store]);
1718
+ return /* @__PURE__ */ jsxRuntime.jsx(MnemonicContext.Provider, { value: store, children: /* @__PURE__ */ jsxRuntime.jsx(MnemonicOptionalBridgeProvider, { bridge: optionalBridge, children }) });
1719
+ }
1720
+ function throwCoreProviderSchemaImportError(propName) {
1721
+ throw new Error(
1722
+ `[Mnemonic] MnemonicProvider from react-mnemonic/core does not support ${propName}. Import MnemonicProvider from "react-mnemonic/schema" or "react-mnemonic" for schema validation, autoschema, and migration support.`
1128
1723
  );
1129
1724
  }
1130
-
1131
- // src/Mnemonic/use-core.ts
1725
+ function assertNoSchemaProps(props) {
1726
+ if (props.schemaMode !== void 0) {
1727
+ throwCoreProviderSchemaImportError("schemaMode");
1728
+ }
1729
+ if (props.schemaRegistry !== void 0) {
1730
+ throwCoreProviderSchemaImportError("schemaRegistry");
1731
+ }
1732
+ }
1733
+ function MnemonicProvider2(props) {
1734
+ assertNoSchemaProps(props);
1735
+ return /* @__PURE__ */ jsxRuntime.jsx(MnemonicProvider, { ...props });
1736
+ }
1132
1737
  function throwCoreSchemaImportError(key) {
1133
1738
  throw new Error(
1134
1739
  `[Mnemonic] useMnemonicKey("${key}") requested schema features from react-mnemonic/core. Import useMnemonicKey from "react-mnemonic/schema" or "react-mnemonic" for schema validation, autoschema, and migration support.`
1135
1740
  );
1136
1741
  }
1137
- function useCoreRuntime(keyOrDescriptor, options) {
1138
- const shared = useMnemonicKeyShared(keyOrDescriptor, options);
1139
- const { api, key, codec, schema, reconcile, parseEnvelope, decodeStringPayload, buildFallbackResult } = shared;
1140
- if (schema?.version !== void 0 || api.schemaMode !== "default") {
1742
+ function useCoreRuntimeFromApi(api, keyOrDescriptor, options, active = true) {
1743
+ const shared = useMnemonicKeySharedFromApi(api, keyOrDescriptor, options);
1744
+ const { key, codec, schema, reconcile, parseEnvelope: parseEnvelope2, decodeStringPayload: decodeStringPayload2, buildFallbackResult: buildFallbackResult2 } = shared;
1745
+ if (active && (schema?.version !== void 0 || api.schemaMode !== "default")) {
1141
1746
  throwCoreSchemaImportError(key);
1142
1747
  }
1143
1748
  const applyReconcile = useApplyReconcile({
1144
1749
  key,
1145
1750
  reconcile,
1146
- buildFallbackResult
1751
+ buildFallbackResult: buildFallbackResult2
1147
1752
  });
1148
1753
  const encodeForWrite = react.useCallback(
1149
1754
  (nextValue) => {
@@ -1153,9 +1758,9 @@ function useCoreRuntime(keyOrDescriptor, options) {
1153
1758
  );
1154
1759
  const decodeForRead = react.useCallback(
1155
1760
  (rawText) => {
1156
- if (rawText == null) return buildFallbackResult();
1157
- const parsed = parseEnvelope(rawText);
1158
- if (!parsed.ok) return buildFallbackResult(parsed.error);
1761
+ if (rawText == null) return buildFallbackResult2();
1762
+ const parsed = parseEnvelope2(rawText);
1763
+ if (!parsed.ok) return buildFallbackResult2(parsed.error);
1159
1764
  const envelope = parsed.envelope;
1160
1765
  if (typeof envelope.payload !== "string") {
1161
1766
  return applyReconcile({
@@ -1165,17 +1770,17 @@ function useCoreRuntime(keyOrDescriptor, options) {
1165
1770
  });
1166
1771
  }
1167
1772
  try {
1168
- const decoded = decodeStringPayload(envelope.payload, codec);
1773
+ const decoded = decodeStringPayload2(envelope.payload, codec);
1169
1774
  return applyReconcile({
1170
1775
  value: decoded,
1171
1776
  persistedVersion: envelope.version,
1172
1777
  serializeForPersist: encodeForWrite
1173
1778
  });
1174
1779
  } catch (err) {
1175
- return buildFallbackResult(err);
1780
+ return buildFallbackResult2(err);
1176
1781
  }
1177
1782
  },
1178
- [applyReconcile, buildFallbackResult, codec, decodeStringPayload, encodeForWrite, parseEnvelope]
1783
+ [applyReconcile, buildFallbackResult2, codec, decodeStringPayload2, encodeForWrite, parseEnvelope2]
1179
1784
  );
1180
1785
  const additionalDevWarnings = react.useCallback(
1181
1786
  ({ warnOnce: warnOnce2 }) => {
@@ -1188,11 +1793,15 @@ function useCoreRuntime(keyOrDescriptor, options) {
1188
1793
  [api.schemaRegistry, key]
1189
1794
  );
1190
1795
  return useMnemonicKeyState(shared, {
1796
+ active,
1191
1797
  decodeForRead,
1192
1798
  encodeForWrite,
1193
1799
  additionalDevWarnings
1194
1800
  });
1195
1801
  }
1802
+ function useCoreRuntime(keyOrDescriptor, options) {
1803
+ return useCoreRuntimeFromApi(useMnemonic(), keyOrDescriptor, options);
1804
+ }
1196
1805
  function useMnemonicKey(keyOrDescriptor, options) {
1197
1806
  return useCoreRuntime(keyOrDescriptor, options);
1198
1807
  }
@@ -1214,11 +1823,17 @@ function warnRecoveryOnce(api, id, message) {
1214
1823
  console.warn(message);
1215
1824
  }
1216
1825
  function useMnemonicRecovery(options = {}) {
1217
- const api = useMnemonic();
1826
+ return useMnemonicRecoveryFromApi(useMnemonic(), options);
1827
+ }
1828
+ function useMnemonicRecoveryFromApi(api, options = {}, active = true) {
1218
1829
  const { onRecover } = options;
1219
- const namespace = react.useMemo(() => api.prefix.endsWith(".") ? api.prefix.slice(0, -1) : api.prefix, [api.prefix]);
1830
+ const namespace = react.useMemo(() => {
1831
+ if (!active) return "";
1832
+ return api.prefix.endsWith(".") ? api.prefix.slice(0, -1) : api.prefix;
1833
+ }, [active, api.prefix]);
1220
1834
  const emitRecovery = react.useCallback(
1221
1835
  (action, clearedKeys) => {
1836
+ if (!active) return;
1222
1837
  const event = {
1223
1838
  action,
1224
1839
  namespace,
@@ -1226,11 +1841,12 @@ function useMnemonicRecovery(options = {}) {
1226
1841
  };
1227
1842
  onRecover?.(event);
1228
1843
  },
1229
- [namespace, onRecover]
1844
+ [active, namespace, onRecover]
1230
1845
  );
1231
- const listKeys = react.useCallback(() => api.keys(), [api]);
1846
+ const listKeys = react.useCallback(() => active ? api.keys() : [], [active, api]);
1232
1847
  const clearResolvedKeys = react.useCallback(
1233
1848
  (action, keys) => {
1849
+ if (!active) return [];
1234
1850
  const clearedKeys = uniqueKeys(keys);
1235
1851
  for (const key of clearedKeys) {
1236
1852
  api.removeRaw(key);
@@ -1238,13 +1854,14 @@ function useMnemonicRecovery(options = {}) {
1238
1854
  emitRecovery(action, clearedKeys);
1239
1855
  return clearedKeys;
1240
1856
  },
1241
- [api, emitRecovery]
1857
+ [active, api, emitRecovery]
1242
1858
  );
1243
1859
  const clearKeys = react.useCallback(
1244
1860
  (keys) => clearResolvedKeys("clear-keys", keys),
1245
1861
  [clearResolvedKeys]
1246
1862
  );
1247
1863
  const clearAll = react.useCallback(() => {
1864
+ if (!active) return [];
1248
1865
  if (!api.canEnumerateKeys) {
1249
1866
  if (isDevelopmentRuntime2()) {
1250
1867
  warnRecoveryOnce(
@@ -1258,9 +1875,10 @@ function useMnemonicRecovery(options = {}) {
1258
1875
  );
1259
1876
  }
1260
1877
  return clearResolvedKeys("clear-all", api.keys());
1261
- }, [api, clearResolvedKeys, namespace]);
1878
+ }, [active, api, clearResolvedKeys, namespace]);
1262
1879
  const clearMatching = react.useCallback(
1263
1880
  (predicate) => {
1881
+ if (!active) return [];
1264
1882
  if (!api.canEnumerateKeys) {
1265
1883
  if (isDevelopmentRuntime2()) {
1266
1884
  warnRecoveryOnce(
@@ -1278,18 +1896,18 @@ function useMnemonicRecovery(options = {}) {
1278
1896
  api.keys().filter((key) => predicate(key))
1279
1897
  );
1280
1898
  },
1281
- [api, clearResolvedKeys, namespace]
1899
+ [active, api, clearResolvedKeys, namespace]
1282
1900
  );
1283
1901
  return react.useMemo(
1284
1902
  () => ({
1285
1903
  namespace,
1286
- canEnumerateKeys: api.canEnumerateKeys,
1904
+ canEnumerateKeys: active ? api.canEnumerateKeys : false,
1287
1905
  listKeys,
1288
1906
  clearAll,
1289
1907
  clearKeys,
1290
1908
  clearMatching
1291
1909
  }),
1292
- [namespace, api.canEnumerateKeys, listKeys, clearAll, clearKeys, clearMatching]
1910
+ [namespace, active, api.canEnumerateKeys, listKeys, clearAll, clearKeys, clearMatching]
1293
1911
  );
1294
1912
  }
1295
1913