react-mnemonic 1.1.0-beta0 → 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/README.md +41 -748
- package/dist/core.cjs +1322 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.d.cts +15 -0
- package/dist/core.d.ts +15 -0
- package/dist/core.js +1313 -0
- package/dist/core.js.map +1 -0
- package/dist/index.cjs +337 -258
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -2069
- package/dist/index.d.ts +4 -2069
- package/dist/index.js +338 -259
- package/dist/index.js.map +1 -1
- package/dist/key-BvFvcKiR.d.cts +1723 -0
- package/dist/key-BvFvcKiR.d.ts +1723 -0
- package/dist/schema.cjs +2276 -0
- package/dist/schema.cjs.map +1 -0
- package/dist/schema.d.cts +317 -0
- package/dist/schema.d.ts +317 -0
- package/dist/schema.js +2256 -0
- package/dist/schema.js.map +1 -0
- package/package.json +19 -4
package/dist/schema.cjs
ADDED
|
@@ -0,0 +1,2276 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
|
|
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
|
+
}
|
|
33
|
+
var MnemonicContext = react.createContext(null);
|
|
34
|
+
function useMnemonic() {
|
|
35
|
+
const context = react.useContext(MnemonicContext);
|
|
36
|
+
if (!context) {
|
|
37
|
+
throw new Error("useMnemonic must be used within a MnemonicProvider");
|
|
38
|
+
}
|
|
39
|
+
return context;
|
|
40
|
+
}
|
|
41
|
+
function defaultBrowserStorage() {
|
|
42
|
+
const globalWindow = globalThis.window;
|
|
43
|
+
if (globalWindow === void 0) return void 0;
|
|
44
|
+
try {
|
|
45
|
+
return globalWindow.localStorage;
|
|
46
|
+
} catch {
|
|
47
|
+
return void 0;
|
|
48
|
+
}
|
|
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
|
+
}
|
|
427
|
+
function MnemonicProvider({
|
|
428
|
+
children,
|
|
429
|
+
namespace,
|
|
430
|
+
storage,
|
|
431
|
+
enableDevTools = false,
|
|
432
|
+
schemaMode = "default",
|
|
433
|
+
schemaRegistry,
|
|
434
|
+
ssr
|
|
435
|
+
}) {
|
|
436
|
+
if (schemaMode === "strict" && !schemaRegistry) {
|
|
437
|
+
throw new Error("MnemonicProvider strict mode requires schemaRegistry");
|
|
438
|
+
}
|
|
439
|
+
if (schemaMode === "autoschema" && typeof schemaRegistry?.registerSchema !== "function") {
|
|
440
|
+
throw new Error("MnemonicProvider autoschema mode requires schemaRegistry.registerSchema");
|
|
441
|
+
}
|
|
442
|
+
const store = react.useMemo(() => {
|
|
443
|
+
const prefix = `${namespace}.`;
|
|
444
|
+
const st = storage ?? defaultBrowserStorage();
|
|
445
|
+
const ssrHydration = ssr?.hydration ?? "immediate";
|
|
446
|
+
const devToolsRoot = ensureDevToolsRoot(enableDevTools);
|
|
447
|
+
const canEnumerateKeys = detectEnumerableStorage(st);
|
|
448
|
+
const crossTabSyncMode = getCrossTabSyncMode(storage, st);
|
|
449
|
+
const cache = /* @__PURE__ */ new Map();
|
|
450
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
451
|
+
let quotaErrorLogged = false;
|
|
452
|
+
let accessErrorLogged = false;
|
|
453
|
+
let asyncContractViolationDetected = false;
|
|
454
|
+
const storageAccessCallbacks = {
|
|
455
|
+
onAccessError: (err) => logAccessError(err),
|
|
456
|
+
onAccessSuccess: () => {
|
|
457
|
+
accessErrorLogged = false;
|
|
458
|
+
},
|
|
459
|
+
onAsyncViolation: (method, thenable) => handleAsyncStorageContractViolation(method, thenable)
|
|
460
|
+
};
|
|
461
|
+
const fullKey = (key) => prefix + key;
|
|
462
|
+
const emit = (key) => {
|
|
463
|
+
const set = listeners.get(key);
|
|
464
|
+
if (!set) return;
|
|
465
|
+
for (const fn of set) fn();
|
|
466
|
+
};
|
|
467
|
+
const logAccessError = (err) => {
|
|
468
|
+
if (!accessErrorLogged && err instanceof DOMException && err.name !== "QuotaExceededError") {
|
|
469
|
+
console.error(
|
|
470
|
+
`[Mnemonic] Storage access error (${err.name}): ${err.message}. Data is cached in memory but may not persist.`
|
|
471
|
+
);
|
|
472
|
+
accessErrorLogged = true;
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
const handleAsyncStorageContractViolation = (method, thenable) => {
|
|
476
|
+
asyncContractViolationDetected = true;
|
|
477
|
+
void Promise.resolve(thenable).catch(() => void 0);
|
|
478
|
+
if (accessErrorLogged) return;
|
|
479
|
+
console.error(
|
|
480
|
+
`[Mnemonic] StorageLike.${method} returned a Promise. StorageLike must remain synchronous for react-mnemonic v1. Wrap async persistence behind a synchronous cache facade instead.`
|
|
481
|
+
);
|
|
482
|
+
accessErrorLogged = true;
|
|
483
|
+
};
|
|
484
|
+
const readThrough = (key) => {
|
|
485
|
+
if (cache.has(key)) return cache.get(key) ?? null;
|
|
486
|
+
if (!st || asyncContractViolationDetected) {
|
|
487
|
+
cache.set(key, null);
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
490
|
+
const raw = readStorageRaw(st, fullKey(key), storageAccessCallbacks);
|
|
491
|
+
cache.set(key, raw);
|
|
492
|
+
return raw;
|
|
493
|
+
};
|
|
494
|
+
const writeRaw = (key, raw) => {
|
|
495
|
+
cache.set(key, raw);
|
|
496
|
+
if (st && !asyncContractViolationDetected) {
|
|
497
|
+
try {
|
|
498
|
+
const result = st.setItem(fullKey(key), raw);
|
|
499
|
+
if (isPromiseLike(result)) {
|
|
500
|
+
handleAsyncStorageContractViolation("setItem", result);
|
|
501
|
+
} else {
|
|
502
|
+
quotaErrorLogged = false;
|
|
503
|
+
accessErrorLogged = false;
|
|
504
|
+
}
|
|
505
|
+
} catch (err) {
|
|
506
|
+
if (!quotaErrorLogged && err instanceof DOMException && err.name === "QuotaExceededError") {
|
|
507
|
+
console.error(
|
|
508
|
+
`[Mnemonic] Storage quota exceeded writing key "${key}". Data is cached in memory but will not persist.`
|
|
509
|
+
);
|
|
510
|
+
quotaErrorLogged = true;
|
|
511
|
+
}
|
|
512
|
+
logAccessError(err);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
emit(key);
|
|
516
|
+
bumpDevToolsVersion(devToolsRoot, namespace, `set:${key}`);
|
|
517
|
+
};
|
|
518
|
+
const removeRaw = (key) => {
|
|
519
|
+
cache.set(key, null);
|
|
520
|
+
if (st && !asyncContractViolationDetected) {
|
|
521
|
+
try {
|
|
522
|
+
const result = st.removeItem(fullKey(key));
|
|
523
|
+
if (isPromiseLike(result)) {
|
|
524
|
+
handleAsyncStorageContractViolation("removeItem", result);
|
|
525
|
+
} else {
|
|
526
|
+
accessErrorLogged = false;
|
|
527
|
+
}
|
|
528
|
+
} catch (err) {
|
|
529
|
+
logAccessError(err);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
emit(key);
|
|
533
|
+
bumpDevToolsVersion(devToolsRoot, namespace, `remove:${key}`);
|
|
534
|
+
};
|
|
535
|
+
const subscribeRaw = (key, listener) => {
|
|
536
|
+
let set = listeners.get(key);
|
|
537
|
+
if (!set) {
|
|
538
|
+
set = /* @__PURE__ */ new Set();
|
|
539
|
+
listeners.set(key, set);
|
|
540
|
+
}
|
|
541
|
+
set.add(listener);
|
|
542
|
+
readThrough(key);
|
|
543
|
+
return () => {
|
|
544
|
+
const s = listeners.get(key);
|
|
545
|
+
if (!s) return;
|
|
546
|
+
s.delete(listener);
|
|
547
|
+
if (s.size === 0) listeners.delete(key);
|
|
548
|
+
};
|
|
549
|
+
};
|
|
550
|
+
const getRawSnapshot = (key) => readThrough(key);
|
|
551
|
+
const keys = () => {
|
|
552
|
+
if (asyncContractViolationDetected) {
|
|
553
|
+
return Array.from(cache.entries()).filter(([, value]) => value != null).map(([key]) => key);
|
|
554
|
+
}
|
|
555
|
+
if (!canEnumerateKeys) return [];
|
|
556
|
+
return enumerateNamespaceKeys(st, prefix, storageAccessCallbacks);
|
|
557
|
+
};
|
|
558
|
+
const dump = () => {
|
|
559
|
+
const out = {};
|
|
560
|
+
for (const k of keys()) {
|
|
561
|
+
const raw = readThrough(k);
|
|
562
|
+
if (raw != null) out[k] = raw;
|
|
563
|
+
}
|
|
564
|
+
return out;
|
|
565
|
+
};
|
|
566
|
+
const reloadFromStorage = createReloadFromStorage({
|
|
567
|
+
storage: st,
|
|
568
|
+
hasAsyncContractViolation: () => asyncContractViolationDetected,
|
|
569
|
+
prefix,
|
|
570
|
+
listeners,
|
|
571
|
+
cache,
|
|
572
|
+
emit,
|
|
573
|
+
callbacks: storageAccessCallbacks,
|
|
574
|
+
devToolsRoot,
|
|
575
|
+
namespace
|
|
576
|
+
});
|
|
577
|
+
const store2 = {
|
|
578
|
+
prefix,
|
|
579
|
+
canEnumerateKeys,
|
|
580
|
+
subscribeRaw,
|
|
581
|
+
getRawSnapshot,
|
|
582
|
+
setRaw: writeRaw,
|
|
583
|
+
removeRaw,
|
|
584
|
+
keys,
|
|
585
|
+
dump,
|
|
586
|
+
reloadFromStorage,
|
|
587
|
+
schemaMode,
|
|
588
|
+
ssrHydration,
|
|
589
|
+
crossTabSyncMode,
|
|
590
|
+
...schemaRegistry ? { schemaRegistry } : {}
|
|
591
|
+
};
|
|
592
|
+
if (devToolsRoot) {
|
|
593
|
+
registerDevToolsProvider({
|
|
594
|
+
devToolsRoot,
|
|
595
|
+
namespace,
|
|
596
|
+
store: store2,
|
|
597
|
+
dump,
|
|
598
|
+
keys,
|
|
599
|
+
readThrough,
|
|
600
|
+
writeRaw,
|
|
601
|
+
removeRaw
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
return store2;
|
|
605
|
+
}, [namespace, storage, enableDevTools, schemaMode, schemaRegistry, ssr?.hydration]);
|
|
606
|
+
react.useEffect(() => {
|
|
607
|
+
if (!storage?.onExternalChange) return;
|
|
608
|
+
return storage.onExternalChange((changedKeys) => store.reloadFromStorage(changedKeys));
|
|
609
|
+
}, [storage, store]);
|
|
610
|
+
return /* @__PURE__ */ jsxRuntime.jsx(MnemonicContext.Provider, { value: store, children });
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// src/Mnemonic/codecs.ts
|
|
614
|
+
var CodecError = class extends Error {
|
|
615
|
+
/**
|
|
616
|
+
* Creates a new CodecError.
|
|
617
|
+
*
|
|
618
|
+
* @param message - Human-readable error description
|
|
619
|
+
* @param cause - Optional underlying error that caused this failure
|
|
620
|
+
*/
|
|
621
|
+
constructor(message, cause) {
|
|
622
|
+
super(message);
|
|
623
|
+
this.name = "CodecError";
|
|
624
|
+
this.cause = cause;
|
|
625
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
626
|
+
}
|
|
627
|
+
};
|
|
628
|
+
var JSONCodec = {
|
|
629
|
+
encode: (value) => JSON.stringify(value),
|
|
630
|
+
decode: (encoded) => JSON.parse(encoded)
|
|
631
|
+
};
|
|
632
|
+
function createCodec(encode, decode) {
|
|
633
|
+
return { encode, decode };
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// src/Mnemonic/schema.ts
|
|
637
|
+
var SchemaError = class extends Error {
|
|
638
|
+
/**
|
|
639
|
+
* Creates a new SchemaError.
|
|
640
|
+
*
|
|
641
|
+
* @param code - Machine-readable failure category
|
|
642
|
+
* @param message - Human-readable error description
|
|
643
|
+
* @param cause - Optional underlying error
|
|
644
|
+
*/
|
|
645
|
+
constructor(code, message, cause) {
|
|
646
|
+
super(message);
|
|
647
|
+
this.name = "SchemaError";
|
|
648
|
+
this.code = code;
|
|
649
|
+
this.cause = cause;
|
|
650
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
651
|
+
}
|
|
652
|
+
};
|
|
653
|
+
|
|
654
|
+
// src/Mnemonic/json-schema.ts
|
|
655
|
+
function matchesType(value, type) {
|
|
656
|
+
switch (type) {
|
|
657
|
+
case "string":
|
|
658
|
+
return typeof value === "string";
|
|
659
|
+
case "number":
|
|
660
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
661
|
+
case "integer":
|
|
662
|
+
return typeof value === "number" && Number.isInteger(value);
|
|
663
|
+
case "boolean":
|
|
664
|
+
return typeof value === "boolean";
|
|
665
|
+
case "null":
|
|
666
|
+
return value === null;
|
|
667
|
+
case "object":
|
|
668
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
669
|
+
case "array":
|
|
670
|
+
return Array.isArray(value);
|
|
671
|
+
default:
|
|
672
|
+
return false;
|
|
673
|
+
}
|
|
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
|
+
}
|
|
692
|
+
function jsonDeepEqual(a, b) {
|
|
693
|
+
if (a === b) return true;
|
|
694
|
+
if (a === null || b === null) return false;
|
|
695
|
+
if (typeof a !== typeof b) return false;
|
|
696
|
+
if (Array.isArray(a)) {
|
|
697
|
+
if (!Array.isArray(b)) return false;
|
|
698
|
+
return jsonDeepEqualArray(a, b);
|
|
699
|
+
}
|
|
700
|
+
if (typeof a === "object") {
|
|
701
|
+
if (Array.isArray(b)) return false;
|
|
702
|
+
return jsonDeepEqualObject(a, b);
|
|
703
|
+
}
|
|
704
|
+
return false;
|
|
705
|
+
}
|
|
706
|
+
var compiledCache = /* @__PURE__ */ new WeakMap();
|
|
707
|
+
function compileSchema(schema) {
|
|
708
|
+
const cached = compiledCache.get(schema);
|
|
709
|
+
if (cached) return cached;
|
|
710
|
+
const compiled = buildValidator(schema);
|
|
711
|
+
compiledCache.set(schema, compiled);
|
|
712
|
+
return compiled;
|
|
713
|
+
}
|
|
714
|
+
function isJsonPrimitive(value) {
|
|
715
|
+
return value === null || typeof value !== "object";
|
|
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
|
+
}
|
|
727
|
+
function buildValidator(schema) {
|
|
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;
|
|
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) {
|
|
804
|
+
const hasMinimum = schema.minimum !== void 0;
|
|
805
|
+
const hasMaximum = schema.maximum !== void 0;
|
|
806
|
+
const hasExMin = schema.exclusiveMinimum !== void 0;
|
|
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;
|
|
814
|
+
const exMax = schema.exclusiveMaximum;
|
|
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) {
|
|
850
|
+
const hasMinLength = schema.minLength !== void 0;
|
|
851
|
+
const hasMaxLength = schema.maxLength !== void 0;
|
|
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;
|
|
883
|
+
const checkAdditional = schema.additionalProperties !== void 0 && schema.additionalProperties !== true;
|
|
884
|
+
const additionalIsFalse = schema.additionalProperties === false;
|
|
885
|
+
const additionalValidator = checkAdditional && !additionalIsFalse ? compileSchema(schema.additionalProperties) : null;
|
|
886
|
+
const definedPropKeys = checkAdditional ? new Set(Object.keys(schema.properties ?? {})) : null;
|
|
887
|
+
const objectValidationSteps = [];
|
|
888
|
+
if (requiredKeys.length > 0) {
|
|
889
|
+
objectValidationSteps.push(createRequiredPropertyStep(requiredKeys));
|
|
890
|
+
}
|
|
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;
|
|
909
|
+
}
|
|
910
|
+
for (const step of objectValidationSteps) {
|
|
911
|
+
step(value, path, errors);
|
|
912
|
+
}
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
function createRequiredPropertyStep(requiredKeys) {
|
|
916
|
+
return (value, path, errors) => {
|
|
917
|
+
for (const requiredKey of requiredKeys) {
|
|
918
|
+
if (objectHasOwn(value, requiredKey)) {
|
|
919
|
+
continue;
|
|
920
|
+
}
|
|
921
|
+
errors.push({
|
|
922
|
+
path,
|
|
923
|
+
message: `Missing required property "${requiredKey}"`,
|
|
924
|
+
keyword: "required"
|
|
925
|
+
});
|
|
926
|
+
}
|
|
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;
|
|
934
|
+
}
|
|
935
|
+
errors.push(...validator(value[propertyName], `${path}/${propertyName}`));
|
|
936
|
+
}
|
|
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;
|
|
948
|
+
}
|
|
949
|
+
if (additionalIsFalse) {
|
|
950
|
+
errors.push({
|
|
951
|
+
path,
|
|
952
|
+
message: `Additional property "${objectKey}" is not allowed`,
|
|
953
|
+
keyword: "additionalProperties"
|
|
954
|
+
});
|
|
955
|
+
continue;
|
|
956
|
+
}
|
|
957
|
+
errors.push(...additionalValidator(value[objectKey], `${path}/${objectKey}`));
|
|
958
|
+
}
|
|
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;
|
|
973
|
+
}
|
|
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}`));
|
|
993
|
+
}
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
function validateJsonSchema(value, schema, path = "") {
|
|
997
|
+
const compiled = compileSchema(schema);
|
|
998
|
+
return compiled(value, path);
|
|
999
|
+
}
|
|
1000
|
+
function jsonTypeLabel(value) {
|
|
1001
|
+
if (value === null) return "null";
|
|
1002
|
+
if (Array.isArray(value)) return "array";
|
|
1003
|
+
return typeof value;
|
|
1004
|
+
}
|
|
1005
|
+
function inferJsonSchema(sample) {
|
|
1006
|
+
if (sample === null) return { type: "null" };
|
|
1007
|
+
if (Array.isArray(sample)) return { type: "array" };
|
|
1008
|
+
switch (typeof sample) {
|
|
1009
|
+
case "string":
|
|
1010
|
+
return { type: "string" };
|
|
1011
|
+
case "number":
|
|
1012
|
+
return { type: "number" };
|
|
1013
|
+
case "boolean":
|
|
1014
|
+
return { type: "boolean" };
|
|
1015
|
+
case "object":
|
|
1016
|
+
return { type: "object" };
|
|
1017
|
+
default:
|
|
1018
|
+
return {};
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
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;
|
|
1133
|
+
const api = useMnemonic();
|
|
1134
|
+
const {
|
|
1135
|
+
defaultValue,
|
|
1136
|
+
onMount,
|
|
1137
|
+
onChange,
|
|
1138
|
+
listenCrossTab,
|
|
1139
|
+
codec: codecOpt,
|
|
1140
|
+
schema,
|
|
1141
|
+
reconcile,
|
|
1142
|
+
ssr: ssrOptions
|
|
1143
|
+
} = resolvedOptions;
|
|
1144
|
+
const codec = codecOpt ?? JSONCodec;
|
|
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
|
+
);
|
|
1172
|
+
const getFallback = react.useCallback(
|
|
1173
|
+
(error) => typeof defaultValue === "function" ? defaultValue(error) : defaultValue,
|
|
1174
|
+
[defaultValue]
|
|
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]);
|
|
1183
|
+
const parseEnvelope = react.useCallback(
|
|
1184
|
+
(rawText) => {
|
|
1185
|
+
try {
|
|
1186
|
+
const parsed = JSON.parse(rawText);
|
|
1187
|
+
if (typeof parsed !== "object" || parsed == null || !Number.isInteger(parsed.version) || parsed.version < 0 || !objectHasOwn2(parsed, "payload")) {
|
|
1188
|
+
return {
|
|
1189
|
+
ok: false,
|
|
1190
|
+
error: new SchemaError("INVALID_ENVELOPE", `Invalid envelope for key "${key}"`)
|
|
1191
|
+
};
|
|
1192
|
+
}
|
|
1193
|
+
return { ok: true, envelope: parsed };
|
|
1194
|
+
} catch (err) {
|
|
1195
|
+
return {
|
|
1196
|
+
ok: false,
|
|
1197
|
+
error: new SchemaError("INVALID_ENVELOPE", `Invalid envelope for key "${key}"`, err)
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
},
|
|
1201
|
+
[key]
|
|
1202
|
+
);
|
|
1203
|
+
const decodeStringPayload = react.useCallback(
|
|
1204
|
+
(payload, activeCodec) => {
|
|
1205
|
+
try {
|
|
1206
|
+
return activeCodec.decode(payload);
|
|
1207
|
+
} catch (err) {
|
|
1208
|
+
throw err instanceof CodecError ? err : new CodecError(`Codec decode failed for key "${key}"`, err);
|
|
1209
|
+
}
|
|
1210
|
+
},
|
|
1211
|
+
[key]
|
|
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;
|
|
1486
|
+
const validateAgainstSchema = react.useCallback(
|
|
1487
|
+
(value, jsonSchema) => {
|
|
1488
|
+
const errors = validateJsonSchema(value, jsonSchema);
|
|
1489
|
+
if (errors.length > 0) {
|
|
1490
|
+
const message = errors.map((e) => `${e.path || "/"}: ${e.message}`).join("; ");
|
|
1491
|
+
throw new SchemaError("TYPE_MISMATCH", `Schema validation failed for key "${key}": ${message}`);
|
|
1492
|
+
}
|
|
1493
|
+
},
|
|
1494
|
+
[key]
|
|
1495
|
+
);
|
|
1496
|
+
const registryCache = react.useMemo(() => {
|
|
1497
|
+
if (!schemaRegistry || schemaMode === "autoschema") return null;
|
|
1498
|
+
return {
|
|
1499
|
+
latestSchema: void 0,
|
|
1500
|
+
latestSchemaSet: false,
|
|
1501
|
+
schemaByVersion: /* @__PURE__ */ new Map(),
|
|
1502
|
+
migrationPaths: /* @__PURE__ */ new Map()
|
|
1503
|
+
};
|
|
1504
|
+
}, [schemaRegistry, schemaMode, key]);
|
|
1505
|
+
const getSchemaForVersion = react.useCallback(
|
|
1506
|
+
(version) => {
|
|
1507
|
+
if (!schemaRegistry) return void 0;
|
|
1508
|
+
if (!registryCache) return schemaRegistry.getSchema(key, version);
|
|
1509
|
+
if (registryCache.schemaByVersion.has(version)) {
|
|
1510
|
+
return registryCache.schemaByVersion.get(version);
|
|
1511
|
+
}
|
|
1512
|
+
const nextSchema = schemaRegistry.getSchema(key, version);
|
|
1513
|
+
registryCache.schemaByVersion.set(version, nextSchema);
|
|
1514
|
+
return nextSchema;
|
|
1515
|
+
},
|
|
1516
|
+
[schemaRegistry, registryCache, key]
|
|
1517
|
+
);
|
|
1518
|
+
const getLatestSchemaForKey = react.useCallback(() => {
|
|
1519
|
+
if (!schemaRegistry) return void 0;
|
|
1520
|
+
if (!registryCache) return schemaRegistry.getLatestSchema(key);
|
|
1521
|
+
if (registryCache.latestSchemaSet) return registryCache.latestSchema;
|
|
1522
|
+
const nextSchema = schemaRegistry.getLatestSchema(key);
|
|
1523
|
+
registryCache.latestSchema = nextSchema;
|
|
1524
|
+
registryCache.latestSchemaSet = true;
|
|
1525
|
+
return nextSchema;
|
|
1526
|
+
}, [schemaRegistry, registryCache, key]);
|
|
1527
|
+
const getMigrationPathForKey = react.useCallback(
|
|
1528
|
+
(fromVersion, toVersion) => {
|
|
1529
|
+
if (!schemaRegistry) return null;
|
|
1530
|
+
if (!registryCache) return schemaRegistry.getMigrationPath(key, fromVersion, toVersion) ?? null;
|
|
1531
|
+
const cacheKey = `${fromVersion}->${toVersion}`;
|
|
1532
|
+
if (registryCache.migrationPaths.has(cacheKey)) {
|
|
1533
|
+
return registryCache.migrationPaths.get(cacheKey) ?? null;
|
|
1534
|
+
}
|
|
1535
|
+
const path = schemaRegistry.getMigrationPath(key, fromVersion, toVersion) ?? null;
|
|
1536
|
+
registryCache.migrationPaths.set(cacheKey, path);
|
|
1537
|
+
return path;
|
|
1538
|
+
},
|
|
1539
|
+
[schemaRegistry, registryCache, key]
|
|
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]);
|
|
1557
|
+
const encodeForWrite = react.useCallback(
|
|
1558
|
+
(nextValue) => {
|
|
1559
|
+
const explicitVersion = schema?.version;
|
|
1560
|
+
const targetSchema = resolveTargetWriteSchema();
|
|
1561
|
+
if (!targetSchema) {
|
|
1562
|
+
if (explicitVersion !== void 0 && schemaMode === "strict") {
|
|
1563
|
+
throw new SchemaError(
|
|
1564
|
+
"WRITE_SCHEMA_REQUIRED",
|
|
1565
|
+
`Write requires schema for key "${key}" in strict mode`
|
|
1566
|
+
);
|
|
1567
|
+
}
|
|
1568
|
+
return serializeEnvelope(0, codec.encode(nextValue));
|
|
1569
|
+
}
|
|
1570
|
+
let valueToStore = nextValue;
|
|
1571
|
+
const writeMigration = schemaRegistry?.getWriteMigration?.(key, targetSchema.version);
|
|
1572
|
+
if (writeMigration) {
|
|
1573
|
+
try {
|
|
1574
|
+
valueToStore = writeMigration.migrate(valueToStore);
|
|
1575
|
+
} catch (err) {
|
|
1576
|
+
throw err instanceof SchemaError ? err : new SchemaError("MIGRATION_FAILED", `Write-time migration failed for key "${key}"`, err);
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
validateAgainstSchema(valueToStore, targetSchema.schema);
|
|
1580
|
+
return buildSchemaManagedResult(targetSchema.version, valueToStore);
|
|
1581
|
+
},
|
|
1582
|
+
[
|
|
1583
|
+
schema?.version,
|
|
1584
|
+
key,
|
|
1585
|
+
schemaMode,
|
|
1586
|
+
codec,
|
|
1587
|
+
schemaRegistry,
|
|
1588
|
+
validateAgainstSchema,
|
|
1589
|
+
resolveTargetWriteSchema,
|
|
1590
|
+
buildSchemaManagedResult
|
|
1591
|
+
]
|
|
1592
|
+
);
|
|
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
|
+
);
|
|
1599
|
+
}
|
|
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
|
+
);
|
|
1607
|
+
}
|
|
1608
|
+
try {
|
|
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
|
+
});
|
|
1626
|
+
} catch (err) {
|
|
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);
|
|
1629
|
+
}
|
|
1630
|
+
},
|
|
1631
|
+
[
|
|
1632
|
+
applyReconcile,
|
|
1633
|
+
buildFallbackResult,
|
|
1634
|
+
buildSchemaManagedResult,
|
|
1635
|
+
codec,
|
|
1636
|
+
decodeStringPayload,
|
|
1637
|
+
key,
|
|
1638
|
+
schemaRegistry
|
|
1639
|
+
]
|
|
1640
|
+
);
|
|
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
|
+
});
|
|
1650
|
+
}
|
|
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);
|
|
1661
|
+
}
|
|
1662
|
+
},
|
|
1663
|
+
[applyReconcile, buildFallbackResult, codec, decodeStringPayload, encodeForWrite]
|
|
1664
|
+
);
|
|
1665
|
+
const decodeSchemaManagedEnvelope = react.useCallback(
|
|
1666
|
+
(envelope, schemaForVersion, latestSchema) => {
|
|
1667
|
+
let current;
|
|
1668
|
+
try {
|
|
1669
|
+
current = envelope.payload;
|
|
1670
|
+
validateAgainstSchema(current, schemaForVersion.schema);
|
|
1671
|
+
} catch (err) {
|
|
1672
|
+
const typedErr = err instanceof SchemaError || err instanceof CodecError ? err : new SchemaError("TYPE_MISMATCH", `Schema decode failed for key "${key}"`, err);
|
|
1673
|
+
return buildFallbackResult(typedErr);
|
|
1674
|
+
}
|
|
1675
|
+
if (!latestSchema || envelope.version >= latestSchema.version) {
|
|
1676
|
+
return applyReconcile({
|
|
1677
|
+
value: current,
|
|
1678
|
+
persistedVersion: envelope.version,
|
|
1679
|
+
...latestSchema ? { latestVersion: latestSchema.version } : {},
|
|
1680
|
+
serializeForPersist: encodeForWrite
|
|
1681
|
+
});
|
|
1682
|
+
}
|
|
1683
|
+
const path = getMigrationPathForKey(envelope.version, latestSchema.version);
|
|
1684
|
+
if (!path) {
|
|
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}`
|
|
1689
|
+
)
|
|
1690
|
+
);
|
|
1691
|
+
}
|
|
1692
|
+
try {
|
|
1693
|
+
let migrated = current;
|
|
1694
|
+
for (const step of path) {
|
|
1695
|
+
migrated = step.migrate(migrated);
|
|
1696
|
+
}
|
|
1697
|
+
validateAgainstSchema(migrated, latestSchema.schema);
|
|
1698
|
+
return applyReconcile({
|
|
1699
|
+
value: migrated,
|
|
1700
|
+
rewriteRaw: buildSchemaManagedResult(latestSchema.version, migrated),
|
|
1701
|
+
persistedVersion: envelope.version,
|
|
1702
|
+
latestVersion: latestSchema.version,
|
|
1703
|
+
serializeForPersist: encodeForWrite
|
|
1704
|
+
});
|
|
1705
|
+
} catch (err) {
|
|
1706
|
+
const typedErr = err instanceof SchemaError || err instanceof CodecError ? err : new SchemaError("MIGRATION_FAILED", `Migration failed for key "${key}"`, err);
|
|
1707
|
+
return buildFallbackResult(typedErr);
|
|
1708
|
+
}
|
|
1709
|
+
},
|
|
1710
|
+
[
|
|
1711
|
+
applyReconcile,
|
|
1712
|
+
buildFallbackResult,
|
|
1713
|
+
buildSchemaManagedResult,
|
|
1714
|
+
encodeForWrite,
|
|
1715
|
+
getMigrationPathForKey,
|
|
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,
|
|
1746
|
+
parseEnvelope,
|
|
1747
|
+
schemaMode,
|
|
1748
|
+
getSchemaForVersion,
|
|
1749
|
+
getLatestSchemaForKey,
|
|
1750
|
+
key
|
|
1751
|
+
]
|
|
1752
|
+
);
|
|
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]
|
|
1762
|
+
);
|
|
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;
|
|
1767
|
+
try {
|
|
1768
|
+
schemaRegistry.registerSchema(decoded.pendingSchema);
|
|
1769
|
+
} catch {
|
|
1770
|
+
}
|
|
1771
|
+
},
|
|
1772
|
+
[schemaRegistry]
|
|
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));
|
|
1783
|
+
}
|
|
1784
|
+
function uniqueKeys(keys) {
|
|
1785
|
+
return [...new Set(keys)];
|
|
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
|
+
}
|
|
1801
|
+
function useMnemonicRecovery(options = {}) {
|
|
1802
|
+
const api = useMnemonic();
|
|
1803
|
+
const { onRecover } = options;
|
|
1804
|
+
const namespace = react.useMemo(() => api.prefix.endsWith(".") ? api.prefix.slice(0, -1) : api.prefix, [api.prefix]);
|
|
1805
|
+
const emitRecovery = react.useCallback(
|
|
1806
|
+
(action, clearedKeys) => {
|
|
1807
|
+
const event = {
|
|
1808
|
+
action,
|
|
1809
|
+
namespace,
|
|
1810
|
+
clearedKeys
|
|
1811
|
+
};
|
|
1812
|
+
onRecover?.(event);
|
|
1813
|
+
},
|
|
1814
|
+
[namespace, onRecover]
|
|
1815
|
+
);
|
|
1816
|
+
const listKeys = react.useCallback(() => api.keys(), [api]);
|
|
1817
|
+
const clearResolvedKeys = react.useCallback(
|
|
1818
|
+
(action, keys) => {
|
|
1819
|
+
const clearedKeys = uniqueKeys(keys);
|
|
1820
|
+
for (const key of clearedKeys) {
|
|
1821
|
+
api.removeRaw(key);
|
|
1822
|
+
}
|
|
1823
|
+
emitRecovery(action, clearedKeys);
|
|
1824
|
+
return clearedKeys;
|
|
1825
|
+
},
|
|
1826
|
+
[api, emitRecovery]
|
|
1827
|
+
);
|
|
1828
|
+
const clearKeys = react.useCallback(
|
|
1829
|
+
(keys) => clearResolvedKeys("clear-keys", keys),
|
|
1830
|
+
[clearResolvedKeys]
|
|
1831
|
+
);
|
|
1832
|
+
const clearAll = react.useCallback(() => {
|
|
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
|
+
}
|
|
1841
|
+
throw new Error(
|
|
1842
|
+
"clearAll requires an enumerable storage backend. Use clearKeys([...]) with an explicit key list instead."
|
|
1843
|
+
);
|
|
1844
|
+
}
|
|
1845
|
+
return clearResolvedKeys("clear-all", api.keys());
|
|
1846
|
+
}, [api, clearResolvedKeys, namespace]);
|
|
1847
|
+
const clearMatching = react.useCallback(
|
|
1848
|
+
(predicate) => {
|
|
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
|
+
}
|
|
1857
|
+
throw new Error(
|
|
1858
|
+
"clearMatching requires an enumerable storage backend. Use clearKeys([...]) with an explicit key list instead."
|
|
1859
|
+
);
|
|
1860
|
+
}
|
|
1861
|
+
return clearResolvedKeys(
|
|
1862
|
+
"clear-matching",
|
|
1863
|
+
api.keys().filter((key) => predicate(key))
|
|
1864
|
+
);
|
|
1865
|
+
},
|
|
1866
|
+
[api, clearResolvedKeys, namespace]
|
|
1867
|
+
);
|
|
1868
|
+
return react.useMemo(
|
|
1869
|
+
() => ({
|
|
1870
|
+
namespace,
|
|
1871
|
+
canEnumerateKeys: api.canEnumerateKeys,
|
|
1872
|
+
listKeys,
|
|
1873
|
+
clearAll,
|
|
1874
|
+
clearKeys,
|
|
1875
|
+
clearMatching
|
|
1876
|
+
}),
|
|
1877
|
+
[namespace, api.canEnumerateKeys, listKeys, clearAll, clearKeys, clearMatching]
|
|
1878
|
+
);
|
|
1879
|
+
}
|
|
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
|
+
|
|
1898
|
+
// src/Mnemonic/schema-registry.ts
|
|
1899
|
+
function schemaVersionKey(key, version) {
|
|
1900
|
+
return `${key}:${version}`;
|
|
1901
|
+
}
|
|
1902
|
+
function migrationVersionKey(key, fromVersion) {
|
|
1903
|
+
return `${key}:${fromVersion}`;
|
|
1904
|
+
}
|
|
1905
|
+
function validateVersion(value, label) {
|
|
1906
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
1907
|
+
throw new SchemaError("MIGRATION_GRAPH_INVALID", `${label} must be a non-negative integer`);
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
function createSchemaRegistry(options = {}) {
|
|
1911
|
+
const { schemas = [], migrations = [] } = options;
|
|
1912
|
+
const schemasByKeyAndVersion = /* @__PURE__ */ new Map();
|
|
1913
|
+
const latestSchemaByKey = /* @__PURE__ */ new Map();
|
|
1914
|
+
const writeMigrationsByKeyAndVersion = /* @__PURE__ */ new Map();
|
|
1915
|
+
const migrationsByKeyAndFromVersion = /* @__PURE__ */ new Map();
|
|
1916
|
+
for (const schema of schemas) {
|
|
1917
|
+
validateVersion(schema.version, `Schema version for key "${schema.key}"`);
|
|
1918
|
+
const id = schemaVersionKey(schema.key, schema.version);
|
|
1919
|
+
if (schemasByKeyAndVersion.has(id)) {
|
|
1920
|
+
throw new SchemaError(
|
|
1921
|
+
"SCHEMA_REGISTRATION_CONFLICT",
|
|
1922
|
+
`Duplicate schema registered for key "${schema.key}" version ${schema.version}`
|
|
1923
|
+
);
|
|
1924
|
+
}
|
|
1925
|
+
schemasByKeyAndVersion.set(id, schema);
|
|
1926
|
+
const currentLatest = latestSchemaByKey.get(schema.key);
|
|
1927
|
+
if (!currentLatest || schema.version > currentLatest.version) {
|
|
1928
|
+
latestSchemaByKey.set(schema.key, schema);
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
for (const migration of migrations) {
|
|
1932
|
+
validateVersion(migration.fromVersion, `Migration fromVersion for key "${migration.key}"`);
|
|
1933
|
+
validateVersion(migration.toVersion, `Migration toVersion for key "${migration.key}"`);
|
|
1934
|
+
if (migration.toVersion < migration.fromVersion) {
|
|
1935
|
+
throw new SchemaError(
|
|
1936
|
+
"MIGRATION_GRAPH_INVALID",
|
|
1937
|
+
`Backward migration "${migration.key}" ${migration.fromVersion} -> ${migration.toVersion} is not supported`
|
|
1938
|
+
);
|
|
1939
|
+
}
|
|
1940
|
+
if (migration.fromVersion === migration.toVersion) {
|
|
1941
|
+
const id = schemaVersionKey(migration.key, migration.fromVersion);
|
|
1942
|
+
if (writeMigrationsByKeyAndVersion.has(id)) {
|
|
1943
|
+
throw new SchemaError(
|
|
1944
|
+
"MIGRATION_GRAPH_INVALID",
|
|
1945
|
+
`Duplicate write migration registered for key "${migration.key}" version ${migration.fromVersion}`
|
|
1946
|
+
);
|
|
1947
|
+
}
|
|
1948
|
+
writeMigrationsByKeyAndVersion.set(id, migration);
|
|
1949
|
+
continue;
|
|
1950
|
+
}
|
|
1951
|
+
const edgeKey = migrationVersionKey(migration.key, migration.fromVersion);
|
|
1952
|
+
if (migrationsByKeyAndFromVersion.has(edgeKey)) {
|
|
1953
|
+
const existing = migrationsByKeyAndFromVersion.get(edgeKey);
|
|
1954
|
+
throw new SchemaError(
|
|
1955
|
+
"MIGRATION_GRAPH_INVALID",
|
|
1956
|
+
`Ambiguous migration graph for key "${migration.key}" at version ${migration.fromVersion}: ${existing.fromVersion} -> ${existing.toVersion} conflicts with ${migration.fromVersion} -> ${migration.toVersion}`
|
|
1957
|
+
);
|
|
1958
|
+
}
|
|
1959
|
+
migrationsByKeyAndFromVersion.set(edgeKey, migration);
|
|
1960
|
+
}
|
|
1961
|
+
return {
|
|
1962
|
+
getSchema(key, version) {
|
|
1963
|
+
return schemasByKeyAndVersion.get(schemaVersionKey(key, version));
|
|
1964
|
+
},
|
|
1965
|
+
getLatestSchema(key) {
|
|
1966
|
+
return latestSchemaByKey.get(key);
|
|
1967
|
+
},
|
|
1968
|
+
getMigrationPath(key, fromVersion, toVersion) {
|
|
1969
|
+
if (fromVersion === toVersion) return [];
|
|
1970
|
+
if (toVersion < fromVersion) return null;
|
|
1971
|
+
const path = [];
|
|
1972
|
+
let currentVersion = fromVersion;
|
|
1973
|
+
while (currentVersion < toVersion) {
|
|
1974
|
+
const next = migrationsByKeyAndFromVersion.get(migrationVersionKey(key, currentVersion));
|
|
1975
|
+
if (!next) return null;
|
|
1976
|
+
path.push(next);
|
|
1977
|
+
currentVersion = next.toVersion;
|
|
1978
|
+
}
|
|
1979
|
+
return currentVersion === toVersion ? path : null;
|
|
1980
|
+
},
|
|
1981
|
+
getWriteMigration(key, version) {
|
|
1982
|
+
return writeMigrationsByKeyAndVersion.get(schemaVersionKey(key, version));
|
|
1983
|
+
}
|
|
1984
|
+
};
|
|
1985
|
+
}
|
|
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
|
+
|
|
2165
|
+
// src/Mnemonic/structural-migrations.ts
|
|
2166
|
+
function resolveHelpers(helpers) {
|
|
2167
|
+
if (helpers) return helpers;
|
|
2168
|
+
return {
|
|
2169
|
+
getId: (node) => node.id,
|
|
2170
|
+
getChildren: (node) => node.children,
|
|
2171
|
+
withChildren: (node, children) => ({ ...node, children }),
|
|
2172
|
+
withId: (node, id) => ({ ...node, id })
|
|
2173
|
+
};
|
|
2174
|
+
}
|
|
2175
|
+
function findNodeById(root, id, helpers) {
|
|
2176
|
+
const tree = resolveHelpers(helpers);
|
|
2177
|
+
if (tree.getId(root) === id) return root;
|
|
2178
|
+
for (const child of tree.getChildren(root) ?? []) {
|
|
2179
|
+
const match = findNodeById(child, id, tree);
|
|
2180
|
+
if (match) return match;
|
|
2181
|
+
}
|
|
2182
|
+
return void 0;
|
|
2183
|
+
}
|
|
2184
|
+
function insertChildIfMissing(root, parentId, child, helpers) {
|
|
2185
|
+
const tree = resolveHelpers(helpers);
|
|
2186
|
+
const childId = tree.getId(child);
|
|
2187
|
+
const visit = (node) => {
|
|
2188
|
+
if (tree.getId(node) === parentId) {
|
|
2189
|
+
const children2 = [...tree.getChildren(node) ?? []];
|
|
2190
|
+
if (children2.some((existing) => tree.getId(existing) === childId)) {
|
|
2191
|
+
return [node, false];
|
|
2192
|
+
}
|
|
2193
|
+
return [tree.withChildren(node, [...children2, child]), true];
|
|
2194
|
+
}
|
|
2195
|
+
const children = tree.getChildren(node);
|
|
2196
|
+
if (!children?.length) return [node, false];
|
|
2197
|
+
let inserted = false;
|
|
2198
|
+
let changed = false;
|
|
2199
|
+
const nextChildren = children.map((existingChild) => {
|
|
2200
|
+
if (inserted) return existingChild;
|
|
2201
|
+
const [nextChild, didInsert] = visit(existingChild);
|
|
2202
|
+
inserted || (inserted = didInsert);
|
|
2203
|
+
changed || (changed = nextChild !== existingChild);
|
|
2204
|
+
return nextChild;
|
|
2205
|
+
});
|
|
2206
|
+
if (!changed) return [node, inserted];
|
|
2207
|
+
return [tree.withChildren(node, nextChildren), inserted];
|
|
2208
|
+
};
|
|
2209
|
+
return visit(root)[0];
|
|
2210
|
+
}
|
|
2211
|
+
function renameNode(root, currentId, nextId, helpers) {
|
|
2212
|
+
const tree = resolveHelpers(helpers);
|
|
2213
|
+
if (currentId === nextId) return root;
|
|
2214
|
+
if (!findNodeById(root, currentId, tree)) return root;
|
|
2215
|
+
if (findNodeById(root, nextId, tree)) return root;
|
|
2216
|
+
const visit = (node) => {
|
|
2217
|
+
let nextNode = tree.getId(node) === currentId ? tree.withId(node, nextId) : node;
|
|
2218
|
+
const children = tree.getChildren(nextNode);
|
|
2219
|
+
if (!children?.length) return nextNode;
|
|
2220
|
+
let changed = nextNode !== node;
|
|
2221
|
+
const nextChildren = children.map((child) => {
|
|
2222
|
+
const nextChild = visit(child);
|
|
2223
|
+
changed || (changed = nextChild !== child);
|
|
2224
|
+
return nextChild;
|
|
2225
|
+
});
|
|
2226
|
+
if (!changed) return node;
|
|
2227
|
+
return tree.withChildren(nextNode, nextChildren);
|
|
2228
|
+
};
|
|
2229
|
+
return visit(root);
|
|
2230
|
+
}
|
|
2231
|
+
function dedupeChildrenBy(root, getKey, helpers) {
|
|
2232
|
+
const tree = resolveHelpers(helpers);
|
|
2233
|
+
const visit = (node) => {
|
|
2234
|
+
const children = tree.getChildren(node);
|
|
2235
|
+
if (!children?.length) return node;
|
|
2236
|
+
let changed = false;
|
|
2237
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2238
|
+
const nextChildren = [];
|
|
2239
|
+
for (const child of children) {
|
|
2240
|
+
const normalizedChild = visit(child);
|
|
2241
|
+
changed || (changed = normalizedChild !== child);
|
|
2242
|
+
const key = getKey(normalizedChild);
|
|
2243
|
+
if (seen.has(key)) {
|
|
2244
|
+
changed = true;
|
|
2245
|
+
continue;
|
|
2246
|
+
}
|
|
2247
|
+
seen.add(key);
|
|
2248
|
+
nextChildren.push(normalizedChild);
|
|
2249
|
+
}
|
|
2250
|
+
if (!changed && nextChildren.length === children.length) return node;
|
|
2251
|
+
return tree.withChildren(node, nextChildren);
|
|
2252
|
+
};
|
|
2253
|
+
return visit(root);
|
|
2254
|
+
}
|
|
2255
|
+
|
|
2256
|
+
exports.CodecError = CodecError;
|
|
2257
|
+
exports.JSONCodec = JSONCodec;
|
|
2258
|
+
exports.MnemonicProvider = MnemonicProvider;
|
|
2259
|
+
exports.SchemaError = SchemaError;
|
|
2260
|
+
exports.compileSchema = compileSchema;
|
|
2261
|
+
exports.createCodec = createCodec;
|
|
2262
|
+
exports.createSchemaRegistry = createSchemaRegistry;
|
|
2263
|
+
exports.dedupeChildrenBy = dedupeChildrenBy;
|
|
2264
|
+
exports.defineKeySchema = defineKeySchema;
|
|
2265
|
+
exports.defineMigration = defineMigration;
|
|
2266
|
+
exports.defineMnemonicKey = defineMnemonicKey;
|
|
2267
|
+
exports.defineWriteMigration = defineWriteMigration;
|
|
2268
|
+
exports.findNodeById = findNodeById;
|
|
2269
|
+
exports.insertChildIfMissing = insertChildIfMissing;
|
|
2270
|
+
exports.mnemonicSchema = mnemonicSchema;
|
|
2271
|
+
exports.renameNode = renameNode;
|
|
2272
|
+
exports.useMnemonicKey = useMnemonicKey;
|
|
2273
|
+
exports.useMnemonicRecovery = useMnemonicRecovery;
|
|
2274
|
+
exports.validateJsonSchema = validateJsonSchema;
|
|
2275
|
+
//# sourceMappingURL=schema.cjs.map
|
|
2276
|
+
//# sourceMappingURL=schema.cjs.map
|