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/core.cjs
ADDED
|
@@ -0,0 +1,1322 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
|
|
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
|
+
}
|
|
33
|
+
var MnemonicContext = react.createContext(null);
|
|
34
|
+
function useMnemonic() {
|
|
35
|
+
const context = react.useContext(MnemonicContext);
|
|
36
|
+
if (!context) {
|
|
37
|
+
throw new Error("useMnemonic must be used within a MnemonicProvider");
|
|
38
|
+
}
|
|
39
|
+
return context;
|
|
40
|
+
}
|
|
41
|
+
function defaultBrowserStorage() {
|
|
42
|
+
const globalWindow = globalThis.window;
|
|
43
|
+
if (globalWindow === void 0) return void 0;
|
|
44
|
+
try {
|
|
45
|
+
return globalWindow.localStorage;
|
|
46
|
+
} catch {
|
|
47
|
+
return void 0;
|
|
48
|
+
}
|
|
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
|
+
}
|
|
427
|
+
function MnemonicProvider({
|
|
428
|
+
children,
|
|
429
|
+
namespace,
|
|
430
|
+
storage,
|
|
431
|
+
enableDevTools = false,
|
|
432
|
+
schemaMode = "default",
|
|
433
|
+
schemaRegistry,
|
|
434
|
+
ssr
|
|
435
|
+
}) {
|
|
436
|
+
if (schemaMode === "strict" && !schemaRegistry) {
|
|
437
|
+
throw new Error("MnemonicProvider strict mode requires schemaRegistry");
|
|
438
|
+
}
|
|
439
|
+
if (schemaMode === "autoschema" && typeof schemaRegistry?.registerSchema !== "function") {
|
|
440
|
+
throw new Error("MnemonicProvider autoschema mode requires schemaRegistry.registerSchema");
|
|
441
|
+
}
|
|
442
|
+
const store = react.useMemo(() => {
|
|
443
|
+
const prefix = `${namespace}.`;
|
|
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);
|
|
449
|
+
const cache = /* @__PURE__ */ new Map();
|
|
450
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
451
|
+
let quotaErrorLogged = false;
|
|
452
|
+
let accessErrorLogged = false;
|
|
453
|
+
let asyncContractViolationDetected = false;
|
|
454
|
+
const storageAccessCallbacks = {
|
|
455
|
+
onAccessError: (err) => logAccessError(err),
|
|
456
|
+
onAccessSuccess: () => {
|
|
457
|
+
accessErrorLogged = false;
|
|
458
|
+
},
|
|
459
|
+
onAsyncViolation: (method, thenable) => handleAsyncStorageContractViolation(method, thenable)
|
|
460
|
+
};
|
|
461
|
+
const fullKey = (key) => prefix + key;
|
|
462
|
+
const emit = (key) => {
|
|
463
|
+
const set = listeners.get(key);
|
|
464
|
+
if (!set) return;
|
|
465
|
+
for (const fn of set) fn();
|
|
466
|
+
};
|
|
467
|
+
const logAccessError = (err) => {
|
|
468
|
+
if (!accessErrorLogged && err instanceof DOMException && err.name !== "QuotaExceededError") {
|
|
469
|
+
console.error(
|
|
470
|
+
`[Mnemonic] Storage access error (${err.name}): ${err.message}. Data is cached in memory but may not persist.`
|
|
471
|
+
);
|
|
472
|
+
accessErrorLogged = true;
|
|
473
|
+
}
|
|
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
|
+
};
|
|
484
|
+
const readThrough = (key) => {
|
|
485
|
+
if (cache.has(key)) return cache.get(key) ?? null;
|
|
486
|
+
if (!st || asyncContractViolationDetected) {
|
|
487
|
+
cache.set(key, null);
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
490
|
+
const raw = readStorageRaw(st, fullKey(key), storageAccessCallbacks);
|
|
491
|
+
cache.set(key, raw);
|
|
492
|
+
return raw;
|
|
493
|
+
};
|
|
494
|
+
const writeRaw = (key, raw) => {
|
|
495
|
+
cache.set(key, raw);
|
|
496
|
+
if (st && !asyncContractViolationDetected) {
|
|
497
|
+
try {
|
|
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
|
+
}
|
|
505
|
+
} catch (err) {
|
|
506
|
+
if (!quotaErrorLogged && err instanceof DOMException && err.name === "QuotaExceededError") {
|
|
507
|
+
console.error(
|
|
508
|
+
`[Mnemonic] Storage quota exceeded writing key "${key}". Data is cached in memory but will not persist.`
|
|
509
|
+
);
|
|
510
|
+
quotaErrorLogged = true;
|
|
511
|
+
}
|
|
512
|
+
logAccessError(err);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
emit(key);
|
|
516
|
+
bumpDevToolsVersion(devToolsRoot, namespace, `set:${key}`);
|
|
517
|
+
};
|
|
518
|
+
const removeRaw = (key) => {
|
|
519
|
+
cache.set(key, null);
|
|
520
|
+
if (st && !asyncContractViolationDetected) {
|
|
521
|
+
try {
|
|
522
|
+
const result = st.removeItem(fullKey(key));
|
|
523
|
+
if (isPromiseLike(result)) {
|
|
524
|
+
handleAsyncStorageContractViolation("removeItem", result);
|
|
525
|
+
} else {
|
|
526
|
+
accessErrorLogged = false;
|
|
527
|
+
}
|
|
528
|
+
} catch (err) {
|
|
529
|
+
logAccessError(err);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
emit(key);
|
|
533
|
+
bumpDevToolsVersion(devToolsRoot, namespace, `remove:${key}`);
|
|
534
|
+
};
|
|
535
|
+
const subscribeRaw = (key, listener) => {
|
|
536
|
+
let set = listeners.get(key);
|
|
537
|
+
if (!set) {
|
|
538
|
+
set = /* @__PURE__ */ new Set();
|
|
539
|
+
listeners.set(key, set);
|
|
540
|
+
}
|
|
541
|
+
set.add(listener);
|
|
542
|
+
readThrough(key);
|
|
543
|
+
return () => {
|
|
544
|
+
const s = listeners.get(key);
|
|
545
|
+
if (!s) return;
|
|
546
|
+
s.delete(listener);
|
|
547
|
+
if (s.size === 0) listeners.delete(key);
|
|
548
|
+
};
|
|
549
|
+
};
|
|
550
|
+
const getRawSnapshot = (key) => readThrough(key);
|
|
551
|
+
const keys = () => {
|
|
552
|
+
if (asyncContractViolationDetected) {
|
|
553
|
+
return Array.from(cache.entries()).filter(([, value]) => value != null).map(([key]) => key);
|
|
554
|
+
}
|
|
555
|
+
if (!canEnumerateKeys) return [];
|
|
556
|
+
return enumerateNamespaceKeys(st, prefix, storageAccessCallbacks);
|
|
557
|
+
};
|
|
558
|
+
const dump = () => {
|
|
559
|
+
const out = {};
|
|
560
|
+
for (const k of keys()) {
|
|
561
|
+
const raw = readThrough(k);
|
|
562
|
+
if (raw != null) out[k] = raw;
|
|
563
|
+
}
|
|
564
|
+
return out;
|
|
565
|
+
};
|
|
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
|
+
});
|
|
577
|
+
const store2 = {
|
|
578
|
+
prefix,
|
|
579
|
+
canEnumerateKeys,
|
|
580
|
+
subscribeRaw,
|
|
581
|
+
getRawSnapshot,
|
|
582
|
+
setRaw: writeRaw,
|
|
583
|
+
removeRaw,
|
|
584
|
+
keys,
|
|
585
|
+
dump,
|
|
586
|
+
reloadFromStorage,
|
|
587
|
+
schemaMode,
|
|
588
|
+
ssrHydration,
|
|
589
|
+
crossTabSyncMode,
|
|
590
|
+
...schemaRegistry ? { schemaRegistry } : {}
|
|
591
|
+
};
|
|
592
|
+
if (devToolsRoot) {
|
|
593
|
+
registerDevToolsProvider({
|
|
594
|
+
devToolsRoot,
|
|
595
|
+
namespace,
|
|
596
|
+
store: store2,
|
|
597
|
+
dump,
|
|
598
|
+
keys,
|
|
599
|
+
readThrough,
|
|
600
|
+
writeRaw,
|
|
601
|
+
removeRaw
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
return store2;
|
|
605
|
+
}, [namespace, storage, enableDevTools, schemaMode, schemaRegistry, ssr?.hydration]);
|
|
606
|
+
react.useEffect(() => {
|
|
607
|
+
if (!storage?.onExternalChange) return;
|
|
608
|
+
return storage.onExternalChange((changedKeys) => store.reloadFromStorage(changedKeys));
|
|
609
|
+
}, [storage, store]);
|
|
610
|
+
return /* @__PURE__ */ jsxRuntime.jsx(MnemonicContext.Provider, { value: store, children });
|
|
611
|
+
}
|
|
612
|
+
function throwCoreProviderSchemaImportError(propName) {
|
|
613
|
+
throw new Error(
|
|
614
|
+
`[Mnemonic] MnemonicProvider from react-mnemonic/core does not support ${propName}. Import MnemonicProvider from "react-mnemonic/schema" or "react-mnemonic" for schema validation, autoschema, and migration support.`
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
function assertNoSchemaProps(props) {
|
|
618
|
+
if (props.schemaMode !== void 0) {
|
|
619
|
+
throwCoreProviderSchemaImportError("schemaMode");
|
|
620
|
+
}
|
|
621
|
+
if (props.schemaRegistry !== void 0) {
|
|
622
|
+
throwCoreProviderSchemaImportError("schemaRegistry");
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
function MnemonicProvider2(props) {
|
|
626
|
+
assertNoSchemaProps(props);
|
|
627
|
+
return /* @__PURE__ */ jsxRuntime.jsx(MnemonicProvider, { ...props });
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// src/Mnemonic/codecs.ts
|
|
631
|
+
var CodecError = class extends Error {
|
|
632
|
+
/**
|
|
633
|
+
* Creates a new CodecError.
|
|
634
|
+
*
|
|
635
|
+
* @param message - Human-readable error description
|
|
636
|
+
* @param cause - Optional underlying error that caused this failure
|
|
637
|
+
*/
|
|
638
|
+
constructor(message, cause) {
|
|
639
|
+
super(message);
|
|
640
|
+
this.name = "CodecError";
|
|
641
|
+
this.cause = cause;
|
|
642
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
var JSONCodec = {
|
|
646
|
+
encode: (value) => JSON.stringify(value),
|
|
647
|
+
decode: (encoded) => JSON.parse(encoded)
|
|
648
|
+
};
|
|
649
|
+
function createCodec(encode, decode) {
|
|
650
|
+
return { encode, decode };
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// src/Mnemonic/schema.ts
|
|
654
|
+
var SchemaError = class extends Error {
|
|
655
|
+
/**
|
|
656
|
+
* Creates a new SchemaError.
|
|
657
|
+
*
|
|
658
|
+
* @param code - Machine-readable failure category
|
|
659
|
+
* @param message - Human-readable error description
|
|
660
|
+
* @param cause - Optional underlying error
|
|
661
|
+
*/
|
|
662
|
+
constructor(code, message, cause) {
|
|
663
|
+
super(message);
|
|
664
|
+
this.name = "SchemaError";
|
|
665
|
+
this.code = code;
|
|
666
|
+
this.cause = cause;
|
|
667
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
// src/Mnemonic/use-shared.ts
|
|
672
|
+
var SSR_SNAPSHOT_TOKEN = /* @__PURE__ */ Symbol("mnemonic:ssr-snapshot");
|
|
673
|
+
var diagnosticContractRegistry = /* @__PURE__ */ new WeakMap();
|
|
674
|
+
var diagnosticWarningRegistry = /* @__PURE__ */ new WeakMap();
|
|
675
|
+
var diagnosticObjectIds = /* @__PURE__ */ new WeakMap();
|
|
676
|
+
var nextDiagnosticObjectId = 1;
|
|
677
|
+
function serializeEnvelope(version, payload) {
|
|
678
|
+
return JSON.stringify({
|
|
679
|
+
version,
|
|
680
|
+
payload
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
function withReadMetadata(value, rewriteRaw, extra) {
|
|
684
|
+
const result = { value };
|
|
685
|
+
if (extra !== void 0) {
|
|
686
|
+
Object.assign(result, extra);
|
|
687
|
+
}
|
|
688
|
+
if (rewriteRaw !== void 0) result.rewriteRaw = rewriteRaw;
|
|
689
|
+
return result;
|
|
690
|
+
}
|
|
691
|
+
function isDevelopmentRuntime() {
|
|
692
|
+
return getRuntimeNodeEnv() === "development";
|
|
693
|
+
}
|
|
694
|
+
function getDiagnosticWarnings(api) {
|
|
695
|
+
let warnings = diagnosticWarningRegistry.get(api);
|
|
696
|
+
if (!warnings) {
|
|
697
|
+
warnings = /* @__PURE__ */ new Set();
|
|
698
|
+
diagnosticWarningRegistry.set(api, warnings);
|
|
699
|
+
}
|
|
700
|
+
return warnings;
|
|
701
|
+
}
|
|
702
|
+
function warnOnce(api, id, message) {
|
|
703
|
+
const warnings = getDiagnosticWarnings(api);
|
|
704
|
+
if (warnings.has(id)) return;
|
|
705
|
+
warnings.add(id);
|
|
706
|
+
console.warn(message);
|
|
707
|
+
}
|
|
708
|
+
function stableDiagnosticValue(value) {
|
|
709
|
+
if (typeof value === "function") {
|
|
710
|
+
const source = Function.prototype.toString.call(value).split(/\s+/).join(" ").trim();
|
|
711
|
+
const name = value.name || "anonymous";
|
|
712
|
+
return `[factory:${name}/${value.length}:${source}]`;
|
|
713
|
+
}
|
|
714
|
+
if (typeof value === "bigint") return `${value.toString()}n`;
|
|
715
|
+
if (typeof value === "symbol") return value.toString();
|
|
716
|
+
if (value === void 0) return "undefined";
|
|
717
|
+
try {
|
|
718
|
+
return JSON.stringify(value);
|
|
719
|
+
} catch {
|
|
720
|
+
const tag = Object.prototype.toString.call(value);
|
|
721
|
+
if (value !== null && (typeof value === "object" || typeof value === "function")) {
|
|
722
|
+
return `${tag}#${getDiagnosticObjectId(value)}`;
|
|
723
|
+
}
|
|
724
|
+
return tag;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
function isObjectLike(value) {
|
|
728
|
+
return value !== null && (typeof value === "object" || typeof value === "function");
|
|
729
|
+
}
|
|
730
|
+
function objectHasOwn(value, property) {
|
|
731
|
+
const hasOwn = Object.hasOwn;
|
|
732
|
+
if (typeof hasOwn === "function") {
|
|
733
|
+
return hasOwn(value, property);
|
|
734
|
+
}
|
|
735
|
+
return Object.getOwnPropertyDescriptor(value, property) !== void 0;
|
|
736
|
+
}
|
|
737
|
+
function getDiagnosticObjectId(value) {
|
|
738
|
+
const existing = diagnosticObjectIds.get(value);
|
|
739
|
+
if (existing !== void 0) return existing;
|
|
740
|
+
const id = nextDiagnosticObjectId++;
|
|
741
|
+
diagnosticObjectIds.set(value, id);
|
|
742
|
+
return id;
|
|
743
|
+
}
|
|
744
|
+
function buildContractFingerprint({
|
|
745
|
+
api,
|
|
746
|
+
key,
|
|
747
|
+
defaultValue,
|
|
748
|
+
codecOpt,
|
|
749
|
+
schemaVersion,
|
|
750
|
+
reconcile,
|
|
751
|
+
listenCrossTab,
|
|
752
|
+
ssrOptions
|
|
753
|
+
}) {
|
|
754
|
+
const codecSignature = codecOpt == null || !isObjectLike(codecOpt) ? "default-json-codec" : `codec:${stableDiagnosticValue(codecOpt.encode)}:${stableDiagnosticValue(codecOpt.decode)}`;
|
|
755
|
+
const reconcileSignature = reconcile == null || !isObjectLike(reconcile) ? "no-reconcile" : `reconcile:${stableDiagnosticValue(reconcile)}`;
|
|
756
|
+
return JSON.stringify({
|
|
757
|
+
key,
|
|
758
|
+
defaultValue: stableDiagnosticValue(defaultValue),
|
|
759
|
+
codec: codecSignature,
|
|
760
|
+
schemaVersion: schemaVersion ?? null,
|
|
761
|
+
listenCrossTab: Boolean(listenCrossTab),
|
|
762
|
+
reconcile: reconcileSignature,
|
|
763
|
+
ssrHydration: ssrOptions?.hydration ?? null,
|
|
764
|
+
hasServerValue: ssrOptions?.serverValue !== void 0,
|
|
765
|
+
providerHydration: api.ssrHydration ?? null
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
function resolveMnemonicKeyArgs(keyOrDescriptor, options) {
|
|
769
|
+
if (typeof keyOrDescriptor !== "string") {
|
|
770
|
+
return keyOrDescriptor;
|
|
771
|
+
}
|
|
772
|
+
if (!options) {
|
|
773
|
+
throw new Error("useMnemonicKey requires options when called with a string key");
|
|
774
|
+
}
|
|
775
|
+
return {
|
|
776
|
+
key: keyOrDescriptor,
|
|
777
|
+
options
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
function useMnemonicKeyShared(keyOrDescriptor, options, schemaVersion) {
|
|
781
|
+
const descriptor = resolveMnemonicKeyArgs(keyOrDescriptor, options);
|
|
782
|
+
const key = descriptor.key;
|
|
783
|
+
const resolvedOptions = descriptor.options;
|
|
784
|
+
const api = useMnemonic();
|
|
785
|
+
const {
|
|
786
|
+
defaultValue,
|
|
787
|
+
onMount,
|
|
788
|
+
onChange,
|
|
789
|
+
listenCrossTab,
|
|
790
|
+
codec: codecOpt,
|
|
791
|
+
schema,
|
|
792
|
+
reconcile,
|
|
793
|
+
ssr: ssrOptions
|
|
794
|
+
} = resolvedOptions;
|
|
795
|
+
const codec = codecOpt ?? JSONCodec;
|
|
796
|
+
const hydrationMode = ssrOptions?.hydration ?? api.ssrHydration;
|
|
797
|
+
const [hasMounted, setHasMounted] = react.useState(hydrationMode !== "client-only");
|
|
798
|
+
const developmentRuntime = isDevelopmentRuntime();
|
|
799
|
+
const contractFingerprint = react.useMemo(
|
|
800
|
+
() => developmentRuntime ? buildContractFingerprint({
|
|
801
|
+
api,
|
|
802
|
+
key,
|
|
803
|
+
defaultValue,
|
|
804
|
+
codecOpt,
|
|
805
|
+
...{} ,
|
|
806
|
+
reconcile,
|
|
807
|
+
listenCrossTab,
|
|
808
|
+
ssrOptions
|
|
809
|
+
}) : null,
|
|
810
|
+
[
|
|
811
|
+
developmentRuntime,
|
|
812
|
+
api,
|
|
813
|
+
key,
|
|
814
|
+
defaultValue,
|
|
815
|
+
codecOpt,
|
|
816
|
+
schemaVersion,
|
|
817
|
+
reconcile,
|
|
818
|
+
listenCrossTab,
|
|
819
|
+
ssrOptions?.hydration,
|
|
820
|
+
ssrOptions?.serverValue
|
|
821
|
+
]
|
|
822
|
+
);
|
|
823
|
+
const getFallback = react.useCallback(
|
|
824
|
+
(error) => typeof defaultValue === "function" ? defaultValue(error) : defaultValue,
|
|
825
|
+
[defaultValue]
|
|
826
|
+
);
|
|
827
|
+
const getServerValue = react.useCallback(() => {
|
|
828
|
+
const serverValue = ssrOptions?.serverValue;
|
|
829
|
+
if (serverValue === void 0) {
|
|
830
|
+
return getFallback();
|
|
831
|
+
}
|
|
832
|
+
return typeof serverValue === "function" ? serverValue() : serverValue;
|
|
833
|
+
}, [getFallback, ssrOptions?.serverValue]);
|
|
834
|
+
const parseEnvelope = react.useCallback(
|
|
835
|
+
(rawText) => {
|
|
836
|
+
try {
|
|
837
|
+
const parsed = JSON.parse(rawText);
|
|
838
|
+
if (typeof parsed !== "object" || parsed == null || !Number.isInteger(parsed.version) || parsed.version < 0 || !objectHasOwn(parsed, "payload")) {
|
|
839
|
+
return {
|
|
840
|
+
ok: false,
|
|
841
|
+
error: new SchemaError("INVALID_ENVELOPE", `Invalid envelope for key "${key}"`)
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
return { ok: true, envelope: parsed };
|
|
845
|
+
} catch (err) {
|
|
846
|
+
return {
|
|
847
|
+
ok: false,
|
|
848
|
+
error: new SchemaError("INVALID_ENVELOPE", `Invalid envelope for key "${key}"`, err)
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
},
|
|
852
|
+
[key]
|
|
853
|
+
);
|
|
854
|
+
const decodeStringPayload = react.useCallback(
|
|
855
|
+
(payload, activeCodec) => {
|
|
856
|
+
try {
|
|
857
|
+
return activeCodec.decode(payload);
|
|
858
|
+
} catch (err) {
|
|
859
|
+
throw err instanceof CodecError ? err : new CodecError(`Codec decode failed for key "${key}"`, err);
|
|
860
|
+
}
|
|
861
|
+
},
|
|
862
|
+
[key]
|
|
863
|
+
);
|
|
864
|
+
const buildFallbackResult = react.useCallback(
|
|
865
|
+
(error, extra) => {
|
|
866
|
+
return withReadMetadata(getFallback(error), void 0, extra);
|
|
867
|
+
},
|
|
868
|
+
[getFallback]
|
|
869
|
+
);
|
|
870
|
+
return {
|
|
871
|
+
api,
|
|
872
|
+
key,
|
|
873
|
+
codec,
|
|
874
|
+
codecOpt,
|
|
875
|
+
schema,
|
|
876
|
+
reconcile,
|
|
877
|
+
onMount,
|
|
878
|
+
onChange,
|
|
879
|
+
listenCrossTab,
|
|
880
|
+
getFallback,
|
|
881
|
+
getServerValue,
|
|
882
|
+
parseEnvelope,
|
|
883
|
+
decodeStringPayload,
|
|
884
|
+
buildFallbackResult,
|
|
885
|
+
developmentRuntime,
|
|
886
|
+
contractFingerprint,
|
|
887
|
+
hasMounted,
|
|
888
|
+
setHasMounted,
|
|
889
|
+
hydrationMode,
|
|
890
|
+
ssrOptions
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
function useApplyReconcile({
|
|
894
|
+
key,
|
|
895
|
+
reconcile,
|
|
896
|
+
buildFallbackResult
|
|
897
|
+
}) {
|
|
898
|
+
return react.useCallback(
|
|
899
|
+
({
|
|
900
|
+
value,
|
|
901
|
+
rewriteRaw,
|
|
902
|
+
extra,
|
|
903
|
+
persistedVersion,
|
|
904
|
+
latestVersion,
|
|
905
|
+
serializeForPersist,
|
|
906
|
+
deriveExtra
|
|
907
|
+
}) => {
|
|
908
|
+
if (!reconcile) {
|
|
909
|
+
return withReadMetadata(value, rewriteRaw, extra);
|
|
910
|
+
}
|
|
911
|
+
const context = {
|
|
912
|
+
key,
|
|
913
|
+
persistedVersion
|
|
914
|
+
};
|
|
915
|
+
if (latestVersion !== void 0) {
|
|
916
|
+
context.latestVersion = latestVersion;
|
|
917
|
+
}
|
|
918
|
+
const baselineSerialized = (() => {
|
|
919
|
+
try {
|
|
920
|
+
return serializeForPersist(value);
|
|
921
|
+
} catch {
|
|
922
|
+
return rewriteRaw;
|
|
923
|
+
}
|
|
924
|
+
})();
|
|
925
|
+
try {
|
|
926
|
+
const reconciled = reconcile(value, context);
|
|
927
|
+
const nextExtra = deriveExtra ? deriveExtra(reconciled, extra) : extra;
|
|
928
|
+
const nextSerialized = serializeForPersist(reconciled);
|
|
929
|
+
const nextRewriteRaw = baselineSerialized === void 0 || nextSerialized !== baselineSerialized ? nextSerialized : rewriteRaw;
|
|
930
|
+
return withReadMetadata(reconciled, nextRewriteRaw, nextExtra);
|
|
931
|
+
} catch (err) {
|
|
932
|
+
const typedErr = err instanceof SchemaError ? err : new SchemaError("RECONCILE_FAILED", `Reconciliation failed for key "${key}"`, err);
|
|
933
|
+
return buildFallbackResult(typedErr, extra);
|
|
934
|
+
}
|
|
935
|
+
},
|
|
936
|
+
[buildFallbackResult, key, reconcile]
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
function useMnemonicKeyState(shared, config) {
|
|
940
|
+
const {
|
|
941
|
+
api,
|
|
942
|
+
key,
|
|
943
|
+
codecOpt,
|
|
944
|
+
schema,
|
|
945
|
+
onMount,
|
|
946
|
+
onChange,
|
|
947
|
+
listenCrossTab,
|
|
948
|
+
getFallback,
|
|
949
|
+
getServerValue,
|
|
950
|
+
developmentRuntime,
|
|
951
|
+
contractFingerprint,
|
|
952
|
+
hasMounted,
|
|
953
|
+
setHasMounted,
|
|
954
|
+
hydrationMode,
|
|
955
|
+
ssrOptions
|
|
956
|
+
} = shared;
|
|
957
|
+
const { decodeForRead, encodeForWrite, additionalDevWarnings, onDecodedEffect } = config;
|
|
958
|
+
const getServerRawSnapshot = react.useCallback(
|
|
959
|
+
() => ssrOptions?.serverValue === void 0 ? null : SSR_SNAPSHOT_TOKEN,
|
|
960
|
+
[ssrOptions?.serverValue]
|
|
961
|
+
);
|
|
962
|
+
const deferStorageRead = hydrationMode === "client-only" && !hasMounted;
|
|
963
|
+
const subscribe = react.useCallback(
|
|
964
|
+
(listener) => {
|
|
965
|
+
if (deferStorageRead) {
|
|
966
|
+
return () => void 0;
|
|
967
|
+
}
|
|
968
|
+
return api.subscribeRaw(key, listener);
|
|
969
|
+
},
|
|
970
|
+
[api, deferStorageRead, key]
|
|
971
|
+
);
|
|
972
|
+
const raw = react.useSyncExternalStore(
|
|
973
|
+
subscribe,
|
|
974
|
+
() => deferStorageRead ? getServerRawSnapshot() : api.getRawSnapshot(key),
|
|
975
|
+
getServerRawSnapshot
|
|
976
|
+
);
|
|
977
|
+
const decoded = react.useMemo(() => {
|
|
978
|
+
if (raw === SSR_SNAPSHOT_TOKEN) {
|
|
979
|
+
return withReadMetadata(getServerValue());
|
|
980
|
+
}
|
|
981
|
+
return decodeForRead(raw);
|
|
982
|
+
}, [decodeForRead, getServerValue, raw]);
|
|
983
|
+
const value = decoded.value;
|
|
984
|
+
react.useEffect(() => {
|
|
985
|
+
if (!developmentRuntime) return;
|
|
986
|
+
const globalWindow = globalThis.window;
|
|
987
|
+
if (listenCrossTab && (api.crossTabSyncMode ?? "none") === "none" && globalWindow !== void 0) {
|
|
988
|
+
warnOnce(
|
|
989
|
+
api,
|
|
990
|
+
`listenCrossTab:${key}`,
|
|
991
|
+
`[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.`
|
|
992
|
+
);
|
|
993
|
+
}
|
|
994
|
+
additionalDevWarnings?.({
|
|
995
|
+
api,
|
|
996
|
+
key,
|
|
997
|
+
listenCrossTab,
|
|
998
|
+
codecOpt,
|
|
999
|
+
schema,
|
|
1000
|
+
warnOnce: (id, message) => warnOnce(api, id, message)
|
|
1001
|
+
});
|
|
1002
|
+
let keyContracts = diagnosticContractRegistry.get(api);
|
|
1003
|
+
if (!keyContracts) {
|
|
1004
|
+
keyContracts = /* @__PURE__ */ new Map();
|
|
1005
|
+
diagnosticContractRegistry.set(api, keyContracts);
|
|
1006
|
+
}
|
|
1007
|
+
if (contractFingerprint === null) {
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
const previousContract = keyContracts.get(key);
|
|
1011
|
+
if (previousContract === void 0) {
|
|
1012
|
+
keyContracts.set(key, contractFingerprint);
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
if (previousContract === contractFingerprint) {
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
warnOnce(
|
|
1019
|
+
api,
|
|
1020
|
+
`contract-conflict:${key}`,
|
|
1021
|
+
`[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.`
|
|
1022
|
+
);
|
|
1023
|
+
}, [
|
|
1024
|
+
additionalDevWarnings,
|
|
1025
|
+
api,
|
|
1026
|
+
key,
|
|
1027
|
+
developmentRuntime,
|
|
1028
|
+
contractFingerprint,
|
|
1029
|
+
listenCrossTab,
|
|
1030
|
+
codecOpt,
|
|
1031
|
+
schema,
|
|
1032
|
+
api.crossTabSyncMode
|
|
1033
|
+
]);
|
|
1034
|
+
react.useEffect(() => {
|
|
1035
|
+
if (hasMounted) return;
|
|
1036
|
+
setHasMounted(true);
|
|
1037
|
+
}, [hasMounted, setHasMounted]);
|
|
1038
|
+
react.useEffect(() => {
|
|
1039
|
+
if (decoded.rewriteRaw && decoded.rewriteRaw !== raw) {
|
|
1040
|
+
api.setRaw(key, decoded.rewriteRaw);
|
|
1041
|
+
}
|
|
1042
|
+
}, [api, decoded.rewriteRaw, key, raw]);
|
|
1043
|
+
react.useEffect(() => {
|
|
1044
|
+
onDecodedEffect?.(decoded);
|
|
1045
|
+
}, [decoded, onDecodedEffect]);
|
|
1046
|
+
const prevRef = react.useRef(value);
|
|
1047
|
+
const mounted = react.useRef(false);
|
|
1048
|
+
react.useEffect(() => {
|
|
1049
|
+
if (mounted.current) return;
|
|
1050
|
+
mounted.current = true;
|
|
1051
|
+
onMount?.(value);
|
|
1052
|
+
prevRef.current = value;
|
|
1053
|
+
}, []);
|
|
1054
|
+
react.useEffect(() => {
|
|
1055
|
+
const prev = prevRef.current;
|
|
1056
|
+
if (Object.is(prev, value)) return;
|
|
1057
|
+
prevRef.current = value;
|
|
1058
|
+
onChange?.(value, prev);
|
|
1059
|
+
}, [value, onChange]);
|
|
1060
|
+
react.useEffect(() => {
|
|
1061
|
+
if (!listenCrossTab) return;
|
|
1062
|
+
const globalWindow = globalThis.window;
|
|
1063
|
+
if (globalWindow === void 0) return;
|
|
1064
|
+
const storageKey = api.prefix + key;
|
|
1065
|
+
const handler = (e) => {
|
|
1066
|
+
if (e.key === null) {
|
|
1067
|
+
api.removeRaw(key);
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
1070
|
+
if (e.key !== storageKey) return;
|
|
1071
|
+
if (e.newValue == null) {
|
|
1072
|
+
api.removeRaw(key);
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
1075
|
+
api.setRaw(key, e.newValue);
|
|
1076
|
+
};
|
|
1077
|
+
globalWindow.addEventListener("storage", handler);
|
|
1078
|
+
return () => globalWindow.removeEventListener("storage", handler);
|
|
1079
|
+
}, [listenCrossTab, api, key]);
|
|
1080
|
+
const set = react.useMemo(() => {
|
|
1081
|
+
return (next) => {
|
|
1082
|
+
const nextVal = typeof next === "function" ? next(decodeForRead(api.getRawSnapshot(key)).value) : next;
|
|
1083
|
+
try {
|
|
1084
|
+
const encoded = encodeForWrite(nextVal);
|
|
1085
|
+
api.setRaw(key, encoded);
|
|
1086
|
+
} catch (err) {
|
|
1087
|
+
if (err instanceof SchemaError) {
|
|
1088
|
+
console.error(`[Mnemonic] Schema error for key "${key}" (${err.code}):`, err.message);
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
if (err instanceof CodecError) {
|
|
1092
|
+
console.error(`[Mnemonic] Codec encode error for key "${key}":`, err.message);
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
console.error(`[Mnemonic] Failed to persist key "${key}":`, err);
|
|
1096
|
+
}
|
|
1097
|
+
};
|
|
1098
|
+
}, [api, key, decodeForRead, encodeForWrite]);
|
|
1099
|
+
const reset = react.useMemo(() => {
|
|
1100
|
+
return () => {
|
|
1101
|
+
const v = getFallback();
|
|
1102
|
+
try {
|
|
1103
|
+
const encoded = encodeForWrite(v);
|
|
1104
|
+
api.setRaw(key, encoded);
|
|
1105
|
+
} catch (err) {
|
|
1106
|
+
if (err instanceof SchemaError) {
|
|
1107
|
+
console.error(`[Mnemonic] Schema error for key "${key}" (${err.code}):`, err.message);
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
if (err instanceof CodecError) {
|
|
1111
|
+
console.error(`[Mnemonic] Codec encode error for key "${key}":`, err.message);
|
|
1112
|
+
}
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
};
|
|
1116
|
+
}, [api, key, getFallback, encodeForWrite]);
|
|
1117
|
+
const remove = react.useMemo(() => {
|
|
1118
|
+
return () => api.removeRaw(key);
|
|
1119
|
+
}, [api, key]);
|
|
1120
|
+
return react.useMemo(
|
|
1121
|
+
() => ({
|
|
1122
|
+
value,
|
|
1123
|
+
set,
|
|
1124
|
+
reset,
|
|
1125
|
+
remove
|
|
1126
|
+
}),
|
|
1127
|
+
[value, set, reset, remove]
|
|
1128
|
+
);
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
// src/Mnemonic/use-core.ts
|
|
1132
|
+
function throwCoreSchemaImportError(key) {
|
|
1133
|
+
throw new Error(
|
|
1134
|
+
`[Mnemonic] useMnemonicKey("${key}") requested schema features from react-mnemonic/core. Import useMnemonicKey from "react-mnemonic/schema" or "react-mnemonic" for schema validation, autoschema, and migration support.`
|
|
1135
|
+
);
|
|
1136
|
+
}
|
|
1137
|
+
function useCoreRuntime(keyOrDescriptor, options) {
|
|
1138
|
+
const shared = useMnemonicKeyShared(keyOrDescriptor, options);
|
|
1139
|
+
const { api, key, codec, schema, reconcile, parseEnvelope, decodeStringPayload, buildFallbackResult } = shared;
|
|
1140
|
+
if (schema?.version !== void 0 || api.schemaMode !== "default") {
|
|
1141
|
+
throwCoreSchemaImportError(key);
|
|
1142
|
+
}
|
|
1143
|
+
const applyReconcile = useApplyReconcile({
|
|
1144
|
+
key,
|
|
1145
|
+
reconcile,
|
|
1146
|
+
buildFallbackResult
|
|
1147
|
+
});
|
|
1148
|
+
const encodeForWrite = react.useCallback(
|
|
1149
|
+
(nextValue) => {
|
|
1150
|
+
return serializeEnvelope(0, codec.encode(nextValue));
|
|
1151
|
+
},
|
|
1152
|
+
[codec]
|
|
1153
|
+
);
|
|
1154
|
+
const decodeForRead = react.useCallback(
|
|
1155
|
+
(rawText) => {
|
|
1156
|
+
if (rawText == null) return buildFallbackResult();
|
|
1157
|
+
const parsed = parseEnvelope(rawText);
|
|
1158
|
+
if (!parsed.ok) return buildFallbackResult(parsed.error);
|
|
1159
|
+
const envelope = parsed.envelope;
|
|
1160
|
+
if (typeof envelope.payload !== "string") {
|
|
1161
|
+
return applyReconcile({
|
|
1162
|
+
value: envelope.payload,
|
|
1163
|
+
persistedVersion: envelope.version,
|
|
1164
|
+
serializeForPersist: encodeForWrite
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
try {
|
|
1168
|
+
const decoded = decodeStringPayload(envelope.payload, codec);
|
|
1169
|
+
return applyReconcile({
|
|
1170
|
+
value: decoded,
|
|
1171
|
+
persistedVersion: envelope.version,
|
|
1172
|
+
serializeForPersist: encodeForWrite
|
|
1173
|
+
});
|
|
1174
|
+
} catch (err) {
|
|
1175
|
+
return buildFallbackResult(err);
|
|
1176
|
+
}
|
|
1177
|
+
},
|
|
1178
|
+
[applyReconcile, buildFallbackResult, codec, decodeStringPayload, encodeForWrite, parseEnvelope]
|
|
1179
|
+
);
|
|
1180
|
+
const additionalDevWarnings = react.useCallback(
|
|
1181
|
+
({ warnOnce: warnOnce2 }) => {
|
|
1182
|
+
if (!api.schemaRegistry) return;
|
|
1183
|
+
warnOnce2(
|
|
1184
|
+
`core-schema-registry:${key}`,
|
|
1185
|
+
`[Mnemonic] useMnemonicKey("${key}") is running from react-mnemonic/core, so registered schemas are ignored for this key. Import useMnemonicKey from "react-mnemonic/schema" or "react-mnemonic" to enable schema validation and migrations.`
|
|
1186
|
+
);
|
|
1187
|
+
},
|
|
1188
|
+
[api.schemaRegistry, key]
|
|
1189
|
+
);
|
|
1190
|
+
return useMnemonicKeyState(shared, {
|
|
1191
|
+
decodeForRead,
|
|
1192
|
+
encodeForWrite,
|
|
1193
|
+
additionalDevWarnings
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
function useMnemonicKey(keyOrDescriptor, options) {
|
|
1197
|
+
return useCoreRuntime(keyOrDescriptor, options);
|
|
1198
|
+
}
|
|
1199
|
+
function uniqueKeys(keys) {
|
|
1200
|
+
return [...new Set(keys)];
|
|
1201
|
+
}
|
|
1202
|
+
function isDevelopmentRuntime2() {
|
|
1203
|
+
return getRuntimeNodeEnv() === "development";
|
|
1204
|
+
}
|
|
1205
|
+
var recoveryDiagnosticWarnings = /* @__PURE__ */ new WeakMap();
|
|
1206
|
+
function warnRecoveryOnce(api, id, message) {
|
|
1207
|
+
let warnings = recoveryDiagnosticWarnings.get(api);
|
|
1208
|
+
if (!warnings) {
|
|
1209
|
+
warnings = /* @__PURE__ */ new Set();
|
|
1210
|
+
recoveryDiagnosticWarnings.set(api, warnings);
|
|
1211
|
+
}
|
|
1212
|
+
if (warnings.has(id)) return;
|
|
1213
|
+
warnings.add(id);
|
|
1214
|
+
console.warn(message);
|
|
1215
|
+
}
|
|
1216
|
+
function useMnemonicRecovery(options = {}) {
|
|
1217
|
+
const api = useMnemonic();
|
|
1218
|
+
const { onRecover } = options;
|
|
1219
|
+
const namespace = react.useMemo(() => api.prefix.endsWith(".") ? api.prefix.slice(0, -1) : api.prefix, [api.prefix]);
|
|
1220
|
+
const emitRecovery = react.useCallback(
|
|
1221
|
+
(action, clearedKeys) => {
|
|
1222
|
+
const event = {
|
|
1223
|
+
action,
|
|
1224
|
+
namespace,
|
|
1225
|
+
clearedKeys
|
|
1226
|
+
};
|
|
1227
|
+
onRecover?.(event);
|
|
1228
|
+
},
|
|
1229
|
+
[namespace, onRecover]
|
|
1230
|
+
);
|
|
1231
|
+
const listKeys = react.useCallback(() => api.keys(), [api]);
|
|
1232
|
+
const clearResolvedKeys = react.useCallback(
|
|
1233
|
+
(action, keys) => {
|
|
1234
|
+
const clearedKeys = uniqueKeys(keys);
|
|
1235
|
+
for (const key of clearedKeys) {
|
|
1236
|
+
api.removeRaw(key);
|
|
1237
|
+
}
|
|
1238
|
+
emitRecovery(action, clearedKeys);
|
|
1239
|
+
return clearedKeys;
|
|
1240
|
+
},
|
|
1241
|
+
[api, emitRecovery]
|
|
1242
|
+
);
|
|
1243
|
+
const clearKeys = react.useCallback(
|
|
1244
|
+
(keys) => clearResolvedKeys("clear-keys", keys),
|
|
1245
|
+
[clearResolvedKeys]
|
|
1246
|
+
);
|
|
1247
|
+
const clearAll = react.useCallback(() => {
|
|
1248
|
+
if (!api.canEnumerateKeys) {
|
|
1249
|
+
if (isDevelopmentRuntime2()) {
|
|
1250
|
+
warnRecoveryOnce(
|
|
1251
|
+
api,
|
|
1252
|
+
"recovery-clear-all-non-enumerable",
|
|
1253
|
+
`[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).`
|
|
1254
|
+
);
|
|
1255
|
+
}
|
|
1256
|
+
throw new Error(
|
|
1257
|
+
"clearAll requires an enumerable storage backend. Use clearKeys([...]) with an explicit key list instead."
|
|
1258
|
+
);
|
|
1259
|
+
}
|
|
1260
|
+
return clearResolvedKeys("clear-all", api.keys());
|
|
1261
|
+
}, [api, clearResolvedKeys, namespace]);
|
|
1262
|
+
const clearMatching = react.useCallback(
|
|
1263
|
+
(predicate) => {
|
|
1264
|
+
if (!api.canEnumerateKeys) {
|
|
1265
|
+
if (isDevelopmentRuntime2()) {
|
|
1266
|
+
warnRecoveryOnce(
|
|
1267
|
+
api,
|
|
1268
|
+
"recovery-clear-matching-non-enumerable",
|
|
1269
|
+
`[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).`
|
|
1270
|
+
);
|
|
1271
|
+
}
|
|
1272
|
+
throw new Error(
|
|
1273
|
+
"clearMatching requires an enumerable storage backend. Use clearKeys([...]) with an explicit key list instead."
|
|
1274
|
+
);
|
|
1275
|
+
}
|
|
1276
|
+
return clearResolvedKeys(
|
|
1277
|
+
"clear-matching",
|
|
1278
|
+
api.keys().filter((key) => predicate(key))
|
|
1279
|
+
);
|
|
1280
|
+
},
|
|
1281
|
+
[api, clearResolvedKeys, namespace]
|
|
1282
|
+
);
|
|
1283
|
+
return react.useMemo(
|
|
1284
|
+
() => ({
|
|
1285
|
+
namespace,
|
|
1286
|
+
canEnumerateKeys: api.canEnumerateKeys,
|
|
1287
|
+
listKeys,
|
|
1288
|
+
clearAll,
|
|
1289
|
+
clearKeys,
|
|
1290
|
+
clearMatching
|
|
1291
|
+
}),
|
|
1292
|
+
[namespace, api.canEnumerateKeys, listKeys, clearAll, clearKeys, clearMatching]
|
|
1293
|
+
);
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
// src/Mnemonic/key.ts
|
|
1297
|
+
function defineMnemonicKey(keyOrSchema, options) {
|
|
1298
|
+
if (typeof keyOrSchema !== "string") {
|
|
1299
|
+
return Object.freeze({
|
|
1300
|
+
key: keyOrSchema.key,
|
|
1301
|
+
options: {
|
|
1302
|
+
...options,
|
|
1303
|
+
schema: { version: keyOrSchema.version }
|
|
1304
|
+
}
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
return Object.freeze({
|
|
1308
|
+
key: keyOrSchema,
|
|
1309
|
+
options
|
|
1310
|
+
});
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
exports.CodecError = CodecError;
|
|
1314
|
+
exports.JSONCodec = JSONCodec;
|
|
1315
|
+
exports.MnemonicProvider = MnemonicProvider2;
|
|
1316
|
+
exports.SchemaError = SchemaError;
|
|
1317
|
+
exports.createCodec = createCodec;
|
|
1318
|
+
exports.defineMnemonicKey = defineMnemonicKey;
|
|
1319
|
+
exports.useMnemonicKey = useMnemonicKey;
|
|
1320
|
+
exports.useMnemonicRecovery = useMnemonicRecovery;
|
|
1321
|
+
//# sourceMappingURL=core.cjs.map
|
|
1322
|
+
//# sourceMappingURL=core.cjs.map
|