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/schema.js CHANGED
@@ -1,1487 +1,1726 @@
1
- import { createContext, useMemo, useEffect, useCallback, useContext, useState, useSyncExternalStore, useRef } from 'react';
1
+ import { createContext, useMemo, useEffect, useCallback, 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/runtime.ts
7
- function getGlobalProcess() {
8
- return globalThis.process;
9
- }
10
- function getRuntimeNodeEnv() {
11
- const runtimeProcess = getGlobalProcess();
12
- if (runtimeProcess?.env?.NODE_ENV !== void 0) {
13
- return runtimeProcess.env.NODE_ENV;
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);
14
19
  }
15
- return void 0;
16
- }
17
- function getNativeBrowserStorages() {
18
- const globalWindow = globalThis.window;
19
- if (!globalWindow) return [];
20
- const storages = [];
21
- const addStorage = (getter) => {
22
- try {
23
- storages.push(getter());
24
- } catch {
25
- }
26
- };
27
- addStorage(() => globalWindow.localStorage);
28
- addStorage(() => globalWindow.sessionStorage);
29
- return storages;
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 };
30
27
  }
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");
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;
36
48
  }
37
- return context;
38
49
  }
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;
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;
46
54
  }
55
+ return true;
47
56
  }
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;
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;
54
64
  }
65
+ return true;
55
66
  }
56
- function isProductionRuntime() {
57
- const env = getRuntimeNodeEnv();
58
- if (env === void 0) {
59
- return true;
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);
60
74
  }
61
- return env === "production";
75
+ if (typeof a === "object") {
76
+ if (Array.isArray(b)) return false;
77
+ return jsonDeepEqualObject(a, b);
78
+ }
79
+ return false;
62
80
  }
63
- function weakRefConstructor() {
64
- const ctor = globalThis.WeakRef;
65
- return typeof ctor === "function" ? ctor : null;
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;
66
88
  }
67
- function hasFinalizationRegistry() {
68
- return typeof globalThis.FinalizationRegistry === "function";
89
+ function isJsonPrimitive(value) {
90
+ return value === null || typeof value !== "object";
69
91
  }
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";
92
+ function isJsonObjectRecord(value) {
93
+ return typeof value === "object" && value !== null && !Array.isArray(value);
74
94
  }
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";
79
- }
80
- if (typeof activeStorage?.onExternalChange === "function") {
81
- return "custom-external-change";
95
+ function objectHasOwn(value, property) {
96
+ const hasOwn = Object.hasOwn;
97
+ if (typeof hasOwn === "function") {
98
+ return hasOwn(value, property);
82
99
  }
83
- return "none";
84
- }
85
- function getDevToolsWindow() {
86
- return globalThis.window;
100
+ return Object.getOwnPropertyDescriptor(value, property) !== void 0;
87
101
  }
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 {
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);
97
122
  }
123
+ return errors;
124
+ };
125
+ }
126
+ function buildTypeValidationStep(schema) {
127
+ if (schema.type === void 0) {
128
+ return null;
98
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
+ };
99
143
  }
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 = {};
144
+ function buildEnumValidationStep(schema) {
145
+ if (schema.enum === void 0) {
146
+ return null;
111
147
  }
112
- if (!root.capabilities || typeof root.capabilities !== "object") {
113
- root.capabilities = {};
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;
114
166
  }
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
- };
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;
124
185
  }
125
- const meta = root.__meta;
126
- if (typeof meta.version !== "number" || !Number.isFinite(meta.version)) {
127
- meta.version = 0;
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;
128
229
  }
129
- if (typeof meta.lastUpdated !== "number" || !Number.isFinite(meta.lastUpdated)) {
130
- meta.lastUpdated = Date.now();
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));
131
265
  }
132
- if (typeof meta.lastChange !== "string") {
133
- meta.lastChange = "";
266
+ if (propertyValidators !== null) {
267
+ objectValidationSteps.push(createDeclaredPropertyStep(propertyValidators));
134
268
  }
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
- };
269
+ if (checkAdditional) {
270
+ objectValidationSteps.push(
271
+ createAdditionalPropertyStep({
272
+ additionalIsFalse,
273
+ additionalValidator,
274
+ definedPropKeys: definedPropKeys ?? /* @__PURE__ */ new Set()
275
+ })
276
+ );
149
277
  }
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));
278
+ if (objectValidationSteps.length === 0) {
279
+ return null;
168
280
  }
169
- globalWindow.__REACT_MNEMONIC_DEVTOOLS__ = root;
170
- return root;
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
+ };
171
289
  }
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}`;
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
+ };
177
303
  }
178
- function decodeDevToolsValue(raw) {
179
- try {
180
- return JSON.parse(raw);
181
- } catch {
182
- return raw;
183
- }
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
+ };
184
313
  }
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;
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}`));
192
333
  }
193
- callbacks.onAccessSuccess();
194
- return raw;
195
- } catch (error) {
196
- callbacks.onAccessError(error);
197
- return null;
198
- }
334
+ };
199
335
  }
200
- function enumerateNamespaceKeys(storage, prefix, callbacks) {
201
- if (!storage) {
202
- return [];
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;
203
342
  }
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 [];
343
+ const minItems = schema.minItems;
344
+ const maxItems = schema.maxItems;
345
+ return (value, path, errors) => {
346
+ if (!Array.isArray(value)) {
347
+ return;
210
348
  }
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));
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
+ });
215
355
  }
216
- callbacks.onAccessSuccess();
217
- } catch (error) {
218
- callbacks.onAccessError(error);
219
- }
220
- return keys;
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
+ };
221
370
  }
222
- function syncCacheEntryFromStorage({
223
- 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;
371
+ function validateJsonSchema(value, schema, path = "") {
372
+ const compiled = compileSchema(schema);
373
+ return compiled(value, path);
238
374
  }
239
- function reloadNamedKeysFromStorage({
240
- changedKeys,
241
- prefix,
242
- storage,
243
- listeners,
244
- cache,
245
- emit,
246
- callbacks
247
- }) {
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;
375
+ function jsonTypeLabel(value) {
376
+ if (value === null) return "null";
377
+ if (Array.isArray(value)) return "array";
378
+ return typeof value;
269
379
  }
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;
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 {};
289
394
  }
290
- for (const key of cache.keys()) {
291
- const listenerSet = listeners.get(key);
292
- if (listenerSet && listenerSet.size > 0) continue;
293
- cache.delete(key);
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);
294
412
  }
295
- return changed;
413
+ };
414
+
415
+ // src/Mnemonic/runtime.ts
416
+ function getGlobalProcess() {
417
+ return globalThis.process;
296
418
  }
297
- function createDevToolsProviderApi({
298
- store,
299
- dump,
300
- keys,
301
- readThrough,
302
- writeRaw,
303
- removeRaw
304
- }) {
305
- 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));
325
- },
326
- remove: (key) => removeRaw(key),
327
- clear: () => {
328
- for (const key of keys()) {
329
- removeRaw(key);
330
- }
331
- },
332
- keys
333
- };
419
+ function getRuntimeNodeEnv() {
420
+ const runtimeProcess = getGlobalProcess();
421
+ if (runtimeProcess?.env?.NODE_ENV !== void 0) {
422
+ return runtimeProcess.env.NODE_ENV;
423
+ }
424
+ return void 0;
334
425
  }
