react-mnemonic 1.0.0-beta.0 → 1.1.0-beta0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +236 -19
- package/dist/index.cjs +1355 -643
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +387 -37
- package/dist/index.d.ts +387 -37
- package/dist/index.js +1352 -645
- package/dist/index.js.map +1 -1
- package/package.json +9 -1
package/dist/index.cjs
CHANGED
|
@@ -4,6 +4,32 @@ var react = require('react');
|
|
|
4
4
|
var jsxRuntime = require('react/jsx-runtime');
|
|
5
5
|
|
|
6
6
|
// src/Mnemonic/provider.tsx
|
|
7
|
+
|
|
8
|
+
// src/Mnemonic/runtime.ts
|
|
9
|
+
function getGlobalProcess() {
|
|
10
|
+
return globalThis.process;
|
|
11
|
+
}
|
|
12
|
+
function getRuntimeNodeEnv() {
|
|
13
|
+
const runtimeProcess = getGlobalProcess();
|
|
14
|
+
if (runtimeProcess?.env?.NODE_ENV !== void 0) {
|
|
15
|
+
return runtimeProcess.env.NODE_ENV;
|
|
16
|
+
}
|
|
17
|
+
return void 0;
|
|
18
|
+
}
|
|
19
|
+
function getNativeBrowserStorages() {
|
|
20
|
+
const globalWindow = globalThis.window;
|
|
21
|
+
if (!globalWindow) return [];
|
|
22
|
+
const storages = [];
|
|
23
|
+
const addStorage = (getter) => {
|
|
24
|
+
try {
|
|
25
|
+
storages.push(getter());
|
|
26
|
+
} catch {
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
addStorage(() => globalWindow.localStorage);
|
|
30
|
+
addStorage(() => globalWindow.sessionStorage);
|
|
31
|
+
return storages;
|
|
32
|
+
}
|
|
7
33
|
var MnemonicContext = react.createContext(null);
|
|
8
34
|
function useMnemonic() {
|
|
9
35
|
const context = react.useContext(MnemonicContext);
|
|
@@ -13,20 +39,399 @@ function useMnemonic() {
|
|
|
13
39
|
return context;
|
|
14
40
|
}
|
|
15
41
|
function defaultBrowserStorage() {
|
|
16
|
-
|
|
42
|
+
const globalWindow = globalThis.window;
|
|
43
|
+
if (globalWindow === void 0) return void 0;
|
|
17
44
|
try {
|
|
18
|
-
return
|
|
45
|
+
return globalWindow.localStorage;
|
|
19
46
|
} catch {
|
|
20
47
|
return void 0;
|
|
21
48
|
}
|
|
22
49
|
}
|
|
50
|
+
function detectEnumerableStorage(storage) {
|
|
51
|
+
if (!storage) return false;
|
|
52
|
+
try {
|
|
53
|
+
return typeof storage.length === "number" && typeof storage.key === "function";
|
|
54
|
+
} catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function isProductionRuntime() {
|
|
59
|
+
const env = getRuntimeNodeEnv();
|
|
60
|
+
if (env === void 0) {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
return env === "production";
|
|
64
|
+
}
|
|
65
|
+
function weakRefConstructor() {
|
|
66
|
+
const ctor = globalThis.WeakRef;
|
|
67
|
+
return typeof ctor === "function" ? ctor : null;
|
|
68
|
+
}
|
|
69
|
+
function hasFinalizationRegistry() {
|
|
70
|
+
return typeof globalThis.FinalizationRegistry === "function";
|
|
71
|
+
}
|
|
72
|
+
function isPromiseLike(value) {
|
|
73
|
+
if (value == null) return false;
|
|
74
|
+
if (typeof value !== "object" && typeof value !== "function") return false;
|
|
75
|
+
return typeof value.then === "function";
|
|
76
|
+
}
|
|
77
|
+
function getCrossTabSyncMode(requestedStorage, activeStorage) {
|
|
78
|
+
const isExplicitNativeBrowserStorage = activeStorage !== void 0 && requestedStorage !== void 0 && getNativeBrowserStorages().includes(activeStorage);
|
|
79
|
+
if (requestedStorage === void 0 && activeStorage !== void 0 || isExplicitNativeBrowserStorage) {
|
|
80
|
+
return "browser-storage-event";
|
|
81
|
+
}
|
|
82
|
+
if (typeof activeStorage?.onExternalChange === "function") {
|
|
83
|
+
return "custom-external-change";
|
|
84
|
+
}
|
|
85
|
+
return "none";
|
|
86
|
+
}
|
|
87
|
+
function getDevToolsWindow() {
|
|
88
|
+
return globalThis.window;
|
|
89
|
+
}
|
|
90
|
+
function sanitizeDevToolsRoot(root) {
|
|
91
|
+
const reserved = /* @__PURE__ */ new Set(["providers", "resolve", "list", "capabilities", "__meta"]);
|
|
92
|
+
for (const key of Object.keys(root)) {
|
|
93
|
+
if (reserved.has(key)) continue;
|
|
94
|
+
const descriptor = Object.getOwnPropertyDescriptor(root, key);
|
|
95
|
+
if (descriptor && !descriptor.configurable) continue;
|
|
96
|
+
try {
|
|
97
|
+
delete root[key];
|
|
98
|
+
} catch {
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function ensureDevToolsRoot(enableDevTools) {
|
|
103
|
+
if (!enableDevTools) return null;
|
|
104
|
+
const globalWindow = getDevToolsWindow();
|
|
105
|
+
if (!globalWindow) return null;
|
|
106
|
+
const weakRefSupported = weakRefConstructor() !== null;
|
|
107
|
+
const finalizationRegistrySupported = hasFinalizationRegistry();
|
|
108
|
+
const existing = globalWindow.__REACT_MNEMONIC_DEVTOOLS__;
|
|
109
|
+
const root = existing && typeof existing === "object" ? existing : {};
|
|
110
|
+
sanitizeDevToolsRoot(root);
|
|
111
|
+
if (!root.providers || typeof root.providers !== "object") {
|
|
112
|
+
root.providers = {};
|
|
113
|
+
}
|
|
114
|
+
if (!root.capabilities || typeof root.capabilities !== "object") {
|
|
115
|
+
root.capabilities = {};
|
|
116
|
+
}
|
|
117
|
+
const capabilities = root.capabilities;
|
|
118
|
+
capabilities.weakRef = weakRefSupported;
|
|
119
|
+
capabilities.finalizationRegistry = finalizationRegistrySupported;
|
|
120
|
+
if (!root.__meta || typeof root.__meta !== "object") {
|
|
121
|
+
root.__meta = {
|
|
122
|
+
version: 0,
|
|
123
|
+
lastUpdated: Date.now(),
|
|
124
|
+
lastChange: ""
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
const meta = root.__meta;
|
|
128
|
+
if (typeof meta.version !== "number" || !Number.isFinite(meta.version)) {
|
|
129
|
+
meta.version = 0;
|
|
130
|
+
}
|
|
131
|
+
if (typeof meta.lastUpdated !== "number" || !Number.isFinite(meta.lastUpdated)) {
|
|
132
|
+
meta.lastUpdated = Date.now();
|
|
133
|
+
}
|
|
134
|
+
if (typeof meta.lastChange !== "string") {
|
|
135
|
+
meta.lastChange = "";
|
|
136
|
+
}
|
|
137
|
+
const providers = root.providers;
|
|
138
|
+
if (typeof root.resolve !== "function") {
|
|
139
|
+
root.resolve = (namespace) => {
|
|
140
|
+
const entry = providers[namespace];
|
|
141
|
+
if (!entry || typeof entry.weakRef?.deref !== "function") return null;
|
|
142
|
+
const live = entry.weakRef.deref();
|
|
143
|
+
if (live) {
|
|
144
|
+
entry.lastSeenAt = Date.now();
|
|
145
|
+
entry.staleSince = null;
|
|
146
|
+
return live;
|
|
147
|
+
}
|
|
148
|
+
entry.staleSince ?? (entry.staleSince = Date.now());
|
|
149
|
+
return null;
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
if (typeof root.list !== "function") {
|
|
153
|
+
root.list = () => Object.entries(providers).map(([namespace, entry]) => {
|
|
154
|
+
const live = typeof entry.weakRef?.deref === "function" ? entry.weakRef.deref() : void 0;
|
|
155
|
+
const available = Boolean(live);
|
|
156
|
+
if (available) {
|
|
157
|
+
entry.lastSeenAt = Date.now();
|
|
158
|
+
entry.staleSince = null;
|
|
159
|
+
} else {
|
|
160
|
+
entry.staleSince ?? (entry.staleSince = Date.now());
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
namespace,
|
|
164
|
+
available,
|
|
165
|
+
registeredAt: entry.registeredAt,
|
|
166
|
+
lastSeenAt: entry.lastSeenAt,
|
|
167
|
+
staleSince: entry.staleSince
|
|
168
|
+
};
|
|
169
|
+
}).sort((left, right) => left.namespace.localeCompare(right.namespace));
|
|
170
|
+
}
|
|
171
|
+
globalWindow.__REACT_MNEMONIC_DEVTOOLS__ = root;
|
|
172
|
+
return root;
|
|
173
|
+
}
|
|
174
|
+
function bumpDevToolsVersion(root, namespace, reason) {
|
|
175
|
+
if (!root) return;
|
|
176
|
+
root.__meta.version += 1;
|
|
177
|
+
root.__meta.lastUpdated = Date.now();
|
|
178
|
+
root.__meta.lastChange = `${namespace}.${reason}`;
|
|
179
|
+
}
|
|
180
|
+
function decodeDevToolsValue(raw) {
|
|
181
|
+
try {
|
|
182
|
+
return JSON.parse(raw);
|
|
183
|
+
} catch {
|
|
184
|
+
return raw;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function readStorageRaw(storage, storageKey, callbacks) {
|
|
188
|
+
if (!storage) return null;
|
|
189
|
+
try {
|
|
190
|
+
const raw = storage.getItem(storageKey);
|
|
191
|
+
if (isPromiseLike(raw)) {
|
|
192
|
+
callbacks.onAsyncViolation("getItem", raw);
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
callbacks.onAccessSuccess();
|
|
196
|
+
return raw;
|
|
197
|
+
} catch (error) {
|
|
198
|
+
callbacks.onAccessError(error);
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function enumerateNamespaceKeys(storage, prefix, callbacks) {
|
|
203
|
+
if (!storage) {
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
const keys = [];
|
|
207
|
+
try {
|
|
208
|
+
const storageLength = storage.length;
|
|
209
|
+
const getStorageKey = storage.key;
|
|
210
|
+
if (typeof storageLength !== "number" || typeof getStorageKey !== "function") {
|
|
211
|
+
return [];
|
|
212
|
+
}
|
|
213
|
+
for (let index = 0; index < storageLength; index++) {
|
|
214
|
+
const fullKey = getStorageKey.call(storage, index);
|
|
215
|
+
if (!fullKey?.startsWith(prefix)) continue;
|
|
216
|
+
keys.push(fullKey.slice(prefix.length));
|
|
217
|
+
}
|
|
218
|
+
callbacks.onAccessSuccess();
|
|
219
|
+
} catch (error) {
|
|
220
|
+
callbacks.onAccessError(error);
|
|
221
|
+
}
|
|
222
|
+
return keys;
|
|
223
|
+
}
|
|
224
|
+
function syncCacheEntryFromStorage({
|
|
225
|
+
key,
|
|
226
|
+
storageKey,
|
|
227
|
+
storage,
|
|
228
|
+
cache,
|
|
229
|
+
emit,
|
|
230
|
+
callbacks
|
|
231
|
+
}) {
|
|
232
|
+
const fresh = readStorageRaw(storage, storageKey, callbacks);
|
|
233
|
+
const cached = cache.get(key) ?? null;
|
|
234
|
+
if (fresh === cached) {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
cache.set(key, fresh);
|
|
238
|
+
emit(key);
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
function reloadNamedKeysFromStorage({
|
|
242
|
+
changedKeys,
|
|
243
|
+
prefix,
|
|
244
|
+
storage,
|
|
245
|
+
listeners,
|
|
246
|
+
cache,
|
|
247
|
+
emit,
|
|
248
|
+
callbacks
|
|
249
|
+
}) {
|
|
250
|
+
let changed = false;
|
|
251
|
+
for (const fullStorageKey of changedKeys) {
|
|
252
|
+
if (!fullStorageKey.startsWith(prefix)) continue;
|
|
253
|
+
const key = fullStorageKey.slice(prefix.length);
|
|
254
|
+
const listenerSet = listeners.get(key);
|
|
255
|
+
if (listenerSet && listenerSet.size > 0) {
|
|
256
|
+
changed = syncCacheEntryFromStorage({
|
|
257
|
+
key,
|
|
258
|
+
storageKey: fullStorageKey,
|
|
259
|
+
storage,
|
|
260
|
+
cache,
|
|
261
|
+
emit,
|
|
262
|
+
callbacks
|
|
263
|
+
}) || changed;
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
if (cache.has(key)) {
|
|
267
|
+
cache.delete(key);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return changed;
|
|
271
|
+
}
|
|
272
|
+
function reloadSubscribedKeysFromStorage({
|
|
273
|
+
prefix,
|
|
274
|
+
storage,
|
|
275
|
+
listeners,
|
|
276
|
+
cache,
|
|
277
|
+
emit,
|
|
278
|
+
callbacks
|
|
279
|
+
}) {
|
|
280
|
+
let changed = false;
|
|
281
|
+
for (const [key, listenerSet] of listeners) {
|
|
282
|
+
if (listenerSet.size === 0) continue;
|
|
283
|
+
changed = syncCacheEntryFromStorage({
|
|
284
|
+
key,
|
|
285
|
+
storageKey: `${prefix}${key}`,
|
|
286
|
+
storage,
|
|
287
|
+
cache,
|
|
288
|
+
emit,
|
|
289
|
+
callbacks
|
|
290
|
+
}) || changed;
|
|
291
|
+
}
|
|
292
|
+
for (const key of cache.keys()) {
|
|
293
|
+
const listenerSet = listeners.get(key);
|
|
294
|
+
if (listenerSet && listenerSet.size > 0) continue;
|
|
295
|
+
cache.delete(key);
|
|
296
|
+
}
|
|
297
|
+
return changed;
|
|
298
|
+
}
|
|
299
|
+
function createDevToolsProviderApi({
|
|
300
|
+
store,
|
|
301
|
+
dump,
|
|
302
|
+
keys,
|
|
303
|
+
readThrough,
|
|
304
|
+
writeRaw,
|
|
305
|
+
removeRaw
|
|
306
|
+
}) {
|
|
307
|
+
return {
|
|
308
|
+
getStore: () => store,
|
|
309
|
+
dump: () => {
|
|
310
|
+
const data = dump();
|
|
311
|
+
console.table(
|
|
312
|
+
Object.entries(data).map(([key, value]) => ({
|
|
313
|
+
key,
|
|
314
|
+
value,
|
|
315
|
+
decoded: decodeDevToolsValue(value)
|
|
316
|
+
}))
|
|
317
|
+
);
|
|
318
|
+
return data;
|
|
319
|
+
},
|
|
320
|
+
get: (key) => {
|
|
321
|
+
const raw = readThrough(key);
|
|
322
|
+
if (raw == null) return void 0;
|
|
323
|
+
return decodeDevToolsValue(raw);
|
|
324
|
+
},
|
|
325
|
+
set: (key, value) => {
|
|
326
|
+
writeRaw(key, JSON.stringify(value));
|
|
327
|
+
},
|
|
328
|
+
remove: (key) => removeRaw(key),
|
|
329
|
+
clear: () => {
|
|
330
|
+
for (const key of keys()) {
|
|
331
|
+
removeRaw(key);
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
keys
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
function createReloadFromStorage({
|
|
338
|
+
storage,
|
|
339
|
+
hasAsyncContractViolation,
|
|
340
|
+
prefix,
|
|
341
|
+
listeners,
|
|
342
|
+
cache,
|
|
343
|
+
emit,
|
|
344
|
+
callbacks,
|
|
345
|
+
devToolsRoot,
|
|
346
|
+
namespace
|
|
347
|
+
}) {
|
|
348
|
+
return (changedKeys) => {
|
|
349
|
+
if (!storage || hasAsyncContractViolation()) return;
|
|
350
|
+
if (changedKeys?.length === 0) return;
|
|
351
|
+
const isFullReload = changedKeys === void 0;
|
|
352
|
+
const changed = isFullReload ? reloadSubscribedKeysFromStorage({
|
|
353
|
+
prefix,
|
|
354
|
+
storage,
|
|
355
|
+
listeners,
|
|
356
|
+
cache,
|
|
357
|
+
emit,
|
|
358
|
+
callbacks
|
|
359
|
+
}) : reloadNamedKeysFromStorage({
|
|
360
|
+
changedKeys,
|
|
361
|
+
prefix,
|
|
362
|
+
storage,
|
|
363
|
+
listeners,
|
|
364
|
+
cache,
|
|
365
|
+
emit,
|
|
366
|
+
callbacks
|
|
367
|
+
});
|
|
368
|
+
if (changed) {
|
|
369
|
+
bumpDevToolsVersion(devToolsRoot, namespace, isFullReload ? "reload:full" : "reload:granular");
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
function registerDevToolsProvider({
|
|
374
|
+
devToolsRoot,
|
|
375
|
+
namespace,
|
|
376
|
+
store,
|
|
377
|
+
dump,
|
|
378
|
+
keys,
|
|
379
|
+
readThrough,
|
|
380
|
+
writeRaw,
|
|
381
|
+
removeRaw
|
|
382
|
+
}) {
|
|
383
|
+
let infoMessage = `[Mnemonic DevTools] Namespace "${namespace}" available via window.__REACT_MNEMONIC_DEVTOOLS__.resolve("${namespace}")`;
|
|
384
|
+
if (!devToolsRoot.capabilities.weakRef) {
|
|
385
|
+
console.info(
|
|
386
|
+
`[Mnemonic DevTools] WeakRef is not available; registry provider "${namespace}" was not registered.`
|
|
387
|
+
);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
const existingLive = devToolsRoot.resolve(namespace);
|
|
391
|
+
if (existingLive) {
|
|
392
|
+
const duplicateMessage = `[Mnemonic DevTools] Duplicate provider namespace "${namespace}" detected. Each window must have at most one live MnemonicProvider per namespace.`;
|
|
393
|
+
if (!isProductionRuntime()) {
|
|
394
|
+
throw new Error(duplicateMessage);
|
|
395
|
+
}
|
|
396
|
+
console.warn(`${duplicateMessage} Keeping the first provider and ignoring the duplicate.`);
|
|
397
|
+
console.info(
|
|
398
|
+
`[Mnemonic DevTools] Namespace "${namespace}" already registered. Keeping existing provider reference.`
|
|
399
|
+
);
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
const providerApi = createDevToolsProviderApi({
|
|
403
|
+
store,
|
|
404
|
+
dump,
|
|
405
|
+
keys,
|
|
406
|
+
readThrough,
|
|
407
|
+
writeRaw,
|
|
408
|
+
removeRaw
|
|
409
|
+
});
|
|
410
|
+
const WeakRefCtor = weakRefConstructor();
|
|
411
|
+
if (!WeakRefCtor) {
|
|
412
|
+
console.info(`[Mnemonic DevTools] WeakRef became unavailable while registering "${namespace}".`);
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
store.__devToolsProviderApiHold = providerApi;
|
|
416
|
+
const now = Date.now();
|
|
417
|
+
devToolsRoot.providers[namespace] = {
|
|
418
|
+
namespace,
|
|
419
|
+
weakRef: new WeakRefCtor(providerApi),
|
|
420
|
+
registeredAt: now,
|
|
421
|
+
lastSeenAt: now,
|
|
422
|
+
staleSince: null
|
|
423
|
+
};
|
|
424
|
+
bumpDevToolsVersion(devToolsRoot, namespace, "registry:namespace-registered");
|
|
425
|
+
console.info(infoMessage);
|
|
426
|
+
}
|
|
23
427
|
function MnemonicProvider({
|
|
24
428
|
children,
|
|
25
429
|
namespace,
|
|
26
430
|
storage,
|
|
27
431
|
enableDevTools = false,
|
|
28
432
|
schemaMode = "default",
|
|
29
|
-
schemaRegistry
|
|
433
|
+
schemaRegistry,
|
|
434
|
+
ssr
|
|
30
435
|
}) {
|
|
31
436
|
if (schemaMode === "strict" && !schemaRegistry) {
|
|
32
437
|
throw new Error("MnemonicProvider strict mode requires schemaRegistry");
|
|
@@ -37,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);
|
|
747
|
+
}
|
|
748
|
+
return errors;
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
function buildTypeValidationStep(schema) {
|
|
752
|
+
if (schema.type === void 0) {
|
|
753
|
+
return null;
|
|
754
|
+
}
|
|
755
|
+
const resolvedTypes = Array.isArray(schema.type) ? schema.type : [schema.type];
|
|
756
|
+
const typeLabel = JSON.stringify(schema.type);
|
|
757
|
+
return (value, path, errors) => {
|
|
758
|
+
if (resolvedTypes.some((type) => matchesType(value, type))) {
|
|
759
|
+
return true;
|
|
760
|
+
}
|
|
761
|
+
errors.push({
|
|
762
|
+
path,
|
|
763
|
+
message: `Expected type ${typeLabel}, got ${jsonTypeLabel(value)}`,
|
|
764
|
+
keyword: "type"
|
|
765
|
+
});
|
|
766
|
+
return false;
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
function buildEnumValidationStep(schema) {
|
|
770
|
+
if (schema.enum === void 0) {
|
|
771
|
+
return null;
|
|
772
|
+
}
|
|
773
|
+
const enumPrimitiveSet = new Set(schema.enum.filter((member) => isJsonPrimitive(member)));
|
|
774
|
+
const enumComplexMembers = schema.enum.filter((member) => !isJsonPrimitive(member));
|
|
775
|
+
return (value, path, errors) => {
|
|
776
|
+
const primitiveMatch = isJsonPrimitive(value) && enumPrimitiveSet.has(value);
|
|
777
|
+
const complexMatch = !primitiveMatch && enumComplexMembers.some((entry) => jsonDeepEqual(value, entry));
|
|
778
|
+
if (primitiveMatch || complexMatch) {
|
|
779
|
+
return;
|
|
543
780
|
}
|
|
544
|
-
|
|
545
|
-
|
|
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;
|
|
546
791
|
}
|
|
547
|
-
|
|
548
|
-
|
|
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":
|
|
@@ -754,21 +1020,173 @@ function inferJsonSchema(sample) {
|
|
|
754
1020
|
}
|
|
755
1021
|
|
|
756
1022
|
// src/Mnemonic/use.ts
|
|
757
|
-
|
|
1023
|
+
var SSR_SNAPSHOT_TOKEN = /* @__PURE__ */ Symbol("mnemonic:ssr-snapshot");
|
|
1024
|
+
var diagnosticContractRegistry = /* @__PURE__ */ new WeakMap();
|
|
1025
|
+
var diagnosticWarningRegistry = /* @__PURE__ */ new WeakMap();
|
|
1026
|
+
var diagnosticObjectIds = /* @__PURE__ */ new WeakMap();
|
|
1027
|
+
var nextDiagnosticObjectId = 1;
|
|
1028
|
+
function serializeEnvelope(version, payload) {
|
|
1029
|
+
return JSON.stringify({
|
|
1030
|
+
version,
|
|
1031
|
+
payload
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
function withReadMetadata(value, rewriteRaw, pendingSchema) {
|
|
1035
|
+
const result = { value };
|
|
1036
|
+
if (rewriteRaw !== void 0) result.rewriteRaw = rewriteRaw;
|
|
1037
|
+
if (pendingSchema !== void 0) result.pendingSchema = pendingSchema;
|
|
1038
|
+
return result;
|
|
1039
|
+
}
|
|
1040
|
+
function isDevelopmentRuntime() {
|
|
1041
|
+
return getRuntimeNodeEnv() === "development";
|
|
1042
|
+
}
|
|
1043
|
+
function getDiagnosticWarnings(api) {
|
|
1044
|
+
let warnings = diagnosticWarningRegistry.get(api);
|
|
1045
|
+
if (!warnings) {
|
|
1046
|
+
warnings = /* @__PURE__ */ new Set();
|
|
1047
|
+
diagnosticWarningRegistry.set(api, warnings);
|
|
1048
|
+
}
|
|
1049
|
+
return warnings;
|
|
1050
|
+
}
|
|
1051
|
+
function warnOnce(api, id, message) {
|
|
1052
|
+
const warnings = getDiagnosticWarnings(api);
|
|
1053
|
+
if (warnings.has(id)) return;
|
|
1054
|
+
warnings.add(id);
|
|
1055
|
+
console.warn(message);
|
|
1056
|
+
}
|
|
1057
|
+
function stableDiagnosticValue(value) {
|
|
1058
|
+
if (typeof value === "function") {
|
|
1059
|
+
const source = Function.prototype.toString.call(value).split(/\s+/).join(" ").trim();
|
|
1060
|
+
const name = value.name || "anonymous";
|
|
1061
|
+
return `[factory:${name}/${value.length}:${source}]`;
|
|
1062
|
+
}
|
|
1063
|
+
if (typeof value === "bigint") return `${value.toString()}n`;
|
|
1064
|
+
if (typeof value === "symbol") return value.toString();
|
|
1065
|
+
if (value === void 0) return "undefined";
|
|
1066
|
+
try {
|
|
1067
|
+
return JSON.stringify(value);
|
|
1068
|
+
} catch {
|
|
1069
|
+
const tag = Object.prototype.toString.call(value);
|
|
1070
|
+
if (value !== null && (typeof value === "object" || typeof value === "function")) {
|
|
1071
|
+
return `${tag}#${getDiagnosticObjectId(value)}`;
|
|
1072
|
+
}
|
|
1073
|
+
return tag;
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
function isObjectLike(value) {
|
|
1077
|
+
return value !== null && (typeof value === "object" || typeof value === "function");
|
|
1078
|
+
}
|
|
1079
|
+
function objectHasOwn2(value, property) {
|
|
1080
|
+
const hasOwn = Object.hasOwn;
|
|
1081
|
+
if (typeof hasOwn === "function") {
|
|
1082
|
+
return hasOwn(value, property);
|
|
1083
|
+
}
|
|
1084
|
+
return Object.getOwnPropertyDescriptor(value, property) !== void 0;
|
|
1085
|
+
}
|
|
1086
|
+
function getDiagnosticObjectId(value) {
|
|
1087
|
+
const existing = diagnosticObjectIds.get(value);
|
|
1088
|
+
if (existing !== void 0) return existing;
|
|
1089
|
+
const id = nextDiagnosticObjectId++;
|
|
1090
|
+
diagnosticObjectIds.set(value, id);
|
|
1091
|
+
return id;
|
|
1092
|
+
}
|
|
1093
|
+
function buildContractFingerprint({
|
|
1094
|
+
api,
|
|
1095
|
+
key,
|
|
1096
|
+
defaultValue,
|
|
1097
|
+
codecOpt,
|
|
1098
|
+
schema,
|
|
1099
|
+
reconcile,
|
|
1100
|
+
listenCrossTab,
|
|
1101
|
+
ssrOptions
|
|
1102
|
+
}) {
|
|
1103
|
+
const codecSignature = codecOpt == null || !isObjectLike(codecOpt) ? "default-json-codec" : `codec:${stableDiagnosticValue(codecOpt.encode)}:${stableDiagnosticValue(codecOpt.decode)}`;
|
|
1104
|
+
const reconcileSignature = reconcile == null || !isObjectLike(reconcile) ? "no-reconcile" : `reconcile:${stableDiagnosticValue(reconcile)}`;
|
|
1105
|
+
return JSON.stringify({
|
|
1106
|
+
key,
|
|
1107
|
+
defaultValue: stableDiagnosticValue(defaultValue),
|
|
1108
|
+
codec: codecSignature,
|
|
1109
|
+
schemaVersion: schema?.version ?? null,
|
|
1110
|
+
listenCrossTab: Boolean(listenCrossTab),
|
|
1111
|
+
reconcile: reconcileSignature,
|
|
1112
|
+
ssrHydration: ssrOptions?.hydration ?? null,
|
|
1113
|
+
hasServerValue: ssrOptions?.serverValue !== void 0,
|
|
1114
|
+
providerHydration: api.ssrHydration ?? null
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
function resolveMnemonicKeyArgs(keyOrDescriptor, options) {
|
|
1118
|
+
if (typeof keyOrDescriptor !== "string") {
|
|
1119
|
+
return keyOrDescriptor;
|
|
1120
|
+
}
|
|
1121
|
+
if (!options) {
|
|
1122
|
+
throw new Error("useMnemonicKey requires options when called with a string key");
|
|
1123
|
+
}
|
|
1124
|
+
return {
|
|
1125
|
+
key: keyOrDescriptor,
|
|
1126
|
+
options
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
function useMnemonicKey(keyOrDescriptor, options) {
|
|
1130
|
+
const descriptor = resolveMnemonicKeyArgs(keyOrDescriptor, options);
|
|
1131
|
+
const key = descriptor.key;
|
|
1132
|
+
const resolvedOptions = descriptor.options;
|
|
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
1145
|
const schemaMode = api.schemaMode;
|
|
762
1146
|
const schemaRegistry = api.schemaRegistry;
|
|
1147
|
+
const hydrationMode = ssrOptions?.hydration ?? api.ssrHydration;
|
|
1148
|
+
const [hasMounted, setHasMounted] = react.useState(hydrationMode !== "client-only");
|
|
1149
|
+
const developmentRuntime = isDevelopmentRuntime();
|
|
1150
|
+
const contractFingerprint = react.useMemo(
|
|
1151
|
+
() => developmentRuntime ? buildContractFingerprint({
|
|
1152
|
+
api,
|
|
1153
|
+
key,
|
|
1154
|
+
defaultValue,
|
|
1155
|
+
codecOpt,
|
|
1156
|
+
schema,
|
|
1157
|
+
reconcile,
|
|
1158
|
+
listenCrossTab,
|
|
1159
|
+
ssrOptions
|
|
1160
|
+
}) : null,
|
|
1161
|
+
[
|
|
1162
|
+
developmentRuntime,
|
|
1163
|
+
api,
|
|
1164
|
+
key,
|
|
1165
|
+
defaultValue,
|
|
1166
|
+
codecOpt,
|
|
1167
|
+
schema?.version,
|
|
1168
|
+
reconcile,
|
|
1169
|
+
listenCrossTab,
|
|
1170
|
+
ssrOptions?.hydration,
|
|
1171
|
+
ssrOptions?.serverValue
|
|
1172
|
+
]
|
|
1173
|
+
);
|
|
763
1174
|
const getFallback = react.useCallback(
|
|
764
1175
|
(error) => typeof defaultValue === "function" ? defaultValue(error) : defaultValue,
|
|
765
1176
|
[defaultValue]
|
|
766
1177
|
);
|
|
1178
|
+
const getServerValue = react.useCallback(() => {
|
|
1179
|
+
const serverValue = ssrOptions?.serverValue;
|
|
1180
|
+
if (serverValue === void 0) {
|
|
1181
|
+
return getFallback();
|
|
1182
|
+
}
|
|
1183
|
+
return typeof serverValue === "function" ? serverValue() : serverValue;
|
|
1184
|
+
}, [getFallback, ssrOptions?.serverValue]);
|
|
767
1185
|
const parseEnvelope = react.useCallback(
|
|
768
1186
|
(rawText) => {
|
|
769
1187
|
try {
|
|
770
1188
|
const parsed = JSON.parse(rawText);
|
|
771
|
-
if (typeof parsed !== "object" || parsed == null || !Number.isInteger(parsed.version) || parsed.version < 0 || !
|
|
1189
|
+
if (typeof parsed !== "object" || parsed == null || !Number.isInteger(parsed.version) || parsed.version < 0 || !objectHasOwn2(parsed, "payload")) {
|
|
772
1190
|
return {
|
|
773
1191
|
ok: false,
|
|
774
1192
|
error: new SchemaError("INVALID_ENVELOPE", `Invalid envelope for key "${key}"`)
|
|
@@ -786,12 +1204,6 @@ function useMnemonicKey(key, options) {
|
|
|
786
1204
|
);
|
|
787
1205
|
const decodeStringPayload = react.useCallback(
|
|
788
1206
|
(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
1207
|
try {
|
|
796
1208
|
return activeCodec.decode(payload);
|
|
797
1209
|
} catch (err) {
|
|
@@ -855,21 +1267,28 @@ function useMnemonicKey(key, options) {
|
|
|
855
1267
|
},
|
|
856
1268
|
[schemaRegistry, registryCache, key]
|
|
857
1269
|
);
|
|
1270
|
+
const buildFallbackResult = react.useCallback(
|
|
1271
|
+
(error) => ({
|
|
1272
|
+
value: getFallback(error)
|
|
1273
|
+
}),
|
|
1274
|
+
[getFallback]
|
|
1275
|
+
);
|
|
1276
|
+
const buildSchemaManagedResult = react.useCallback(
|
|
1277
|
+
(version, value2) => serializeEnvelope(version, value2),
|
|
1278
|
+
[]
|
|
1279
|
+
);
|
|
1280
|
+
const resolveTargetWriteSchema = react.useCallback(() => {
|
|
1281
|
+
const explicitVersion = schema?.version;
|
|
1282
|
+
const latestSchema = getLatestSchemaForKey();
|
|
1283
|
+
if (explicitVersion === void 0) return latestSchema;
|
|
1284
|
+
const explicitSchema = getSchemaForVersion(explicitVersion);
|
|
1285
|
+
if (explicitSchema) return explicitSchema;
|
|
1286
|
+
return schemaMode === "strict" ? void 0 : latestSchema;
|
|
1287
|
+
}, [getLatestSchemaForKey, getSchemaForVersion, schema?.version, schemaMode]);
|
|
858
1288
|
const encodeForWrite = react.useCallback(
|
|
859
1289
|
(nextValue) => {
|
|
860
1290
|
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
|
-
}
|
|
1291
|
+
const targetSchema = resolveTargetWriteSchema();
|
|
873
1292
|
if (!targetSchema) {
|
|
874
1293
|
if (explicitVersion !== void 0 && schemaMode === "strict") {
|
|
875
1294
|
throw new SchemaError(
|
|
@@ -877,11 +1296,7 @@ function useMnemonicKey(key, options) {
|
|
|
877
1296
|
`Write requires schema for key "${key}" in strict mode`
|
|
878
1297
|
);
|
|
879
1298
|
}
|
|
880
|
-
|
|
881
|
-
version: 0,
|
|
882
|
-
payload: codec.encode(nextValue)
|
|
883
|
-
};
|
|
884
|
-
return JSON.stringify(envelope2);
|
|
1299
|
+
return serializeEnvelope(0, codec.encode(nextValue));
|
|
885
1300
|
}
|
|
886
1301
|
let valueToStore = nextValue;
|
|
887
1302
|
const writeMigration = schemaRegistry?.getWriteMigration?.(key, targetSchema.version);
|
|
@@ -893,11 +1308,7 @@ function useMnemonicKey(key, options) {
|
|
|
893
1308
|
}
|
|
894
1309
|
}
|
|
895
1310
|
validateAgainstSchema(valueToStore, targetSchema.schema);
|
|
896
|
-
|
|
897
|
-
version: targetSchema.version,
|
|
898
|
-
payload: valueToStore
|
|
899
|
-
};
|
|
900
|
-
return JSON.stringify(envelope);
|
|
1311
|
+
return buildSchemaManagedResult(targetSchema.version, valueToStore);
|
|
901
1312
|
},
|
|
902
1313
|
[
|
|
903
1314
|
schema?.version,
|
|
@@ -906,8 +1317,8 @@ function useMnemonicKey(key, options) {
|
|
|
906
1317
|
codec,
|
|
907
1318
|
schemaRegistry,
|
|
908
1319
|
validateAgainstSchema,
|
|
909
|
-
|
|
910
|
-
|
|
1320
|
+
resolveTargetWriteSchema,
|
|
1321
|
+
buildSchemaManagedResult
|
|
911
1322
|
]
|
|
912
1323
|
);
|
|
913
1324
|
const applyReconcile = react.useCallback(
|
|
@@ -921,135 +1332,112 @@ function useMnemonicKey(key, options) {
|
|
|
921
1332
|
derivePendingSchema
|
|
922
1333
|
}) => {
|
|
923
1334
|
if (!reconcile) {
|
|
924
|
-
|
|
925
|
-
if (rewriteRaw !== void 0) result.rewriteRaw = rewriteRaw;
|
|
926
|
-
if (pendingSchema !== void 0) result.pendingSchema = pendingSchema;
|
|
927
|
-
return result;
|
|
1335
|
+
return withReadMetadata(value2, rewriteRaw, pendingSchema);
|
|
928
1336
|
}
|
|
929
1337
|
const context = {
|
|
930
1338
|
key,
|
|
931
1339
|
persistedVersion,
|
|
932
1340
|
...latestVersion === void 0 ? {} : { latestVersion }
|
|
933
1341
|
};
|
|
934
|
-
|
|
935
|
-
if (serializeForPersist) {
|
|
1342
|
+
const baselineSerialized = (() => {
|
|
936
1343
|
try {
|
|
937
|
-
|
|
1344
|
+
return serializeForPersist(value2);
|
|
938
1345
|
} catch {
|
|
939
|
-
|
|
1346
|
+
return rewriteRaw;
|
|
940
1347
|
}
|
|
941
|
-
}
|
|
1348
|
+
})();
|
|
942
1349
|
try {
|
|
943
1350
|
const reconciled = reconcile(value2, context);
|
|
944
1351
|
const nextPendingSchema = derivePendingSchema ? derivePendingSchema(reconciled) : pendingSchema;
|
|
945
|
-
if (!serializeForPersist) {
|
|
946
|
-
const result2 = { value: reconciled };
|
|
947
|
-
if (rewriteRaw !== void 0) result2.rewriteRaw = rewriteRaw;
|
|
948
|
-
if (nextPendingSchema !== void 0) result2.pendingSchema = nextPendingSchema;
|
|
949
|
-
return result2;
|
|
950
|
-
}
|
|
951
1352
|
const nextSerialized = serializeForPersist(reconciled);
|
|
952
1353
|
const nextRewriteRaw = baselineSerialized === void 0 || nextSerialized !== baselineSerialized ? nextSerialized : rewriteRaw;
|
|
953
|
-
|
|
954
|
-
if (nextRewriteRaw !== void 0) result.rewriteRaw = nextRewriteRaw;
|
|
955
|
-
if (nextPendingSchema !== void 0) result.pendingSchema = nextPendingSchema;
|
|
956
|
-
return result;
|
|
1354
|
+
return withReadMetadata(reconciled, nextRewriteRaw, nextPendingSchema);
|
|
957
1355
|
} catch (err) {
|
|
958
1356
|
const typedErr = err instanceof SchemaError ? err : new SchemaError("RECONCILE_FAILED", `Reconciliation failed for key "${key}"`, err);
|
|
959
|
-
return
|
|
1357
|
+
return buildFallbackResult(typedErr);
|
|
960
1358
|
}
|
|
961
1359
|
},
|
|
962
|
-
[
|
|
1360
|
+
[buildFallbackResult, key, reconcile]
|
|
963
1361
|
);
|
|
964
|
-
const
|
|
965
|
-
(
|
|
966
|
-
if (
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
1362
|
+
const decodeAutoschemaEnvelope = react.useCallback(
|
|
1363
|
+
(envelope, latestSchema) => {
|
|
1364
|
+
if (latestSchema) {
|
|
1365
|
+
return buildFallbackResult(
|
|
1366
|
+
new SchemaError("SCHEMA_NOT_FOUND", `No schema for key "${key}" v${envelope.version}`)
|
|
1367
|
+
);
|
|
1368
|
+
}
|
|
1369
|
+
if (!schemaRegistry || typeof schemaRegistry.registerSchema !== "function") {
|
|
1370
|
+
return buildFallbackResult(
|
|
1371
|
+
new SchemaError(
|
|
1372
|
+
"MODE_CONFIGURATION_INVALID",
|
|
1373
|
+
`Autoschema mode requires schema registry registration for key "${key}"`
|
|
976
1374
|
)
|
|
977
|
-
|
|
1375
|
+
);
|
|
978
1376
|
}
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
}
|
|
997
|
-
|
|
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
|
-
}
|
|
1377
|
+
try {
|
|
1378
|
+
const decoded2 = typeof envelope.payload === "string" ? decodeStringPayload(envelope.payload, codec) : envelope.payload;
|
|
1379
|
+
const inferSchemaForValue = (value2) => ({
|
|
1380
|
+
key,
|
|
1381
|
+
version: 1,
|
|
1382
|
+
schema: inferJsonSchema(value2)
|
|
1383
|
+
});
|
|
1384
|
+
const inferred = inferSchemaForValue(decoded2);
|
|
1385
|
+
return applyReconcile({
|
|
1386
|
+
value: decoded2,
|
|
1387
|
+
pendingSchema: inferred,
|
|
1388
|
+
rewriteRaw: buildSchemaManagedResult(inferred.version, decoded2),
|
|
1389
|
+
persistedVersion: envelope.version,
|
|
1390
|
+
serializeForPersist: (value2) => buildSchemaManagedResult(inferred.version, value2),
|
|
1391
|
+
derivePendingSchema: inferSchemaForValue
|
|
1392
|
+
});
|
|
1393
|
+
} catch (err) {
|
|
1394
|
+
const typedErr = err instanceof SchemaError || err instanceof CodecError ? err : new SchemaError("TYPE_MISMATCH", `Autoschema inference failed for key "${key}"`, err);
|
|
1395
|
+
return buildFallbackResult(typedErr);
|
|
1023
1396
|
}
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1397
|
+
},
|
|
1398
|
+
[
|
|
1399
|
+
applyReconcile,
|
|
1400
|
+
buildFallbackResult,
|
|
1401
|
+
buildSchemaManagedResult,
|
|
1402
|
+
codec,
|
|
1403
|
+
decodeStringPayload,
|
|
1404
|
+
key,
|
|
1405
|
+
schemaRegistry
|
|
1406
|
+
]
|
|
1407
|
+
);
|
|
1408
|
+
const decodeCodecManagedEnvelope = react.useCallback(
|
|
1409
|
+
(envelope, latestSchema) => {
|
|
1410
|
+
if (typeof envelope.payload !== "string") {
|
|
1411
|
+
return applyReconcile({
|
|
1412
|
+
value: envelope.payload,
|
|
1413
|
+
persistedVersion: envelope.version,
|
|
1414
|
+
...latestSchema ? { latestVersion: latestSchema.version } : {},
|
|
1415
|
+
serializeForPersist: encodeForWrite
|
|
1416
|
+
});
|
|
1417
|
+
}
|
|
1418
|
+
try {
|
|
1419
|
+
const decoded2 = decodeStringPayload(envelope.payload, codec);
|
|
1420
|
+
return applyReconcile({
|
|
1421
|
+
value: decoded2,
|
|
1422
|
+
persistedVersion: envelope.version,
|
|
1423
|
+
...latestSchema ? { latestVersion: latestSchema.version } : {},
|
|
1424
|
+
serializeForPersist: encodeForWrite
|
|
1425
|
+
});
|
|
1426
|
+
} catch (err) {
|
|
1427
|
+
return buildFallbackResult(err);
|
|
1045
1428
|
}
|
|
1429
|
+
},
|
|
1430
|
+
[applyReconcile, buildFallbackResult, codec, decodeStringPayload, encodeForWrite]
|
|
1431
|
+
);
|
|
1432
|
+
const decodeSchemaManagedEnvelope = react.useCallback(
|
|
1433
|
+
(envelope, schemaForVersion, latestSchema) => {
|
|
1046
1434
|
let current;
|
|
1047
1435
|
try {
|
|
1048
1436
|
current = envelope.payload;
|
|
1049
1437
|
validateAgainstSchema(current, schemaForVersion.schema);
|
|
1050
1438
|
} catch (err) {
|
|
1051
1439
|
const typedErr = err instanceof SchemaError || err instanceof CodecError ? err : new SchemaError("TYPE_MISMATCH", `Schema decode failed for key "${key}"`, err);
|
|
1052
|
-
return
|
|
1440
|
+
return buildFallbackResult(typedErr);
|
|
1053
1441
|
}
|
|
1054
1442
|
if (!latestSchema || envelope.version >= latestSchema.version) {
|
|
1055
1443
|
return applyReconcile({
|
|
@@ -1061,14 +1449,12 @@ function useMnemonicKey(key, options) {
|
|
|
1061
1449
|
}
|
|
1062
1450
|
const path = getMigrationPathForKey(envelope.version, latestSchema.version);
|
|
1063
1451
|
if (!path) {
|
|
1064
|
-
return
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
`No migration path for key "${key}" from v${envelope.version} to v${latestSchema.version}`
|
|
1069
|
-
)
|
|
1452
|
+
return buildFallbackResult(
|
|
1453
|
+
new SchemaError(
|
|
1454
|
+
"MIGRATION_PATH_NOT_FOUND",
|
|
1455
|
+
`No migration path for key "${key}" from v${envelope.version} to v${latestSchema.version}`
|
|
1070
1456
|
)
|
|
1071
|
-
|
|
1457
|
+
);
|
|
1072
1458
|
}
|
|
1073
1459
|
try {
|
|
1074
1460
|
let migrated = current;
|
|
@@ -1078,43 +1464,140 @@ function useMnemonicKey(key, options) {
|
|
|
1078
1464
|
validateAgainstSchema(migrated, latestSchema.schema);
|
|
1079
1465
|
return applyReconcile({
|
|
1080
1466
|
value: migrated,
|
|
1081
|
-
rewriteRaw:
|
|
1082
|
-
version: latestSchema.version,
|
|
1083
|
-
payload: migrated
|
|
1084
|
-
}),
|
|
1467
|
+
rewriteRaw: buildSchemaManagedResult(latestSchema.version, migrated),
|
|
1085
1468
|
persistedVersion: envelope.version,
|
|
1086
1469
|
latestVersion: latestSchema.version,
|
|
1087
1470
|
serializeForPersist: encodeForWrite
|
|
1088
1471
|
});
|
|
1089
1472
|
} catch (err) {
|
|
1090
1473
|
const typedErr = err instanceof SchemaError || err instanceof CodecError ? err : new SchemaError("MIGRATION_FAILED", `Migration failed for key "${key}"`, err);
|
|
1091
|
-
return
|
|
1474
|
+
return buildFallbackResult(typedErr);
|
|
1092
1475
|
}
|
|
1093
1476
|
},
|
|
1094
1477
|
[
|
|
1095
1478
|
applyReconcile,
|
|
1096
|
-
|
|
1097
|
-
|
|
1479
|
+
buildFallbackResult,
|
|
1480
|
+
buildSchemaManagedResult,
|
|
1098
1481
|
encodeForWrite,
|
|
1099
|
-
|
|
1482
|
+
getMigrationPathForKey,
|
|
1100
1483
|
key,
|
|
1484
|
+
validateAgainstSchema
|
|
1485
|
+
]
|
|
1486
|
+
);
|
|
1487
|
+
const decodeForRead = react.useCallback(
|
|
1488
|
+
(rawText) => {
|
|
1489
|
+
if (rawText == null) return buildFallbackResult();
|
|
1490
|
+
const parsed = parseEnvelope(rawText);
|
|
1491
|
+
if (!parsed.ok) return buildFallbackResult(parsed.error);
|
|
1492
|
+
const envelope = parsed.envelope;
|
|
1493
|
+
const schemaForVersion = getSchemaForVersion(envelope.version);
|
|
1494
|
+
const latestSchema = getLatestSchemaForKey();
|
|
1495
|
+
if (schemaMode === "strict" && !schemaForVersion) {
|
|
1496
|
+
return buildFallbackResult(
|
|
1497
|
+
new SchemaError("SCHEMA_NOT_FOUND", `No schema for key "${key}" v${envelope.version}`)
|
|
1498
|
+
);
|
|
1499
|
+
}
|
|
1500
|
+
if (schemaMode === "autoschema" && !schemaForVersion) {
|
|
1501
|
+
return decodeAutoschemaEnvelope(envelope, latestSchema);
|
|
1502
|
+
}
|
|
1503
|
+
if (!schemaForVersion) {
|
|
1504
|
+
return decodeCodecManagedEnvelope(envelope, latestSchema);
|
|
1505
|
+
}
|
|
1506
|
+
return decodeSchemaManagedEnvelope(envelope, schemaForVersion, latestSchema);
|
|
1507
|
+
},
|
|
1508
|
+
[
|
|
1509
|
+
buildFallbackResult,
|
|
1510
|
+
decodeAutoschemaEnvelope,
|
|
1511
|
+
decodeCodecManagedEnvelope,
|
|
1512
|
+
decodeSchemaManagedEnvelope,
|
|
1101
1513
|
parseEnvelope,
|
|
1102
1514
|
schemaMode,
|
|
1103
|
-
schemaRegistry,
|
|
1104
1515
|
getSchemaForVersion,
|
|
1105
|
-
getLatestSchemaForKey
|
|
1106
|
-
getMigrationPathForKey,
|
|
1107
|
-
validateAgainstSchema
|
|
1516
|
+
getLatestSchemaForKey
|
|
1108
1517
|
]
|
|
1109
1518
|
);
|
|
1519
|
+
const getServerRawSnapshot = react.useCallback(
|
|
1520
|
+
() => ssrOptions?.serverValue === void 0 ? null : SSR_SNAPSHOT_TOKEN,
|
|
1521
|
+
[ssrOptions?.serverValue]
|
|
1522
|
+
);
|
|
1523
|
+
const deferStorageRead = hydrationMode === "client-only" && !hasMounted;
|
|
1524
|
+
const subscribe = react.useCallback(
|
|
1525
|
+
(listener) => {
|
|
1526
|
+
if (deferStorageRead) {
|
|
1527
|
+
return () => void 0;
|
|
1528
|
+
}
|
|
1529
|
+
return api.subscribeRaw(key, listener);
|
|
1530
|
+
},
|
|
1531
|
+
[api, deferStorageRead, key]
|
|
1532
|
+
);
|
|
1110
1533
|
const raw = react.useSyncExternalStore(
|
|
1111
|
-
|
|
1112
|
-
() => api.getRawSnapshot(key),
|
|
1113
|
-
|
|
1114
|
-
// SSR snapshot - no storage in server environment
|
|
1534
|
+
subscribe,
|
|
1535
|
+
() => deferStorageRead ? getServerRawSnapshot() : api.getRawSnapshot(key),
|
|
1536
|
+
getServerRawSnapshot
|
|
1115
1537
|
);
|
|
1116
|
-
const decoded = react.useMemo(() =>
|
|
1538
|
+
const decoded = react.useMemo(() => {
|
|
1539
|
+
if (raw === SSR_SNAPSHOT_TOKEN) {
|
|
1540
|
+
return {
|
|
1541
|
+
value: getServerValue(),
|
|
1542
|
+
rewriteRaw: void 0,
|
|
1543
|
+
pendingSchema: void 0
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
return decodeForRead(raw);
|
|
1547
|
+
}, [decodeForRead, getServerValue, raw]);
|
|
1117
1548
|
const value = decoded.value;
|
|
1549
|
+
react.useEffect(() => {
|
|
1550
|
+
if (!developmentRuntime) return;
|
|
1551
|
+
if (listenCrossTab && (api.crossTabSyncMode ?? "none") === "none" && globalThis.window !== void 0) {
|
|
1552
|
+
warnOnce(
|
|
1553
|
+
api,
|
|
1554
|
+
`listenCrossTab:${key}`,
|
|
1555
|
+
`[Mnemonic] useMnemonicKey("${key}") enabled listenCrossTab, but the active storage backend may not be able to notify external changes. If you're using a custom Storage-like wrapper around localStorage, ensure it forwards browser "storage" events or implements storage.onExternalChange(...); otherwise, use localStorage or implement storage.onExternalChange(...) on your custom backend.`
|
|
1556
|
+
);
|
|
1557
|
+
}
|
|
1558
|
+
if (codecOpt && schema?.version !== void 0 && api.schemaRegistry) {
|
|
1559
|
+
warnOnce(
|
|
1560
|
+
api,
|
|
1561
|
+
`codec+schema:${key}`,
|
|
1562
|
+
`[Mnemonic] useMnemonicKey("${key}") received both a custom codec and schema.version. Schema-managed reads/writes do not use the codec path. Remove the codec for schema-managed storage, or remove schema.version if you intended codec-only persistence.`
|
|
1563
|
+
);
|
|
1564
|
+
}
|
|
1565
|
+
let keyContracts = diagnosticContractRegistry.get(api);
|
|
1566
|
+
if (!keyContracts) {
|
|
1567
|
+
keyContracts = /* @__PURE__ */ new Map();
|
|
1568
|
+
diagnosticContractRegistry.set(api, keyContracts);
|
|
1569
|
+
}
|
|
1570
|
+
if (contractFingerprint === null) {
|
|
1571
|
+
return;
|
|
1572
|
+
}
|
|
1573
|
+
const previousContract = keyContracts.get(key);
|
|
1574
|
+
if (previousContract === void 0) {
|
|
1575
|
+
keyContracts.set(key, contractFingerprint);
|
|
1576
|
+
return;
|
|
1577
|
+
}
|
|
1578
|
+
if (previousContract === contractFingerprint) {
|
|
1579
|
+
return;
|
|
1580
|
+
}
|
|
1581
|
+
warnOnce(
|
|
1582
|
+
api,
|
|
1583
|
+
`contract-conflict:${key}`,
|
|
1584
|
+
`[Mnemonic] Conflicting useMnemonicKey contracts detected for key "${key}" in namespace "${api.prefix.slice(0, -1)}". Reuse a shared descriptor with defineMnemonicKey(...) or align defaultValue/codec/schema/reconcile options so every consumer describes the same persisted contract.`
|
|
1585
|
+
);
|
|
1586
|
+
}, [
|
|
1587
|
+
api,
|
|
1588
|
+
key,
|
|
1589
|
+
developmentRuntime,
|
|
1590
|
+
contractFingerprint,
|
|
1591
|
+
listenCrossTab,
|
|
1592
|
+
codecOpt,
|
|
1593
|
+
schema?.version,
|
|
1594
|
+
api.schemaRegistry,
|
|
1595
|
+
api.crossTabSyncMode
|
|
1596
|
+
]);
|
|
1597
|
+
react.useEffect(() => {
|
|
1598
|
+
if (hasMounted) return;
|
|
1599
|
+
setHasMounted(true);
|
|
1600
|
+
}, [hasMounted]);
|
|
1118
1601
|
react.useEffect(() => {
|
|
1119
1602
|
if (decoded.rewriteRaw && decoded.rewriteRaw !== raw) {
|
|
1120
1603
|
api.setRaw(key, decoded.rewriteRaw);
|
|
@@ -1144,7 +1627,8 @@ function useMnemonicKey(key, options) {
|
|
|
1144
1627
|
}, [value, onChange]);
|
|
1145
1628
|
react.useEffect(() => {
|
|
1146
1629
|
if (!listenCrossTab) return;
|
|
1147
|
-
|
|
1630
|
+
const globalWindow = globalThis.window;
|
|
1631
|
+
if (globalWindow === void 0) return;
|
|
1148
1632
|
const storageKey = api.prefix + key;
|
|
1149
1633
|
const handler = (e) => {
|
|
1150
1634
|
if (e.key === null) {
|
|
@@ -1158,8 +1642,8 @@ function useMnemonicKey(key, options) {
|
|
|
1158
1642
|
}
|
|
1159
1643
|
api.setRaw(key, e.newValue);
|
|
1160
1644
|
};
|
|
1161
|
-
|
|
1162
|
-
return () =>
|
|
1645
|
+
globalWindow.addEventListener("storage", handler);
|
|
1646
|
+
return () => globalWindow.removeEventListener("storage", handler);
|
|
1163
1647
|
}, [listenCrossTab, api, key]);
|
|
1164
1648
|
const set = react.useMemo(() => {
|
|
1165
1649
|
return (next) => {
|
|
@@ -1221,6 +1705,20 @@ function useMnemonicKey(key, options) {
|
|
|
1221
1705
|
function uniqueKeys(keys) {
|
|
1222
1706
|
return [...new Set(keys)];
|
|
1223
1707
|
}
|
|
1708
|
+
function isDevelopmentRuntime2() {
|
|
1709
|
+
return getRuntimeNodeEnv() === "development";
|
|
1710
|
+
}
|
|
1711
|
+
var recoveryDiagnosticWarnings = /* @__PURE__ */ new WeakMap();
|
|
1712
|
+
function warnRecoveryOnce(api, id, message) {
|
|
1713
|
+
let warnings = recoveryDiagnosticWarnings.get(api);
|
|
1714
|
+
if (!warnings) {
|
|
1715
|
+
warnings = /* @__PURE__ */ new Set();
|
|
1716
|
+
recoveryDiagnosticWarnings.set(api, warnings);
|
|
1717
|
+
}
|
|
1718
|
+
if (warnings.has(id)) return;
|
|
1719
|
+
warnings.add(id);
|
|
1720
|
+
console.warn(message);
|
|
1721
|
+
}
|
|
1224
1722
|
function useMnemonicRecovery(options = {}) {
|
|
1225
1723
|
const api = useMnemonic();
|
|
1226
1724
|
const { onRecover } = options;
|
|
@@ -1254,15 +1752,29 @@ function useMnemonicRecovery(options = {}) {
|
|
|
1254
1752
|
);
|
|
1255
1753
|
const clearAll = react.useCallback(() => {
|
|
1256
1754
|
if (!api.canEnumerateKeys) {
|
|
1755
|
+
if (isDevelopmentRuntime2()) {
|
|
1756
|
+
warnRecoveryOnce(
|
|
1757
|
+
api,
|
|
1758
|
+
"recovery-clear-all-non-enumerable",
|
|
1759
|
+
`[Mnemonic] clearAll() requires an enumerable storage backend in namespace "${namespace}". Use clearKeys([...]) with an explicit durable-key list, or supply a storage backend that implements length and key(index).`
|
|
1760
|
+
);
|
|
1761
|
+
}
|
|
1257
1762
|
throw new Error(
|
|
1258
1763
|
"clearAll requires an enumerable storage backend. Use clearKeys([...]) with an explicit key list instead."
|
|
1259
1764
|
);
|
|
1260
1765
|
}
|
|
1261
1766
|
return clearResolvedKeys("clear-all", api.keys());
|
|
1262
|
-
}, [api, clearResolvedKeys]);
|
|
1767
|
+
}, [api, clearResolvedKeys, namespace]);
|
|
1263
1768
|
const clearMatching = react.useCallback(
|
|
1264
1769
|
(predicate) => {
|
|
1265
1770
|
if (!api.canEnumerateKeys) {
|
|
1771
|
+
if (isDevelopmentRuntime2()) {
|
|
1772
|
+
warnRecoveryOnce(
|
|
1773
|
+
api,
|
|
1774
|
+
"recovery-clear-matching-non-enumerable",
|
|
1775
|
+
`[Mnemonic] clearMatching() requires an enumerable storage backend in namespace "${namespace}". Use clearKeys([...]) with an explicit durable-key list, or supply a storage backend that implements length and key(index).`
|
|
1776
|
+
);
|
|
1777
|
+
}
|
|
1266
1778
|
throw new Error(
|
|
1267
1779
|
"clearMatching requires an enumerable storage backend. Use clearKeys([...]) with an explicit key list instead."
|
|
1268
1780
|
);
|
|
@@ -1272,7 +1784,7 @@ function useMnemonicRecovery(options = {}) {
|
|
|
1272
1784
|
api.keys().filter((key) => predicate(key))
|
|
1273
1785
|
);
|
|
1274
1786
|
},
|
|
1275
|
-
[api, clearResolvedKeys]
|
|
1787
|
+
[api, clearResolvedKeys, namespace]
|
|
1276
1788
|
);
|
|
1277
1789
|
return react.useMemo(
|
|
1278
1790
|
() => ({
|
|
@@ -1287,6 +1799,23 @@ function useMnemonicRecovery(options = {}) {
|
|
|
1287
1799
|
);
|
|
1288
1800
|
}
|
|
1289
1801
|
|
|
1802
|
+
// src/Mnemonic/key.ts
|
|
1803
|
+
function defineMnemonicKey(keyOrSchema, options) {
|
|
1804
|
+
if (typeof keyOrSchema !== "string") {
|
|
1805
|
+
return Object.freeze({
|
|
1806
|
+
key: keyOrSchema.key,
|
|
1807
|
+
options: {
|
|
1808
|
+
...options,
|
|
1809
|
+
schema: { version: keyOrSchema.version }
|
|
1810
|
+
}
|
|
1811
|
+
});
|
|
1812
|
+
}
|
|
1813
|
+
return Object.freeze({
|
|
1814
|
+
key: keyOrSchema,
|
|
1815
|
+
options
|
|
1816
|
+
});
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1290
1819
|
// src/Mnemonic/schema-registry.ts
|
|
1291
1820
|
function schemaVersionKey(key, version) {
|
|
1292
1821
|
return `${key}:${version}`;
|
|
@@ -1376,6 +1905,184 @@ function createSchemaRegistry(options = {}) {
|
|
|
1376
1905
|
};
|
|
1377
1906
|
}
|
|
1378
1907
|
|
|
1908
|
+
// src/Mnemonic/schema-helpers.ts
|
|
1909
|
+
function defineKeySchema(key, version, schema) {
|
|
1910
|
+
return Object.freeze({
|
|
1911
|
+
key,
|
|
1912
|
+
version,
|
|
1913
|
+
schema
|
|
1914
|
+
});
|
|
1915
|
+
}
|
|
1916
|
+
function defineMigration(fromSchema, toSchema, migrate) {
|
|
1917
|
+
if (fromSchema.key !== toSchema.key) {
|
|
1918
|
+
throw new SchemaError(
|
|
1919
|
+
"MIGRATION_GRAPH_INVALID",
|
|
1920
|
+
`Migration schemas must target the same key: "${fromSchema.key}" !== "${toSchema.key}"`
|
|
1921
|
+
);
|
|
1922
|
+
}
|
|
1923
|
+
return Object.freeze({
|
|
1924
|
+
key: fromSchema.key,
|
|
1925
|
+
fromVersion: fromSchema.version,
|
|
1926
|
+
toVersion: toSchema.version,
|
|
1927
|
+
migrate
|
|
1928
|
+
});
|
|
1929
|
+
}
|
|
1930
|
+
function defineWriteMigration(schema, migrate) {
|
|
1931
|
+
return Object.freeze({
|
|
1932
|
+
key: schema.key,
|
|
1933
|
+
fromVersion: schema.version,
|
|
1934
|
+
toVersion: schema.version,
|
|
1935
|
+
migrate
|
|
1936
|
+
});
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
// src/Mnemonic/typed-schema.ts
|
|
1940
|
+
var optionalSchemaMarker = /* @__PURE__ */ Symbol("mnemonicOptionalSchema");
|
|
1941
|
+
function cloneSchema(schema) {
|
|
1942
|
+
const clone = { ...schema };
|
|
1943
|
+
if (schema[optionalSchemaMarker]) {
|
|
1944
|
+
Object.defineProperty(clone, optionalSchemaMarker, {
|
|
1945
|
+
value: true,
|
|
1946
|
+
enumerable: false,
|
|
1947
|
+
configurable: false
|
|
1948
|
+
});
|
|
1949
|
+
}
|
|
1950
|
+
return clone;
|
|
1951
|
+
}
|
|
1952
|
+
function isOptionalSchema(schema) {
|
|
1953
|
+
return Boolean(schema[optionalSchemaMarker]);
|
|
1954
|
+
}
|
|
1955
|
+
function markOptional(schema) {
|
|
1956
|
+
const clone = cloneSchema(schema);
|
|
1957
|
+
Object.defineProperty(clone, optionalSchemaMarker, {
|
|
1958
|
+
value: true,
|
|
1959
|
+
enumerable: false,
|
|
1960
|
+
configurable: false
|
|
1961
|
+
});
|
|
1962
|
+
return clone;
|
|
1963
|
+
}
|
|
1964
|
+
function withoutOptionalMarker(schema) {
|
|
1965
|
+
const clone = { ...schema };
|
|
1966
|
+
delete clone[optionalSchemaMarker];
|
|
1967
|
+
return clone;
|
|
1968
|
+
}
|
|
1969
|
+
function withType(type, extra = {}) {
|
|
1970
|
+
return {
|
|
1971
|
+
type,
|
|
1972
|
+
...extra
|
|
1973
|
+
};
|
|
1974
|
+
}
|
|
1975
|
+
function toTypeArray(type) {
|
|
1976
|
+
if (type === void 0) return null;
|
|
1977
|
+
return Array.isArray(type) ? [...type] : [type];
|
|
1978
|
+
}
|
|
1979
|
+
function appendNullType(type) {
|
|
1980
|
+
const types = toTypeArray(type);
|
|
1981
|
+
if (types === null) {
|
|
1982
|
+
return void 0;
|
|
1983
|
+
}
|
|
1984
|
+
return types.includes("null") ? types : [...types, "null"];
|
|
1985
|
+
}
|
|
1986
|
+
function nullableSchema(schema) {
|
|
1987
|
+
if (schema.enum) {
|
|
1988
|
+
const nullableType = appendNullType(schema.type);
|
|
1989
|
+
return {
|
|
1990
|
+
...schema,
|
|
1991
|
+
...nullableType ? { type: nullableType } : {},
|
|
1992
|
+
enum: schema.enum.includes(null) ? schema.enum : [...schema.enum, null]
|
|
1993
|
+
};
|
|
1994
|
+
}
|
|
1995
|
+
if ("const" in schema) {
|
|
1996
|
+
const { const: constValue, ...rest } = schema;
|
|
1997
|
+
const nullableType = appendNullType(rest.type);
|
|
1998
|
+
if (constValue === null || constValue === void 0) {
|
|
1999
|
+
return {
|
|
2000
|
+
...schema,
|
|
2001
|
+
...nullableType ? { type: nullableType } : {}
|
|
2002
|
+
};
|
|
2003
|
+
}
|
|
2004
|
+
return {
|
|
2005
|
+
...rest,
|
|
2006
|
+
...nullableType ? { type: nullableType } : {},
|
|
2007
|
+
enum: [constValue, null]
|
|
2008
|
+
};
|
|
2009
|
+
}
|
|
2010
|
+
const types = toTypeArray(schema.type);
|
|
2011
|
+
if (types === null) {
|
|
2012
|
+
throw new SchemaError(
|
|
2013
|
+
"MODE_CONFIGURATION_INVALID",
|
|
2014
|
+
"mnemonicSchema.nullable(...) requires a schema with type, enum, or const"
|
|
2015
|
+
);
|
|
2016
|
+
}
|
|
2017
|
+
return {
|
|
2018
|
+
...schema,
|
|
2019
|
+
type: types.includes("null") ? types : [...types, "null"]
|
|
2020
|
+
};
|
|
2021
|
+
}
|
|
2022
|
+
var mnemonicSchema = {
|
|
2023
|
+
string(options = {}) {
|
|
2024
|
+
return withType("string", options);
|
|
2025
|
+
},
|
|
2026
|
+
number(options = {}) {
|
|
2027
|
+
return withType("number", options);
|
|
2028
|
+
},
|
|
2029
|
+
integer(options = {}) {
|
|
2030
|
+
return withType("integer", options);
|
|
2031
|
+
},
|
|
2032
|
+
boolean() {
|
|
2033
|
+
return withType("boolean");
|
|
2034
|
+
},
|
|
2035
|
+
nullValue() {
|
|
2036
|
+
return withType("null");
|
|
2037
|
+
},
|
|
2038
|
+
literal(value) {
|
|
2039
|
+
return {
|
|
2040
|
+
const: value
|
|
2041
|
+
};
|
|
2042
|
+
},
|
|
2043
|
+
enum(values) {
|
|
2044
|
+
return {
|
|
2045
|
+
enum: values
|
|
2046
|
+
};
|
|
2047
|
+
},
|
|
2048
|
+
optional(schema) {
|
|
2049
|
+
return markOptional(schema);
|
|
2050
|
+
},
|
|
2051
|
+
nullable(schema) {
|
|
2052
|
+
return nullableSchema(schema);
|
|
2053
|
+
},
|
|
2054
|
+
array(itemSchema, options = {}) {
|
|
2055
|
+
return withType("array", {
|
|
2056
|
+
items: withoutOptionalMarker(itemSchema),
|
|
2057
|
+
...options
|
|
2058
|
+
});
|
|
2059
|
+
},
|
|
2060
|
+
object(shape, options = {}) {
|
|
2061
|
+
const properties = {};
|
|
2062
|
+
const required = [];
|
|
2063
|
+
for (const [name, schema] of Object.entries(shape)) {
|
|
2064
|
+
properties[name] = withoutOptionalMarker(schema);
|
|
2065
|
+
if (!isOptionalSchema(schema)) {
|
|
2066
|
+
required.push(name);
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
const result = {
|
|
2070
|
+
type: "object",
|
|
2071
|
+
properties,
|
|
2072
|
+
...options
|
|
2073
|
+
};
|
|
2074
|
+
if (required.length > 0) {
|
|
2075
|
+
result.required = required;
|
|
2076
|
+
}
|
|
2077
|
+
return result;
|
|
2078
|
+
},
|
|
2079
|
+
record(valueSchema) {
|
|
2080
|
+
return withType("object", {
|
|
2081
|
+
additionalProperties: withoutOptionalMarker(valueSchema)
|
|
2082
|
+
});
|
|
2083
|
+
}
|
|
2084
|
+
};
|
|
2085
|
+
|
|
1379
2086
|
// src/Mnemonic/structural-migrations.ts
|
|
1380
2087
|
function resolveHelpers(helpers) {
|
|
1381
2088
|
if (helpers) return helpers;
|
|
@@ -1475,8 +2182,13 @@ exports.compileSchema = compileSchema;
|
|
|
1475
2182
|
exports.createCodec = createCodec;
|
|
1476
2183
|
exports.createSchemaRegistry = createSchemaRegistry;
|
|
1477
2184
|
exports.dedupeChildrenBy = dedupeChildrenBy;
|
|
2185
|
+
exports.defineKeySchema = defineKeySchema;
|
|
2186
|
+
exports.defineMigration = defineMigration;
|
|
2187
|
+
exports.defineMnemonicKey = defineMnemonicKey;
|
|
2188
|
+
exports.defineWriteMigration = defineWriteMigration;
|
|
1478
2189
|
exports.findNodeById = findNodeById;
|
|
1479
2190
|
exports.insertChildIfMissing = insertChildIfMissing;
|
|
2191
|
+
exports.mnemonicSchema = mnemonicSchema;
|
|
1480
2192
|
exports.renameNode = renameNode;
|
|
1481
2193
|
exports.useMnemonicKey = useMnemonicKey;
|
|
1482
2194
|
exports.useMnemonicRecovery = useMnemonicRecovery;
|