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.js CHANGED
@@ -1,4 +1,4 @@
1
- import { createContext, useMemo, useEffect, useState, useCallback, useSyncExternalStore, useRef, useContext } from 'react';
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, pendingSchema) {
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
- schema,
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: schema?.version ?? null,
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 useMnemonicKey(keyOrDescriptor, options) {
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
- schema,
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
- schema?.version,
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
- (value2, jsonSchema) => {
1215
- const errors = validateJsonSchema(value2, jsonSchema);
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 s = schemaRegistry.getSchema(key, version);
1240
- registryCache.schemaByVersion.set(version, s);
1241
- return s;
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 s = schemaRegistry.getLatestSchema(key);
1250
- registryCache.latestSchema = s;
1520
+ const nextSchema = schemaRegistry.getLatestSchema(key);
1521
+ registryCache.latestSchema = nextSchema;
1251
1522
  registryCache.latestSchemaSet = true;
1252
- return s;
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 buildFallbackResult = useCallback(
1269
- (error) => ({
1270
- value: getFallback(error)
1271
- }),
1272
- [getFallback]
1273
- );
1274
- const buildSchemaManagedResult = useCallback(
1275
- (version, value2) => serializeEnvelope(version, value2),
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 decoded2 = typeof envelope.payload === "string" ? decodeStringPayload(envelope.payload, codec) : envelope.payload;
1377
- const inferSchemaForValue = (value2) => ({
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(value2)
1611
+ schema: inferJsonSchema(value)
1381
1612
  });
1382
- const inferred = inferSchemaForValue(decoded2);
1613
+ const inferred = inferSchemaForValue(decoded);
1383
1614
  return applyReconcile({
1384
- value: decoded2,
1385
- pendingSchema: inferred,
1386
- rewriteRaw: buildSchemaManagedResult(inferred.version, decoded2),
1615
+ value: decoded,
1616
+ extra: { pendingSchema: inferred },
1617
+ rewriteRaw: buildSchemaManagedResult(inferred.version, decoded),
1387
1618
  persistedVersion: envelope.version,
1388
- serializeForPersist: (value2) => buildSchemaManagedResult(inferred.version, value2),
1389
- derivePendingSchema: inferSchemaForValue
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 decoded2 = decodeStringPayload(envelope.payload, codec);
1650
+ const decoded = decodeStringPayload(envelope.payload, codec);
1418
1651
  return applyReconcile({
1419
- value: decoded2,
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 getServerRawSnapshot = useCallback(
1518
- () => ssrOptions?.serverValue === void 0 ? null : SSR_SNAPSHOT_TOKEN,
1519
- [ssrOptions?.serverValue]
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
- let keyContracts = diagnosticContractRegistry.get(api);
1564
- if (!keyContracts) {
1565
- keyContracts = /* @__PURE__ */ new Map();
1566
- diagnosticContractRegistry.set(api, keyContracts);
1567
- }
1568
- if (contractFingerprint === null) {
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
- const encoded = encodeForWrite(v);
1670
- api.setRaw(key, encoded);
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
- }, [api, key, getFallback, encodeForWrite]);
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)];