335
- function createReloadFromStorage({
336
- storage,
337
- hasAsyncContractViolation,
338
- prefix,
339
- listeners,
340
- cache,
341
- emit,
342
- callbacks,
343
- devToolsRoot,
344
- namespace
345
- }) {
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
365
- });
366
- if (changed) {
367
- bumpDevToolsVersion(devToolsRoot, namespace, isFullReload ? "reload:full" : "reload:granular");
426
+ function getNativeBrowserStorages() {
427
+ const globalWindow = globalThis.window;
428
+ if (!globalWindow) return [];
429
+ const storages = [];
430
+ const addStorage = (getter) => {
431
+ try {
432
+ storages.push(getter());
433
+ } catch {
368
434
  }
369
435
  };
436
+ addStorage(() => globalWindow.localStorage);
437
+ addStorage(() => globalWindow.sessionStorage);
438
+ return storages;
370
439
  }
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);
393
- }
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.`
397
- );
398
- return;
399
- }
400
- const providerApi = createDevToolsProviderApi({
401
- store,
402
- dump,
403
- keys,
404
- readThrough,
405
- writeRaw,
406
- removeRaw
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
407
451
  });
408
- const WeakRefCtor = weakRefConstructor();
409
- if (!WeakRefCtor) {
410
- console.info(`[Mnemonic DevTools] WeakRef became unavailable while registering "${namespace}".`);
411
- return;
452
+ }
453
+ function withReadMetadata(value, rewriteRaw, extra) {
454
+ const result = { value };
455
+ if (extra !== void 0) {
456
+ Object.assign(result, extra);
412
457
  }
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);
458
+ if (rewriteRaw !== void 0) result.rewriteRaw = rewriteRaw;
459
+ return result;
424
460
  }
425
- function MnemonicProvider({
426
- children,
427
- namespace,
428
- storage,
429
- enableDevTools = false,
430
- schemaMode = "default",
431
- schemaRegistry,
432
- ssr
461
+ function isDevelopmentRuntime() {
462
+ return getRuntimeNodeEnv() === "development";
463
+ }
464
+ function getDiagnosticWarnings(api) {
465
+ let warnings = diagnosticWarningRegistry.get(api);
466
+ if (!warnings) {
467
+ warnings = /* @__PURE__ */ new Set();
468
+ diagnosticWarningRegistry.set(api, warnings);
469
+ }
470
+ return warnings;
471
+ }
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);
477
+ }
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}]`;
483
+ }
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;
495
+ }
496
+ }
497
+ function isObjectLike(value) {
498
+ return value !== null && (typeof value === "object" || typeof value === "function");
499
+ }
500
+ function objectHasOwn2(value, property) {
501
+ const hasOwn = Object.hasOwn;
502
+ if (typeof hasOwn === "function") {
503
+ return hasOwn(value, property);
504
+ }
505
+ return Object.getOwnPropertyDescriptor(value, property) !== void 0;
506
+ }
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;
513
+ }
514
+ function buildContractFingerprint({
515
+ api,
516
+ key,
517
+ defaultValue,
518
+ codecOpt,
519
+ schemaVersion,
520
+ reconcile,
521
+ listenCrossTab,
522
+ ssrOptions
433
523
  }) {
434
- if (schemaMode === "strict" && !schemaRegistry) {
435
- throw new Error("MnemonicProvider strict mode requires schemaRegistry");
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
+ });
537
+ }
538
+ function resolveMnemonicKeyArgs(keyOrDescriptor, options) {
539
+ if (typeof keyOrDescriptor !== "string") {
540
+ return keyOrDescriptor;
436
541
  }
437
- if (schemaMode === "autoschema" && typeof schemaRegistry?.registerSchema !== "function") {
438
- throw new Error("MnemonicProvider autoschema mode requires schemaRegistry.registerSchema");
542
+ if (!options) {
543
+ throw new Error("useMnemonicKey requires options when called with a string key");
439
544
  }
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;
545
+ return {
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
+ ...schemaVersion === void 0 ? {} : { schemaVersion },
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
+ };
471
619
  }
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.`
620
+ },
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);
629
+ }
630
+ },
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
660
+ };
661
+ }
662
+ function useApplyReconcile({
663
+ key,
664
+ reconcile,
665
+ buildFallbackResult: buildFallbackResult2
666
+ }) {
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.`
479
765
  );
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;
766
+ }
767
+ additionalDevWarnings?.({
768
+ api,
769
+ key,
770
+ listenCrossTab,
771
+ codecOpt,
772
+ schema,
773
+ warnOnce: (id, message) => warnOnce(api, id, message)
774
+ });
775
+ let keyContracts = diagnosticContractRegistry.get(api);
776
+ if (!keyContracts) {
777
+ keyContracts = /* @__PURE__ */ new Map();
778
+ diagnosticContractRegistry.set(api, keyContracts);
779
+ }
780
+ if (contractFingerprint === null) {
781
+ return;
782
+ }
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.`
795
+ );
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;
487
848
  }
488
- const raw = readStorageRaw(st, fullKey(key), storageAccessCallbacks);
489
- cache.set(key, raw);
490
- return raw;
491
- };
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);
511
- }
849
+ if (e.key !== storageKey) return;
850
+ if (e.newValue == null) {
851
+ api.removeRaw(key);
852
+ return;
512
853
  }
513
- emit(key);
514
- bumpDevToolsVersion(devToolsRoot, namespace, `set:${key}`);
854
+ api.setRaw(key, e.newValue);
515
855
  };
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);
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;
528
872
  }
873
+ if (err instanceof CodecError) {
874
+ console.error(`[Mnemonic] Codec encode error for key "${key}":`, err.message);
875
+ return;
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
- };
556
- const dump = () => {
557
- const out = {};
558
- for (const k of keys()) {
559
- const raw = readThrough(k);
560
- if (raw != null) out[k] = raw;
561
- }
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
879
  };
590
- if (devToolsRoot) {
591
- registerDevToolsProvider({
592
- devToolsRoot,
593
- namespace,
594
- store: store2,
595
- dump,
596
- keys,
597
- readThrough,
598
- writeRaw,
599
- removeRaw
600
- });
880
+ }, [active, api, key, decodeForRead, encodeForWrite]);
881
+ const reset = useMemo(() => {
882
+ if (!active) {
883
+ return () => void 0;
601
884
  }
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
-
611
- // src/Mnemonic/codecs.ts
612
- var CodecError = class extends Error {
613
- /**
614
- * Creates a new CodecError.
615
- *
616
- * @param message - Human-readable error description
617
- * @param cause - Optional underlying error that caused this failure
618
- */
619
- constructor(message, cause) {
620
- super(message);
621
- this.name = "CodecError";
622
- this.cause = cause;
623
- Object.setPrototypeOf(this, new.target.prototype);
624
- }
625
- };
626
- var JSONCodec = {
627
- encode: (value) => JSON.stringify(value),
628
- decode: (encoded) => JSON.parse(encoded)
629
- };
630
- function createCodec(encode, decode) {
631
- return { encode, decode };
632
- }
633
-
634
- // src/Mnemonic/schema.ts
635
- var SchemaError = class extends Error {
636
- /**
637
- * Creates a new SchemaError.
638
- *
639
- * @param code - Machine-readable failure category
640
- * @param message - Human-readable error description
641
- * @param cause - Optional underlying error
642
- */
643
- constructor(code, message, cause) {
644
- super(message);
645
- this.name = "SchemaError";
646
- this.code = code;
647
- this.cause = cause;
648
- Object.setPrototypeOf(this, new.target.prototype);
649
- }
650
- };
651
-
652
- // src/Mnemonic/json-schema.ts
653
- function matchesType(value, type) {
654
- switch (type) {
655
- case "string":
656
- return typeof value === "string";
657
- case "number":
658
- return typeof value === "number" && Number.isFinite(value);
659
- case "integer":
660
- return typeof value === "number" && Number.isInteger(value);
661
- case "boolean":
662
- return typeof value === "boolean";
663
- case "null":
664
- return value === null;
665
- case "object":
666
- return typeof value === "object" && value !== null && !Array.isArray(value);
667
- case "array":
668
- return Array.isArray(value);
669
- default:
670
- return false;
671
- }
672
- }
673
- function jsonDeepEqualArray(a, b) {
674
- if (a.length !== b.length) return false;
675
- for (let i = 0; i < a.length; i++) {
676
- if (!jsonDeepEqual(a[i], b[i])) return false;
677
- }
678
- return true;
679
- }
680
- function jsonDeepEqualObject(a, b) {
681
- const aKeys = Object.keys(a);
682
- const bKeys = Object.keys(b);
683
- if (aKeys.length !== bKeys.length) return false;
684
- for (const key of aKeys) {
685
- if (!objectHasOwn(b, key)) return false;
686
- if (!jsonDeepEqual(a[key], b[key])) return false;
687
- }
688
- return true;
689
- }
690
- function jsonDeepEqual(a, b) {
691
- if (a === b) return true;
692
- if (a === null || b === null) return false;
693
- if (typeof a !== typeof b) return false;
694
- if (Array.isArray(a)) {
695
- if (!Array.isArray(b)) return false;
696
- return jsonDeepEqualArray(a, b);
697
- }
698
- if (typeof a === "object") {
699
- if (Array.isArray(b)) return false;
700
- return jsonDeepEqualObject(a, b);
701
- }
702
- return false;
703
- }
704
- var compiledCache = /* @__PURE__ */ new WeakMap();
705
- function compileSchema(schema) {
706
- const cached = compiledCache.get(schema);
707
- if (cached) return cached;
708
- const compiled = buildValidator(schema);
709
- compiledCache.set(schema, compiled);
710
- return compiled;
711
- }
712
- function isJsonPrimitive(value) {
713
- return value === null || typeof value !== "object";
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;
899
+ }
900
+ };
901
+ }, [active, api, key, getFallback, encodeForWrite]);
902
+ const remove = useMemo(() => {
903
+ if (!active) {
904
+ return () => void 0;
905
+ }
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]
916
+ );
714
917
  }
715
- function isJsonObjectRecord(value) {
716
- return typeof value === "object" && value !== null && !Array.isArray(value);
918
+
919
+ // src/Mnemonic/optional-bridge-adapter.ts
920
+ function resolveOptionalDefaultValue(defaultValue) {
921
+ return typeof defaultValue === "function" ? defaultValue() : defaultValue;
717
922
  }
718
- function objectHasOwn(value, property) {
923
+ function objectHasOwn3(value, property) {
719
924
  const hasOwn = Object.hasOwn;
720
925
  if (typeof hasOwn === "function") {
721
926
  return hasOwn(value, property);
722
927
  }
723
928
  return Object.getOwnPropertyDescriptor(value, property) !== void 0;
724
929
  }
725
- function buildValidator(schema) {
726
- const typeStep = buildTypeValidationStep(schema);
727
- const validationSteps = [
728
- buildEnumValidationStep(schema),
729
- buildConstValidationStep(schema),
730
- buildNumberValidationStep(schema),
731
- buildStringValidationStep(schema),
732
- buildObjectValidationStep(schema),
733
- buildArrayValidationStep(schema)
734
- ].filter((step) => step !== null);
735
- if (typeStep === null && validationSteps.length === 0) {
736
- return (_value, _path) => [];
737
- }
738
- return (value, path = "") => {
739
- const errors = [];
740
- if (typeStep && !typeStep(value, path, errors)) {
741
- return errors;
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}"`);
742
935
  }
