react-mnemonic 0.1.1-alpha.0
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/LICENSE.md +21 -0
- package/README.md +497 -0
- package/dist/index.cjs +996 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1151 -0
- package/dist/index.d.ts +1151 -0
- package/dist/index.js +987 -0
- package/dist/index.js.map +1 -0
- package/package.json +80 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,987 @@
|
|
|
1
|
+
import { createContext, useMemo, useEffect, useCallback, useSyncExternalStore, useRef, useContext } from 'react';
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
// src/Mnemonic/provider.tsx
|
|
5
|
+
var MnemonicContext = createContext(null);
|
|
6
|
+
function useMnemonic() {
|
|
7
|
+
const context = useContext(MnemonicContext);
|
|
8
|
+
if (!context) {
|
|
9
|
+
throw new Error("useMnemonic must be used within a MnemonicProvider");
|
|
10
|
+
}
|
|
11
|
+
return context;
|
|
12
|
+
}
|
|
13
|
+
function defaultBrowserStorage() {
|
|
14
|
+
if (typeof window === "undefined") return void 0;
|
|
15
|
+
try {
|
|
16
|
+
return window.localStorage;
|
|
17
|
+
} catch {
|
|
18
|
+
return void 0;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function MnemonicProvider({
|
|
22
|
+
children,
|
|
23
|
+
namespace,
|
|
24
|
+
storage,
|
|
25
|
+
enableDevTools = false,
|
|
26
|
+
schemaMode = "default",
|
|
27
|
+
schemaRegistry
|
|
28
|
+
}) {
|
|
29
|
+
if (schemaMode === "strict" && !schemaRegistry) {
|
|
30
|
+
throw new Error("MnemonicProvider strict mode requires schemaRegistry");
|
|
31
|
+
}
|
|
32
|
+
if (schemaMode === "autoschema" && typeof schemaRegistry?.registerSchema !== "function") {
|
|
33
|
+
throw new Error("MnemonicProvider autoschema mode requires schemaRegistry.registerSchema");
|
|
34
|
+
}
|
|
35
|
+
const store = useMemo(() => {
|
|
36
|
+
const prefix = `${namespace}.`;
|
|
37
|
+
const st = storage ?? defaultBrowserStorage();
|
|
38
|
+
const cache = /* @__PURE__ */ new Map();
|
|
39
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
40
|
+
let quotaErrorLogged = false;
|
|
41
|
+
let accessErrorLogged = false;
|
|
42
|
+
const fullKey = (key) => prefix + key;
|
|
43
|
+
const emit = (key) => {
|
|
44
|
+
const set = listeners.get(key);
|
|
45
|
+
if (!set) return;
|
|
46
|
+
for (const fn of set) fn();
|
|
47
|
+
};
|
|
48
|
+
const logAccessError = (err) => {
|
|
49
|
+
if (!accessErrorLogged && err instanceof DOMException && err.name !== "QuotaExceededError") {
|
|
50
|
+
console.error(
|
|
51
|
+
`[Mnemonic] Storage access error (${err.name}): ${err.message}. Data is cached in memory but may not persist.`
|
|
52
|
+
);
|
|
53
|
+
accessErrorLogged = true;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
const readThrough = (key) => {
|
|
57
|
+
if (cache.has(key)) return cache.get(key) ?? null;
|
|
58
|
+
if (!st) {
|
|
59
|
+
cache.set(key, null);
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const raw = st.getItem(fullKey(key));
|
|
64
|
+
cache.set(key, raw);
|
|
65
|
+
accessErrorLogged = false;
|
|
66
|
+
return raw;
|
|
67
|
+
} catch (err) {
|
|
68
|
+
logAccessError(err);
|
|
69
|
+
cache.set(key, null);
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
const writeRaw = (key, raw) => {
|
|
74
|
+
cache.set(key, raw);
|
|
75
|
+
if (st) {
|
|
76
|
+
try {
|
|
77
|
+
st.setItem(fullKey(key), raw);
|
|
78
|
+
quotaErrorLogged = false;
|
|
79
|
+
accessErrorLogged = false;
|
|
80
|
+
} catch (err) {
|
|
81
|
+
if (!quotaErrorLogged && err instanceof DOMException && err.name === "QuotaExceededError") {
|
|
82
|
+
console.error(
|
|
83
|
+
`[Mnemonic] Storage quota exceeded writing key "${key}". Data is cached in memory but will not persist.`
|
|
84
|
+
);
|
|
85
|
+
quotaErrorLogged = true;
|
|
86
|
+
}
|
|
87
|
+
logAccessError(err);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
emit(key);
|
|
91
|
+
};
|
|
92
|
+
const removeRaw = (key) => {
|
|
93
|
+
cache.set(key, null);
|
|
94
|
+
if (st) {
|
|
95
|
+
try {
|
|
96
|
+
st.removeItem(fullKey(key));
|
|
97
|
+
accessErrorLogged = false;
|
|
98
|
+
} catch (err) {
|
|
99
|
+
logAccessError(err);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
emit(key);
|
|
103
|
+
};
|
|
104
|
+
const subscribeRaw = (key, listener) => {
|
|
105
|
+
let set = listeners.get(key);
|
|
106
|
+
if (!set) {
|
|
107
|
+
set = /* @__PURE__ */ new Set();
|
|
108
|
+
listeners.set(key, set);
|
|
109
|
+
}
|
|
110
|
+
set.add(listener);
|
|
111
|
+
readThrough(key);
|
|
112
|
+
return () => {
|
|
113
|
+
const s = listeners.get(key);
|
|
114
|
+
if (!s) return;
|
|
115
|
+
s.delete(listener);
|
|
116
|
+
if (s.size === 0) listeners.delete(key);
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
const getRawSnapshot = (key) => readThrough(key);
|
|
120
|
+
const keys = () => {
|
|
121
|
+
if (!st || typeof st.length !== "number" || typeof st.key !== "function") return [];
|
|
122
|
+
const out = [];
|
|
123
|
+
try {
|
|
124
|
+
for (let i = 0; i < st.length; i++) {
|
|
125
|
+
const k = st.key(i);
|
|
126
|
+
if (!k) continue;
|
|
127
|
+
if (k.startsWith(prefix)) out.push(k.slice(prefix.length));
|
|
128
|
+
}
|
|
129
|
+
accessErrorLogged = false;
|
|
130
|
+
} catch (err) {
|
|
131
|
+
logAccessError(err);
|
|
132
|
+
}
|
|
133
|
+
return out;
|
|
134
|
+
};
|
|
135
|
+
const dump = () => {
|
|
136
|
+
const out = {};
|
|
137
|
+
for (const k of keys()) {
|
|
138
|
+
const raw = readThrough(k);
|
|
139
|
+
if (raw != null) out[k] = raw;
|
|
140
|
+
}
|
|
141
|
+
return out;
|
|
142
|
+
};
|
|
143
|
+
const reloadFromStorage = (changedKeys) => {
|
|
144
|
+
if (!st) return;
|
|
145
|
+
if (changedKeys !== void 0 && changedKeys.length === 0) return;
|
|
146
|
+
if (changedKeys !== void 0) {
|
|
147
|
+
for (const fk of changedKeys) {
|
|
148
|
+
if (!fk.startsWith(prefix)) continue;
|
|
149
|
+
const key = fk.slice(prefix.length);
|
|
150
|
+
const listenerSet = listeners.get(key);
|
|
151
|
+
if (listenerSet && listenerSet.size > 0) {
|
|
152
|
+
let fresh;
|
|
153
|
+
try {
|
|
154
|
+
fresh = st.getItem(fk);
|
|
155
|
+
accessErrorLogged = false;
|
|
156
|
+
} catch (err) {
|
|
157
|
+
logAccessError(err);
|
|
158
|
+
fresh = null;
|
|
159
|
+
}
|
|
160
|
+
const cached = cache.get(key) ?? null;
|
|
161
|
+
if (fresh !== cached) {
|
|
162
|
+
cache.set(key, fresh);
|
|
163
|
+
emit(key);
|
|
164
|
+
}
|
|
165
|
+
} else if (cache.has(key)) {
|
|
166
|
+
cache.delete(key);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
for (const [key, listenerSet] of listeners) {
|
|
172
|
+
if (listenerSet.size === 0) continue;
|
|
173
|
+
let fresh;
|
|
174
|
+
try {
|
|
175
|
+
fresh = st.getItem(fullKey(key));
|
|
176
|
+
accessErrorLogged = false;
|
|
177
|
+
} catch (err) {
|
|
178
|
+
logAccessError(err);
|
|
179
|
+
fresh = null;
|
|
180
|
+
}
|
|
181
|
+
const cached = cache.get(key) ?? null;
|
|
182
|
+
if (fresh !== cached) {
|
|
183
|
+
cache.set(key, fresh);
|
|
184
|
+
emit(key);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
for (const key of cache.keys()) {
|
|
188
|
+
if (!listeners.has(key) || listeners.get(key).size === 0) {
|
|
189
|
+
cache.delete(key);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
const store2 = {
|
|
194
|
+
prefix,
|
|
195
|
+
subscribeRaw,
|
|
196
|
+
getRawSnapshot,
|
|
197
|
+
setRaw: writeRaw,
|
|
198
|
+
removeRaw,
|
|
199
|
+
keys,
|
|
200
|
+
dump,
|
|
201
|
+
reloadFromStorage,
|
|
202
|
+
schemaMode,
|
|
203
|
+
...schemaRegistry ? { schemaRegistry } : {}
|
|
204
|
+
};
|
|
205
|
+
if (enableDevTools && typeof window !== "undefined") {
|
|
206
|
+
window.__REACT_MNEMONIC_DEVTOOLS__ = window.__REACT_MNEMONIC_DEVTOOLS__ || {};
|
|
207
|
+
window.__REACT_MNEMONIC_DEVTOOLS__[namespace] = {
|
|
208
|
+
/** Access the underlying store instance */
|
|
209
|
+
getStore: () => store2,
|
|
210
|
+
/** Dump all key-value pairs and display as a console table */
|
|
211
|
+
dump: () => {
|
|
212
|
+
const data = dump();
|
|
213
|
+
console.table(
|
|
214
|
+
Object.entries(data).map(([key, value]) => ({
|
|
215
|
+
key,
|
|
216
|
+
value,
|
|
217
|
+
decoded: (() => {
|
|
218
|
+
try {
|
|
219
|
+
return JSON.parse(value);
|
|
220
|
+
} catch {
|
|
221
|
+
return value;
|
|
222
|
+
}
|
|
223
|
+
})()
|
|
224
|
+
}))
|
|
225
|
+
);
|
|
226
|
+
return data;
|
|
227
|
+
},
|
|
228
|
+
/** Get a decoded value by key */
|
|
229
|
+
get: (key) => {
|
|
230
|
+
const raw = readThrough(key);
|
|
231
|
+
if (raw == null) return void 0;
|
|
232
|
+
try {
|
|
233
|
+
return JSON.parse(raw);
|
|
234
|
+
} catch {
|
|
235
|
+
return raw;
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
/** Set a value by key (automatically JSON-encoded) */
|
|
239
|
+
set: (key, value) => {
|
|
240
|
+
writeRaw(key, JSON.stringify(value));
|
|
241
|
+
},
|
|
242
|
+
/** Remove a key from storage */
|
|
243
|
+
remove: (key) => removeRaw(key),
|
|
244
|
+
/** Clear all keys in this namespace */
|
|
245
|
+
clear: () => {
|
|
246
|
+
for (const k of keys()) {
|
|
247
|
+
removeRaw(k);
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
/** List all keys in this namespace */
|
|
251
|
+
keys
|
|
252
|
+
};
|
|
253
|
+
console.info(
|
|
254
|
+
`[Mnemonic DevTools] Namespace "${namespace}" available at window.__REACT_MNEMONIC_DEVTOOLS__.${namespace}`
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
return store2;
|
|
258
|
+
}, [namespace, storage, enableDevTools, schemaMode, schemaRegistry]);
|
|
259
|
+
useEffect(() => {
|
|
260
|
+
if (!storage?.onExternalChange) return;
|
|
261
|
+
return storage.onExternalChange((changedKeys) => store.reloadFromStorage(changedKeys));
|
|
262
|
+
}, [storage, store]);
|
|
263
|
+
return /* @__PURE__ */ jsx(MnemonicContext.Provider, { value: store, children });
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// src/Mnemonic/codecs.ts
|
|
267
|
+
var CodecError = class extends Error {
|
|
268
|
+
/**
|
|
269
|
+
* Creates a new CodecError.
|
|
270
|
+
*
|
|
271
|
+
* @param message - Human-readable error description
|
|
272
|
+
* @param cause - Optional underlying error that caused this failure
|
|
273
|
+
*/
|
|
274
|
+
constructor(message, cause) {
|
|
275
|
+
super(message);
|
|
276
|
+
this.name = "CodecError";
|
|
277
|
+
this.cause = cause;
|
|
278
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
var JSONCodec = {
|
|
282
|
+
encode: (value) => JSON.stringify(value),
|
|
283
|
+
decode: (encoded) => JSON.parse(encoded)
|
|
284
|
+
};
|
|
285
|
+
function createCodec(encode, decode) {
|
|
286
|
+
return { encode, decode };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// src/Mnemonic/schema.ts
|
|
290
|
+
var SchemaError = class extends Error {
|
|
291
|
+
/**
|
|
292
|
+
* Creates a new SchemaError.
|
|
293
|
+
*
|
|
294
|
+
* @param code - Machine-readable failure category
|
|
295
|
+
* @param message - Human-readable error description
|
|
296
|
+
* @param cause - Optional underlying error
|
|
297
|
+
*/
|
|
298
|
+
constructor(code, message, cause) {
|
|
299
|
+
super(message);
|
|
300
|
+
this.name = "SchemaError";
|
|
301
|
+
this.code = code;
|
|
302
|
+
this.cause = cause;
|
|
303
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
// src/Mnemonic/json-schema.ts
|
|
308
|
+
function matchesType(value, type) {
|
|
309
|
+
switch (type) {
|
|
310
|
+
case "string":
|
|
311
|
+
return typeof value === "string";
|
|
312
|
+
case "number":
|
|
313
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
314
|
+
case "integer":
|
|
315
|
+
return typeof value === "number" && Number.isInteger(value);
|
|
316
|
+
case "boolean":
|
|
317
|
+
return typeof value === "boolean";
|
|
318
|
+
case "null":
|
|
319
|
+
return value === null;
|
|
320
|
+
case "object":
|
|
321
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
322
|
+
case "array":
|
|
323
|
+
return Array.isArray(value);
|
|
324
|
+
default:
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
function jsonDeepEqual(a, b) {
|
|
329
|
+
if (a === b) return true;
|
|
330
|
+
if (a === null || b === null) return false;
|
|
331
|
+
if (typeof a !== typeof b) return false;
|
|
332
|
+
if (Array.isArray(a)) {
|
|
333
|
+
if (!Array.isArray(b)) return false;
|
|
334
|
+
if (a.length !== b.length) return false;
|
|
335
|
+
for (let i = 0; i < a.length; i++) {
|
|
336
|
+
if (!jsonDeepEqual(a[i], b[i])) return false;
|
|
337
|
+
}
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
if (typeof a === "object") {
|
|
341
|
+
if (Array.isArray(b)) return false;
|
|
342
|
+
const aObj = a;
|
|
343
|
+
const bObj = b;
|
|
344
|
+
const aKeys = Object.keys(aObj);
|
|
345
|
+
const bKeys = Object.keys(bObj);
|
|
346
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
347
|
+
for (const key of aKeys) {
|
|
348
|
+
if (!Object.prototype.hasOwnProperty.call(bObj, key)) return false;
|
|
349
|
+
if (!jsonDeepEqual(aObj[key], bObj[key])) return false;
|
|
350
|
+
}
|
|
351
|
+
return true;
|
|
352
|
+
}
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
var compiledCache = /* @__PURE__ */ new WeakMap();
|
|
356
|
+
function compileSchema(schema) {
|
|
357
|
+
const cached = compiledCache.get(schema);
|
|
358
|
+
if (cached) return cached;
|
|
359
|
+
const compiled = buildValidator(schema);
|
|
360
|
+
compiledCache.set(schema, compiled);
|
|
361
|
+
return compiled;
|
|
362
|
+
}
|
|
363
|
+
function isJsonPrimitive(value) {
|
|
364
|
+
return value === null || typeof value !== "object";
|
|
365
|
+
}
|
|
366
|
+
function buildValidator(schema) {
|
|
367
|
+
const resolvedTypes = schema.type !== void 0 ? Array.isArray(schema.type) ? schema.type : [schema.type] : null;
|
|
368
|
+
const typeLabel = resolvedTypes !== null ? JSON.stringify(schema.type) : "";
|
|
369
|
+
const enumMembers = schema.enum;
|
|
370
|
+
let enumPrimitiveSet = null;
|
|
371
|
+
let enumComplexMembers = null;
|
|
372
|
+
if (enumMembers !== void 0) {
|
|
373
|
+
const primitives = [];
|
|
374
|
+
const complex = [];
|
|
375
|
+
for (const member of enumMembers) {
|
|
376
|
+
if (isJsonPrimitive(member)) {
|
|
377
|
+
primitives.push(member);
|
|
378
|
+
} else {
|
|
379
|
+
complex.push(member);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
if (primitives.length > 0) enumPrimitiveSet = new Set(primitives);
|
|
383
|
+
if (complex.length > 0) enumComplexMembers = complex;
|
|
384
|
+
}
|
|
385
|
+
const hasConst = "const" in schema;
|
|
386
|
+
const constValue = schema.const;
|
|
387
|
+
const hasMinimum = schema.minimum !== void 0;
|
|
388
|
+
const minimum = schema.minimum;
|
|
389
|
+
const hasMaximum = schema.maximum !== void 0;
|
|
390
|
+
const maximum = schema.maximum;
|
|
391
|
+
const hasExMin = schema.exclusiveMinimum !== void 0;
|
|
392
|
+
const exMin = schema.exclusiveMinimum;
|
|
393
|
+
const hasExMax = schema.exclusiveMaximum !== void 0;
|
|
394
|
+
const exMax = schema.exclusiveMaximum;
|
|
395
|
+
const hasNumberConstraints = hasMinimum || hasMaximum || hasExMin || hasExMax;
|
|
396
|
+
const hasMinLength = schema.minLength !== void 0;
|
|
397
|
+
const minLen = schema.minLength;
|
|
398
|
+
const hasMaxLength = schema.maxLength !== void 0;
|
|
399
|
+
const maxLen = schema.maxLength;
|
|
400
|
+
const hasStringConstraints = hasMinLength || hasMaxLength;
|
|
401
|
+
const requiredKeys = schema.required;
|
|
402
|
+
const hasRequired = requiredKeys !== void 0 && requiredKeys.length > 0;
|
|
403
|
+
const hasProperties = schema.properties !== void 0;
|
|
404
|
+
const propertyValidators = hasProperties ? Object.entries(schema.properties).map(
|
|
405
|
+
([name, propSchema]) => [name, compileSchema(propSchema)]
|
|
406
|
+
) : null;
|
|
407
|
+
const checkAdditional = schema.additionalProperties !== void 0 && schema.additionalProperties !== true;
|
|
408
|
+
const additionalIsFalse = schema.additionalProperties === false;
|
|
409
|
+
const additionalValidator = checkAdditional && !additionalIsFalse ? compileSchema(schema.additionalProperties) : null;
|
|
410
|
+
const definedPropKeys = checkAdditional ? new Set(schema.properties ? Object.keys(schema.properties) : []) : null;
|
|
411
|
+
const hasObjectConstraints = hasRequired || hasProperties || checkAdditional;
|
|
412
|
+
const hasMinItems = schema.minItems !== void 0;
|
|
413
|
+
const minItems = schema.minItems;
|
|
414
|
+
const hasMaxItems = schema.maxItems !== void 0;
|
|
415
|
+
const maxItems = schema.maxItems;
|
|
416
|
+
const itemsValidator = schema.items !== void 0 ? compileSchema(schema.items) : null;
|
|
417
|
+
const hasArrayConstraints = hasMinItems || hasMaxItems || itemsValidator !== null;
|
|
418
|
+
if (resolvedTypes === null && enumMembers === void 0 && !hasConst && !hasNumberConstraints && !hasStringConstraints && !hasObjectConstraints && !hasArrayConstraints) {
|
|
419
|
+
return (_value, _path) => [];
|
|
420
|
+
}
|
|
421
|
+
return (value, path = "") => {
|
|
422
|
+
const errors = [];
|
|
423
|
+
if (resolvedTypes !== null) {
|
|
424
|
+
const matched = resolvedTypes.some((t) => matchesType(value, t));
|
|
425
|
+
if (!matched) {
|
|
426
|
+
errors.push({
|
|
427
|
+
path,
|
|
428
|
+
message: `Expected type ${typeLabel}, got ${jsonTypeLabel(value)}`,
|
|
429
|
+
keyword: "type"
|
|
430
|
+
});
|
|
431
|
+
return errors;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
if (enumMembers !== void 0) {
|
|
435
|
+
let matched = false;
|
|
436
|
+
if (enumPrimitiveSet !== null && isJsonPrimitive(value)) {
|
|
437
|
+
matched = enumPrimitiveSet.has(value);
|
|
438
|
+
}
|
|
439
|
+
if (!matched && enumComplexMembers !== null) {
|
|
440
|
+
matched = enumComplexMembers.some((entry) => jsonDeepEqual(value, entry));
|
|
441
|
+
}
|
|
442
|
+
if (!matched) {
|
|
443
|
+
errors.push({
|
|
444
|
+
path,
|
|
445
|
+
message: `Value does not match any enum member`,
|
|
446
|
+
keyword: "enum"
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
if (hasConst) {
|
|
451
|
+
if (!jsonDeepEqual(value, constValue)) {
|
|
452
|
+
errors.push({
|
|
453
|
+
path,
|
|
454
|
+
message: `Value does not match const`,
|
|
455
|
+
keyword: "const"
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
if (hasNumberConstraints && typeof value === "number") {
|
|
460
|
+
if (hasMinimum && value < minimum) {
|
|
461
|
+
errors.push({
|
|
462
|
+
path,
|
|
463
|
+
message: `Value ${value} is less than minimum ${minimum}`,
|
|
464
|
+
keyword: "minimum"
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
if (hasMaximum && value > maximum) {
|
|
468
|
+
errors.push({
|
|
469
|
+
path,
|
|
470
|
+
message: `Value ${value} is greater than maximum ${maximum}`,
|
|
471
|
+
keyword: "maximum"
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
if (hasExMin && value <= exMin) {
|
|
475
|
+
errors.push({
|
|
476
|
+
path,
|
|
477
|
+
message: `Value ${value} is not greater than exclusiveMinimum ${exMin}`,
|
|
478
|
+
keyword: "exclusiveMinimum"
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
if (hasExMax && value >= exMax) {
|
|
482
|
+
errors.push({
|
|
483
|
+
path,
|
|
484
|
+
message: `Value ${value} is not less than exclusiveMaximum ${exMax}`,
|
|
485
|
+
keyword: "exclusiveMaximum"
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
if (hasStringConstraints && typeof value === "string") {
|
|
490
|
+
if (hasMinLength && value.length < minLen) {
|
|
491
|
+
errors.push({
|
|
492
|
+
path,
|
|
493
|
+
message: `String length ${value.length} is less than minLength ${minLen}`,
|
|
494
|
+
keyword: "minLength"
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
if (hasMaxLength && value.length > maxLen) {
|
|
498
|
+
errors.push({
|
|
499
|
+
path,
|
|
500
|
+
message: `String length ${value.length} is greater than maxLength ${maxLen}`,
|
|
501
|
+
keyword: "maxLength"
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
if (hasObjectConstraints && typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
506
|
+
const obj = value;
|
|
507
|
+
if (hasRequired) {
|
|
508
|
+
for (const reqKey of requiredKeys) {
|
|
509
|
+
if (!Object.prototype.hasOwnProperty.call(obj, reqKey)) {
|
|
510
|
+
errors.push({
|
|
511
|
+
path,
|
|
512
|
+
message: `Missing required property "${reqKey}"`,
|
|
513
|
+
keyword: "required"
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
if (propertyValidators !== null) {
|
|
519
|
+
for (const [propName, propValidator] of propertyValidators) {
|
|
520
|
+
if (Object.prototype.hasOwnProperty.call(obj, propName)) {
|
|
521
|
+
const propErrors = propValidator(obj[propName], `${path}/${propName}`);
|
|
522
|
+
errors.push(...propErrors);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
if (checkAdditional) {
|
|
527
|
+
for (const objKey of Object.keys(obj)) {
|
|
528
|
+
if (!definedPropKeys.has(objKey)) {
|
|
529
|
+
if (additionalIsFalse) {
|
|
530
|
+
errors.push({
|
|
531
|
+
path,
|
|
532
|
+
message: `Additional property "${objKey}" is not allowed`,
|
|
533
|
+
keyword: "additionalProperties"
|
|
534
|
+
});
|
|
535
|
+
} else {
|
|
536
|
+
const propErrors = additionalValidator(obj[objKey], `${path}/${objKey}`);
|
|
537
|
+
errors.push(...propErrors);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
if (hasArrayConstraints && Array.isArray(value)) {
|
|
544
|
+
if (hasMinItems && value.length < minItems) {
|
|
545
|
+
errors.push({
|
|
546
|
+
path,
|
|
547
|
+
message: `Array length ${value.length} is less than minItems ${minItems}`,
|
|
548
|
+
keyword: "minItems"
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
if (hasMaxItems && value.length > maxItems) {
|
|
552
|
+
errors.push({
|
|
553
|
+
path,
|
|
554
|
+
message: `Array length ${value.length} is greater than maxItems ${maxItems}`,
|
|
555
|
+
keyword: "maxItems"
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
if (itemsValidator !== null) {
|
|
559
|
+
for (let i = 0; i < value.length; i++) {
|
|
560
|
+
const itemErrors = itemsValidator(value[i], `${path}/${i}`);
|
|
561
|
+
errors.push(...itemErrors);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
return errors;
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
function validateJsonSchema(value, schema, path = "") {
|
|
569
|
+
const compiled = compileSchema(schema);
|
|
570
|
+
return compiled(value, path);
|
|
571
|
+
}
|
|
572
|
+
function jsonTypeLabel(value) {
|
|
573
|
+
if (value === null) return "null";
|
|
574
|
+
if (Array.isArray(value)) return "array";
|
|
575
|
+
return typeof value;
|
|
576
|
+
}
|
|
577
|
+
function inferJsonSchema(sample) {
|
|
578
|
+
if (sample === null) return { type: "null" };
|
|
579
|
+
if (Array.isArray(sample)) return { type: "array" };
|
|
580
|
+
switch (typeof sample) {
|
|
581
|
+
case "string":
|
|
582
|
+
return { type: "string" };
|
|
583
|
+
case "number":
|
|
584
|
+
return Number.isInteger(sample) ? { type: "number" } : { type: "number" };
|
|
585
|
+
case "boolean":
|
|
586
|
+
return { type: "boolean" };
|
|
587
|
+
case "object":
|
|
588
|
+
return { type: "object" };
|
|
589
|
+
default:
|
|
590
|
+
return {};
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// src/Mnemonic/use.ts
|
|
595
|
+
function useMnemonicKey(key, options) {
|
|
596
|
+
const api = useMnemonic();
|
|
597
|
+
const { defaultValue, onMount, onChange, listenCrossTab, codec: codecOpt, schema } = options;
|
|
598
|
+
const codec = codecOpt ?? JSONCodec;
|
|
599
|
+
const schemaMode = api.schemaMode;
|
|
600
|
+
const schemaRegistry = api.schemaRegistry;
|
|
601
|
+
const getFallback = useCallback(
|
|
602
|
+
(error) => typeof defaultValue === "function" ? defaultValue(error) : defaultValue,
|
|
603
|
+
[defaultValue]
|
|
604
|
+
);
|
|
605
|
+
const parseEnvelope = useCallback(
|
|
606
|
+
(rawText) => {
|
|
607
|
+
try {
|
|
608
|
+
const parsed = JSON.parse(rawText);
|
|
609
|
+
if (typeof parsed !== "object" || parsed == null || !Number.isInteger(parsed.version) || parsed.version < 0 || !Object.prototype.hasOwnProperty.call(parsed, "payload")) {
|
|
610
|
+
return {
|
|
611
|
+
ok: false,
|
|
612
|
+
error: new SchemaError("INVALID_ENVELOPE", `Invalid envelope for key "${key}"`)
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
return { ok: true, envelope: parsed };
|
|
616
|
+
} catch (err) {
|
|
617
|
+
return {
|
|
618
|
+
ok: false,
|
|
619
|
+
error: new SchemaError("INVALID_ENVELOPE", `Invalid envelope for key "${key}"`, err)
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
},
|
|
623
|
+
[key]
|
|
624
|
+
);
|
|
625
|
+
const decodeStringPayload = useCallback(
|
|
626
|
+
(payload, activeCodec) => {
|
|
627
|
+
if (typeof payload !== "string") {
|
|
628
|
+
throw new SchemaError(
|
|
629
|
+
"INVALID_ENVELOPE",
|
|
630
|
+
`Envelope payload must be a string for codec-managed key "${key}"`
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
try {
|
|
634
|
+
return activeCodec.decode(payload);
|
|
635
|
+
} catch (err) {
|
|
636
|
+
throw err instanceof CodecError ? err : new CodecError(`Codec decode failed for key "${key}"`, err);
|
|
637
|
+
}
|
|
638
|
+
},
|
|
639
|
+
[key]
|
|
640
|
+
);
|
|
641
|
+
const validateAgainstSchema = useCallback(
|
|
642
|
+
(value2, jsonSchema) => {
|
|
643
|
+
const errors = validateJsonSchema(value2, jsonSchema);
|
|
644
|
+
if (errors.length > 0) {
|
|
645
|
+
const message = errors.map((e) => `${e.path || "/"}: ${e.message}`).join("; ");
|
|
646
|
+
throw new SchemaError("TYPE_MISMATCH", `Schema validation failed for key "${key}": ${message}`);
|
|
647
|
+
}
|
|
648
|
+
},
|
|
649
|
+
[key]
|
|
650
|
+
);
|
|
651
|
+
const registryCache = useMemo(() => {
|
|
652
|
+
if (!schemaRegistry || schemaMode === "autoschema") return null;
|
|
653
|
+
return {
|
|
654
|
+
latestSchema: void 0,
|
|
655
|
+
latestSchemaSet: false,
|
|
656
|
+
schemaByVersion: /* @__PURE__ */ new Map(),
|
|
657
|
+
migrationPaths: /* @__PURE__ */ new Map()
|
|
658
|
+
};
|
|
659
|
+
}, [schemaRegistry, schemaMode, key]);
|
|
660
|
+
const getSchemaForVersion = useCallback(
|
|
661
|
+
(version) => {
|
|
662
|
+
if (!schemaRegistry) return void 0;
|
|
663
|
+
if (!registryCache) return schemaRegistry.getSchema(key, version);
|
|
664
|
+
if (registryCache.schemaByVersion.has(version)) {
|
|
665
|
+
return registryCache.schemaByVersion.get(version);
|
|
666
|
+
}
|
|
667
|
+
const s = schemaRegistry.getSchema(key, version);
|
|
668
|
+
registryCache.schemaByVersion.set(version, s);
|
|
669
|
+
return s;
|
|
670
|
+
},
|
|
671
|
+
[schemaRegistry, registryCache, key]
|
|
672
|
+
);
|
|
673
|
+
const getLatestSchemaForKey = useCallback(() => {
|
|
674
|
+
if (!schemaRegistry) return void 0;
|
|
675
|
+
if (!registryCache) return schemaRegistry.getLatestSchema(key);
|
|
676
|
+
if (registryCache.latestSchemaSet) return registryCache.latestSchema;
|
|
677
|
+
const s = schemaRegistry.getLatestSchema(key);
|
|
678
|
+
registryCache.latestSchema = s;
|
|
679
|
+
registryCache.latestSchemaSet = true;
|
|
680
|
+
return s;
|
|
681
|
+
}, [schemaRegistry, registryCache, key]);
|
|
682
|
+
const getMigrationPathForKey = useCallback(
|
|
683
|
+
(fromVersion, toVersion) => {
|
|
684
|
+
if (!schemaRegistry) return null;
|
|
685
|
+
if (!registryCache) return schemaRegistry.getMigrationPath(key, fromVersion, toVersion) ?? null;
|
|
686
|
+
const cacheKey = `${fromVersion}->${toVersion}`;
|
|
687
|
+
if (registryCache.migrationPaths.has(cacheKey)) {
|
|
688
|
+
return registryCache.migrationPaths.get(cacheKey) ?? null;
|
|
689
|
+
}
|
|
690
|
+
const path = schemaRegistry.getMigrationPath(key, fromVersion, toVersion) ?? null;
|
|
691
|
+
registryCache.migrationPaths.set(cacheKey, path);
|
|
692
|
+
return path;
|
|
693
|
+
},
|
|
694
|
+
[schemaRegistry, registryCache, key]
|
|
695
|
+
);
|
|
696
|
+
const decodeForRead = useCallback(
|
|
697
|
+
(rawText) => {
|
|
698
|
+
if (rawText == null) return { value: getFallback() };
|
|
699
|
+
const parsed = parseEnvelope(rawText);
|
|
700
|
+
if (!parsed.ok) return { value: getFallback(parsed.error) };
|
|
701
|
+
const envelope = parsed.envelope;
|
|
702
|
+
const schemaForVersion = getSchemaForVersion(envelope.version);
|
|
703
|
+
const latestSchema = getLatestSchemaForKey();
|
|
704
|
+
if (schemaMode === "strict" && !schemaForVersion) {
|
|
705
|
+
return {
|
|
706
|
+
value: getFallback(
|
|
707
|
+
new SchemaError("SCHEMA_NOT_FOUND", `No schema for key "${key}" v${envelope.version}`)
|
|
708
|
+
)
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
if (schemaMode === "autoschema" && !schemaForVersion) {
|
|
712
|
+
if (latestSchema) {
|
|
713
|
+
return {
|
|
714
|
+
value: getFallback(
|
|
715
|
+
new SchemaError("SCHEMA_NOT_FOUND", `No schema for key "${key}" v${envelope.version}`)
|
|
716
|
+
)
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
if (!schemaRegistry || typeof schemaRegistry.registerSchema !== "function") {
|
|
720
|
+
return {
|
|
721
|
+
value: getFallback(
|
|
722
|
+
new SchemaError(
|
|
723
|
+
"MODE_CONFIGURATION_INVALID",
|
|
724
|
+
`Autoschema mode requires schema registry registration for key "${key}"`
|
|
725
|
+
)
|
|
726
|
+
)
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
try {
|
|
730
|
+
const decoded2 = typeof envelope.payload === "string" ? decodeStringPayload(envelope.payload, codec) : envelope.payload;
|
|
731
|
+
const inferredJsonSchema = inferJsonSchema(decoded2);
|
|
732
|
+
const inferred = {
|
|
733
|
+
key,
|
|
734
|
+
version: 1,
|
|
735
|
+
schema: inferredJsonSchema
|
|
736
|
+
};
|
|
737
|
+
const rewriteEnvelope = {
|
|
738
|
+
version: inferred.version,
|
|
739
|
+
payload: decoded2
|
|
740
|
+
};
|
|
741
|
+
return {
|
|
742
|
+
value: decoded2,
|
|
743
|
+
pendingSchema: inferred,
|
|
744
|
+
rewriteRaw: JSON.stringify(rewriteEnvelope)
|
|
745
|
+
};
|
|
746
|
+
} catch (err) {
|
|
747
|
+
const typedErr = err instanceof SchemaError || err instanceof CodecError ? err : new SchemaError("TYPE_MISMATCH", `Autoschema inference failed for key "${key}"`, err);
|
|
748
|
+
return { value: getFallback(typedErr) };
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
if (!schemaForVersion) {
|
|
752
|
+
if (typeof envelope.payload !== "string") {
|
|
753
|
+
return { value: envelope.payload };
|
|
754
|
+
}
|
|
755
|
+
try {
|
|
756
|
+
const decoded2 = decodeStringPayload(envelope.payload, codec);
|
|
757
|
+
return { value: decoded2 };
|
|
758
|
+
} catch (err) {
|
|
759
|
+
const typedErr = err instanceof SchemaError || err instanceof CodecError ? err : new CodecError(`Codec decode failed for key "${key}"`, err);
|
|
760
|
+
return { value: getFallback(typedErr) };
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
let current;
|
|
764
|
+
try {
|
|
765
|
+
current = envelope.payload;
|
|
766
|
+
validateAgainstSchema(current, schemaForVersion.schema);
|
|
767
|
+
} catch (err) {
|
|
768
|
+
const typedErr = err instanceof SchemaError || err instanceof CodecError ? err : new SchemaError("TYPE_MISMATCH", `Schema decode failed for key "${key}"`, err);
|
|
769
|
+
return { value: getFallback(typedErr) };
|
|
770
|
+
}
|
|
771
|
+
if (!latestSchema || envelope.version >= latestSchema.version) {
|
|
772
|
+
return { value: current };
|
|
773
|
+
}
|
|
774
|
+
const path = getMigrationPathForKey(envelope.version, latestSchema.version);
|
|
775
|
+
if (!path) {
|
|
776
|
+
return {
|
|
777
|
+
value: getFallback(
|
|
778
|
+
new SchemaError(
|
|
779
|
+
"MIGRATION_PATH_NOT_FOUND",
|
|
780
|
+
`No migration path for key "${key}" from v${envelope.version} to v${latestSchema.version}`
|
|
781
|
+
)
|
|
782
|
+
)
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
try {
|
|
786
|
+
let migrated = current;
|
|
787
|
+
for (const step of path) {
|
|
788
|
+
migrated = step.migrate(migrated);
|
|
789
|
+
}
|
|
790
|
+
validateAgainstSchema(migrated, latestSchema.schema);
|
|
791
|
+
const rewriteEnvelope = {
|
|
792
|
+
version: latestSchema.version,
|
|
793
|
+
payload: migrated
|
|
794
|
+
};
|
|
795
|
+
return {
|
|
796
|
+
value: migrated,
|
|
797
|
+
rewriteRaw: JSON.stringify(rewriteEnvelope)
|
|
798
|
+
};
|
|
799
|
+
} catch (err) {
|
|
800
|
+
const typedErr = err instanceof SchemaError || err instanceof CodecError ? err : new SchemaError("MIGRATION_FAILED", `Migration failed for key "${key}"`, err);
|
|
801
|
+
return { value: getFallback(typedErr) };
|
|
802
|
+
}
|
|
803
|
+
},
|
|
804
|
+
[
|
|
805
|
+
codec,
|
|
806
|
+
decodeStringPayload,
|
|
807
|
+
getFallback,
|
|
808
|
+
key,
|
|
809
|
+
parseEnvelope,
|
|
810
|
+
schemaMode,
|
|
811
|
+
schemaRegistry,
|
|
812
|
+
getSchemaForVersion,
|
|
813
|
+
getLatestSchemaForKey,
|
|
814
|
+
getMigrationPathForKey,
|
|
815
|
+
validateAgainstSchema
|
|
816
|
+
]
|
|
817
|
+
);
|
|
818
|
+
const encodeForWrite = useCallback(
|
|
819
|
+
(nextValue) => {
|
|
820
|
+
const explicitVersion = schema?.version;
|
|
821
|
+
const latestSchema = getLatestSchemaForKey();
|
|
822
|
+
const explicitSchema = explicitVersion !== void 0 ? getSchemaForVersion(explicitVersion) : void 0;
|
|
823
|
+
let targetSchema = explicitSchema;
|
|
824
|
+
if (!targetSchema) {
|
|
825
|
+
if (explicitVersion !== void 0) {
|
|
826
|
+
if (schemaMode !== "strict") {
|
|
827
|
+
targetSchema = latestSchema;
|
|
828
|
+
}
|
|
829
|
+
} else {
|
|
830
|
+
targetSchema = latestSchema;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
if (!targetSchema) {
|
|
834
|
+
if (explicitVersion !== void 0 && schemaMode === "strict") {
|
|
835
|
+
throw new SchemaError(
|
|
836
|
+
"WRITE_SCHEMA_REQUIRED",
|
|
837
|
+
`Write requires schema for key "${key}" in strict mode`
|
|
838
|
+
);
|
|
839
|
+
}
|
|
840
|
+
const envelope2 = {
|
|
841
|
+
version: 0,
|
|
842
|
+
payload: codec.encode(nextValue)
|
|
843
|
+
};
|
|
844
|
+
return JSON.stringify(envelope2);
|
|
845
|
+
}
|
|
846
|
+
let valueToStore = nextValue;
|
|
847
|
+
const writeMigration = schemaRegistry?.getWriteMigration?.(key, targetSchema.version);
|
|
848
|
+
if (writeMigration) {
|
|
849
|
+
try {
|
|
850
|
+
valueToStore = writeMigration.migrate(valueToStore);
|
|
851
|
+
} catch (err) {
|
|
852
|
+
throw err instanceof SchemaError ? err : new SchemaError("MIGRATION_FAILED", `Write-time migration failed for key "${key}"`, err);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
validateAgainstSchema(valueToStore, targetSchema.schema);
|
|
856
|
+
const envelope = {
|
|
857
|
+
version: targetSchema.version,
|
|
858
|
+
payload: valueToStore
|
|
859
|
+
};
|
|
860
|
+
return JSON.stringify(envelope);
|
|
861
|
+
},
|
|
862
|
+
[
|
|
863
|
+
schema?.version,
|
|
864
|
+
key,
|
|
865
|
+
schemaMode,
|
|
866
|
+
codec,
|
|
867
|
+
schemaRegistry,
|
|
868
|
+
validateAgainstSchema,
|
|
869
|
+
getLatestSchemaForKey,
|
|
870
|
+
getSchemaForVersion
|
|
871
|
+
]
|
|
872
|
+
);
|
|
873
|
+
const raw = useSyncExternalStore(
|
|
874
|
+
(listener) => api.subscribeRaw(key, listener),
|
|
875
|
+
() => api.getRawSnapshot(key),
|
|
876
|
+
() => null
|
|
877
|
+
// SSR snapshot - no storage in server environment
|
|
878
|
+
);
|
|
879
|
+
const decoded = useMemo(() => decodeForRead(raw), [decodeForRead, raw]);
|
|
880
|
+
const value = decoded.value;
|
|
881
|
+
useEffect(() => {
|
|
882
|
+
if (decoded.rewriteRaw && decoded.rewriteRaw !== raw) {
|
|
883
|
+
api.setRaw(key, decoded.rewriteRaw);
|
|
884
|
+
}
|
|
885
|
+
}, [api, decoded.rewriteRaw, key, raw]);
|
|
886
|
+
useEffect(() => {
|
|
887
|
+
if (!decoded.pendingSchema || !schemaRegistry?.registerSchema) return;
|
|
888
|
+
if (schemaRegistry.getSchema(decoded.pendingSchema.key, decoded.pendingSchema.version)) return;
|
|
889
|
+
try {
|
|
890
|
+
schemaRegistry.registerSchema(decoded.pendingSchema);
|
|
891
|
+
} catch {
|
|
892
|
+
}
|
|
893
|
+
}, [decoded.pendingSchema, schemaRegistry]);
|
|
894
|
+
const prevRef = useRef(value);
|
|
895
|
+
const mounted = useRef(false);
|
|
896
|
+
useEffect(() => {
|
|
897
|
+
if (mounted.current) return;
|
|
898
|
+
mounted.current = true;
|
|
899
|
+
onMount?.(value);
|
|
900
|
+
prevRef.current = value;
|
|
901
|
+
}, []);
|
|
902
|
+
useEffect(() => {
|
|
903
|
+
const prev = prevRef.current;
|
|
904
|
+
if (Object.is(prev, value)) return;
|
|
905
|
+
prevRef.current = value;
|
|
906
|
+
onChange?.(value, prev);
|
|
907
|
+
}, [value, onChange]);
|
|
908
|
+
useEffect(() => {
|
|
909
|
+
if (!listenCrossTab) return;
|
|
910
|
+
if (typeof window === "undefined") return;
|
|
911
|
+
const storageKey = api.prefix + key;
|
|
912
|
+
const handler = (e) => {
|
|
913
|
+
if (e.key === null) {
|
|
914
|
+
api.removeRaw(key);
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
if (e.key !== storageKey) return;
|
|
918
|
+
if (e.newValue == null) {
|
|
919
|
+
api.removeRaw(key);
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
api.setRaw(key, e.newValue);
|
|
923
|
+
};
|
|
924
|
+
window.addEventListener("storage", handler);
|
|
925
|
+
return () => window.removeEventListener("storage", handler);
|
|
926
|
+
}, [listenCrossTab, api, key]);
|
|
927
|
+
const set = useMemo(() => {
|
|
928
|
+
return (next) => {
|
|
929
|
+
const nextVal = typeof next === "function" ? next(decodeForRead(api.getRawSnapshot(key)).value) : next;
|
|
930
|
+
try {
|
|
931
|
+
const encoded = encodeForWrite(nextVal);
|
|
932
|
+
api.setRaw(key, encoded);
|
|
933
|
+
} catch (err) {
|
|
934
|
+
if (err instanceof SchemaError) {
|
|
935
|
+
console.error(`[Mnemonic] Schema error for key "${key}" (${err.code}):`, err.message);
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
if (err instanceof CodecError) {
|
|
939
|
+
console.error(`[Mnemonic] Codec encode error for key "${key}":`, err.message);
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
console.error(`[Mnemonic] Failed to persist key "${key}":`, err);
|
|
943
|
+
}
|
|
944
|
+
};
|
|
945
|
+
}, [api, key, decodeForRead, encodeForWrite]);
|
|
946
|
+
const reset = useMemo(() => {
|
|
947
|
+
return () => {
|
|
948
|
+
const v = getFallback();
|
|
949
|
+
try {
|
|
950
|
+
const encoded = encodeForWrite(v);
|
|
951
|
+
api.setRaw(key, encoded);
|
|
952
|
+
} catch (err) {
|
|
953
|
+
if (err instanceof SchemaError) {
|
|
954
|
+
console.error(`[Mnemonic] Schema error for key "${key}" (${err.code}):`, err.message);
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
if (err instanceof CodecError) {
|
|
958
|
+
console.error(`[Mnemonic] Codec encode error for key "${key}":`, err.message);
|
|
959
|
+
}
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
};
|
|
963
|
+
}, [api, key, getFallback, encodeForWrite]);
|
|
964
|
+
const remove = useMemo(() => {
|
|
965
|
+
return () => api.removeRaw(key);
|
|
966
|
+
}, [api, key]);
|
|
967
|
+
return useMemo(
|
|
968
|
+
() => (
|
|
969
|
+
/** @see {@link UseMnemonicKeyOptions} for configuration details */
|
|
970
|
+
{
|
|
971
|
+
/** Current decoded value, or the default when the key is absent or invalid. */
|
|
972
|
+
value,
|
|
973
|
+
/** Persist a new value (direct or updater function). */
|
|
974
|
+
set,
|
|
975
|
+
/** Reset to `defaultValue` and persist it. */
|
|
976
|
+
reset,
|
|
977
|
+
/** Delete the key from storage entirely. */
|
|
978
|
+
remove
|
|
979
|
+
}
|
|
980
|
+
),
|
|
981
|
+
[value, set, reset, remove]
|
|
982
|
+
);
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
export { CodecError, JSONCodec, MnemonicProvider, SchemaError, compileSchema, createCodec, useMnemonicKey, validateJsonSchema };
|
|
986
|
+
//# sourceMappingURL=index.js.map
|
|
987
|
+
//# sourceMappingURL=index.js.map
|