react-mnemonic 1.1.0-beta0 → 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 +41 -748
- 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 +337 -258
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -2069
- package/dist/index.d.ts +4 -2069
- package/dist/index.js +338 -259
- 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 +19 -4
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createContext, useMemo, useEffect,
|
|
1
|
+
import { createContext, useMemo, useEffect, useCallback, useContext, useState, useSyncExternalStore, useRef } from 'react';
|
|
2
2
|
import { jsx } from 'react/jsx-runtime';
|
|
3
3
|
|
|
4
4
|
// src/Mnemonic/provider.tsx
|
|
@@ -1016,8 +1016,6 @@ function inferJsonSchema(sample) {
|
|
|
1016
1016
|
return {};
|
|
1017
1017
|
}
|
|
1018
1018
|
}
|
|
1019
|
-
|
|
1020
|
-
// src/Mnemonic/use.ts
|
|
1021
1019
|
var SSR_SNAPSHOT_TOKEN = /* @__PURE__ */ Symbol("mnemonic:ssr-snapshot");
|
|
1022
1020
|
var diagnosticContractRegistry = /* @__PURE__ */ new WeakMap();
|
|
1023
1021
|
var diagnosticWarningRegistry = /* @__PURE__ */ new WeakMap();
|
|
@@ -1029,10 +1027,12 @@ function serializeEnvelope(version, payload) {
|
|
|
1029
1027
|
payload
|
|
1030
1028
|
});
|
|
1031
1029
|
}
|
|
1032
|
-
function withReadMetadata(value, rewriteRaw,
|
|
1030
|
+
function withReadMetadata(value, rewriteRaw, extra) {
|
|
1033
1031
|
const result = { value };
|
|
1032
|
+
if (extra !== void 0) {
|
|
1033
|
+
Object.assign(result, extra);
|
|
1034
|
+
}
|
|
1034
1035
|
if (rewriteRaw !== void 0) result.rewriteRaw = rewriteRaw;
|
|
1035
|
-
if (pendingSchema !== void 0) result.pendingSchema = pendingSchema;
|
|
1036
1036
|
return result;
|
|
1037
1037
|
}
|
|
1038
1038
|
function isDevelopmentRuntime() {
|
|
@@ -1093,7 +1093,7 @@ function buildContractFingerprint({
|
|
|
1093
1093
|
key,
|
|
1094
1094
|
defaultValue,
|
|
1095
1095
|
codecOpt,
|
|
1096
|
-
|
|
1096
|
+
schemaVersion,
|
|
1097
1097
|
reconcile,
|
|
1098
1098
|
listenCrossTab,
|
|
1099
1099
|
ssrOptions
|
|
@@ -1104,7 +1104,7 @@ function buildContractFingerprint({
|
|
|
1104
1104
|
key,
|
|
1105
1105
|
defaultValue: stableDiagnosticValue(defaultValue),
|
|
1106
1106
|
codec: codecSignature,
|
|
1107
|
-
schemaVersion:
|
|
1107
|
+
schemaVersion: schemaVersion ?? null,
|
|
1108
1108
|
listenCrossTab: Boolean(listenCrossTab),
|
|
1109
1109
|
reconcile: reconcileSignature,
|
|
1110
1110
|
ssrHydration: ssrOptions?.hydration ?? null,
|
|
@@ -1124,7 +1124,7 @@ function resolveMnemonicKeyArgs(keyOrDescriptor, options) {
|
|
|
1124
1124
|
options
|
|
1125
1125
|
};
|
|
1126
1126
|
}
|
|
1127
|
-
function
|
|
1127
|
+
function useMnemonicKeyShared(keyOrDescriptor, options, schemaVersion) {
|
|
1128
1128
|
const descriptor = resolveMnemonicKeyArgs(keyOrDescriptor, options);
|
|
1129
1129
|
const key = descriptor.key;
|
|
1130
1130
|
const resolvedOptions = descriptor.options;
|
|
@@ -1140,8 +1140,6 @@ function useMnemonicKey(keyOrDescriptor, options) {
|
|
|
1140
1140
|
ssr: ssrOptions
|
|
1141
1141
|
} = resolvedOptions;
|
|
1142
1142
|
const codec = codecOpt ?? JSONCodec;
|
|
1143
|
-
const schemaMode = api.schemaMode;
|
|
1144
|
-
const schemaRegistry = api.schemaRegistry;
|
|
1145
1143
|
const hydrationMode = ssrOptions?.hydration ?? api.ssrHydration;
|
|
1146
1144
|
const [hasMounted, setHasMounted] = useState(hydrationMode !== "client-only");
|
|
1147
1145
|
const developmentRuntime = isDevelopmentRuntime();
|
|
@@ -1151,7 +1149,7 @@ function useMnemonicKey(keyOrDescriptor, options) {
|
|
|
1151
1149
|
key,
|
|
1152
1150
|
defaultValue,
|
|
1153
1151
|
codecOpt,
|
|
1154
|
-
|
|
1152
|
+
...schemaVersion === void 0 ? {} : { schemaVersion },
|
|
1155
1153
|
reconcile,
|
|
1156
1154
|
listenCrossTab,
|
|
1157
1155
|
ssrOptions
|
|
@@ -1162,7 +1160,7 @@ function useMnemonicKey(keyOrDescriptor, options) {
|
|
|
1162
1160
|
key,
|
|
1163
1161
|
defaultValue,
|
|
1164
1162
|
codecOpt,
|
|
1165
|
-
|
|
1163
|
+
schemaVersion,
|
|
1166
1164
|
reconcile,
|
|
1167
1165
|
listenCrossTab,
|
|
1168
1166
|
ssrOptions?.hydration,
|
|
@@ -1210,9 +1208,282 @@ function useMnemonicKey(keyOrDescriptor, options) {
|
|
|
1210
1208
|
},
|
|
1211
1209
|
[key]
|
|
1212
1210
|
);
|
|
1211
|
+
const buildFallbackResult = useCallback(
|
|
1212
|
+
(error, extra) => {
|
|
1213
|
+
return withReadMetadata(getFallback(error), void 0, extra);
|
|
1214
|
+
},
|
|
1215
|
+
[getFallback]
|
|
1216
|
+
);
|
|
1217
|
+
return {
|
|
1218
|
+
api,
|
|
1219
|
+
key,
|
|
1220
|
+
codec,
|
|
1221
|
+
codecOpt,
|
|
1222
|
+
schema,
|
|
1223
|
+
reconcile,
|
|
1224
|
+
onMount,
|
|
1225
|
+
onChange,
|
|
1226
|
+
listenCrossTab,
|
|
1227
|
+
getFallback,
|
|
1228
|
+
getServerValue,
|
|
1229
|
+
parseEnvelope,
|
|
1230
|
+
decodeStringPayload,
|
|
1231
|
+
buildFallbackResult,
|
|
1232
|
+
developmentRuntime,
|
|
1233
|
+
contractFingerprint,
|
|
1234
|
+
hasMounted,
|
|
1235
|
+
setHasMounted,
|
|
1236
|
+
hydrationMode,
|
|
1237
|
+
ssrOptions
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1240
|
+
function useApplyReconcile({
|
|
1241
|
+
key,
|
|
1242
|
+
reconcile,
|
|
1243
|
+
buildFallbackResult
|
|
1244
|
+
}) {
|
|
1245
|
+
return useCallback(
|
|
1246
|
+
({
|
|
1247
|
+
value,
|
|
1248
|
+
rewriteRaw,
|
|
1249
|
+
extra,
|
|
1250
|
+
persistedVersion,
|
|
1251
|
+
latestVersion,
|
|
1252
|
+
serializeForPersist,
|
|
1253
|
+
deriveExtra
|
|
1254
|
+
}) => {
|
|
1255
|
+
if (!reconcile) {
|
|
1256
|
+
return withReadMetadata(value, rewriteRaw, extra);
|
|
1257
|
+
}
|
|
1258
|
+
const context = {
|
|
1259
|
+
key,
|
|
1260
|
+
persistedVersion
|
|
1261
|
+
};
|
|
1262
|
+
if (latestVersion !== void 0) {
|
|
1263
|
+
context.latestVersion = latestVersion;
|
|
1264
|
+
}
|
|
1265
|
+
const baselineSerialized = (() => {
|
|
1266
|
+
try {
|
|
1267
|
+
return serializeForPersist(value);
|
|
1268
|
+
} catch {
|
|
1269
|
+
return rewriteRaw;
|
|
1270
|
+
}
|
|
1271
|
+
})();
|
|
1272
|
+
try {
|
|
1273
|
+
const reconciled = reconcile(value, context);
|
|
1274
|
+
const nextExtra = deriveExtra ? deriveExtra(reconciled, extra) : extra;
|
|
1275
|
+
const nextSerialized = serializeForPersist(reconciled);
|
|
1276
|
+
const nextRewriteRaw = baselineSerialized === void 0 || nextSerialized !== baselineSerialized ? nextSerialized : rewriteRaw;
|
|
1277
|
+
return withReadMetadata(reconciled, nextRewriteRaw, nextExtra);
|
|
1278
|
+
} catch (err) {
|
|
1279
|
+
const typedErr = err instanceof SchemaError ? err : new SchemaError("RECONCILE_FAILED", `Reconciliation failed for key "${key}"`, err);
|
|
1280
|
+
return buildFallbackResult(typedErr, extra);
|
|
1281
|
+
}
|
|
1282
|
+
},
|
|
1283
|
+
[buildFallbackResult, key, reconcile]
|
|
1284
|
+
);
|
|
1285
|
+
}
|
|
1286
|
+
function useMnemonicKeyState(shared, config) {
|
|
1287
|
+
const {
|
|
1288
|
+
api,
|
|
1289
|
+
key,
|
|
1290
|
+
codecOpt,
|
|
1291
|
+
schema,
|
|
1292
|
+
onMount,
|
|
1293
|
+
onChange,
|
|
1294
|
+
listenCrossTab,
|
|
1295
|
+
getFallback,
|
|
1296
|
+
getServerValue,
|
|
1297
|
+
developmentRuntime,
|
|
1298
|
+
contractFingerprint,
|
|
1299
|
+
hasMounted,
|
|
1300
|
+
setHasMounted,
|
|
1301
|
+
hydrationMode,
|
|
1302
|
+
ssrOptions
|
|
1303
|
+
} = shared;
|
|
1304
|
+
const { decodeForRead, encodeForWrite, additionalDevWarnings, onDecodedEffect } = config;
|
|
1305
|
+
const getServerRawSnapshot = useCallback(
|
|
1306
|
+
() => ssrOptions?.serverValue === void 0 ? null : SSR_SNAPSHOT_TOKEN,
|
|
1307
|
+
[ssrOptions?.serverValue]
|
|
1308
|
+
);
|
|
1309
|
+
const deferStorageRead = hydrationMode === "client-only" && !hasMounted;
|
|
1310
|
+
const subscribe = useCallback(
|
|
1311
|
+
(listener) => {
|
|
1312
|
+
if (deferStorageRead) {
|
|
1313
|
+
return () => void 0;
|
|
1314
|
+
}
|
|
1315
|
+
return api.subscribeRaw(key, listener);
|
|
1316
|
+
},
|
|
1317
|
+
[api, deferStorageRead, key]
|
|
1318
|
+
);
|
|
1319
|
+
const raw = useSyncExternalStore(
|
|
1320
|
+
subscribe,
|
|
1321
|
+
() => deferStorageRead ? getServerRawSnapshot() : api.getRawSnapshot(key),
|
|
1322
|
+
getServerRawSnapshot
|
|
1323
|
+
);
|
|
1324
|
+
const decoded = useMemo(() => {
|
|
1325
|
+
if (raw === SSR_SNAPSHOT_TOKEN) {
|
|
1326
|
+
return withReadMetadata(getServerValue());
|
|
1327
|
+
}
|
|
1328
|
+
return decodeForRead(raw);
|
|
1329
|
+
}, [decodeForRead, getServerValue, raw]);
|
|
1330
|
+
const value = decoded.value;
|
|
1331
|
+
useEffect(() => {
|
|
1332
|
+
if (!developmentRuntime) return;
|
|
1333
|
+
const globalWindow = globalThis.window;
|
|
1334
|
+
if (listenCrossTab && (api.crossTabSyncMode ?? "none") === "none" && globalWindow !== void 0) {
|
|
1335
|
+
warnOnce(
|
|
1336
|
+
api,
|
|
1337
|
+
`listenCrossTab:${key}`,
|
|
1338
|
+
`[Mnemonic] useMnemonicKey("${key}") enabled listenCrossTab, but the active storage backend may not be able to notify external changes. If you're using a custom Storage-like wrapper around localStorage, ensure it forwards browser "storage" events or implements storage.onExternalChange(...); otherwise, use localStorage or implement storage.onExternalChange(...) on your custom backend.`
|
|
1339
|
+
);
|
|
1340
|
+
}
|
|
1341
|
+
additionalDevWarnings?.({
|
|
1342
|
+
api,
|
|
1343
|
+
key,
|
|
1344
|
+
listenCrossTab,
|
|
1345
|
+
codecOpt,
|
|
1346
|
+
schema,
|
|
1347
|
+
warnOnce: (id, message) => warnOnce(api, id, message)
|
|
1348
|
+
});
|
|
1349
|
+
let keyContracts = diagnosticContractRegistry.get(api);
|
|
1350
|
+
if (!keyContracts) {
|
|
1351
|
+
keyContracts = /* @__PURE__ */ new Map();
|
|
1352
|
+
diagnosticContractRegistry.set(api, keyContracts);
|
|
1353
|
+
}
|
|
1354
|
+
if (contractFingerprint === null) {
|
|
1355
|
+
return;
|
|
1356
|
+
}
|
|
1357
|
+
const previousContract = keyContracts.get(key);
|
|
1358
|
+
if (previousContract === void 0) {
|
|
1359
|
+
keyContracts.set(key, contractFingerprint);
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
if (previousContract === contractFingerprint) {
|
|
1363
|
+
return;
|
|
1364
|
+
}
|
|
1365
|
+
warnOnce(
|
|
1366
|
+
api,
|
|
1367
|
+
`contract-conflict:${key}`,
|
|
1368
|
+
`[Mnemonic] Conflicting useMnemonicKey contracts detected for key "${key}" in namespace "${api.prefix.slice(0, -1)}". Reuse a shared descriptor with defineMnemonicKey(...) or align defaultValue/codec/schema/reconcile options so every consumer describes the same persisted contract.`
|
|
1369
|
+
);
|
|
1370
|
+
}, [
|
|
1371
|
+
additionalDevWarnings,
|
|
1372
|
+
api,
|
|
1373
|
+
key,
|
|
1374
|
+
developmentRuntime,
|
|
1375
|
+
contractFingerprint,
|
|
1376
|
+
listenCrossTab,
|
|
1377
|
+
codecOpt,
|
|
1378
|
+
schema,
|
|
1379
|
+
api.crossTabSyncMode
|
|
1380
|
+
]);
|
|
1381
|
+
useEffect(() => {
|
|
1382
|
+
if (hasMounted) return;
|
|
1383
|
+
setHasMounted(true);
|
|
1384
|
+
}, [hasMounted, setHasMounted]);
|
|
1385
|
+
useEffect(() => {
|
|
1386
|
+
if (decoded.rewriteRaw && decoded.rewriteRaw !== raw) {
|
|
1387
|
+
api.setRaw(key, decoded.rewriteRaw);
|
|
1388
|
+
}
|
|
1389
|
+
}, [api, decoded.rewriteRaw, key, raw]);
|
|
1390
|
+
useEffect(() => {
|
|
1391
|
+
onDecodedEffect?.(decoded);
|
|
1392
|
+
}, [decoded, onDecodedEffect]);
|
|
1393
|
+
const prevRef = useRef(value);
|
|
1394
|
+
const mounted = useRef(false);
|
|
1395
|
+
useEffect(() => {
|
|
1396
|
+
if (mounted.current) return;
|
|
1397
|
+
mounted.current = true;
|
|
1398
|
+
onMount?.(value);
|
|
1399
|
+
prevRef.current = value;
|
|
1400
|
+
}, []);
|
|
1401
|
+
useEffect(() => {
|
|
1402
|
+
const prev = prevRef.current;
|
|
1403
|
+
if (Object.is(prev, value)) return;
|
|
1404
|
+
prevRef.current = value;
|
|
1405
|
+
onChange?.(value, prev);
|
|
1406
|
+
}, [value, onChange]);
|
|
1407
|
+
useEffect(() => {
|
|
1408
|
+
if (!listenCrossTab) return;
|
|
1409
|
+
const globalWindow = globalThis.window;
|
|
1410
|
+
if (globalWindow === void 0) return;
|
|
1411
|
+
const storageKey = api.prefix + key;
|
|
1412
|
+
const handler = (e) => {
|
|
1413
|
+
if (e.key === null) {
|
|
1414
|
+
api.removeRaw(key);
|
|
1415
|
+
return;
|
|
1416
|
+
}
|
|
1417
|
+
if (e.key !== storageKey) return;
|
|
1418
|
+
if (e.newValue == null) {
|
|
1419
|
+
api.removeRaw(key);
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
1422
|
+
api.setRaw(key, e.newValue);
|
|
1423
|
+
};
|
|
1424
|
+
globalWindow.addEventListener("storage", handler);
|
|
1425
|
+
return () => globalWindow.removeEventListener("storage", handler);
|
|
1426
|
+
}, [listenCrossTab, api, key]);
|
|
1427
|
+
const set = useMemo(() => {
|
|
1428
|
+
return (next) => {
|
|
1429
|
+
const nextVal = typeof next === "function" ? next(decodeForRead(api.getRawSnapshot(key)).value) : next;
|
|
1430
|
+
try {
|
|
1431
|
+
const encoded = encodeForWrite(nextVal);
|
|
1432
|
+
api.setRaw(key, encoded);
|
|
1433
|
+
} catch (err) {
|
|
1434
|
+
if (err instanceof SchemaError) {
|
|
1435
|
+
console.error(`[Mnemonic] Schema error for key "${key}" (${err.code}):`, err.message);
|
|
1436
|
+
return;
|
|
1437
|
+
}
|
|
1438
|
+
if (err instanceof CodecError) {
|
|
1439
|
+
console.error(`[Mnemonic] Codec encode error for key "${key}":`, err.message);
|
|
1440
|
+
return;
|
|
1441
|
+
}
|
|
1442
|
+
console.error(`[Mnemonic] Failed to persist key "${key}":`, err);
|
|
1443
|
+
}
|
|
1444
|
+
};
|
|
1445
|
+
}, [api, key, decodeForRead, encodeForWrite]);
|
|
1446
|
+
const reset = useMemo(() => {
|
|
1447
|
+
return () => {
|
|
1448
|
+
const v = getFallback();
|
|
1449
|
+
try {
|
|
1450
|
+
const encoded = encodeForWrite(v);
|
|
1451
|
+
api.setRaw(key, encoded);
|
|
1452
|
+
} catch (err) {
|
|
1453
|
+
if (err instanceof SchemaError) {
|
|
1454
|
+
console.error(`[Mnemonic] Schema error for key "${key}" (${err.code}):`, err.message);
|
|
1455
|
+
return;
|
|
1456
|
+
}
|
|
1457
|
+
if (err instanceof CodecError) {
|
|
1458
|
+
console.error(`[Mnemonic] Codec encode error for key "${key}":`, err.message);
|
|
1459
|
+
}
|
|
1460
|
+
return;
|
|
1461
|
+
}
|
|
1462
|
+
};
|
|
1463
|
+
}, [api, key, getFallback, encodeForWrite]);
|
|
1464
|
+
const remove = useMemo(() => {
|
|
1465
|
+
return () => api.removeRaw(key);
|
|
1466
|
+
}, [api, key]);
|
|
1467
|
+
return useMemo(
|
|
1468
|
+
() => ({
|
|
1469
|
+
value,
|
|
1470
|
+
set,
|
|
1471
|
+
reset,
|
|
1472
|
+
remove
|
|
1473
|
+
}),
|
|
1474
|
+
[value, set, reset, remove]
|
|
1475
|
+
);
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
// src/Mnemonic/use.ts
|
|
1479
|
+
function useSchemaMnemonicKey(descriptor) {
|
|
1480
|
+
const shared = useMnemonicKeyShared(descriptor, void 0, descriptor.options.schema?.version);
|
|
1481
|
+
const { api, key, codec, codecOpt, schema, reconcile, parseEnvelope, decodeStringPayload, buildFallbackResult } = shared;
|
|
1482
|
+
const schemaMode = api.schemaMode;
|
|
1483
|
+
const schemaRegistry = api.schemaRegistry;
|
|
1213
1484
|
const validateAgainstSchema = useCallback(
|
|
1214
|
-
(
|
|
1215
|
-
const errors = validateJsonSchema(
|
|
1485
|
+
(value, jsonSchema) => {
|
|
1486
|
+
const errors = validateJsonSchema(value, jsonSchema);
|
|
1216
1487
|
if (errors.length > 0) {
|
|
1217
1488
|
const message = errors.map((e) => `${e.path || "/"}: ${e.message}`).join("; ");
|
|
1218
1489
|
throw new SchemaError("TYPE_MISMATCH", `Schema validation failed for key "${key}": ${message}`);
|
|
@@ -1236,9 +1507,9 @@ function useMnemonicKey(keyOrDescriptor, options) {
|
|
|
1236
1507
|
if (registryCache.schemaByVersion.has(version)) {
|
|
1237
1508
|
return registryCache.schemaByVersion.get(version);
|
|
1238
1509
|
}
|
|
1239
|
-
const
|
|
1240
|
-
registryCache.schemaByVersion.set(version,
|
|
1241
|
-
return
|
|
1510
|
+
const nextSchema = schemaRegistry.getSchema(key, version);
|
|
1511
|
+
registryCache.schemaByVersion.set(version, nextSchema);
|
|
1512
|
+
return nextSchema;
|
|
1242
1513
|
},
|
|
1243
1514
|
[schemaRegistry, registryCache, key]
|
|
1244
1515
|
);
|
|
@@ -1246,10 +1517,10 @@ function useMnemonicKey(keyOrDescriptor, options) {
|
|
|
1246
1517
|
if (!schemaRegistry) return void 0;
|
|
1247
1518
|
if (!registryCache) return schemaRegistry.getLatestSchema(key);
|
|
1248
1519
|
if (registryCache.latestSchemaSet) return registryCache.latestSchema;
|
|
1249
|
-
const
|
|
1250
|
-
registryCache.latestSchema =
|
|
1520
|
+
const nextSchema = schemaRegistry.getLatestSchema(key);
|
|
1521
|
+
registryCache.latestSchema = nextSchema;
|
|
1251
1522
|
registryCache.latestSchemaSet = true;
|
|
1252
|
-
return
|
|
1523
|
+
return nextSchema;
|
|
1253
1524
|
}, [schemaRegistry, registryCache, key]);
|
|
1254
1525
|
const getMigrationPathForKey = useCallback(
|
|
1255
1526
|
(fromVersion, toVersion) => {
|
|
@@ -1265,16 +1536,14 @@ function useMnemonicKey(keyOrDescriptor, options) {
|
|
|
1265
1536
|
},
|
|
1266
1537
|
[schemaRegistry, registryCache, key]
|
|
1267
1538
|
);
|
|
1268
|
-
const
|
|
1269
|
-
(
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
[]
|
|
1277
|
-
);
|
|
1539
|
+
const buildSchemaManagedResult = useCallback((version, value) => {
|
|
1540
|
+
return serializeEnvelope(version, value);
|
|
1541
|
+
}, []);
|
|
1542
|
+
const applyReconcile = useApplyReconcile({
|
|
1543
|
+
key,
|
|
1544
|
+
reconcile,
|
|
1545
|
+
buildFallbackResult
|
|
1546
|
+
});
|
|
1278
1547
|
const resolveTargetWriteSchema = useCallback(() => {
|
|
1279
1548
|
const explicitVersion = schema?.version;
|
|
1280
1549
|
const latestSchema = getLatestSchemaForKey();
|
|
@@ -1319,44 +1588,6 @@ function useMnemonicKey(keyOrDescriptor, options) {
|
|
|
1319
1588
|
buildSchemaManagedResult
|
|
1320
1589
|
]
|
|
1321
1590
|
);
|
|
1322
|
-
const applyReconcile = useCallback(
|
|
1323
|
-
({
|
|
1324
|
-
value: value2,
|
|
1325
|
-
rewriteRaw,
|
|
1326
|
-
pendingSchema,
|
|
1327
|
-
persistedVersion,
|
|
1328
|
-
latestVersion,
|
|
1329
|
-
serializeForPersist,
|
|
1330
|
-
derivePendingSchema
|
|
1331
|
-
}) => {
|
|
1332
|
-
if (!reconcile) {
|
|
1333
|
-
return withReadMetadata(value2, rewriteRaw, pendingSchema);
|
|
1334
|
-
}
|
|
1335
|
-
const context = {
|
|
1336
|
-
key,
|
|
1337
|
-
persistedVersion,
|
|
1338
|
-
...latestVersion === void 0 ? {} : { latestVersion }
|
|
1339
|
-
};
|
|
1340
|
-
const baselineSerialized = (() => {
|
|
1341
|
-
try {
|
|
1342
|
-
return serializeForPersist(value2);
|
|
1343
|
-
} catch {
|
|
1344
|
-
return rewriteRaw;
|
|
1345
|
-
}
|
|
1346
|
-
})();
|
|
1347
|
-
try {
|
|
1348
|
-
const reconciled = reconcile(value2, context);
|
|
1349
|
-
const nextPendingSchema = derivePendingSchema ? derivePendingSchema(reconciled) : pendingSchema;
|
|
1350
|
-
const nextSerialized = serializeForPersist(reconciled);
|
|
1351
|
-
const nextRewriteRaw = baselineSerialized === void 0 || nextSerialized !== baselineSerialized ? nextSerialized : rewriteRaw;
|
|
1352
|
-
return withReadMetadata(reconciled, nextRewriteRaw, nextPendingSchema);
|
|
1353
|
-
} catch (err) {
|
|
1354
|
-
const typedErr = err instanceof SchemaError ? err : new SchemaError("RECONCILE_FAILED", `Reconciliation failed for key "${key}"`, err);
|
|
1355
|
-
return buildFallbackResult(typedErr);
|
|
1356
|
-
}
|
|
1357
|
-
},
|
|
1358
|
-
[buildFallbackResult, key, reconcile]
|
|
1359
|
-
);
|
|
1360
1591
|
const decodeAutoschemaEnvelope = useCallback(
|
|
1361
1592
|
(envelope, latestSchema) => {
|
|
1362
1593
|
if (latestSchema) {
|
|
@@ -1373,20 +1604,22 @@ function useMnemonicKey(keyOrDescriptor, options) {
|
|
|
1373
1604
|
);
|
|
1374
1605
|
}
|
|
1375
1606
|
try {
|
|
1376
|
-
const
|
|
1377
|
-
const inferSchemaForValue = (
|
|
1607
|
+
const decoded = typeof envelope.payload === "string" ? decodeStringPayload(envelope.payload, codec) : envelope.payload;
|
|
1608
|
+
const inferSchemaForValue = (value) => ({
|
|
1378
1609
|
key,
|
|
1379
1610
|
version: 1,
|
|
1380
|
-
schema: inferJsonSchema(
|
|
1611
|
+
schema: inferJsonSchema(value)
|
|
1381
1612
|
});
|
|
1382
|
-
const inferred = inferSchemaForValue(
|
|
1613
|
+
const inferred = inferSchemaForValue(decoded);
|
|
1383
1614
|
return applyReconcile({
|
|
1384
|
-
value:
|
|
1385
|
-
pendingSchema: inferred,
|
|
1386
|
-
rewriteRaw: buildSchemaManagedResult(inferred.version,
|
|
1615
|
+
value: decoded,
|
|
1616
|
+
extra: { pendingSchema: inferred },
|
|
1617
|
+
rewriteRaw: buildSchemaManagedResult(inferred.version, decoded),
|
|
1387
1618
|
persistedVersion: envelope.version,
|
|
1388
|
-
serializeForPersist: (
|
|
1389
|
-
|
|
1619
|
+
serializeForPersist: (value) => buildSchemaManagedResult(inferred.version, value),
|
|
1620
|
+
deriveExtra: (value) => ({
|
|
1621
|
+
pendingSchema: inferSchemaForValue(value)
|
|
1622
|
+
})
|
|
1390
1623
|
});
|
|
1391
1624
|
} catch (err) {
|
|
1392
1625
|
const typedErr = err instanceof SchemaError || err instanceof CodecError ? err : new SchemaError("TYPE_MISMATCH", `Autoschema inference failed for key "${key}"`, err);
|
|
@@ -1414,9 +1647,9 @@ function useMnemonicKey(keyOrDescriptor, options) {
|
|
|
1414
1647
|
});
|
|
1415
1648
|
}
|
|
1416
1649
|
try {
|
|
1417
|
-
const
|
|
1650
|
+
const decoded = decodeStringPayload(envelope.payload, codec);
|
|
1418
1651
|
return applyReconcile({
|
|
1419
|
-
value:
|
|
1652
|
+
value: decoded,
|
|
1420
1653
|
persistedVersion: envelope.version,
|
|
1421
1654
|
...latestSchema ? { latestVersion: latestSchema.version } : {},
|
|
1422
1655
|
serializeForPersist: encodeForWrite
|
|
@@ -1511,194 +1744,40 @@ function useMnemonicKey(keyOrDescriptor, options) {
|
|
|
1511
1744
|
parseEnvelope,
|
|
1512
1745
|
schemaMode,
|
|
1513
1746
|
getSchemaForVersion,
|
|
1514
|
-
getLatestSchemaForKey
|
|
1747
|
+
getLatestSchemaForKey,
|
|
1748
|
+
key
|
|
1515
1749
|
]
|
|
1516
1750
|
);
|
|
1517
|
-
const
|
|
1518
|
-
(
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
const deferStorageRead = hydrationMode === "client-only" && !hasMounted;
|
|
1522
|
-
const subscribe = useCallback(
|
|
1523
|
-
(listener) => {
|
|
1524
|
-
if (deferStorageRead) {
|
|
1525
|
-
return () => void 0;
|
|
1526
|
-
}
|
|
1527
|
-
return api.subscribeRaw(key, listener);
|
|
1528
|
-
},
|
|
1529
|
-
[api, deferStorageRead, key]
|
|
1530
|
-
);
|
|
1531
|
-
const raw = useSyncExternalStore(
|
|
1532
|
-
subscribe,
|
|
1533
|
-
() => deferStorageRead ? getServerRawSnapshot() : api.getRawSnapshot(key),
|
|
1534
|
-
getServerRawSnapshot
|
|
1535
|
-
);
|
|
1536
|
-
const decoded = useMemo(() => {
|
|
1537
|
-
if (raw === SSR_SNAPSHOT_TOKEN) {
|
|
1538
|
-
return {
|
|
1539
|
-
value: getServerValue(),
|
|
1540
|
-
rewriteRaw: void 0,
|
|
1541
|
-
pendingSchema: void 0
|
|
1542
|
-
};
|
|
1543
|
-
}
|
|
1544
|
-
return decodeForRead(raw);
|
|
1545
|
-
}, [decodeForRead, getServerValue, raw]);
|
|
1546
|
-
const value = decoded.value;
|
|
1547
|
-
useEffect(() => {
|
|
1548
|
-
if (!developmentRuntime) return;
|
|
1549
|
-
if (listenCrossTab && (api.crossTabSyncMode ?? "none") === "none" && globalThis.window !== void 0) {
|
|
1550
|
-
warnOnce(
|
|
1551
|
-
api,
|
|
1552
|
-
`listenCrossTab:${key}`,
|
|
1553
|
-
`[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.`
|
|
1554
|
-
);
|
|
1555
|
-
}
|
|
1556
|
-
if (codecOpt && schema?.version !== void 0 && api.schemaRegistry) {
|
|
1557
|
-
warnOnce(
|
|
1558
|
-
api,
|
|
1751
|
+
const additionalDevWarnings = useCallback(
|
|
1752
|
+
({ warnOnce: warnOnce2 }) => {
|
|
1753
|
+
if (!codecOpt || schema?.version === void 0 || !api.schemaRegistry) return;
|
|
1754
|
+
warnOnce2(
|
|
1559
1755
|
`codec+schema:${key}`,
|
|
1560
1756
|
`[Mnemonic] useMnemonicKey("${key}") received both a custom codec and schema.version. Schema-managed reads/writes do not use the codec path. Remove the codec for schema-managed storage, or remove schema.version if you intended codec-only persistence.`
|
|
1561
1757
|
);
|
|
1562
|
-
}
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
return;
|
|
1570
|
-
}
|
|
1571
|
-
const previousContract = keyContracts.get(key);
|
|
1572
|
-
if (previousContract === void 0) {
|
|
1573
|
-
keyContracts.set(key, contractFingerprint);
|
|
1574
|
-
return;
|
|
1575
|
-
}
|
|
1576
|
-
if (previousContract === contractFingerprint) {
|
|
1577
|
-
return;
|
|
1578
|
-
}
|
|
1579
|
-
warnOnce(
|
|
1580
|
-
api,
|
|
1581
|
-
`contract-conflict:${key}`,
|
|
1582
|
-
`[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.`
|
|
1583
|
-
);
|
|
1584
|
-
}, [
|
|
1585
|
-
api,
|
|
1586
|
-
key,
|
|
1587
|
-
developmentRuntime,
|
|
1588
|
-
contractFingerprint,
|
|
1589
|
-
listenCrossTab,
|
|
1590
|
-
codecOpt,
|
|
1591
|
-
schema?.version,
|
|
1592
|
-
api.schemaRegistry,
|
|
1593
|
-
api.crossTabSyncMode
|
|
1594
|
-
]);
|
|
1595
|
-
useEffect(() => {
|
|
1596
|
-
if (hasMounted) return;
|
|
1597
|
-
setHasMounted(true);
|
|
1598
|
-
}, [hasMounted]);
|
|
1599
|
-
useEffect(() => {
|
|
1600
|
-
if (decoded.rewriteRaw && decoded.rewriteRaw !== raw) {
|
|
1601
|
-
api.setRaw(key, decoded.rewriteRaw);
|
|
1602
|
-
}
|
|
1603
|
-
}, [api, decoded.rewriteRaw, key, raw]);
|
|
1604
|
-
useEffect(() => {
|
|
1605
|
-
if (!decoded.pendingSchema || !schemaRegistry?.registerSchema) return;
|
|
1606
|
-
if (schemaRegistry.getSchema(decoded.pendingSchema.key, decoded.pendingSchema.version)) return;
|
|
1607
|
-
try {
|
|
1608
|
-
schemaRegistry.registerSchema(decoded.pendingSchema);
|
|
1609
|
-
} catch {
|
|
1610
|
-
}
|
|
1611
|
-
}, [decoded.pendingSchema, schemaRegistry]);
|
|
1612
|
-
const prevRef = useRef(value);
|
|
1613
|
-
const mounted = useRef(false);
|
|
1614
|
-
useEffect(() => {
|
|
1615
|
-
if (mounted.current) return;
|
|
1616
|
-
mounted.current = true;
|
|
1617
|
-
onMount?.(value);
|
|
1618
|
-
prevRef.current = value;
|
|
1619
|
-
}, []);
|
|
1620
|
-
useEffect(() => {
|
|
1621
|
-
const prev = prevRef.current;
|
|
1622
|
-
if (Object.is(prev, value)) return;
|
|
1623
|
-
prevRef.current = value;
|
|
1624
|
-
onChange?.(value, prev);
|
|
1625
|
-
}, [value, onChange]);
|
|
1626
|
-
useEffect(() => {
|
|
1627
|
-
if (!listenCrossTab) return;
|
|
1628
|
-
const globalWindow = globalThis.window;
|
|
1629
|
-
if (globalWindow === void 0) return;
|
|
1630
|
-
const storageKey = api.prefix + key;
|
|
1631
|
-
const handler = (e) => {
|
|
1632
|
-
if (e.key === null) {
|
|
1633
|
-
api.removeRaw(key);
|
|
1634
|
-
return;
|
|
1635
|
-
}
|
|
1636
|
-
if (e.key !== storageKey) return;
|
|
1637
|
-
if (e.newValue == null) {
|
|
1638
|
-
api.removeRaw(key);
|
|
1639
|
-
return;
|
|
1640
|
-
}
|
|
1641
|
-
api.setRaw(key, e.newValue);
|
|
1642
|
-
};
|
|
1643
|
-
globalWindow.addEventListener("storage", handler);
|
|
1644
|
-
return () => globalWindow.removeEventListener("storage", handler);
|
|
1645
|
-
}, [listenCrossTab, api, key]);
|
|
1646
|
-
const set = useMemo(() => {
|
|
1647
|
-
return (next) => {
|
|
1648
|
-
const nextVal = typeof next === "function" ? next(decodeForRead(api.getRawSnapshot(key)).value) : next;
|
|
1649
|
-
try {
|
|
1650
|
-
const encoded = encodeForWrite(nextVal);
|
|
1651
|
-
api.setRaw(key, encoded);
|
|
1652
|
-
} catch (err) {
|
|
1653
|
-
if (err instanceof SchemaError) {
|
|
1654
|
-
console.error(`[Mnemonic] Schema error for key "${key}" (${err.code}):`, err.message);
|
|
1655
|
-
return;
|
|
1656
|
-
}
|
|
1657
|
-
if (err instanceof CodecError) {
|
|
1658
|
-
console.error(`[Mnemonic] Codec encode error for key "${key}":`, err.message);
|
|
1659
|
-
return;
|
|
1660
|
-
}
|
|
1661
|
-
console.error(`[Mnemonic] Failed to persist key "${key}":`, err);
|
|
1662
|
-
}
|
|
1663
|
-
};
|
|
1664
|
-
}, [api, key, decodeForRead, encodeForWrite]);
|
|
1665
|
-
const reset = useMemo(() => {
|
|
1666
|
-
return () => {
|
|
1667
|
-
const v = getFallback();
|
|
1758
|
+
},
|
|
1759
|
+
[api.schemaRegistry, codecOpt, key, schema?.version]
|
|
1760
|
+
);
|
|
1761
|
+
const onDecodedEffect = useCallback(
|
|
1762
|
+
(decoded) => {
|
|
1763
|
+
if (!decoded.pendingSchema || !schemaRegistry?.registerSchema) return;
|
|
1764
|
+
if (schemaRegistry.getSchema(decoded.pendingSchema.key, decoded.pendingSchema.version)) return;
|
|
1668
1765
|
try {
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
} catch (err) {
|
|
1672
|
-
if (err instanceof SchemaError) {
|
|
1673
|
-
console.error(`[Mnemonic] Schema error for key "${key}" (${err.code}):`, err.message);
|
|
1674
|
-
return;
|
|
1675
|
-
}
|
|
1676
|
-
if (err instanceof CodecError) {
|
|
1677
|
-
console.error(`[Mnemonic] Codec encode error for key "${key}":`, err.message);
|
|
1678
|
-
}
|
|
1679
|
-
return;
|
|
1766
|
+
schemaRegistry.registerSchema(decoded.pendingSchema);
|
|
1767
|
+
} catch {
|
|
1680
1768
|
}
|
|
1681
|
-
}
|
|
1682
|
-
|
|
1683
|
-
const remove = useMemo(() => {
|
|
1684
|
-
return () => api.removeRaw(key);
|
|
1685
|
-
}, [api, key]);
|
|
1686
|
-
return useMemo(
|
|
1687
|
-
() => (
|
|
1688
|
-
/** @see {@link UseMnemonicKeyOptions} for configuration details */
|
|
1689
|
-
{
|
|
1690
|
-
/** Current decoded value, or the default when the key is absent or invalid. */
|
|
1691
|
-
value,
|
|
1692
|
-
/** Persist a new value (direct or updater function). */
|
|
1693
|
-
set,
|
|
1694
|
-
/** Reset to `defaultValue` and persist it. */
|
|
1695
|
-
reset,
|
|
1696
|
-
/** Delete the key from storage entirely. */
|
|
1697
|
-
remove
|
|
1698
|
-
}
|
|
1699
|
-
),
|
|
1700
|
-
[value, set, reset, remove]
|
|
1769
|
+
},
|
|
1770
|
+
[schemaRegistry]
|
|
1701
1771
|
);
|
|
1772
|
+
return useMnemonicKeyState(shared, {
|
|
1773
|
+
decodeForRead,
|
|
1774
|
+
encodeForWrite,
|
|
1775
|
+
additionalDevWarnings,
|
|
1776
|
+
onDecodedEffect
|
|
1777
|
+
});
|
|
1778
|
+
}
|
|
1779
|
+
function useMnemonicKey(keyOrDescriptor, options) {
|
|
1780
|
+
return useSchemaMnemonicKey(resolveMnemonicKeyArgs(keyOrDescriptor, options));
|
|
1702
1781
|
}
|
|
1703
1782
|
function uniqueKeys(keys) {
|
|
1704
1783
|
return [...new Set(keys)];
|