react-mnemonic 1.0.0-beta.0 → 1.2.0-beta1

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