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