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/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, pendingSchema) {
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
- schema,
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: schema?.version ?? null,
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 useMnemonicKey(keyOrDescriptor, options) {
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
- schema,
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
- schema?.version,
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
- (value2, jsonSchema) => {
1217
- const errors = validateJsonSchema(value2, jsonSchema);
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 s = schemaRegistry.getSchema(key, version);
1242
- registryCache.schemaByVersion.set(version, s);
1243
- return s;
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 s = schemaRegistry.getLatestSchema(key);
1252
- registryCache.latestSchema = s;
1522
+ const nextSchema = schemaRegistry.getLatestSchema(key);
1523
+ registryCache.latestSchema = nextSchema;
1253
1524
  registryCache.latestSchemaSet = true;
1254
- return s;
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 buildFallbackResult = react.useCallback(
1271
- (error) => ({
1272
- value: getFallback(error)
1273
- }),
1274
- [getFallback]
1275
- );
1276
- const buildSchemaManagedResult = react.useCallback(
1277
- (version, value2) => serializeEnvelope(version, value2),
1278
- []
1279
- );
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 decoded2 = typeof envelope.payload === "string" ? decodeStringPayload(envelope.payload, codec) : envelope.payload;
1379
- const inferSchemaForValue = (value2) => ({
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(value2)
1613
+ schema: inferJsonSchema(value)
1383
1614
  });
1384
- const inferred = inferSchemaForValue(decoded2);
1615
+ const inferred = inferSchemaForValue(decoded);
1385
1616
  return applyReconcile({
1386
- value: decoded2,
1387
- pendingSchema: inferred,
1388
- rewriteRaw: buildSchemaManagedResult(inferred.version, decoded2),
1617
+ value: decoded,
1618
+ extra: { pendingSchema: inferred },
1619
+ rewriteRaw: buildSchemaManagedResult(inferred.version, decoded),
1389
1620
  persistedVersion: envelope.version,
1390
- serializeForPersist: (value2) => buildSchemaManagedResult(inferred.version, value2),
1391
- derivePendingSchema: inferSchemaForValue
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 decoded2 = decodeStringPayload(envelope.payload, codec);
1652
+ const decoded = decodeStringPayload(envelope.payload, codec);
1420
1653
  return applyReconcile({
1421
- value: decoded2,
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 getServerRawSnapshot = react.useCallback(
1520
- () => ssrOptions?.serverValue === void 0 ? null : SSR_SNAPSHOT_TOKEN,
1521
- [ssrOptions?.serverValue]
1522
- );
1523
- const deferStorageRead = hydrationMode === "client-only" && !hasMounted;
1524
- const subscribe = react.useCallback(
1525
- (listener) => {
1526
- if (deferStorageRead) {
1527
- return () => void 0;
1528
- }
1529
- return api.subscribeRaw(key, listener);
1530
- },
1531
- [api, deferStorageRead, key]
1532
- );
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
- let keyContracts = diagnosticContractRegistry.get(api);
1566
- if (!keyContracts) {
1567
- keyContracts = /* @__PURE__ */ new Map();
1568
- diagnosticContractRegistry.set(api, keyContracts);
1569
- }
1570
- if (contractFingerprint === null) {
1571
- return;
1572
- }
1573
- const previousContract = keyContracts.get(key);
1574
- if (previousContract === void 0) {
1575
- keyContracts.set(key, contractFingerprint);
1576
- return;
1577
- }
1578
- if (previousContract === contractFingerprint) {
1579
- return;
1580
- }
1581
- warnOnce(
1582
- api,
1583
- `contract-conflict:${key}`,
1584
- `[Mnemonic] Conflicting useMnemonicKey contracts detected for key "${key}" in namespace "${api.prefix.slice(0, -1)}". Reuse a shared descriptor with defineMnemonicKey(...) or align defaultValue/codec/schema/reconcile options so every consumer describes the same persisted contract.`
1585
- );
1586
- }, [
1587
- api,
1588
- key,
1589
- developmentRuntime,
1590
- contractFingerprint,
1591
- listenCrossTab,
1592
- codecOpt,
1593
- schema?.version,
1594
- api.schemaRegistry,
1595
- api.crossTabSyncMode
1596
- ]);
1597
- react.useEffect(() => {
1598
- if (hasMounted) return;
1599
- setHasMounted(true);
1600
- }, [hasMounted]);
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
- const encoded = encodeForWrite(v);
1672
- api.setRaw(key, encoded);
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
- }, [api, key, getFallback, encodeForWrite]);
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)];