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