743
- for (const step of validationSteps) {
744
- step(value, path, errors);
936
+ return parsed;
937
+ } catch (error) {
938
+ if (error instanceof SchemaError) {
939
+ throw error;
745
940
  }
746
- return errors;
747
- };
941
+ throw new SchemaError("INVALID_ENVELOPE", `Invalid envelope for key "${key}"`, error);
942
+ }
748
943
  }
749
- function buildTypeValidationStep(schema) {
750
- if (schema.type === void 0) {
751
- return null;
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);
752
950
  }
753
- const resolvedTypes = Array.isArray(schema.type) ? schema.type : [schema.type];
754
- const typeLabel = JSON.stringify(schema.type);
755
- return (value, path, errors) => {
756
- if (resolvedTypes.some((type) => matchesType(value, type))) {
757
- return true;
758
- }
759
- errors.push({
760
- path,
761
- message: `Expected type ${typeLabel}, got ${jsonTypeLabel(value)}`,
762
- keyword: "type"
763
- });
764
- return false;
765
- };
766
951
  }
767
- function buildEnumValidationStep(schema) {
768
- if (schema.enum === void 0) {
769
- return null;
952
+ function validateAgainstSchema(key, value, jsonSchema) {
953
+ const errors = validateJsonSchema(value, jsonSchema);
954
+ if (errors.length === 0) {
955
+ return;
770
956
  }
771
- const enumPrimitiveSet = new Set(schema.enum.filter((member) => isJsonPrimitive(member)));
772
- const enumComplexMembers = schema.enum.filter((member) => !isJsonPrimitive(member));
773
- return (value, path, errors) => {
774
- const primitiveMatch = isJsonPrimitive(value) && enumPrimitiveSet.has(value);
775
- const complexMatch = !primitiveMatch && enumComplexMembers.some((entry) => jsonDeepEqual(value, entry));
776
- if (primitiveMatch || complexMatch) {
777
- return;
778
- }
779
- errors.push({
780
- path,
781
- message: `Value does not match any enum member`,
782
- keyword: "enum"
783
- });
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)
784
966
  };
785
967
  }
786
- function buildConstValidationStep(schema) {
787
- if (!("const" in schema)) {
788
- return null;
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));
789
984
  }
790
- return (value, path, errors) => {
791
- if (jsonDeepEqual(value, schema.const)) {
792
- return;
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);
793
992
  }
794
- errors.push({
795
- path,
796
- message: `Value does not match const`,
797
- keyword: "const"
798
- });
799
- };
993
+ }
994
+ validateAgainstSchema(key, valueToStore, targetSchema.schema);
995
+ return serializeEnvelope(targetSchema.version, valueToStore);
800
996
  }
