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