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