801
- function buildNumberValidationStep(schema) {
802
- const hasMinimum = schema.minimum !== void 0;
803
- const hasMaximum = schema.maximum !== void 0;
804
- const hasExMin = schema.exclusiveMinimum !== void 0;
805
- const hasExMax = schema.exclusiveMaximum !== void 0;
806
- if (!hasMinimum && !hasMaximum && !hasExMin && !hasExMax) {
807
- return null;
997
+ function decodeCodecManagedEnvelope(key, envelope, options) {
998
+ if (typeof envelope.payload !== "string") {
999
+ return {
1000
+ value: envelope.payload
1001
+ };
808
1002
  }
809
- const minimum = schema.minimum;
810
- const maximum = schema.maximum;
811
- const exMin = schema.exclusiveMinimum;
812
- const exMax = schema.exclusiveMaximum;
813
- return (value, path, errors) => {
814
- if (typeof value !== "number") {
815
- return;
816
- }
817
- if (hasMinimum && value < minimum) {
818
- errors.push({
819
- path,
820
- message: `Value ${value} is less than minimum ${minimum}`,
821
- keyword: "minimum"
822
- });
823
- }
824
- if (hasMaximum && value > maximum) {
825
- errors.push({
826
- path,
827
- message: `Value ${value} is greater than maximum ${maximum}`,
828
- keyword: "maximum"
829
- });
830
- }
831
- if (hasExMin && value <= exMin) {
832
- errors.push({
833
- path,
834
- message: `Value ${value} is not greater than exclusiveMinimum ${exMin}`,
835
- keyword: "exclusiveMinimum"
836
- });
837
- }
838
- if (hasExMax && value >= exMax) {
839
- errors.push({
840
- path,
841
- message: `Value ${value} is not less than exclusiveMaximum ${exMax}`,
842
- keyword: "exclusiveMaximum"
843
- });
844
- }
1003
+ return {
1004
+ value: decodeStringPayload(key, envelope.payload, options)
845
1005
  };
846
1006
  }
847
- function buildStringValidationStep(schema) {
848
- const hasMinLength = schema.minLength !== void 0;
849
- const hasMaxLength = schema.maxLength !== void 0;
850
- if (!hasMinLength && !hasMaxLength) {
851
- return null;
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
+ );
852
1013
  }
853
- const minLength = schema.minLength;
854
- const maxLength = schema.maxLength;
855
- return (value, path, errors) => {
856
- if (typeof value !== "string") {
857
- return;
858
- }
859
- if (hasMinLength && value.length < minLength) {
860
- errors.push({
861
- path,
862
- message: `String length ${value.length} is less than minLength ${minLength}`,
863
- keyword: "minLength"
864
- });
865
- }
866
- if (hasMaxLength && value.length > maxLength) {
867
- errors.push({
868
- path,
869
- message: `String length ${value.length} is greater than maxLength ${maxLength}`,
870
- keyword: "maxLength"
871
- });
872
- }
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
873
1024
  };
874
1025
  }
875
- function buildObjectValidationStep(schema) {
876
- const requiredKeys = schema.required ?? [];
877
- const propertyValidators = schema.properties ? Object.entries(schema.properties).map(([name, propertySchema]) => [
878
- name,
879
- compileSchema(propertySchema)
880
- ]) : null;
881
- const checkAdditional = schema.additionalProperties !== void 0 && schema.additionalProperties !== true;
882
- const additionalIsFalse = schema.additionalProperties === false;
883
- const additionalValidator = checkAdditional && !additionalIsFalse ? compileSchema(schema.additionalProperties) : null;
884
- const definedPropKeys = checkAdditional ? new Set(Object.keys(schema.properties ?? {})) : null;
885
- const objectValidationSteps = [];
886
- if (requiredKeys.length > 0) {
887
- objectValidationSteps.push(createRequiredPropertyStep(requiredKeys));
888
- }
889
- if (propertyValidators !== null) {
890
- objectValidationSteps.push(createDeclaredPropertyStep(propertyValidators));
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
+ };
891
1033
  }
892
- if (checkAdditional) {
893
- objectValidationSteps.push(
894
- createAdditionalPropertyStep({
895
- additionalIsFalse,
896
- additionalValidator,
897
- definedPropKeys: definedPropKeys ?? /* @__PURE__ */ new Set()
898
- })
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}`
899
1039
  );
900
1040
  }
901
- if (objectValidationSteps.length === 0) {
902
- return null;
1041
+ for (const step of path) {
1042
+ current = step.migrate(current);
903
1043
  }
904
- return (value, path, errors) => {
905
- if (!isJsonObjectRecord(value)) {
906
- return;
907
- }
908
- for (const step of objectValidationSteps) {
909
- step(value, path, errors);
910
- }
911
- };
912
- }
913
- function createRequiredPropertyStep(requiredKeys) {
914
- return (value, path, errors) => {
915
- for (const requiredKey of requiredKeys) {
916
- if (objectHasOwn(value, requiredKey)) {
917
- continue;
918
- }
919
- errors.push({
920
- path,
921
- message: `Missing required property "${requiredKey}"`,
922
- keyword: "required"
923
- });
924
- }
925
- };
926
- }
927
- function createDeclaredPropertyStep(propertyValidators) {
928
- return (value, path, errors) => {
929
- for (const [propertyName, validator] of propertyValidators) {
930
- if (!objectHasOwn(value, propertyName)) {
931
- continue;
932
- }
933
- errors.push(...validator(value[propertyName], `${path}/${propertyName}`));
934
- }
1044
+ validateAgainstSchema(key, current, latestSchema.schema);
1045
+ return {
1046
+ value: current,
1047
+ rewriteRaw: serializeEnvelope(latestSchema.version, current)
935
1048
  };
936
1049
  }
937
- function createAdditionalPropertyStep({
938
- additionalIsFalse,
939
- additionalValidator,
940
- definedPropKeys
1050
+ function decodePersistedValue(key, raw, options, api, schemaRegistry) {
1051
+ if (raw == null) {
1052
+ return buildFallbackResult(options);
1053
+ }
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);
1065
+ }
1066
+ return decodeSchemaManagedEnvelope(key, envelope, schemaForVersion, latestSchema, schemaRegistry);
1067
+ }
1068
+ function createMnemonicOptionalBridge({
1069
+ api,
1070
+ schemaRegistry
941
1071
  }) {
942
- return (value, path, errors) => {
943
- for (const objectKey of Object.keys(value)) {
944
- if (definedPropKeys.has(objectKey)) {
945
- continue;
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);
946
1085
  }
947
- if (additionalIsFalse) {
948
- errors.push({
949
- path,
950
- message: `Additional property "${objectKey}" is not allowed`,
951
- keyword: "additionalProperties"
952
- });
953
- continue;
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);
954
1116
  }
955
- errors.push(...additionalValidator(value[objectKey], `${path}/${objectKey}`));
956
1117
  }
957
1118
  };
958
1119
  }
959
- function buildArrayValidationStep(schema) {
960
- const hasMinItems = schema.minItems !== void 0;
961
- const hasMaxItems = schema.maxItems !== void 0;
962
- const itemsValidator = schema.items ? compileSchema(schema.items) : null;
963
- if (!hasMinItems && !hasMaxItems && itemsValidator === null) {
964
- return null;
1120
+ var MnemonicOptionalBridgeContext = createContext(null);
1121
+
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");
965
1134
  }
966
- const minItems = schema.minItems;
967
- const maxItems = schema.maxItems;
968
- return (value, path, errors) => {
969
- if (!Array.isArray(value)) {
970
- return;
971
- }
972
- if (hasMinItems && value.length < minItems) {
973
- errors.push({
974
- path,
975
- message: `Array length ${value.length} is less than minItems ${minItems}`,
976
- keyword: "minItems"
977
- });
978
- }
979
- if (hasMaxItems && value.length > maxItems) {
980
- errors.push({
981
- path,
982
- message: `Array length ${value.length} is greater than maxItems ${maxItems}`,
983
- keyword: "maxItems"
984
- });
985
- }
986
- if (itemsValidator === null) {
987
- return;
988
- }
989
- for (const [index, item] of value.entries()) {
990
- errors.push(...itemsValidator(item, `${path}/${index}`));
991
- }
992
- };
1135
+ return context;
993
1136
  }
994
- function validateJsonSchema(value, schema, path = "") {
995
- const compiled = compileSchema(schema);
996
- return compiled(value, path);
1137
+ function useMnemonicOptional() {
1138
+ return useContext(MnemonicContext);
997
1139
  }
998
- function jsonTypeLabel(value) {
999
- if (value === null) return "null";
1000
- if (Array.isArray(value)) return "array";
1001
- return typeof value;
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;
1147
+ }
1002
1148
  }
1003
- function inferJsonSchema(sample) {
1004
- if (sample === null) return { type: "null" };
1005
- if (Array.isArray(sample)) return { type: "array" };
1006
- switch (typeof sample) {
1007
- case "string":
1008
- return { type: "string" };
1009
- case "number":
1010
- return { type: "number" };
1011
- case "boolean":
1012
- return { type: "boolean" };
1013
- case "object":
1014
- return { type: "object" };
1015
- default:
1016
- return {};
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;
1017
1155
  }
1018
1156
  }
1019
- var SSR_SNAPSHOT_TOKEN = /* @__PURE__ */ Symbol("mnemonic:ssr-snapshot");
1020
- var diagnosticContractRegistry = /* @__PURE__ */ new WeakMap();
1021
- var diagnosticWarningRegistry = /* @__PURE__ */ new WeakMap();
1022
- var diagnosticObjectIds = /* @__PURE__ */ new WeakMap();
1023
- var nextDiagnosticObjectId = 1;
1024
- function serializeEnvelope(version, payload) {
1025
- return JSON.stringify({
1026
- version,
1027
- payload
1028
- });
1157
+ function isProductionRuntime() {
1158
+ const env = getRuntimeNodeEnv();
1159
+ if (env === void 0) {
1160
+ return true;
1161
+ }
1162
+ return env === "production";
1029
1163
  }
1030
- function withReadMetadata(value, rewriteRaw, extra) {
1031
- const result = { value };
1032
- if (extra !== void 0) {
1033
- Object.assign(result, extra);
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";
1034
1180
  }
1035
- if (rewriteRaw !== void 0) result.rewriteRaw = rewriteRaw;
1036
- return result;
1181
+ if (typeof activeStorage?.onExternalChange === "function") {
1182
+ return "custom-external-change";
1183
+ }
1184
+ return "none";
1037
1185
  }
1038
- function isDevelopmentRuntime() {
1039
- return getRuntimeNodeEnv() === "development";
1186
+ function getDevToolsWindow() {
1187
+ return globalThis.window;
1040
1188
  }
1041
- function getDiagnosticWarnings(api) {
1042
- let warnings = diagnosticWarningRegistry.get(api);
1043
- if (!warnings) {
1044
- warnings = /* @__PURE__ */ new Set();
1045
- diagnosticWarningRegistry.set(api, warnings);
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
+ }
1199
+ }
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) {
1280
+ try {
1281
+ return JSON.parse(raw);
1282
+ } catch {
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;
1293
+ }
1294
+ callbacks.onAccessSuccess();
1295
+ return raw;
1296
+ } catch (error) {
1297
+ callbacks.onAccessError(error);
1298
+ return null;
1046
1299
  }
1047
- return warnings;
1048
- }
1049
- function warnOnce(api, id, message) {
1050
- const warnings = getDiagnosticWarnings(api);
1051
- if (warnings.has(id)) return;
1052
- warnings.add(id);
1053
- console.warn(message);
1054
1300
  }
1055
- function stableDiagnosticValue(value) {
1056
- if (typeof value === "function") {
1057
- const source = Function.prototype.toString.call(value).split(/\s+/).join(" ").trim();
1058
- const name = value.name || "anonymous";
1059
- return `[factory:${name}/${value.length}:${source}]`;
1301
+ function enumerateNamespaceKeys(storage, prefix, callbacks) {
1302
+ if (!storage) {
1303
+ return [];
1060
1304
  }
1061
- if (typeof value === "bigint") return `${value.toString()}n`;
1062
- if (typeof value === "symbol") return value.toString();
1063
- if (value === void 0) return "undefined";
1305
+ const keys = [];
1064
1306
  try {
1065
- return JSON.stringify(value);
1066
- } catch {
1067
- const tag = Object.prototype.toString.call(value);
1068
- if (value !== null && (typeof value === "object" || typeof value === "function")) {
1069
- return `${tag}#${getDiagnosticObjectId(value)}`;
1307
+ const storageLength = storage.length;
1308
+ const getStorageKey = storage.key;
1309
+ if (typeof storageLength !== "number" || typeof getStorageKey !== "function") {
1310
+ return [];
1070
1311
  }
1071
- return tag;
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);
1072
1320
  }
1321
+ return keys;
1073
1322
  }
