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