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