1074
- function isObjectLike(value) {
1075
- return value !== null && (typeof value === "object" || typeof value === "function");
1076
- }
1077
- function objectHasOwn2(value, property) {
1078
- const hasOwn = Object.hasOwn;
1079
- if (typeof hasOwn === "function") {
1080
- return hasOwn(value, property);
1323
+ function syncCacheEntryFromStorage({
1324
+ key,
1325
+ storageKey,
1326
+ storage,
1327
+ cache,
1328
+ emit,
1329
+ callbacks
1330
+ }) {
1331
+ const fresh = readStorageRaw(storage, storageKey, callbacks);
1332
+ const cached = cache.get(key) ?? null;
1333
+ if (fresh === cached) {
1334
+ return false;
1081
1335
  }
1082
- return Object.getOwnPropertyDescriptor(value, property) !== void 0;
1083
- }
1084
- function getDiagnosticObjectId(value) {
1085
- const existing = diagnosticObjectIds.get(value);
1086
- if (existing !== void 0) return existing;
1087
- const id = nextDiagnosticObjectId++;
1088
- diagnosticObjectIds.set(value, id);
1089
- return id;
1336
+ cache.set(key, fresh);
1337
+ emit(key);
1338
+ return true;
1090
1339
  }
1091
- function buildContractFingerprint({
1092
- api,
1093
- key,
1094
- defaultValue,
1095
- codecOpt,
1096
- schemaVersion,
1097
- reconcile,
1098
- listenCrossTab,
1099
- ssrOptions
1340
+ function reloadNamedKeysFromStorage({
1341
+ changedKeys,
1342
+ prefix,
1343
+ storage,
1344
+ listeners,
1345
+ cache,
1346
+ emit,
1347
+ callbacks
1100
1348
  }) {
1101
- const codecSignature = codecOpt == null || !isObjectLike(codecOpt) ? "default-json-codec" : `codec:${stableDiagnosticValue(codecOpt.encode)}:${stableDiagnosticValue(codecOpt.decode)}`;
1102
- const reconcileSignature = reconcile == null || !isObjectLike(reconcile) ? "no-reconcile" : `reconcile:${stableDiagnosticValue(reconcile)}`;
1103
- return JSON.stringify({
1104
- key,
1105
- defaultValue: stableDiagnosticValue(defaultValue),
1106
- codec: codecSignature,
1107
- schemaVersion: schemaVersion ?? null,
1108
- listenCrossTab: Boolean(listenCrossTab),
1109
- reconcile: reconcileSignature,
1110
- ssrHydration: ssrOptions?.hydration ?? null,
1111
- hasServerValue: ssrOptions?.serverValue !== void 0,
1112
- providerHydration: api.ssrHydration ?? null
1113
- });
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
+ }
1368
+ }
1369
+ return changed;
1114
1370
  }
1115
- function resolveMnemonicKeyArgs(keyOrDescriptor, options) {
1116
- if (typeof keyOrDescriptor !== "string") {
1117
- return keyOrDescriptor;
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({
1383
+ key,
1384
+ storageKey: `${prefix}${key}`,
1385
+ storage,
1386
+ cache,
1387
+ emit,
1388
+ callbacks
1389
+ }) || changed;
1118
1390
  }
1119
- if (!options) {
1120
- throw new Error("useMnemonicKey requires options when called with a string key");
1391
+ for (const key of cache.keys()) {
1392
+ const listenerSet = listeners.get(key);
1393
+ if (listenerSet && listenerSet.size > 0) continue;
1394
+ cache.delete(key);
1121
1395
  }
1122
- return {
1123
- key: keyOrDescriptor,
1124
- options
1125
- };
1396
+ return changed;
1126
1397
  }
1127
- function useMnemonicKeyShared(keyOrDescriptor, options, schemaVersion) {
1128
- const descriptor = resolveMnemonicKeyArgs(keyOrDescriptor, options);
1129
- const key = descriptor.key;
1130
- const resolvedOptions = descriptor.options;
1131
- const api = useMnemonic();
1132
- const {
1133
- defaultValue,
1134
- onMount,
1135
- onChange,
1136
- listenCrossTab,
1137
- codec: codecOpt,
1138
- schema,
1139
- reconcile,
1140
- ssr: ssrOptions
1141
- } = resolvedOptions;
1142
- const codec = codecOpt ?? JSONCodec;
1143
- const hydrationMode = ssrOptions?.hydration ?? api.ssrHydration;
1144
- const [hasMounted, setHasMounted] = useState(hydrationMode !== "client-only");
1145
- const developmentRuntime = isDevelopmentRuntime();
1146
- const contractFingerprint = useMemo(
1147
- () => developmentRuntime ? buildContractFingerprint({
1148
- api,
1149
- key,
1150
- defaultValue,
1151
- codecOpt,
1152
- ...schemaVersion === void 0 ? {} : { schemaVersion },
1153
- reconcile,
1154
- listenCrossTab,
1155
- ssrOptions
1156
- }) : null,
1157
- [
1158
- developmentRuntime,
1159
- api,
1160
- key,
1161
- defaultValue,
1162
- codecOpt,
1163
- schemaVersion,
1164
- reconcile,
1165
- listenCrossTab,
1166
- ssrOptions?.hydration,
1167
- ssrOptions?.serverValue
1168
- ]
1169
- );
1170
- const getFallback = useCallback(
1171
- (error) => typeof defaultValue === "function" ? defaultValue(error) : defaultValue,
1172
- [defaultValue]
1173
- );
1174
- const getServerValue = useCallback(() => {
1175
- const serverValue = ssrOptions?.serverValue;
1176
- if (serverValue === void 0) {
1177
- return getFallback();
1178
- }
1179
- return typeof serverValue === "function" ? serverValue() : serverValue;
1180
- }, [getFallback, ssrOptions?.serverValue]);
1181
- const parseEnvelope = useCallback(
1182
- (rawText) => {
1183
- try {
1184
- const parsed = JSON.parse(rawText);
1185
- if (typeof parsed !== "object" || parsed == null || !Number.isInteger(parsed.version) || parsed.version < 0 || !objectHasOwn2(parsed, "payload")) {
1186
- return {
1187
- ok: false,
1188
- error: new SchemaError("INVALID_ENVELOPE", `Invalid envelope for key "${key}"`)
1189
- };
1190
- }
1191
- return { ok: true, envelope: parsed };
1192
- } catch (err) {
1193
- return {
1194
- ok: false,
1195
- error: new SchemaError("INVALID_ENVELOPE", `Invalid envelope for key "${key}"`, err)
1196
- };
1197
- }
1398
+ function createDevToolsProviderApi({
1399
+ store,
1400
+ dump,
1401
+ keys,
1402
+ readThrough,
1403
+ writeRaw,
1404
+ removeRaw
1405
+ }) {
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;
1418
+ },
1419
+ get: (key) => {
1420
+ const raw = readThrough(key);
1421
+ if (raw == null) return void 0;
1422
+ return decodeDevToolsValue(raw);
1198
1423
  },
1199
- [key]
1200
- );
1201
- const decodeStringPayload = useCallback(
1202
- (payload, activeCodec) => {
1203
- try {
1204
- return activeCodec.decode(payload);
1205
- } catch (err) {
1206
- throw err instanceof CodecError ? err : new CodecError(`Codec decode failed for key "${key}"`, err);
1207
- }
1424
+ set: (key, value) => {
1425
+ writeRaw(key, JSON.stringify(value));
1208
1426
  },
1209
- [key]
1210
- );
1211
- const buildFallbackResult = useCallback(
1212
- (error, extra) => {
1213
- return withReadMetadata(getFallback(error), void 0, extra);
1427
+ remove: (key) => removeRaw(key),
1428
+ clear: () => {
1429
+ for (const key of keys()) {
1430
+ removeRaw(key);
1431
+ }
1214
1432
  },
1215
- [getFallback]
1216
- );
1217
- return {
1218
- api,
1219
- key,
1220
- codec,
1221
- codecOpt,
1222
- schema,
1223
- reconcile,
1224
- onMount,
1225
- onChange,
1226
- listenCrossTab,
1227
- getFallback,
1228
- getServerValue,
1229
- parseEnvelope,
1230
- decodeStringPayload,
1231
- buildFallbackResult,
1232
- developmentRuntime,
1233
- contractFingerprint,
1234
- hasMounted,
1235
- setHasMounted,
1236
- hydrationMode,
1237
- ssrOptions
1433
+ keys
1238
1434
  };
1239
1435
  }
1240
- function useApplyReconcile({
1241
- key,
1242
- reconcile,
1243
- buildFallbackResult
1436
+ function createReloadFromStorage({
1437
+ storage,
1438
+ hasAsyncContractViolation,
1439
+ prefix,
1440
+ listeners,
1441
+ cache,
1442
+ emit,
1443
+ callbacks,
1444
+ devToolsRoot,
1445
+ namespace
1244
1446
  }) {
1245
- return useCallback(
1246
- ({
1247
- value,
1248
- rewriteRaw,
1249
- extra,
1250
- persistedVersion,
1251
- latestVersion,
1252
- serializeForPersist,
1253
- deriveExtra
1254
- }) => {
1255
- if (!reconcile) {
1256
- return withReadMetadata(value, rewriteRaw, extra);
1257
- }
1258
- const context = {
1259
- key,
1260
- persistedVersion
1261
- };
1262
- if (latestVersion !== void 0) {
1263
- context.latestVersion = latestVersion;
1264
- }
1265
- const baselineSerialized = (() => {
1266
- try {
1267
- return serializeForPersist(value);
1268
- } catch {
1269
- return rewriteRaw;
1270
- }
1271
- })();
1272
- try {
1273
- const reconciled = reconcile(value, context);
1274
- const nextExtra = deriveExtra ? deriveExtra(reconciled, extra) : extra;
1275
- const nextSerialized = serializeForPersist(reconciled);
1276
- const nextRewriteRaw = baselineSerialized === void 0 || nextSerialized !== baselineSerialized ? nextSerialized : rewriteRaw;
1277
- return withReadMetadata(reconciled, nextRewriteRaw, nextExtra);
1278
- } catch (err) {
1279
- const typedErr = err instanceof SchemaError ? err : new SchemaError("RECONCILE_FAILED", `Reconciliation failed for key "${key}"`, err);
1280
- return buildFallbackResult(typedErr, extra);
1281
- }
1282
- },
1283
- [buildFallbackResult, key, reconcile]
1284
- );
1285
- }
1286
- function useMnemonicKeyState(shared, config) {
1287
- const {
1288
- api,
1289
- key,
1290
- codecOpt,
1291
- schema,
1292
- onMount,
1293
- onChange,
1294
- listenCrossTab,
1295
- getFallback,
1296
- getServerValue,
1297
- developmentRuntime,
1298
- contractFingerprint,
1299
- hasMounted,
1300
- setHasMounted,
1301
- hydrationMode,
1302
- ssrOptions
1303
- } = shared;
1304
- const { decodeForRead, encodeForWrite, additionalDevWarnings, onDecodedEffect } = config;
1305
- const getServerRawSnapshot = useCallback(
1306
- () => ssrOptions?.serverValue === void 0 ? null : SSR_SNAPSHOT_TOKEN,
1307
- [ssrOptions?.serverValue]
1308
- );
1309
- const deferStorageRead = hydrationMode === "client-only" && !hasMounted;
1310
- const subscribe = useCallback(
1311
- (listener) => {
1312
- if (deferStorageRead) {
1313
- return () => void 0;
1314
- }
1315
- return api.subscribeRaw(key, listener);
1316
- },
1317
- [api, deferStorageRead, key]
1318
- );
1319
- const raw = useSyncExternalStore(
1320
- subscribe,
1321
- () => deferStorageRead ? getServerRawSnapshot() : api.getRawSnapshot(key),
1322
- getServerRawSnapshot
1323
- );
1324
- const decoded = useMemo(() => {
1325
- if (raw === SSR_SNAPSHOT_TOKEN) {
1326
- return withReadMetadata(getServerValue());
1327
- }
1328
- return decodeForRead(raw);
1329
- }, [decodeForRead, getServerValue, raw]);
1330
- const value = decoded.value;
1331
- useEffect(() => {
1332
- if (!developmentRuntime) return;
1333
- const globalWindow = globalThis.window;
1334
- if (listenCrossTab && (api.crossTabSyncMode ?? "none") === "none" && globalWindow !== void 0) {
1335
- warnOnce(
1336
- api,
1337
- `listenCrossTab:${key}`,
1338
- `[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.`
1339
- );
1340
- }
1341
- additionalDevWarnings?.({
1342
- api,
1343
- key,
1344
- listenCrossTab,
1345
- codecOpt,
1346
- schema,
1347
- warnOnce: (id, message) => warnOnce(api, id, message)
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
1348
1466
  });
1349
- let keyContracts = diagnosticContractRegistry.get(api);
1350
- if (!keyContracts) {
1351
- keyContracts = /* @__PURE__ */ new Map();
1352
- diagnosticContractRegistry.set(api, keyContracts);
1353
- }
1354
- if (contractFingerprint === null) {
1355
- return;
1356
- }
1357
- const previousContract = keyContracts.get(key);
1358
- if (previousContract === void 0) {
1359
- keyContracts.set(key, contractFingerprint);
1360
- return;
1361
- }
1362
- if (previousContract === contractFingerprint) {
1363
- return;
1467
+ if (changed) {
1468
+ bumpDevToolsVersion(devToolsRoot, namespace, isFullReload ? "reload:full" : "reload:granular");
1364
1469
  }
1365
- warnOnce(
1366
- api,
1367
- `contract-conflict:${key}`,
1368
- `[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.`
1369
1486
  );
1370
- }, [
1371
- additionalDevWarnings,
1372
- api,
1373
- key,
1374
- developmentRuntime,
1375
- contractFingerprint,
1376
- listenCrossTab,
1377
- codecOpt,
1378
- schema,
1379
- api.crossTabSyncMode
1380
- ]);
1381
- useEffect(() => {
1382
- if (hasMounted) return;
1383
- setHasMounted(true);
1384
- }, [hasMounted, setHasMounted]);
1385
- useEffect(() => {
1386
- if (decoded.rewriteRaw && decoded.rewriteRaw !== raw) {
1387
- 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);
1388
1494
  }
1389
- }, [api, decoded.rewriteRaw, key, raw]);
1390
- useEffect(() => {
1391
- onDecodedEffect?.(decoded);
1392
- }, [decoded, onDecodedEffect]);
1393
- const prevRef = useRef(value);
1394
- const mounted = useRef(false);
1395
- useEffect(() => {
1396
- if (mounted.current) return;
1397
- mounted.current = true;
1398
- onMount?.(value);
1399
- prevRef.current = value;
1400
- }, []);
1401
- useEffect(() => {
1402
- const prev = prevRef.current;
1403
- if (Object.is(prev, value)) return;
1404
- prevRef.current = value;
1405
- onChange?.(value, prev);
1406
- }, [value, onChange]);
1407
- useEffect(() => {
1408
- if (!listenCrossTab) return;
1409
- const globalWindow = globalThis.window;
1410
- if (globalWindow === void 0) return;
1411
- const storageKey = api.prefix + key;
1412
- const handler = (e) => {
1413
- if (e.key === null) {
1414
- api.removeRaw(key);
1415
- 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;
1416
1572
  }
1417
- if (e.key !== storageKey) return;
1418
- if (e.newValue == null) {
1419
- api.removeRaw(key);
1420
- 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;
1421
1588
  }
1422
- api.setRaw(key, e.newValue);
1589
+ const raw = readStorageRaw(st, fullKey(key), storageAccessCallbacks);
1590
+ cache.set(key, raw);
1591
+ return raw;
1423
1592
  };
1424
- globalWindow.addEventListener("storage", handler);
1425
- return () => globalWindow.removeEventListener("storage", handler);
1426
- }, [listenCrossTab, api, key]);
1427
- const set = useMemo(() => {
1428
- return (next) => {
1429
- const nextVal = typeof next === "function" ? next(decodeForRead(api.getRawSnapshot(key)).value) : next;
1430
- try {
1431
- const encoded = encodeForWrite(nextVal);
1432
- api.setRaw(key, encoded);
1433
- } catch (err) {
1434
- if (err instanceof SchemaError) {
1435
- console.error(`[Mnemonic] Schema error for key "${key}" (${err.code}):`, err.message);
1436
- return;
1437
- }
1438
- if (err instanceof CodecError) {
1439
- console.error(`[Mnemonic] Codec encode error for key "${key}":`, err.message);
1440
- 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);
1441
1612
  }
1442
- console.error(`[Mnemonic] Failed to persist key "${key}":`, err);
1443
1613
  }
1614
+ emit(key);
1615
+ bumpDevToolsVersion(devToolsRoot, namespace, `set:${key}`);
1444
1616
  };
1445
- }, [api, key, decodeForRead, encodeForWrite]);
1446
- const reset = useMemo(() => {
1447
- return () => {
1448
- const v = getFallback();
1449
- try {
1450
- const encoded = encodeForWrite(v);
1451
- api.setRaw(key, encoded);
1452
- } catch (err) {
1453
- if (err instanceof SchemaError) {
1454
- console.error(`[Mnemonic] Schema error for key "${key}" (${err.code}):`, err.message);
1455
- return;
1456
- }
1457
- if (err instanceof CodecError) {
1458
- 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);
1459
1629
  }
1460
- return;
1461
1630
  }
1631
+ emit(key);
1632
+ bumpDevToolsVersion(devToolsRoot, namespace, `remove:${key}`);
1462
1633
  };
1463
- }, [api, key, getFallback, encodeForWrite]);
1464
- const remove = useMemo(() => {
1465
- return () => api.removeRaw(key);
1466
- }, [api, key]);
1467
- return useMemo(
1468
- () => ({
1469
- value,
1470
- set,
1471
- reset,
1472
- 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 } : {}
1473
1709
  }),
1474
- [value, set, reset, remove]
1710
+ [schemaRegistry, store]
1475
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 }) });
1476
1717
  }
1477
-
1478
- // src/Mnemonic/use.ts
1479
- function useSchemaMnemonicKey(descriptor) {
1480
- const shared = useMnemonicKeyShared(descriptor, void 0, descriptor.options.schema?.version);
1481
- const { api, key, codec, codecOpt, schema, reconcile, parseEnvelope, decodeStringPayload, buildFallbackResult } = shared;
1718
+ function useSchemaMnemonicKeyFromApi(store, descriptor, active = true) {
1719
+ const shared = useMnemonicKeySharedFromApi(store, descriptor, void 0, descriptor.options.schema?.version);
1720
+ const { api, key, codec, codecOpt, schema, reconcile, parseEnvelope: parseEnvelope2, decodeStringPayload: decodeStringPayload2, buildFallbackResult: buildFallbackResult2 } = shared;
1482
1721
  const schemaMode = api.schemaMode;
1483
1722
  const schemaRegistry = api.schemaRegistry;
1484
- const validateAgainstSchema = useCallback(
1723
+ const validateAgainstSchema2 = useCallback(
1485
1724
  (value, jsonSchema) => {
1486
1725
  const errors = validateJsonSchema(value, jsonSchema);
1487
1726
  if (errors.length > 0) {
@@ -1500,7 +1739,7 @@ function useSchemaMnemonicKey(descriptor) {
1500
1739
  migrationPaths: /* @__PURE__ */ new Map()
1501
1740
  };
1502
1741
  }, [schemaRegistry, schemaMode, key]);
1503
- const getSchemaForVersion = useCallback(
1742
+ const getSchemaForVersion2 = useCallback(
1504
1743
  (version) => {
1505
1744
  if (!schemaRegistry) return void 0;
1506
1745
  if (!registryCache) return schemaRegistry.getSchema(key, version);
@@ -1542,16 +1781,16 @@ function useSchemaMnemonicKey(descriptor) {
1542
1781
  const applyReconcile = useApplyReconcile({
1543
1782
  key,
1544
1783
  reconcile,
1545
- buildFallbackResult
1784
+ buildFallbackResult: buildFallbackResult2
1546
1785
  });
1547
1786
  const resolveTargetWriteSchema = useCallback(() => {
1548
1787
  const explicitVersion = schema?.version;
1549
1788
  const latestSchema = getLatestSchemaForKey();
1550
1789
  if (explicitVersion === void 0) return latestSchema;
1551
- const explicitSchema = getSchemaForVersion(explicitVersion);
1790
+ const explicitSchema = getSchemaForVersion2(explicitVersion);
1552
1791
  if (explicitSchema) return explicitSchema;
1553
1792
  return schemaMode === "strict" ? void 0 : latestSchema;
1554
- }, [getLatestSchemaForKey, getSchemaForVersion, schema?.version, schemaMode]);
1793
+ }, [getLatestSchemaForKey, getSchemaForVersion2, schema?.version, schemaMode]);
1555
1794
  const encodeForWrite = useCallback(
1556
1795
  (nextValue) => {
1557
1796
  const explicitVersion = schema?.version;
@@ -1574,7 +1813,7 @@ function useSchemaMnemonicKey(descriptor) {
1574
1813
  throw err instanceof SchemaError ? err : new SchemaError("MIGRATION_FAILED", `Write-time migration failed for key "${key}"`, err);
1575
1814
  }
1576
1815
  }
1577
- validateAgainstSchema(valueToStore, targetSchema.schema);
1816
+ validateAgainstSchema2(valueToStore, targetSchema.schema);
1578
1817
  return buildSchemaManagedResult(targetSchema.version, valueToStore);
1579
1818
  },
1580
1819
  [
@@ -1583,20 +1822,20 @@ function useSchemaMnemonicKey(descriptor) {
1583
1822
  schemaMode,
1584
1823
  codec,
1585
1824
  schemaRegistry,
1586
- validateAgainstSchema,
1825
+ validateAgainstSchema2,
1587
1826
  resolveTargetWriteSchema,
1588
1827
  buildSchemaManagedResult
1589
1828
  ]
1590
1829
  );
1591
- const decodeAutoschemaEnvelope = useCallback(
1830
+ const decodeAutoschemaEnvelope2 = useCallback(
1592
1831
  (envelope, latestSchema) => {
1593
1832
  if (latestSchema) {
1594
- return buildFallbackResult(
1833
+ return buildFallbackResult2(
1595
1834
  new SchemaError("SCHEMA_NOT_FOUND", `No schema for key "${key}" v${envelope.version}`)
1596
1835
  );
1597
1836
  }
1598
1837
  if (!schemaRegistry || typeof schemaRegistry.registerSchema !== "function") {
1599
- return buildFallbackResult(
1838
+ return buildFallbackResult2(
1600
1839
  new SchemaError(
1601
1840
  "MODE_CONFIGURATION_INVALID",
1602
1841
  `Autoschema mode requires schema registry registration for key "${key}"`
@@ -1604,7 +1843,7 @@ function useSchemaMnemonicKey(descriptor) {
1604
1843
  );
1605
1844
  }
1606
1845
  try {
1607
- const decoded = typeof envelope.payload === "string" ? decodeStringPayload(envelope.payload, codec) : envelope.payload;
1846
+ const decoded = typeof envelope.payload === "string" ? decodeStringPayload2(envelope.payload, codec) : envelope.payload;
1608
1847
  const inferSchemaForValue = (value) => ({
1609
1848
  key,
1610
1849
  version: 1,
@@ -1623,20 +1862,20 @@ function useSchemaMnemonicKey(descriptor) {
1623
1862
  });
1624
1863
  } catch (err) {
1625
1864
  const typedErr = err instanceof SchemaError || err instanceof CodecError ? err : new SchemaError("TYPE_MISMATCH", `Autoschema inference failed for key "${key}"`, err);
1626
- return buildFallbackResult(typedErr);
1865
+ return buildFallbackResult2(typedErr);
1627
1866
  }
1628
1867
  },
1629
1868
  [
1630
1869
  applyReconcile,
1631
- buildFallbackResult,
1870
+ buildFallbackResult2,
1632
1871
  buildSchemaManagedResult,
1633
1872
  codec,
1634
- decodeStringPayload,
1873
+ decodeStringPayload2,
1635
1874
  key,
1636
1875
  schemaRegistry
1637
1876
  ]
1638
1877
  );
1639
- const decodeCodecManagedEnvelope = useCallback(
1878
+ const decodeCodecManagedEnvelope2 = useCallback(
1640
1879
  (envelope, latestSchema) => {
1641
1880
  if (typeof envelope.payload !== "string") {
1642
1881
  return applyReconcile({
@@ -1647,7 +1886,7 @@ function useSchemaMnemonicKey(descriptor) {
1647
1886
  });
1648
1887
  }
1649
1888
  try {
1650
- const decoded = decodeStringPayload(envelope.payload, codec);
1889
+ const decoded = decodeStringPayload2(envelope.payload, codec);
1651
1890
  return applyReconcile({
1652
1891
  value: decoded,
1653
1892
  persistedVersion: envelope.version,
@@ -1655,20 +1894,20 @@ function useSchemaMnemonicKey(descriptor) {
1655
1894
  serializeForPersist: encodeForWrite
1656
1895
  });
1657
1896
  } catch (err) {
1658
- return buildFallbackResult(err);
1897
+ return buildFallbackResult2(err);
1659
1898
  }
1660
1899
  },
1661
- [applyReconcile, buildFallbackResult, codec, decodeStringPayload, encodeForWrite]
1900
+ [applyReconcile, buildFallbackResult2, codec, decodeStringPayload2, encodeForWrite]
1662
1901
  );
1663
- const decodeSchemaManagedEnvelope = useCallback(
1902
+ const decodeSchemaManagedEnvelope2 = useCallback(
1664
1903
  (envelope, schemaForVersion, latestSchema) => {
1665
1904
  let current;
1666
1905
  try {
1667
1906
  current = envelope.payload;
1668
- validateAgainstSchema(current, schemaForVersion.schema);
1907
+ validateAgainstSchema2(current, schemaForVersion.schema);
1669
1908
  } catch (err) {
1670
1909
  const typedErr = err instanceof SchemaError || err instanceof CodecError ? err : new SchemaError("TYPE_MISMATCH", `Schema decode failed for key "${key}"`, err);
1671
- return buildFallbackResult(typedErr);
1910
+ return buildFallbackResult2(typedErr);
1672
1911
  }
1673
1912
  if (!latestSchema || envelope.version >= latestSchema.version) {
1674
1913
  return applyReconcile({
@@ -1680,7 +1919,7 @@ function useSchemaMnemonicKey(descriptor) {
1680
1919
  }
1681
1920
  const path = getMigrationPathForKey(envelope.version, latestSchema.version);
1682
1921
  if (!path) {
1683
- return buildFallbackResult(
1922
+ return buildFallbackResult2(
1684
1923
  new SchemaError(
1685
1924
  "MIGRATION_PATH_NOT_FOUND",
1686
1925
  `No migration path for key "${key}" from v${envelope.version} to v${latestSchema.version}`
@@ -1692,7 +1931,7 @@ function useSchemaMnemonicKey(descriptor) {
1692
1931
  for (const step of path) {
1693
1932
  migrated = step.migrate(migrated);
1694
1933
  }
1695
- validateAgainstSchema(migrated, latestSchema.schema);
1934
+ validateAgainstSchema2(migrated, latestSchema.schema);
1696
1935
  return applyReconcile({
1697
1936
  value: migrated,
1698
1937
  rewriteRaw: buildSchemaManagedResult(latestSchema.version, migrated),
@@ -1702,48 +1941,48 @@ function useSchemaMnemonicKey(descriptor) {
1702
1941
  });
1703
1942
  } catch (err) {
1704
1943
  const typedErr = err instanceof SchemaError || err instanceof CodecError ? err : new SchemaError("MIGRATION_FAILED", `Migration failed for key "${key}"`, err);
1705
- return buildFallbackResult(typedErr);
1944
+ return buildFallbackResult2(typedErr);
1706
1945
  }
1707
1946
  },
1708
1947
  [
1709
1948
  applyReconcile,
1710
- buildFallbackResult,
1949
+ buildFallbackResult2,
1711
1950
  buildSchemaManagedResult,
1712
1951
  encodeForWrite,
1713
1952
  getMigrationPathForKey,
1714
1953
  key,
1715
- validateAgainstSchema
1954
+ validateAgainstSchema2
1716
1955
  ]
1717
1956
  );
1718
1957
  const decodeForRead = useCallback(
1719
1958
  (rawText) => {
1720
- if (rawText == null) return buildFallbackResult();
1721
- const parsed = parseEnvelope(rawText);
1722
- if (!parsed.ok) return buildFallbackResult(parsed.error);
1959
+ if (rawText == null) return buildFallbackResult2();
1960
+ const parsed = parseEnvelope2(rawText);
1961
+ if (!parsed.ok) return buildFallbackResult2(parsed.error);
1723
1962
  const envelope = parsed.envelope;
1724
- const schemaForVersion = getSchemaForVersion(envelope.version);
1963
+ const schemaForVersion = getSchemaForVersion2(envelope.version);
1725
1964
  const latestSchema = getLatestSchemaForKey();
1726
1965
  if (schemaMode === "strict" && !schemaForVersion) {
1727
- return buildFallbackResult(
1966
+ return buildFallbackResult2(
1728
1967
  new SchemaError("SCHEMA_NOT_FOUND", `No schema for key "${key}" v${envelope.version}`)
1729
1968
  );
1730
1969
  }
1731
1970
  if (schemaMode === "autoschema" && !schemaForVersion) {
1732
- return decodeAutoschemaEnvelope(envelope, latestSchema);
1971
+ return decodeAutoschemaEnvelope2(envelope, latestSchema);
1733
1972
  }
1734
1973
  if (!schemaForVersion) {
1735
- return decodeCodecManagedEnvelope(envelope, latestSchema);
1974
+ return decodeCodecManagedEnvelope2(envelope, latestSchema);
1736
1975
  }
1737
- return decodeSchemaManagedEnvelope(envelope, schemaForVersion, latestSchema);
1976
+ return decodeSchemaManagedEnvelope2(envelope, schemaForVersion, latestSchema);
1738
1977
  },
1739
1978
  [
1740
- buildFallbackResult,
1741
- decodeAutoschemaEnvelope,
1742
- decodeCodecManagedEnvelope,
1743
- decodeSchemaManagedEnvelope,
1744
- parseEnvelope,
1979
+ buildFallbackResult2,
1980
+ decodeAutoschemaEnvelope2,
1981
+ decodeCodecManagedEnvelope2,
1982
+ decodeSchemaManagedEnvelope2,
1983
+ parseEnvelope2,
1745
1984
  schemaMode,
1746
- getSchemaForVersion,
1985
+ getSchemaForVersion2,
1747
1986
  getLatestSchemaForKey,
1748
1987
  key
1749
1988
  ]
@@ -1770,12 +2009,16 @@ function useSchemaMnemonicKey(descriptor) {
1770
2009
  [schemaRegistry]
1771
2010
  );
1772
2011
  return useMnemonicKeyState(shared, {
2012
+ active,
1773
2013
  decodeForRead,
1774
2014
  encodeForWrite,
1775
2015
  additionalDevWarnings,
1776
2016
  onDecodedEffect
1777
2017
  });
1778
2018
  }
2019
+ function useSchemaMnemonicKey(descriptor) {
2020
+ return useSchemaMnemonicKeyFromApi(useMnemonic(), descriptor);
2021
+ }
1779
2022
  function useMnemonicKey(keyOrDescriptor, options) {
1780
2023
  return useSchemaMnemonicKey(resolveMnemonicKeyArgs(keyOrDescriptor, options));
1781
2024
  }
@@ -1797,11 +2040,17 @@ function warnRecoveryOnce(api, id, message) {
1797
2040
  console.warn(message);
1798
2041
  }
1799
2042
  function useMnemonicRecovery(options = {}) {
1800
- const api = useMnemonic();
2043
+ return useMnemonicRecoveryFromApi(useMnemonic(), options);
2044
+ }
2045
+ function useMnemonicRecoveryFromApi(api, options = {}, active = true) {
1801
2046
  const { onRecover } = options;
1802
- const namespace = useMemo(() => api.prefix.endsWith(".") ? api.prefix.slice(0, -1) : api.prefix, [api.prefix]);
2047
+ const namespace = useMemo(() => {
2048
+ if (!active) return "";
2049
+ return api.prefix.endsWith(".") ? api.prefix.slice(0, -1) : api.prefix;
2050
+ }, [active, api.prefix]);
1803
2051
  const emitRecovery = useCallback(
1804
2052
  (action, clearedKeys) => {
2053
+ if (!active) return;
1805
2054
  const event = {
1806
2055
  action,
1807
2056
  namespace,
@@ -1809,11 +2058,12 @@ function useMnemonicRecovery(options = {}) {
1809
2058
  };
1810
2059
  onRecover?.(event);
1811
2060
  },
1812
- [namespace, onRecover]
2061
+ [active, namespace, onRecover]
1813
2062
  );
1814
- const listKeys = useCallback(() => api.keys(), [api]);
2063
+ const listKeys = useCallback(() => active ? api.keys() : [], [active, api]);
1815
2064
  const clearResolvedKeys = useCallback(
1816
2065
  (action, keys) => {
2066
+ if (!active) return [];
1817
2067
  const clearedKeys = uniqueKeys(keys);
1818
2068
  for (const key of clearedKeys) {
1819
2069
  api.removeRaw(key);
@@ -1821,13 +2071,14 @@ function useMnemonicRecovery(options = {}) {
1821
2071
  emitRecovery(action, clearedKeys);
1822
2072
  return clearedKeys;
1823
2073
  },
1824
- [api, emitRecovery]
2074
+ [active, api, emitRecovery]
1825
2075
  );
1826
2076
  const clearKeys = useCallback(
1827
2077
  (keys) => clearResolvedKeys("clear-keys", keys),
1828
2078
  [clearResolvedKeys]
1829
2079
  );
1830
2080
  const clearAll = useCallback(() => {
2081
+ if (!active) return [];
1831
2082
  if (!api.canEnumerateKeys) {
1832
2083
  if (isDevelopmentRuntime2()) {
1833
2084
  warnRecoveryOnce(
@@ -1841,9 +2092,10 @@ function useMnemonicRecovery(options = {}) {
1841
2092
  );
1842
2093
  }
1843
2094
  return clearResolvedKeys("clear-all", api.keys());
1844
- }, [api, clearResolvedKeys, namespace]);
2095
+ }, [active, api, clearResolvedKeys, namespace]);
1845
2096
  const clearMatching = useCallback(
1846
2097
  (predicate) => {
2098
+ if (!active) return [];
1847
2099
  if (!api.canEnumerateKeys) {
1848
2100
  if (isDevelopmentRuntime2()) {
1849
2101
  warnRecoveryOnce(
@@ -1861,18 +2113,18 @@ function useMnemonicRecovery(options = {}) {
1861
2113
  api.keys().filter((key) => predicate(key))
1862
2114
  );
1863
2115
  },
1864
- [api, clearResolvedKeys, namespace]
2116
+ [active, api, clearResolvedKeys, namespace]
1865
2117
  );
1866
2118
  return useMemo(
1867
2119
  () => ({
1868
2120
  namespace,
1869
- canEnumerateKeys: api.canEnumerateKeys,
2121
+ canEnumerateKeys: active ? api.canEnumerateKeys : false,
1870
2122
  listKeys,
1871
2123
  clearAll,
1872
2124
  clearKeys,
1873
2125
  clearMatching
1874
2126
  }),
1875
- [namespace, api.canEnumerateKeys, listKeys, clearAll, clearKeys, clearMatching]
2127
+ [namespace, active, api.canEnumerateKeys, listKeys, clearAll, clearKeys, clearMatching]
1876
2128
  );
1877
2129
  }
1878
2130