react-mnemonic 0.1.1-alpha.0 → 1.1.0-beta0

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,10 +442,22 @@ 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;
453
+ let asyncContractViolationDetected = false;
454
+ const storageAccessCallbacks = {
455
+ onAccessError: (err) => logAccessError(err),
456
+ onAccessSuccess: () => {
457
+ accessErrorLogged = false;
458
+ },
459
+ onAsyncViolation: (method, thenable) => handleAsyncStorageContractViolation(method, thenable)
460
+ };
44
461
  const fullKey = (key) => prefix + key;
45
462
  const emit = (key) => {
46
463
  const set = listeners.get(key);
@@ -55,30 +472,36 @@ function MnemonicProvider({
55
472
  accessErrorLogged = true;
56
473
  }
57
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
+ };
58
484
  const readThrough = (key) => {
59
485
  if (cache.has(key)) return cache.get(key) ?? null;
60
- if (!st) {
61
- cache.set(key, null);
62
- return null;
63
- }
64
- try {
65
- const raw = st.getItem(fullKey(key));
66
- cache.set(key, raw);
67
- accessErrorLogged = false;
68
- return raw;
69
- } catch (err) {
70
- logAccessError(err);
486
+ if (!st || asyncContractViolationDetected) {
71
487
  cache.set(key, null);
72
488
  return null;
73
489
  }
490
+ const raw = readStorageRaw(st, fullKey(key), storageAccessCallbacks);
491
+ cache.set(key, raw);
492
+ return raw;
74
493
  };
75
494
  const writeRaw = (key, raw) => {
76
495
  cache.set(key, raw);
77
- if (st) {
496
+ if (st && !asyncContractViolationDetected) {
78
497
  try {
79
- st.setItem(fullKey(key), raw);
80
- quotaErrorLogged = false;
81
- 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
+ }
82
505
  } catch (err) {
83
506
  if (!quotaErrorLogged && err instanceof DOMException && err.name === "QuotaExceededError") {
84
507
  console.error(
@@ -90,18 +513,24 @@ function MnemonicProvider({
90
513
  }
91
514
  }
92
515
  emit(key);
516
+ bumpDevToolsVersion(devToolsRoot, namespace, `set:${key}`);
93
517
  };
94
518
  const removeRaw = (key) => {
95
519
  cache.set(key, null);
96
- if (st) {
520
+ if (st && !asyncContractViolationDetected) {
97
521
  try {
98
- st.removeItem(fullKey(key));
99
- accessErrorLogged = false;
522
+ const result = st.removeItem(fullKey(key));
523
+ if (isPromiseLike(result)) {
524
+ handleAsyncStorageContractViolation("removeItem", result);
525
+ } else {
526
+ accessErrorLogged = false;
527
+ }
100
528
  } catch (err) {
101
529
  logAccessError(err);
102
530
  }
103
531
  }
104
532
  emit(key);
533
+ bumpDevToolsVersion(devToolsRoot, namespace, `remove:${key}`);
105
534
  };
106
535
  const subscribeRaw = (key, listener) => {
107
536
  let set = listeners.get(key);
@@ -120,19 +549,11 @@ function MnemonicProvider({
120
549
  };
121
550
  const getRawSnapshot = (key) => readThrough(key);
122
551
  const keys = () => {
123
- if (!st || typeof st.length !== "number" || typeof st.key !== "function") return [];
124
- const out = [];
125
- try {
126
- for (let i = 0; i < st.length; i++) {
127
- const k = st.key(i);
128
- if (!k) continue;
129
- if (k.startsWith(prefix)) out.push(k.slice(prefix.length));
130
- }
131
- accessErrorLogged = false;
132
- } catch (err) {
133
- logAccessError(err);
552
+ if (asyncContractViolationDetected) {
553
+ return Array.from(cache.entries()).filter(([, value]) => value != null).map(([key]) => key);
134
554
  }
135
- return out;
555
+ if (!canEnumerateKeys) return [];
556
+ return enumerateNamespaceKeys(st, prefix, storageAccessCallbacks);
136
557
  };
137
558
  const dump = () => {
138
559
  const out = {};
@@ -142,58 +563,20 @@ function MnemonicProvider({
142
563
  }
143
564
  return out;
144
565
  };
145
- const reloadFromStorage = (changedKeys) => {
146
- if (!st) return;
147
- if (changedKeys !== void 0 && changedKeys.length === 0) return;
148
- if (changedKeys !== void 0) {
149
- for (const fk of changedKeys) {
150
- if (!fk.startsWith(prefix)) continue;
151
- const key = fk.slice(prefix.length);
152
- const listenerSet = listeners.get(key);
153
- if (listenerSet && listenerSet.size > 0) {
154
- let fresh;
155
- try {
156
- fresh = st.getItem(fk);
157
- accessErrorLogged = false;
158
- } catch (err) {
159
- logAccessError(err);
160
- fresh = null;
161
- }
162
- const cached = cache.get(key) ?? null;
163
- if (fresh !== cached) {
164
- cache.set(key, fresh);
165
- emit(key);
166
- }
167
- } else if (cache.has(key)) {
168
- cache.delete(key);
169
- }
170
- }
171
- return;
172
- }
173
- for (const [key, listenerSet] of listeners) {
174
- if (listenerSet.size === 0) continue;
175
- let fresh;
176
- try {
177
- fresh = st.getItem(fullKey(key));
178
- accessErrorLogged = false;
179
- } catch (err) {
180
- logAccessError(err);
181
- fresh = null;
182
- }
183
- const cached = cache.get(key) ?? null;
184
- if (fresh !== cached) {
185
- cache.set(key, fresh);
186
- emit(key);
187
- }
188
- }
189
- for (const key of cache.keys()) {
190
- if (!listeners.has(key) || listeners.get(key).size === 0) {
191
- cache.delete(key);
192
- }
193
- }
194
- };
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
+ });
195
577
  const store2 = {
196
578
  prefix,
579
+ canEnumerateKeys,
197
580
  subscribeRaw,
198
581
  getRawSnapshot,
199
582
  setRaw: writeRaw,
@@ -202,62 +585,24 @@ function MnemonicProvider({
202
585
  dump,
203
586
  reloadFromStorage,
204
587
  schemaMode,
588
+ ssrHydration,
589
+ crossTabSyncMode,
205
590
  ...schemaRegistry ? { schemaRegistry } : {}
206
591
  };
207
- if (enableDevTools && typeof window !== "undefined") {
208
- window.__REACT_MNEMONIC_DEVTOOLS__ = window.__REACT_MNEMONIC_DEVTOOLS__ || {};
209
- window.__REACT_MNEMONIC_DEVTOOLS__[namespace] = {
210
- /** Access the underlying store instance */
211
- getStore: () => store2,
212
- /** Dump all key-value pairs and display as a console table */
213
- dump: () => {
214
- const data = dump();
215
- console.table(
216
- Object.entries(data).map(([key, value]) => ({
217
- key,
218
- value,
219
- decoded: (() => {
220
- try {
221
- return JSON.parse(value);
222
- } catch {
223
- return value;
224
- }
225
- })()
226
- }))
227
- );
228
- return data;
229
- },
230
- /** Get a decoded value by key */
231
- get: (key) => {
232
- const raw = readThrough(key);
233
- if (raw == null) return void 0;
234
- try {
235
- return JSON.parse(raw);
236
- } catch {
237
- return raw;
238
- }
239
- },
240
- /** Set a value by key (automatically JSON-encoded) */
241
- set: (key, value) => {
242
- writeRaw(key, JSON.stringify(value));
243
- },
244
- /** Remove a key from storage */
245
- remove: (key) => removeRaw(key),
246
- /** Clear all keys in this namespace */
247
- clear: () => {
248
- for (const k of keys()) {
249
- removeRaw(k);
250
- }
251
- },
252
- /** List all keys in this namespace */
253
- keys
254
- };
255
- console.info(
256
- `[Mnemonic DevTools] Namespace "${namespace}" available at window.__REACT_MNEMONIC_DEVTOOLS__.${namespace}`
257
- );
592
+ if (devToolsRoot) {
593
+ registerDevToolsProvider({
594
+ devToolsRoot,
595
+ namespace,
596
+ store: store2,
597
+ dump,
598
+ keys,
599
+ readThrough,
600
+ writeRaw,
601
+ removeRaw
602
+ });
258
603
  }
259
604
  return store2;
260
- }, [namespace, storage, enableDevTools, schemaMode, schemaRegistry]);
605
+ }, [namespace, storage, enableDevTools, schemaMode, schemaRegistry, ssr?.hydration]);
261
606
  react.useEffect(() => {
262
607
  if (!storage?.onExternalChange) return;
263
608
  return storage.onExternalChange((changedKeys) => store.reloadFromStorage(changedKeys));
@@ -327,30 +672,34 @@ function matchesType(value, type) {
327
672
  return false;
328
673
  }
329
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
+ }
330
692
  function jsonDeepEqual(a, b) {
331
693
  if (a === b) return true;
332
694
  if (a === null || b === null) return false;
333
695
  if (typeof a !== typeof b) return false;
334
696
  if (Array.isArray(a)) {
335
697
  if (!Array.isArray(b)) return false;
336
- if (a.length !== b.length) return false;
337
- for (let i = 0; i < a.length; i++) {
338
- if (!jsonDeepEqual(a[i], b[i])) return false;
339
- }
340
- return true;
698
+ return jsonDeepEqualArray(a, b);
341
699
  }
342
700
  if (typeof a === "object") {
343
701
  if (Array.isArray(b)) return false;
344
- const aObj = a;
345
- const bObj = b;
346
- const aKeys = Object.keys(aObj);
347
- const bKeys = Object.keys(bObj);
348
- if (aKeys.length !== bKeys.length) return false;
349
- for (const key of aKeys) {
350
- if (!Object.prototype.hasOwnProperty.call(bObj, key)) return false;
351
- if (!jsonDeepEqual(aObj[key], bObj[key])) return false;
352
- }
353
- return true;
702
+ return jsonDeepEqualObject(a, b);
354
703
  }
355
704
  return false;
356
705
  }
@@ -365,206 +714,283 @@ function compileSchema(schema) {
365
714
  function isJsonPrimitive(value) {
366
715
  return value === null || typeof value !== "object";
367
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
+ }
368
727
  function buildValidator(schema) {
369
- const resolvedTypes = schema.type !== void 0 ? Array.isArray(schema.type) ? schema.type : [schema.type] : null;
370
- const typeLabel = resolvedTypes !== null ? JSON.stringify(schema.type) : "";
371
- const enumMembers = schema.enum;
372
- let enumPrimitiveSet = null;
373
- let enumComplexMembers = null;
374
- if (enumMembers !== void 0) {
375
- const primitives = [];
376
- const complex = [];
377
- for (const member of enumMembers) {
378
- if (isJsonPrimitive(member)) {
379
- primitives.push(member);
380
- } else {
381
- complex.push(member);
382
- }
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);
747
+ }
748
+ return errors;
749
+ };
750
+ }
751
+ function buildTypeValidationStep(schema) {
752
+ if (schema.type === void 0) {
753
+ return null;
754
+ }
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;
383
780
  }
384
- if (primitives.length > 0) enumPrimitiveSet = new Set(primitives);
385
- if (complex.length > 0) enumComplexMembers = complex;
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;
386
791
  }
387
- const hasConst = "const" in schema;
388
- const constValue = schema.const;
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) {
389
804
  const hasMinimum = schema.minimum !== void 0;
390
- const minimum = schema.minimum;
391
805
  const hasMaximum = schema.maximum !== void 0;
392
- const maximum = schema.maximum;
393
806
  const hasExMin = schema.exclusiveMinimum !== void 0;
394
- const exMin = schema.exclusiveMinimum;
395
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;
396
814
  const exMax = schema.exclusiveMaximum;
397
- 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) {
398
850
  const hasMinLength = schema.minLength !== void 0;
399
- const minLen = schema.minLength;
400
851
  const hasMaxLength = schema.maxLength !== void 0;
401
- const maxLen = schema.maxLength;
402
- const hasStringConstraints = hasMinLength || hasMaxLength;
403
- const requiredKeys = schema.required;
404
- const hasRequired = requiredKeys !== void 0 && requiredKeys.length > 0;
405
- const hasProperties = schema.properties !== void 0;
406
- const propertyValidators = hasProperties ? Object.entries(schema.properties).map(
407
- ([name, propSchema]) => [name, compileSchema(propSchema)]
408
- ) : 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;
409
883
  const checkAdditional = schema.additionalProperties !== void 0 && schema.additionalProperties !== true;
410
884
  const additionalIsFalse = schema.additionalProperties === false;
411
885
  const additionalValidator = checkAdditional && !additionalIsFalse ? compileSchema(schema.additionalProperties) : null;
412
- const definedPropKeys = checkAdditional ? new Set(schema.properties ? Object.keys(schema.properties) : []) : null;
413
- const hasObjectConstraints = hasRequired || hasProperties || checkAdditional;
414
- const hasMinItems = schema.minItems !== void 0;
415
- const minItems = schema.minItems;
416
- const hasMaxItems = schema.maxItems !== void 0;
417
- const maxItems = schema.maxItems;
418
- const itemsValidator = schema.items !== void 0 ? compileSchema(schema.items) : null;
419
- const hasArrayConstraints = hasMinItems || hasMaxItems || itemsValidator !== null;
420
- if (resolvedTypes === null && enumMembers === void 0 && !hasConst && !hasNumberConstraints && !hasStringConstraints && !hasObjectConstraints && !hasArrayConstraints) {
421
- 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));
422
890
  }
423
- return (value, path = "") => {
424
- const errors = [];
425
- if (resolvedTypes !== null) {
426
- const matched = resolvedTypes.some((t) => matchesType(value, t));
427
- if (!matched) {
428
- errors.push({
429
- path,
430
- message: `Expected type ${typeLabel}, got ${jsonTypeLabel(value)}`,
431
- keyword: "type"
432
- });
433
- return errors;
434
- }
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;
435
909
  }
436
- if (enumMembers !== void 0) {
437
- let matched = false;
438
- if (enumPrimitiveSet !== null && isJsonPrimitive(value)) {
439
- matched = enumPrimitiveSet.has(value);
440
- }
441
- if (!matched && enumComplexMembers !== null) {
442
- matched = enumComplexMembers.some((entry) => jsonDeepEqual(value, entry));
443
- }
444
- if (!matched) {
445
- errors.push({
446
- path,
447
- message: `Value does not match any enum member`,
448
- keyword: "enum"
449
- });
450
- }
910
+ for (const step of objectValidationSteps) {
911
+ step(value, path, errors);
451
912
  }
452
- if (hasConst) {
453
- if (!jsonDeepEqual(value, constValue)) {
454
- errors.push({
455
- path,
456
- message: `Value does not match const`,
457
- keyword: "const"
458
- });
913
+ };
914
+ }
915
+ function createRequiredPropertyStep(requiredKeys) {
916
+ return (value, path, errors) => {
917
+ for (const requiredKey of requiredKeys) {
918
+ if (objectHasOwn(value, requiredKey)) {
919
+ continue;
459
920
  }
921
+ errors.push({
922
+ path,
923
+ message: `Missing required property "${requiredKey}"`,
924
+ keyword: "required"
925
+ });
460
926
  }
461
- if (hasNumberConstraints && typeof value === "number") {
462
- if (hasMinimum && value < minimum) {
463
- errors.push({
464
- path,
465
- message: `Value ${value} is less than minimum ${minimum}`,
466
- keyword: "minimum"
467
- });
468
- }
469
- if (hasMaximum && value > maximum) {
470
- errors.push({
471
- path,
472
- message: `Value ${value} is greater than maximum ${maximum}`,
473
- keyword: "maximum"
474
- });
475
- }
476
- if (hasExMin && value <= exMin) {
477
- errors.push({
478
- path,
479
- message: `Value ${value} is not greater than exclusiveMinimum ${exMin}`,
480
- keyword: "exclusiveMinimum"
481
- });
482
- }
483
- if (hasExMax && value >= exMax) {
484
- errors.push({
485
- path,
486
- message: `Value ${value} is not less than exclusiveMaximum ${exMax}`,
487
- keyword: "exclusiveMaximum"
488
- });
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;
489
934
  }
935
+ errors.push(...validator(value[propertyName], `${path}/${propertyName}`));
490
936
  }
491
- if (hasStringConstraints && typeof value === "string") {
492
- if (hasMinLength && value.length < minLen) {
493
- errors.push({
494
- path,
495
- message: `String length ${value.length} is less than minLength ${minLen}`,
496
- keyword: "minLength"
497
- });
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;
498
948
  }
499
- if (hasMaxLength && value.length > maxLen) {
949
+ if (additionalIsFalse) {
500
950
  errors.push({
501
951
  path,
502
- message: `String length ${value.length} is greater than maxLength ${maxLen}`,
503
- keyword: "maxLength"
952
+ message: `Additional property "${objectKey}" is not allowed`,
953
+ keyword: "additionalProperties"
504
954
  });
955
+ continue;
505
956
  }
957
+ errors.push(...additionalValidator(value[objectKey], `${path}/${objectKey}`));
506
958
  }
507
- if (hasObjectConstraints && typeof value === "object" && value !== null && !Array.isArray(value)) {
508
- const obj = value;
509
- if (hasRequired) {
510
- for (const reqKey of requiredKeys) {
511
- if (!Object.prototype.hasOwnProperty.call(obj, reqKey)) {
512
- errors.push({
513
- path,
514
- message: `Missing required property "${reqKey}"`,
515
- keyword: "required"
516
- });
517
- }
518
- }
519
- }
520
- if (propertyValidators !== null) {
521
- for (const [propName, propValidator] of propertyValidators) {
522
- if (Object.prototype.hasOwnProperty.call(obj, propName)) {
523
- const propErrors = propValidator(obj[propName], `${path}/${propName}`);
524
- errors.push(...propErrors);
525
- }
526
- }
527
- }
528
- if (checkAdditional) {
529
- for (const objKey of Object.keys(obj)) {
530
- if (!definedPropKeys.has(objKey)) {
531
- if (additionalIsFalse) {
532
- errors.push({
533
- path,
534
- message: `Additional property "${objKey}" is not allowed`,
535
- keyword: "additionalProperties"
536
- });
537
- } else {
538
- const propErrors = additionalValidator(obj[objKey], `${path}/${objKey}`);
539
- errors.push(...propErrors);
540
- }
541
- }
542
- }
543
- }
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;
544
973
  }
545
- if (hasArrayConstraints && Array.isArray(value)) {
546
- if (hasMinItems && value.length < minItems) {
547
- errors.push({
548
- path,
549
- message: `Array length ${value.length} is less than minItems ${minItems}`,
550
- keyword: "minItems"
551
- });
552
- }
553
- if (hasMaxItems && value.length > maxItems) {
554
- errors.push({
555
- path,
556
- message: `Array length ${value.length} is greater than maxItems ${maxItems}`,
557
- keyword: "maxItems"
558
- });
559
- }
560
- if (itemsValidator !== null) {
561
- for (let i = 0; i < value.length; i++) {
562
- const itemErrors = itemsValidator(value[i], `${path}/${i}`);
563
- errors.push(...itemErrors);
564
- }
565
- }
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}`));
566
993
  }
567
- return errors;
568
994
  };
569
995
  }
570
996
  function validateJsonSchema(value, schema, path = "") {
@@ -583,7 +1009,7 @@ function inferJsonSchema(sample) {
583
1009
  case "string":
584
1010
  return { type: "string" };
585
1011
  case "number":
586
- return Number.isInteger(sample) ? { type: "number" } : { type: "number" };
1012
+ return { type: "number" };
587
1013
  case "boolean":
588
1014
  return { type: "boolean" };
589
1015
  case "object":
@@ -594,21 +1020,173 @@ function inferJsonSchema(sample) {
594
1020
  }
595
1021
 
596
1022
  // src/Mnemonic/use.ts
597
- function useMnemonicKey(key, options) {
1023
+ var SSR_SNAPSHOT_TOKEN = /* @__PURE__ */ Symbol("mnemonic:ssr-snapshot");
1024
+ var diagnosticContractRegistry = /* @__PURE__ */ new WeakMap();
1025
+ var diagnosticWarningRegistry = /* @__PURE__ */ new WeakMap();
1026
+ var diagnosticObjectIds = /* @__PURE__ */ new WeakMap();
1027
+ var nextDiagnosticObjectId = 1;
1028
+ function serializeEnvelope(version, payload) {
1029
+ return JSON.stringify({
1030
+ version,
1031
+ payload
1032
+ });
1033
+ }
1034
+ function withReadMetadata(value, rewriteRaw, pendingSchema) {
1035
+ const result = { value };
1036
+ if (rewriteRaw !== void 0) result.rewriteRaw = rewriteRaw;
1037
+ if (pendingSchema !== void 0) result.pendingSchema = pendingSchema;
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
+ schema,
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: schema?.version ?? 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 useMnemonicKey(keyOrDescriptor, options) {
1130
+ const descriptor = resolveMnemonicKeyArgs(keyOrDescriptor, options);
1131
+ const key = descriptor.key;
1132
+ const resolvedOptions = descriptor.options;
598
1133
  const api = useMnemonic();
599
- const { defaultValue, onMount, onChange, listenCrossTab, codec: codecOpt, schema } = options;
1134
+ const {
1135
+ defaultValue,
1136
+ onMount,
1137
+ onChange,
1138
+ listenCrossTab,
1139
+ codec: codecOpt,
1140
+ schema,
1141
+ reconcile,
1142
+ ssr: ssrOptions
1143
+ } = resolvedOptions;
600
1144
  const codec = codecOpt ?? JSONCodec;
601
1145
  const schemaMode = api.schemaMode;
602
1146
  const schemaRegistry = api.schemaRegistry;
1147
+ const hydrationMode = ssrOptions?.hydration ?? api.ssrHydration;
1148
+ const [hasMounted, setHasMounted] = react.useState(hydrationMode !== "client-only");
1149
+ const developmentRuntime = isDevelopmentRuntime();
1150
+ const contractFingerprint = react.useMemo(
1151
+ () => developmentRuntime ? buildContractFingerprint({
1152
+ api,
1153
+ key,
1154
+ defaultValue,
1155
+ codecOpt,
1156
+ schema,
1157
+ reconcile,
1158
+ listenCrossTab,
1159
+ ssrOptions
1160
+ }) : null,
1161
+ [
1162
+ developmentRuntime,
1163
+ api,
1164
+ key,
1165
+ defaultValue,
1166
+ codecOpt,
1167
+ schema?.version,
1168
+ reconcile,
1169
+ listenCrossTab,
1170
+ ssrOptions?.hydration,
1171
+ ssrOptions?.serverValue
1172
+ ]
1173
+ );
603
1174
  const getFallback = react.useCallback(
604
1175
  (error) => typeof defaultValue === "function" ? defaultValue(error) : defaultValue,
605
1176
  [defaultValue]
606
1177
  );
1178
+ const getServerValue = react.useCallback(() => {
1179
+ const serverValue = ssrOptions?.serverValue;
1180
+ if (serverValue === void 0) {
1181
+ return getFallback();
1182
+ }
1183
+ return typeof serverValue === "function" ? serverValue() : serverValue;
1184
+ }, [getFallback, ssrOptions?.serverValue]);
607
1185
  const parseEnvelope = react.useCallback(
608
1186
  (rawText) => {
609
1187
  try {
610
1188
  const parsed = JSON.parse(rawText);
611
- if (typeof parsed !== "object" || parsed == null || !Number.isInteger(parsed.version) || parsed.version < 0 || !Object.prototype.hasOwnProperty.call(parsed, "payload")) {
1189
+ if (typeof parsed !== "object" || parsed == null || !Number.isInteger(parsed.version) || parsed.version < 0 || !objectHasOwn2(parsed, "payload")) {
612
1190
  return {
613
1191
  ok: false,
614
1192
  error: new SchemaError("INVALID_ENVELOPE", `Invalid envelope for key "${key}"`)
@@ -626,12 +1204,6 @@ function useMnemonicKey(key, options) {
626
1204
  );
627
1205
  const decodeStringPayload = react.useCallback(
628
1206
  (payload, activeCodec) => {
629
- if (typeof payload !== "string") {
630
- throw new SchemaError(
631
- "INVALID_ENVELOPE",
632
- `Envelope payload must be a string for codec-managed key "${key}"`
633
- );
634
- }
635
1207
  try {
636
1208
  return activeCodec.decode(payload);
637
1209
  } catch (err) {
@@ -695,94 +1267,194 @@ function useMnemonicKey(key, options) {
695
1267
  },
696
1268
  [schemaRegistry, registryCache, key]
697
1269
  );
698
- const decodeForRead = react.useCallback(
699
- (rawText) => {
700
- if (rawText == null) return { value: getFallback() };
701
- const parsed = parseEnvelope(rawText);
702
- if (!parsed.ok) return { value: getFallback(parsed.error) };
703
- const envelope = parsed.envelope;
704
- const schemaForVersion = getSchemaForVersion(envelope.version);
705
- const latestSchema = getLatestSchemaForKey();
706
- if (schemaMode === "strict" && !schemaForVersion) {
707
- return {
708
- value: getFallback(
709
- new SchemaError("SCHEMA_NOT_FOUND", `No schema for key "${key}" v${envelope.version}`)
710
- )
711
- };
712
- }
713
- if (schemaMode === "autoschema" && !schemaForVersion) {
714
- if (latestSchema) {
715
- return {
716
- value: getFallback(
717
- new SchemaError("SCHEMA_NOT_FOUND", `No schema for key "${key}" v${envelope.version}`)
718
- )
719
- };
720
- }
721
- if (!schemaRegistry || typeof schemaRegistry.registerSchema !== "function") {
722
- return {
723
- value: getFallback(
724
- new SchemaError(
725
- "MODE_CONFIGURATION_INVALID",
726
- `Autoschema mode requires schema registry registration for key "${key}"`
727
- )
728
- )
729
- };
1270
+ const buildFallbackResult = react.useCallback(
1271
+ (error) => ({
1272
+ value: getFallback(error)
1273
+ }),
1274
+ [getFallback]
1275
+ );
1276
+ const buildSchemaManagedResult = react.useCallback(
1277
+ (version, value2) => serializeEnvelope(version, value2),
1278
+ []
1279
+ );
1280
+ const resolveTargetWriteSchema = react.useCallback(() => {
1281
+ const explicitVersion = schema?.version;
1282
+ const latestSchema = getLatestSchemaForKey();
1283
+ if (explicitVersion === void 0) return latestSchema;
1284
+ const explicitSchema = getSchemaForVersion(explicitVersion);
1285
+ if (explicitSchema) return explicitSchema;
1286
+ return schemaMode === "strict" ? void 0 : latestSchema;
1287
+ }, [getLatestSchemaForKey, getSchemaForVersion, schema?.version, schemaMode]);
1288
+ const encodeForWrite = react.useCallback(
1289
+ (nextValue) => {
1290
+ const explicitVersion = schema?.version;
1291
+ const targetSchema = resolveTargetWriteSchema();
1292
+ if (!targetSchema) {
1293
+ if (explicitVersion !== void 0 && schemaMode === "strict") {
1294
+ throw new SchemaError(
1295
+ "WRITE_SCHEMA_REQUIRED",
1296
+ `Write requires schema for key "${key}" in strict mode`
1297
+ );
730
1298
  }
1299
+ return serializeEnvelope(0, codec.encode(nextValue));
1300
+ }
1301
+ let valueToStore = nextValue;
1302
+ const writeMigration = schemaRegistry?.getWriteMigration?.(key, targetSchema.version);
1303
+ if (writeMigration) {
731
1304
  try {
732
- const decoded2 = typeof envelope.payload === "string" ? decodeStringPayload(envelope.payload, codec) : envelope.payload;
733
- const inferredJsonSchema = inferJsonSchema(decoded2);
734
- const inferred = {
735
- key,
736
- version: 1,
737
- schema: inferredJsonSchema
738
- };
739
- const rewriteEnvelope = {
740
- version: inferred.version,
741
- payload: decoded2
742
- };
743
- return {
744
- value: decoded2,
745
- pendingSchema: inferred,
746
- rewriteRaw: JSON.stringify(rewriteEnvelope)
747
- };
1305
+ valueToStore = writeMigration.migrate(valueToStore);
748
1306
  } catch (err) {
749
- const typedErr = err instanceof SchemaError || err instanceof CodecError ? err : new SchemaError("TYPE_MISMATCH", `Autoschema inference failed for key "${key}"`, err);
750
- return { value: getFallback(typedErr) };
1307
+ throw err instanceof SchemaError ? err : new SchemaError("MIGRATION_FAILED", `Write-time migration failed for key "${key}"`, err);
751
1308
  }
752
1309
  }
753
- if (!schemaForVersion) {
754
- if (typeof envelope.payload !== "string") {
755
- return { value: envelope.payload };
756
- }
1310
+ validateAgainstSchema(valueToStore, targetSchema.schema);
1311
+ return buildSchemaManagedResult(targetSchema.version, valueToStore);
1312
+ },
1313
+ [
1314
+ schema?.version,
1315
+ key,
1316
+ schemaMode,
1317
+ codec,
1318
+ schemaRegistry,
1319
+ validateAgainstSchema,
1320
+ resolveTargetWriteSchema,
1321
+ buildSchemaManagedResult
1322
+ ]
1323
+ );
1324
+ const applyReconcile = react.useCallback(
1325
+ ({
1326
+ value: value2,
1327
+ rewriteRaw,
1328
+ pendingSchema,
1329
+ persistedVersion,
1330
+ latestVersion,
1331
+ serializeForPersist,
1332
+ derivePendingSchema
1333
+ }) => {
1334
+ if (!reconcile) {
1335
+ return withReadMetadata(value2, rewriteRaw, pendingSchema);
1336
+ }
1337
+ const context = {
1338
+ key,
1339
+ persistedVersion,
1340
+ ...latestVersion === void 0 ? {} : { latestVersion }
1341
+ };
1342
+ const baselineSerialized = (() => {
757
1343
  try {
758
- const decoded2 = decodeStringPayload(envelope.payload, codec);
759
- return { value: decoded2 };
760
- } catch (err) {
761
- const typedErr = err instanceof SchemaError || err instanceof CodecError ? err : new CodecError(`Codec decode failed for key "${key}"`, err);
762
- return { value: getFallback(typedErr) };
1344
+ return serializeForPersist(value2);
1345
+ } catch {
1346
+ return rewriteRaw;
763
1347
  }
1348
+ })();
1349
+ try {
1350
+ const reconciled = reconcile(value2, context);
1351
+ const nextPendingSchema = derivePendingSchema ? derivePendingSchema(reconciled) : pendingSchema;
1352
+ const nextSerialized = serializeForPersist(reconciled);
1353
+ const nextRewriteRaw = baselineSerialized === void 0 || nextSerialized !== baselineSerialized ? nextSerialized : rewriteRaw;
1354
+ return withReadMetadata(reconciled, nextRewriteRaw, nextPendingSchema);
1355
+ } catch (err) {
1356
+ const typedErr = err instanceof SchemaError ? err : new SchemaError("RECONCILE_FAILED", `Reconciliation failed for key "${key}"`, err);
1357
+ return buildFallbackResult(typedErr);
1358
+ }
1359
+ },
1360
+ [buildFallbackResult, key, reconcile]
1361
+ );
1362
+ const decodeAutoschemaEnvelope = react.useCallback(
1363
+ (envelope, latestSchema) => {
1364
+ if (latestSchema) {
1365
+ return buildFallbackResult(
1366
+ new SchemaError("SCHEMA_NOT_FOUND", `No schema for key "${key}" v${envelope.version}`)
1367
+ );
1368
+ }
1369
+ if (!schemaRegistry || typeof schemaRegistry.registerSchema !== "function") {
1370
+ return buildFallbackResult(
1371
+ new SchemaError(
1372
+ "MODE_CONFIGURATION_INVALID",
1373
+ `Autoschema mode requires schema registry registration for key "${key}"`
1374
+ )
1375
+ );
1376
+ }
1377
+ try {
1378
+ const decoded2 = typeof envelope.payload === "string" ? decodeStringPayload(envelope.payload, codec) : envelope.payload;
1379
+ const inferSchemaForValue = (value2) => ({
1380
+ key,
1381
+ version: 1,
1382
+ schema: inferJsonSchema(value2)
1383
+ });
1384
+ const inferred = inferSchemaForValue(decoded2);
1385
+ return applyReconcile({
1386
+ value: decoded2,
1387
+ pendingSchema: inferred,
1388
+ rewriteRaw: buildSchemaManagedResult(inferred.version, decoded2),
1389
+ persistedVersion: envelope.version,
1390
+ serializeForPersist: (value2) => buildSchemaManagedResult(inferred.version, value2),
1391
+ derivePendingSchema: inferSchemaForValue
1392
+ });
1393
+ } catch (err) {
1394
+ const typedErr = err instanceof SchemaError || err instanceof CodecError ? err : new SchemaError("TYPE_MISMATCH", `Autoschema inference failed for key "${key}"`, err);
1395
+ return buildFallbackResult(typedErr);
1396
+ }
1397
+ },
1398
+ [
1399
+ applyReconcile,
1400
+ buildFallbackResult,
1401
+ buildSchemaManagedResult,
1402
+ codec,
1403
+ decodeStringPayload,
1404
+ key,
1405
+ schemaRegistry
1406
+ ]
1407
+ );
1408
+ const decodeCodecManagedEnvelope = react.useCallback(
1409
+ (envelope, latestSchema) => {
1410
+ if (typeof envelope.payload !== "string") {
1411
+ return applyReconcile({
1412
+ value: envelope.payload,
1413
+ persistedVersion: envelope.version,
1414
+ ...latestSchema ? { latestVersion: latestSchema.version } : {},
1415
+ serializeForPersist: encodeForWrite
1416
+ });
1417
+ }
1418
+ try {
1419
+ const decoded2 = decodeStringPayload(envelope.payload, codec);
1420
+ return applyReconcile({
1421
+ value: decoded2,
1422
+ persistedVersion: envelope.version,
1423
+ ...latestSchema ? { latestVersion: latestSchema.version } : {},
1424
+ serializeForPersist: encodeForWrite
1425
+ });
1426
+ } catch (err) {
1427
+ return buildFallbackResult(err);
764
1428
  }
1429
+ },
1430
+ [applyReconcile, buildFallbackResult, codec, decodeStringPayload, encodeForWrite]
1431
+ );
1432
+ const decodeSchemaManagedEnvelope = react.useCallback(
1433
+ (envelope, schemaForVersion, latestSchema) => {
765
1434
  let current;
766
1435
  try {
767
1436
  current = envelope.payload;
768
1437
  validateAgainstSchema(current, schemaForVersion.schema);
769
1438
  } catch (err) {
770
1439
  const typedErr = err instanceof SchemaError || err instanceof CodecError ? err : new SchemaError("TYPE_MISMATCH", `Schema decode failed for key "${key}"`, err);
771
- return { value: getFallback(typedErr) };
1440
+ return buildFallbackResult(typedErr);
772
1441
  }
773
1442
  if (!latestSchema || envelope.version >= latestSchema.version) {
774
- return { value: current };
1443
+ return applyReconcile({
1444
+ value: current,
1445
+ persistedVersion: envelope.version,
1446
+ ...latestSchema ? { latestVersion: latestSchema.version } : {},
1447
+ serializeForPersist: encodeForWrite
1448
+ });
775
1449
  }
776
1450
  const path = getMigrationPathForKey(envelope.version, latestSchema.version);
777
1451
  if (!path) {
778
- return {
779
- value: getFallback(
780
- new SchemaError(
781
- "MIGRATION_PATH_NOT_FOUND",
782
- `No migration path for key "${key}" from v${envelope.version} to v${latestSchema.version}`
783
- )
1452
+ return buildFallbackResult(
1453
+ new SchemaError(
1454
+ "MIGRATION_PATH_NOT_FOUND",
1455
+ `No migration path for key "${key}" from v${envelope.version} to v${latestSchema.version}`
784
1456
  )
785
- };
1457
+ );
786
1458
  }
787
1459
  try {
788
1460
  let migrated = current;
@@ -790,96 +1462,142 @@ function useMnemonicKey(key, options) {
790
1462
  migrated = step.migrate(migrated);
791
1463
  }
792
1464
  validateAgainstSchema(migrated, latestSchema.schema);
793
- const rewriteEnvelope = {
794
- version: latestSchema.version,
795
- payload: migrated
796
- };
797
- return {
1465
+ return applyReconcile({
798
1466
  value: migrated,
799
- rewriteRaw: JSON.stringify(rewriteEnvelope)
800
- };
1467
+ rewriteRaw: buildSchemaManagedResult(latestSchema.version, migrated),
1468
+ persistedVersion: envelope.version,
1469
+ latestVersion: latestSchema.version,
1470
+ serializeForPersist: encodeForWrite
1471
+ });
801
1472
  } catch (err) {
802
1473
  const typedErr = err instanceof SchemaError || err instanceof CodecError ? err : new SchemaError("MIGRATION_FAILED", `Migration failed for key "${key}"`, err);
803
- return { value: getFallback(typedErr) };
1474
+ return buildFallbackResult(typedErr);
804
1475
  }
805
1476
  },
806
1477
  [
807
- codec,
808
- decodeStringPayload,
809
- getFallback,
810
- key,
811
- parseEnvelope,
812
- schemaMode,
813
- schemaRegistry,
814
- getSchemaForVersion,
815
- getLatestSchemaForKey,
1478
+ applyReconcile,
1479
+ buildFallbackResult,
1480
+ buildSchemaManagedResult,
1481
+ encodeForWrite,
816
1482
  getMigrationPathForKey,
1483
+ key,
817
1484
  validateAgainstSchema
818
1485
  ]
819
1486
  );
820
- const encodeForWrite = react.useCallback(
821
- (nextValue) => {
822
- const explicitVersion = schema?.version;
1487
+ const decodeForRead = react.useCallback(
1488
+ (rawText) => {
1489
+ if (rawText == null) return buildFallbackResult();
1490
+ const parsed = parseEnvelope(rawText);
1491
+ if (!parsed.ok) return buildFallbackResult(parsed.error);
1492
+ const envelope = parsed.envelope;
1493
+ const schemaForVersion = getSchemaForVersion(envelope.version);
823
1494
  const latestSchema = getLatestSchemaForKey();
824
- const explicitSchema = explicitVersion !== void 0 ? getSchemaForVersion(explicitVersion) : void 0;
825
- let targetSchema = explicitSchema;
826
- if (!targetSchema) {
827
- if (explicitVersion !== void 0) {
828
- if (schemaMode !== "strict") {
829
- targetSchema = latestSchema;
830
- }
831
- } else {
832
- targetSchema = latestSchema;
833
- }
1495
+ if (schemaMode === "strict" && !schemaForVersion) {
1496
+ return buildFallbackResult(
1497
+ new SchemaError("SCHEMA_NOT_FOUND", `No schema for key "${key}" v${envelope.version}`)
1498
+ );
834
1499
  }
835
- if (!targetSchema) {
836
- if (explicitVersion !== void 0 && schemaMode === "strict") {
837
- throw new SchemaError(
838
- "WRITE_SCHEMA_REQUIRED",
839
- `Write requires schema for key "${key}" in strict mode`
840
- );
841
- }
842
- const envelope2 = {
843
- version: 0,
844
- payload: codec.encode(nextValue)
845
- };
846
- return JSON.stringify(envelope2);
1500
+ if (schemaMode === "autoschema" && !schemaForVersion) {
1501
+ return decodeAutoschemaEnvelope(envelope, latestSchema);
847
1502
  }
848
- let valueToStore = nextValue;
849
- const writeMigration = schemaRegistry?.getWriteMigration?.(key, targetSchema.version);
850
- if (writeMigration) {
851
- try {
852
- valueToStore = writeMigration.migrate(valueToStore);
853
- } catch (err) {
854
- throw err instanceof SchemaError ? err : new SchemaError("MIGRATION_FAILED", `Write-time migration failed for key "${key}"`, err);
855
- }
1503
+ if (!schemaForVersion) {
1504
+ return decodeCodecManagedEnvelope(envelope, latestSchema);
856
1505
  }
857
- validateAgainstSchema(valueToStore, targetSchema.schema);
858
- const envelope = {
859
- version: targetSchema.version,
860
- payload: valueToStore
861
- };
862
- return JSON.stringify(envelope);
1506
+ return decodeSchemaManagedEnvelope(envelope, schemaForVersion, latestSchema);
863
1507
  },
864
1508
  [
865
- schema?.version,
866
- key,
1509
+ buildFallbackResult,
1510
+ decodeAutoschemaEnvelope,
1511
+ decodeCodecManagedEnvelope,
1512
+ decodeSchemaManagedEnvelope,
1513
+ parseEnvelope,
867
1514
  schemaMode,
868
- codec,
869
- schemaRegistry,
870
- validateAgainstSchema,
871
- getLatestSchemaForKey,
872
- getSchemaForVersion
1515
+ getSchemaForVersion,
1516
+ getLatestSchemaForKey
873
1517
  ]
874
1518
  );
1519
+ const getServerRawSnapshot = react.useCallback(
1520
+ () => ssrOptions?.serverValue === void 0 ? null : SSR_SNAPSHOT_TOKEN,
1521
+ [ssrOptions?.serverValue]
1522
+ );
1523
+ const deferStorageRead = hydrationMode === "client-only" && !hasMounted;
1524
+ const subscribe = react.useCallback(
1525
+ (listener) => {
1526
+ if (deferStorageRead) {
1527
+ return () => void 0;
1528
+ }
1529
+ return api.subscribeRaw(key, listener);
1530
+ },
1531
+ [api, deferStorageRead, key]
1532
+ );
875
1533
  const raw = react.useSyncExternalStore(
876
- (listener) => api.subscribeRaw(key, listener),
877
- () => api.getRawSnapshot(key),
878
- () => null
879
- // SSR snapshot - no storage in server environment
1534
+ subscribe,
1535
+ () => deferStorageRead ? getServerRawSnapshot() : api.getRawSnapshot(key),
1536
+ getServerRawSnapshot
880
1537
  );
881
- const decoded = react.useMemo(() => decodeForRead(raw), [decodeForRead, raw]);
1538
+ const decoded = react.useMemo(() => {
1539
+ if (raw === SSR_SNAPSHOT_TOKEN) {
1540
+ return {
1541
+ value: getServerValue(),
1542
+ rewriteRaw: void 0,
1543
+ pendingSchema: void 0
1544
+ };
1545
+ }
1546
+ return decodeForRead(raw);
1547
+ }, [decodeForRead, getServerValue, raw]);
882
1548
  const value = decoded.value;
1549
+ react.useEffect(() => {
1550
+ if (!developmentRuntime) return;
1551
+ if (listenCrossTab && (api.crossTabSyncMode ?? "none") === "none" && globalThis.window !== void 0) {
1552
+ warnOnce(
1553
+ api,
1554
+ `listenCrossTab:${key}`,
1555
+ `[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.`
1556
+ );
1557
+ }
1558
+ if (codecOpt && schema?.version !== void 0 && api.schemaRegistry) {
1559
+ warnOnce(
1560
+ api,
1561
+ `codec+schema:${key}`,
1562
+ `[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.`
1563
+ );
1564
+ }
1565
+ let keyContracts = diagnosticContractRegistry.get(api);
1566
+ if (!keyContracts) {
1567
+ keyContracts = /* @__PURE__ */ new Map();
1568
+ diagnosticContractRegistry.set(api, keyContracts);
1569
+ }
1570
+ if (contractFingerprint === null) {
1571
+ return;
1572
+ }
1573
+ const previousContract = keyContracts.get(key);
1574
+ if (previousContract === void 0) {
1575
+ keyContracts.set(key, contractFingerprint);
1576
+ return;
1577
+ }
1578
+ if (previousContract === contractFingerprint) {
1579
+ return;
1580
+ }
1581
+ warnOnce(
1582
+ api,
1583
+ `contract-conflict:${key}`,
1584
+ `[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.`
1585
+ );
1586
+ }, [
1587
+ api,
1588
+ key,
1589
+ developmentRuntime,
1590
+ contractFingerprint,
1591
+ listenCrossTab,
1592
+ codecOpt,
1593
+ schema?.version,
1594
+ api.schemaRegistry,
1595
+ api.crossTabSyncMode
1596
+ ]);
1597
+ react.useEffect(() => {
1598
+ if (hasMounted) return;
1599
+ setHasMounted(true);
1600
+ }, [hasMounted]);
883
1601
  react.useEffect(() => {
884
1602
  if (decoded.rewriteRaw && decoded.rewriteRaw !== raw) {
885
1603
  api.setRaw(key, decoded.rewriteRaw);
@@ -909,7 +1627,8 @@ function useMnemonicKey(key, options) {
909
1627
  }, [value, onChange]);
910
1628
  react.useEffect(() => {
911
1629
  if (!listenCrossTab) return;
912
- if (typeof window === "undefined") return;
1630
+ const globalWindow = globalThis.window;
1631
+ if (globalWindow === void 0) return;
913
1632
  const storageKey = api.prefix + key;
914
1633
  const handler = (e) => {
915
1634
  if (e.key === null) {
@@ -923,8 +1642,8 @@ function useMnemonicKey(key, options) {
923
1642
  }
924
1643
  api.setRaw(key, e.newValue);
925
1644
  };
926
- window.addEventListener("storage", handler);
927
- return () => window.removeEventListener("storage", handler);
1645
+ globalWindow.addEventListener("storage", handler);
1646
+ return () => globalWindow.removeEventListener("storage", handler);
928
1647
  }, [listenCrossTab, api, key]);
929
1648
  const set = react.useMemo(() => {
930
1649
  return (next) => {
@@ -983,6 +1702,477 @@ function useMnemonicKey(key, options) {
983
1702
  [value, set, reset, remove]
984
1703
  );
985
1704
  }
1705
+ function uniqueKeys(keys) {
1706
+ return [...new Set(keys)];
1707
+ }
1708
+ function isDevelopmentRuntime2() {
1709
+ return getRuntimeNodeEnv() === "development";
1710
+ }
1711
+ var recoveryDiagnosticWarnings = /* @__PURE__ */ new WeakMap();
1712
+ function warnRecoveryOnce(api, id, message) {
1713
+ let warnings = recoveryDiagnosticWarnings.get(api);
1714
+ if (!warnings) {
1715
+ warnings = /* @__PURE__ */ new Set();
1716
+ recoveryDiagnosticWarnings.set(api, warnings);
1717
+ }
1718
+ if (warnings.has(id)) return;
1719
+ warnings.add(id);
1720
+ console.warn(message);
1721
+ }
1722
+ function useMnemonicRecovery(options = {}) {
1723
+ const api = useMnemonic();
1724
+ const { onRecover } = options;
1725
+ const namespace = react.useMemo(() => api.prefix.endsWith(".") ? api.prefix.slice(0, -1) : api.prefix, [api.prefix]);
1726
+ const emitRecovery = react.useCallback(
1727
+ (action, clearedKeys) => {
1728
+ const event = {
1729
+ action,
1730
+ namespace,
1731
+ clearedKeys
1732
+ };
1733
+ onRecover?.(event);
1734
+ },
1735
+ [namespace, onRecover]
1736
+ );
1737
+ const listKeys = react.useCallback(() => api.keys(), [api]);
1738
+ const clearResolvedKeys = react.useCallback(
1739
+ (action, keys) => {
1740
+ const clearedKeys = uniqueKeys(keys);
1741
+ for (const key of clearedKeys) {
1742
+ api.removeRaw(key);
1743
+ }
1744
+ emitRecovery(action, clearedKeys);
1745
+ return clearedKeys;
1746
+ },
1747
+ [api, emitRecovery]
1748
+ );
1749
+ const clearKeys = react.useCallback(
1750
+ (keys) => clearResolvedKeys("clear-keys", keys),
1751
+ [clearResolvedKeys]
1752
+ );
1753
+ const clearAll = react.useCallback(() => {
1754
+ if (!api.canEnumerateKeys) {
1755
+ if (isDevelopmentRuntime2()) {
1756
+ warnRecoveryOnce(
1757
+ api,
1758
+ "recovery-clear-all-non-enumerable",
1759
+ `[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).`
1760
+ );
1761
+ }
1762
+ throw new Error(
1763
+ "clearAll requires an enumerable storage backend. Use clearKeys([...]) with an explicit key list instead."
1764
+ );
1765
+ }
1766
+ return clearResolvedKeys("clear-all", api.keys());
1767
+ }, [api, clearResolvedKeys, namespace]);
1768
+ const clearMatching = react.useCallback(
1769
+ (predicate) => {
1770
+ if (!api.canEnumerateKeys) {
1771
+ if (isDevelopmentRuntime2()) {
1772
+ warnRecoveryOnce(
1773
+ api,
1774
+ "recovery-clear-matching-non-enumerable",
1775
+ `[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).`
1776
+ );
1777
+ }
1778
+ throw new Error(
1779
+ "clearMatching requires an enumerable storage backend. Use clearKeys([...]) with an explicit key list instead."
1780
+ );
1781
+ }
1782
+ return clearResolvedKeys(
1783
+ "clear-matching",
1784
+ api.keys().filter((key) => predicate(key))
1785
+ );
1786
+ },
1787
+ [api, clearResolvedKeys, namespace]
1788
+ );
1789
+ return react.useMemo(
1790
+ () => ({
1791
+ namespace,
1792
+ canEnumerateKeys: api.canEnumerateKeys,
1793
+ listKeys,
1794
+ clearAll,
1795
+ clearKeys,
1796
+ clearMatching
1797
+ }),
1798
+ [namespace, api.canEnumerateKeys, listKeys, clearAll, clearKeys, clearMatching]
1799
+ );
1800
+ }
1801
+
1802
+ // src/Mnemonic/key.ts
1803
+ function defineMnemonicKey(keyOrSchema, options) {
1804
+ if (typeof keyOrSchema !== "string") {
1805
+ return Object.freeze({
1806
+ key: keyOrSchema.key,
1807
+ options: {
1808
+ ...options,
1809
+ schema: { version: keyOrSchema.version }
1810
+ }
1811
+ });
1812
+ }
1813
+ return Object.freeze({
1814
+ key: keyOrSchema,
1815
+ options
1816
+ });
1817
+ }
1818
+
1819
+ // src/Mnemonic/schema-registry.ts
1820
+ function schemaVersionKey(key, version) {
1821
+ return `${key}:${version}`;
1822
+ }
1823
+ function migrationVersionKey(key, fromVersion) {
1824
+ return `${key}:${fromVersion}`;
1825
+ }
1826
+ function validateVersion(value, label) {
1827
+ if (!Number.isInteger(value) || value < 0) {
1828
+ throw new SchemaError("MIGRATION_GRAPH_INVALID", `${label} must be a non-negative integer`);
1829
+ }
1830
+ }
1831
+ function createSchemaRegistry(options = {}) {
1832
+ const { schemas = [], migrations = [] } = options;
1833
+ const schemasByKeyAndVersion = /* @__PURE__ */ new Map();
1834
+ const latestSchemaByKey = /* @__PURE__ */ new Map();
1835
+ const writeMigrationsByKeyAndVersion = /* @__PURE__ */ new Map();
1836
+ const migrationsByKeyAndFromVersion = /* @__PURE__ */ new Map();
1837
+ for (const schema of schemas) {
1838
+ validateVersion(schema.version, `Schema version for key "${schema.key}"`);
1839
+ const id = schemaVersionKey(schema.key, schema.version);
1840
+ if (schemasByKeyAndVersion.has(id)) {
1841
+ throw new SchemaError(
1842
+ "SCHEMA_REGISTRATION_CONFLICT",
1843
+ `Duplicate schema registered for key "${schema.key}" version ${schema.version}`
1844
+ );
1845
+ }
1846
+ schemasByKeyAndVersion.set(id, schema);
1847
+ const currentLatest = latestSchemaByKey.get(schema.key);
1848
+ if (!currentLatest || schema.version > currentLatest.version) {
1849
+ latestSchemaByKey.set(schema.key, schema);
1850
+ }
1851
+ }
1852
+ for (const migration of migrations) {
1853
+ validateVersion(migration.fromVersion, `Migration fromVersion for key "${migration.key}"`);
1854
+ validateVersion(migration.toVersion, `Migration toVersion for key "${migration.key}"`);
1855
+ if (migration.toVersion < migration.fromVersion) {
1856
+ throw new SchemaError(
1857
+ "MIGRATION_GRAPH_INVALID",
1858
+ `Backward migration "${migration.key}" ${migration.fromVersion} -> ${migration.toVersion} is not supported`
1859
+ );
1860
+ }
1861
+ if (migration.fromVersion === migration.toVersion) {
1862
+ const id = schemaVersionKey(migration.key, migration.fromVersion);
1863
+ if (writeMigrationsByKeyAndVersion.has(id)) {
1864
+ throw new SchemaError(
1865
+ "MIGRATION_GRAPH_INVALID",
1866
+ `Duplicate write migration registered for key "${migration.key}" version ${migration.fromVersion}`
1867
+ );
1868
+ }
1869
+ writeMigrationsByKeyAndVersion.set(id, migration);
1870
+ continue;
1871
+ }
1872
+ const edgeKey = migrationVersionKey(migration.key, migration.fromVersion);
1873
+ if (migrationsByKeyAndFromVersion.has(edgeKey)) {
1874
+ const existing = migrationsByKeyAndFromVersion.get(edgeKey);
1875
+ throw new SchemaError(
1876
+ "MIGRATION_GRAPH_INVALID",
1877
+ `Ambiguous migration graph for key "${migration.key}" at version ${migration.fromVersion}: ${existing.fromVersion} -> ${existing.toVersion} conflicts with ${migration.fromVersion} -> ${migration.toVersion}`
1878
+ );
1879
+ }
1880
+ migrationsByKeyAndFromVersion.set(edgeKey, migration);
1881
+ }
1882
+ return {
1883
+ getSchema(key, version) {
1884
+ return schemasByKeyAndVersion.get(schemaVersionKey(key, version));
1885
+ },
1886
+ getLatestSchema(key) {
1887
+ return latestSchemaByKey.get(key);
1888
+ },
1889
+ getMigrationPath(key, fromVersion, toVersion) {
1890
+ if (fromVersion === toVersion) return [];
1891
+ if (toVersion < fromVersion) return null;
1892
+ const path = [];
1893
+ let currentVersion = fromVersion;
1894
+ while (currentVersion < toVersion) {
1895
+ const next = migrationsByKeyAndFromVersion.get(migrationVersionKey(key, currentVersion));
1896
+ if (!next) return null;
1897
+ path.push(next);
1898
+ currentVersion = next.toVersion;
1899
+ }
1900
+ return currentVersion === toVersion ? path : null;
1901
+ },
1902
+ getWriteMigration(key, version) {
1903
+ return writeMigrationsByKeyAndVersion.get(schemaVersionKey(key, version));
1904
+ }
1905
+ };
1906
+ }
1907
+
1908
+ // src/Mnemonic/schema-helpers.ts
1909
+ function defineKeySchema(key, version, schema) {
1910
+ return Object.freeze({
1911
+ key,
1912
+ version,
1913
+ schema
1914
+ });
1915
+ }
1916
+ function defineMigration(fromSchema, toSchema, migrate) {
1917
+ if (fromSchema.key !== toSchema.key) {
1918
+ throw new SchemaError(
1919
+ "MIGRATION_GRAPH_INVALID",
1920
+ `Migration schemas must target the same key: "${fromSchema.key}" !== "${toSchema.key}"`
1921
+ );
1922
+ }
1923
+ return Object.freeze({
1924
+ key: fromSchema.key,
1925
+ fromVersion: fromSchema.version,
1926
+ toVersion: toSchema.version,
1927
+ migrate
1928
+ });
1929
+ }
1930
+ function defineWriteMigration(schema, migrate) {
1931
+ return Object.freeze({
1932
+ key: schema.key,
1933
+ fromVersion: schema.version,
1934
+ toVersion: schema.version,
1935
+ migrate
1936
+ });
1937
+ }
1938
+
1939
+ // src/Mnemonic/typed-schema.ts
1940
+ var optionalSchemaMarker = /* @__PURE__ */ Symbol("mnemonicOptionalSchema");
1941
+ function cloneSchema(schema) {
1942
+ const clone = { ...schema };
1943
+ if (schema[optionalSchemaMarker]) {
1944
+ Object.defineProperty(clone, optionalSchemaMarker, {
1945
+ value: true,
1946
+ enumerable: false,
1947
+ configurable: false
1948
+ });
1949
+ }
1950
+ return clone;
1951
+ }
1952
+ function isOptionalSchema(schema) {
1953
+ return Boolean(schema[optionalSchemaMarker]);
1954
+ }
1955
+ function markOptional(schema) {
1956
+ const clone = cloneSchema(schema);
1957
+ Object.defineProperty(clone, optionalSchemaMarker, {
1958
+ value: true,
1959
+ enumerable: false,
1960
+ configurable: false
1961
+ });
1962
+ return clone;
1963
+ }
1964
+ function withoutOptionalMarker(schema) {
1965
+ const clone = { ...schema };
1966
+ delete clone[optionalSchemaMarker];
1967
+ return clone;
1968
+ }
1969
+ function withType(type, extra = {}) {
1970
+ return {
1971
+ type,
1972
+ ...extra
1973
+ };
1974
+ }
1975
+ function toTypeArray(type) {
1976
+ if (type === void 0) return null;
1977
+ return Array.isArray(type) ? [...type] : [type];
1978
+ }
1979
+ function appendNullType(type) {
1980
+ const types = toTypeArray(type);
1981
+ if (types === null) {
1982
+ return void 0;
1983
+ }
1984
+ return types.includes("null") ? types : [...types, "null"];
1985
+ }
1986
+ function nullableSchema(schema) {
1987
+ if (schema.enum) {
1988
+ const nullableType = appendNullType(schema.type);
1989
+ return {
1990
+ ...schema,
1991
+ ...nullableType ? { type: nullableType } : {},
1992
+ enum: schema.enum.includes(null) ? schema.enum : [...schema.enum, null]
1993
+ };
1994
+ }
1995
+ if ("const" in schema) {
1996
+ const { const: constValue, ...rest } = schema;
1997
+ const nullableType = appendNullType(rest.type);
1998
+ if (constValue === null || constValue === void 0) {
1999
+ return {
2000
+ ...schema,
2001
+ ...nullableType ? { type: nullableType } : {}
2002
+ };
2003
+ }
2004
+ return {
2005
+ ...rest,
2006
+ ...nullableType ? { type: nullableType } : {},
2007
+ enum: [constValue, null]
2008
+ };
2009
+ }
2010
+ const types = toTypeArray(schema.type);
2011
+ if (types === null) {
2012
+ throw new SchemaError(
2013
+ "MODE_CONFIGURATION_INVALID",
2014
+ "mnemonicSchema.nullable(...) requires a schema with type, enum, or const"
2015
+ );
2016
+ }
2017
+ return {
2018
+ ...schema,
2019
+ type: types.includes("null") ? types : [...types, "null"]
2020
+ };
2021
+ }
2022
+ var mnemonicSchema = {
2023
+ string(options = {}) {
2024
+ return withType("string", options);
2025
+ },
2026
+ number(options = {}) {
2027
+ return withType("number", options);
2028
+ },
2029
+ integer(options = {}) {
2030
+ return withType("integer", options);
2031
+ },
2032
+ boolean() {
2033
+ return withType("boolean");
2034
+ },
2035
+ nullValue() {
2036
+ return withType("null");
2037
+ },
2038
+ literal(value) {
2039
+ return {
2040
+ const: value
2041
+ };
2042
+ },
2043
+ enum(values) {
2044
+ return {
2045
+ enum: values
2046
+ };
2047
+ },
2048
+ optional(schema) {
2049
+ return markOptional(schema);
2050
+ },
2051
+ nullable(schema) {
2052
+ return nullableSchema(schema);
2053
+ },
2054
+ array(itemSchema, options = {}) {
2055
+ return withType("array", {
2056
+ items: withoutOptionalMarker(itemSchema),
2057
+ ...options
2058
+ });
2059
+ },
2060
+ object(shape, options = {}) {
2061
+ const properties = {};
2062
+ const required = [];
2063
+ for (const [name, schema] of Object.entries(shape)) {
2064
+ properties[name] = withoutOptionalMarker(schema);
2065
+ if (!isOptionalSchema(schema)) {
2066
+ required.push(name);
2067
+ }
2068
+ }
2069
+ const result = {
2070
+ type: "object",
2071
+ properties,
2072
+ ...options
2073
+ };
2074
+ if (required.length > 0) {
2075
+ result.required = required;
2076
+ }
2077
+ return result;
2078
+ },
2079
+ record(valueSchema) {
2080
+ return withType("object", {
2081
+ additionalProperties: withoutOptionalMarker(valueSchema)
2082
+ });
2083
+ }
2084
+ };
2085
+
2086
+ // src/Mnemonic/structural-migrations.ts
2087
+ function resolveHelpers(helpers) {
2088
+ if (helpers) return helpers;
2089
+ return {
2090
+ getId: (node) => node.id,
2091
+ getChildren: (node) => node.children,
2092
+ withChildren: (node, children) => ({ ...node, children }),
2093
+ withId: (node, id) => ({ ...node, id })
2094
+ };
2095
+ }
2096
+ function findNodeById(root, id, helpers) {
2097
+ const tree = resolveHelpers(helpers);
2098
+ if (tree.getId(root) === id) return root;
2099
+ for (const child of tree.getChildren(root) ?? []) {
2100
+ const match = findNodeById(child, id, tree);
2101
+ if (match) return match;
2102
+ }
2103
+ return void 0;
2104
+ }
2105
+ function insertChildIfMissing(root, parentId, child, helpers) {
2106
+ const tree = resolveHelpers(helpers);
2107
+ const childId = tree.getId(child);
2108
+ const visit = (node) => {
2109
+ if (tree.getId(node) === parentId) {
2110
+ const children2 = [...tree.getChildren(node) ?? []];
2111
+ if (children2.some((existing) => tree.getId(existing) === childId)) {
2112
+ return [node, false];
2113
+ }
2114
+ return [tree.withChildren(node, [...children2, child]), true];
2115
+ }
2116
+ const children = tree.getChildren(node);
2117
+ if (!children?.length) return [node, false];
2118
+ let inserted = false;
2119
+ let changed = false;
2120
+ const nextChildren = children.map((existingChild) => {
2121
+ if (inserted) return existingChild;
2122
+ const [nextChild, didInsert] = visit(existingChild);
2123
+ inserted || (inserted = didInsert);
2124
+ changed || (changed = nextChild !== existingChild);
2125
+ return nextChild;
2126
+ });
2127
+ if (!changed) return [node, inserted];
2128
+ return [tree.withChildren(node, nextChildren), inserted];
2129
+ };
2130
+ return visit(root)[0];
2131
+ }
2132
+ function renameNode(root, currentId, nextId, helpers) {
2133
+ const tree = resolveHelpers(helpers);
2134
+ if (currentId === nextId) return root;
2135
+ if (!findNodeById(root, currentId, tree)) return root;
2136
+ if (findNodeById(root, nextId, tree)) return root;
2137
+ const visit = (node) => {
2138
+ let nextNode = tree.getId(node) === currentId ? tree.withId(node, nextId) : node;
2139
+ const children = tree.getChildren(nextNode);
2140
+ if (!children?.length) return nextNode;
2141
+ let changed = nextNode !== node;
2142
+ const nextChildren = children.map((child) => {
2143
+ const nextChild = visit(child);
2144
+ changed || (changed = nextChild !== child);
2145
+ return nextChild;
2146
+ });
2147
+ if (!changed) return node;
2148
+ return tree.withChildren(nextNode, nextChildren);
2149
+ };
2150
+ return visit(root);
2151
+ }
2152
+ function dedupeChildrenBy(root, getKey, helpers) {
2153
+ const tree = resolveHelpers(helpers);
2154
+ const visit = (node) => {
2155
+ const children = tree.getChildren(node);
2156
+ if (!children?.length) return node;
2157
+ let changed = false;
2158
+ const seen = /* @__PURE__ */ new Set();
2159
+ const nextChildren = [];
2160
+ for (const child of children) {
2161
+ const normalizedChild = visit(child);
2162
+ changed || (changed = normalizedChild !== child);
2163
+ const key = getKey(normalizedChild);
2164
+ if (seen.has(key)) {
2165
+ changed = true;
2166
+ continue;
2167
+ }
2168
+ seen.add(key);
2169
+ nextChildren.push(normalizedChild);
2170
+ }
2171
+ if (!changed && nextChildren.length === children.length) return node;
2172
+ return tree.withChildren(node, nextChildren);
2173
+ };
2174
+ return visit(root);
2175
+ }
986
2176
 
987
2177
  exports.CodecError = CodecError;
988
2178
  exports.JSONCodec = JSONCodec;
@@ -990,7 +2180,18 @@ exports.MnemonicProvider = MnemonicProvider;
990
2180
  exports.SchemaError = SchemaError;
991
2181
  exports.compileSchema = compileSchema;
992
2182
  exports.createCodec = createCodec;
2183
+ exports.createSchemaRegistry = createSchemaRegistry;
2184
+ exports.dedupeChildrenBy = dedupeChildrenBy;
2185
+ exports.defineKeySchema = defineKeySchema;
2186
+ exports.defineMigration = defineMigration;
2187
+ exports.defineMnemonicKey = defineMnemonicKey;
2188
+ exports.defineWriteMigration = defineWriteMigration;
2189
+ exports.findNodeById = findNodeById;
2190
+ exports.insertChildIfMissing = insertChildIfMissing;
2191
+ exports.mnemonicSchema = mnemonicSchema;
2192
+ exports.renameNode = renameNode;
993
2193
  exports.useMnemonicKey = useMnemonicKey;
2194
+ exports.useMnemonicRecovery = useMnemonicRecovery;
994
2195
  exports.validateJsonSchema = validateJsonSchema;
995
2196
  //# sourceMappingURL=index.cjs.map
996
2197
  //# sourceMappingURL=index.cjs.map